Python学习【爬虫】(一)

隐身守侯 提交于 2020-01-30 01:35:35

爬虫

网络爬虫,是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。

Python如何访问互联网

使用Urllib库

  • URL
    一般格式:
    protocol://hostname[:port]/path/[;parameters][?query]#fragment
    中括号中为可选项
    url由三部分组成:
    (1)协议:http,https,ftp,file,ed2k…
    (2)存放资源的服务器的域名系统或IP地址(有时需要包含端口号,各传输协议都有默认的端口号,如http默认端口号80)
    (3)资源的具体地址,如目录或文件名等
  • urlopen函数
    urllib.request 文档
    urllib.request.urlopen(url, data=None,[timeout,]*, cafile=None, capath=None, cadeful=False)
>>> import urllib.request
>>> response = urllib.request.urlopen("https://www.csdn.net/")
>>> html = response.read()	# 读取网页内容
>>> html = html.decode("utf-8")	# 以“utf-8”格式编码显示
>>> print(html)	# 打印内容

在这里插入图片描述


实战

  • 下载一张猫图
import urllib.request

response = urllib.request.urlopen('http://placekitten.com/g/300/300')
cat_img = response.read()

with open('cat_300_300.jpg', 'wb') as f:
    f.write(cat_img)   

urlopen的参数可以是一个字符串地址或Request对象,字符串地址会默认自动转换成一个Request对象。
上面的代码还可修改为

import urllib.request

req = urllib.request.Request('http://placekitten.com/g/300/300')
response = urllib.request.urlopen(req)
# 实际上如果直接用urlopen的话,会默认执行以上两句
cat_img = response.read()

with open('cat_300_300.jpg', 'wb') as f:
    f.write(cat_img)   

response

在这里插入图片描述

  • response.geturl()
    得到访问的地址
    在这里插入图片描述
  • response.info()
    返回一个HTTPMessage的对象
    在这里插入图片描述
    打印
    在这里插入图片描述
    包含了远程服务器返回的header信息
  • response.getcode()
    在这里插入图片描述
    返回HTTP的状态,200即为可以正常响应

运用词典翻译文本

打开有道翻译
进入浏览器的开发者工具,点击Network
在这里插入图片描述
输入内容
在这里插入图片描述
可以看到开发者工具界面有很多浏览器和客户端的通信内容
在这里插入图片描述
method一栏可以看到有GET和POST,GET是指从服务器请求获得数据,而POST是向指定服务器提交被处理的数据。(如果没有Method一栏 可以右击Name处将Method勾选)
点击一个POST文件,点击Preview
在这里插入图片描述
发现其中有我们需要的翻译内容
先来分析以下其中的内容

  • Headers
    在这里插入图片描述
    Request URL:即是urlopen要打开的地址,urlopen要打开的不是浏览器地址栏的地址,在此处是实现翻译机制的地址
    Request Method:请求的方法,在这里是POST形式
    Status Code:状态码
    Remote Address:IP地址加打开的端口号
    Referrer Policy:Referrer策略,no-referrer-when-downgrade表示从https协议降为http协议时不发送referrer给跳转网站的服务器。
    在这里插入图片描述
    Request Headers 是客户端,此处为浏览器,被服务端用来判断是否非人类访问(防止使用代码大批量访问网站,造成服务器压力过大的问题)
    Request Headers 中有一个 User-Agent 用来识别是浏览器访问还是代码访问,其中显示你访问所使用的的系统架构、浏览器实现的核心平台,使用的浏览器类型和版本号,所以当你使用 Python 来访问的时候就会被 User-Agent 识别并屏蔽,不过 User-Agent 是可以进行自定义的,所以这并不能阻拦我们用 Python 进行访问。
    在这里插入图片描述
    From Data: 表单数据,即POST提交的主要内容
    可以看出 i 后即为我们输入的待翻译的内容

  • 操作
    利用Python提交POST表单
    urllib.parse 模块

import urllib.request
import urllib.parse # 用于解析

url= 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
# url: 此处填写Headers里的Request URL的地址
# 本来是http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule,但是会出现errorCode:50的错误
# 所以去掉了_o
data = {}
data ['i'] = 'hello python'
data ['from'] = 'AUTO'
data ['to'] = 'AUTO'
data ['smartresult'] = 'dict'
data ['client'] = 'fanyideskweb'
data ['salt'] = '15795035731918'
data ['sign'] = 'f46e6f89190536f01015ca437b0d5157'
data ['ts'] = '1579503573191'
data ['bv'] = 'd7923968d7ea53caf8c2c98c014700ff'
# ts是时间戳
# salt是时间戳后面加上一位随机数,
# sign是由md5加密过的内容
# bv是浏览器头部信息MD5的值
data ['doctype'] = 'json'
data ['version'] = '2.1'
data ['keyfrom'] = 'fanyi.web'
data ['action'] = 'FY_BY_REALTlME'
#data [''] = ''
# data是一个字典: 此处填入Form Data的内容
data = urllib.parse.urlencode(data).encode('utf-8')
# 将编码后的值赋给data,这里encode就是将unicode格式转化为utf-8的编码形式

response = urllib.request.urlopen(url, data)
# 发出请求,将响应存入response
html = response.read().decode('utf-8')
# windows系统下不用decode也没问题,其他系统可能就需要加decode
# 由于read后得到的是utf-8编码形式的文件,decode就是把其他编码形式变为unicode形式,encode就是把unicode形式变为其他形式
# 这里的decode即为解码

print(html)

执行后输出了我们需要的内容
在这里插入图片描述
输出的是json结构的数据,json是一种轻量级的数据交换格式,即用字符串的形式把Python数据结构给封装起来,就得到了json。
这个字符串内部实际上是包含了一个字典,字典中键“translateResult”的值内部是一个列表中的列表中的字典。
在这里插入图片描述
变为用户输入数据,并简化输出

import urllib.request
import urllib.parse # 用于解析
import json

content = input("请输入需要翻译的内容:")

url= 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
# url: 此处填写Headers里的Request URL的地址
# 本来是http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule,但是会出现errorCode:50的错误
# 所以去掉了_o
data = {}
data ['i'] = content
data ['from'] = 'AUTO'
data ['to'] = 'AUTO'
data ['smartresult'] = 'dict'
data ['client'] = 'fanyideskweb'
data ['salt'] = '15795035731918'
data ['sign'] = 'f46e6f89190536f01015ca437b0d5157'
data ['ts'] = '1579503573191'
data ['bv'] = 'd7923968d7ea53caf8c2c98c014700ff'
# ts是时间戳
# salt是时间戳后面加上一位随机数,
# sign是由md5加密过的内容
# bv是浏览器头部信息MD5的值
data ['doctype'] = 'json'
data ['version'] = '2.1'
data ['keyfrom'] = 'fanyi.web'
data ['action'] = 'FY_BY_REALTlME'
#data [''] = ''
# data是一个字典: 此处填入Form Data的内容
data = urllib.parse.urlencode(data).encode('utf-8')
# 将编码后的值赋给data,这里encode就是将unicode格式转化为utf-8的编码形式

response = urllib.request.urlopen(url, data)
# 发出请求,将响应存入response
html = response.read().decode('utf-8')
# windows系统下不用decode也没问题,其他系统可能就需要加decode
# 由于read后得到的是utf-8编码形式的文件,decode就是把其他编码形式变为unicode形式,encode就是把unicode形式变为其他形式
# 这里的decode即为解码

target = json.loads(html)
print("翻译结果:%s" % (target['translateResult'][0][0]['tgt']))

得到结果
在这里插入图片描述
但是此代码还不能应用于现实生产环境中,因为容易被服务器发现是非人访问或ip访问过于频繁而被屏蔽。


隐藏

为了使我们的程序不会被服务器判定为非人类而被屏蔽,需要对代码进行隐藏。
服务器检查链接,通常情况下是检查链接的Headers里的User-Agent 来判断访问是来自于代码还是来自于浏览器,所以可以通过修改Headers来模拟正常的浏览器访问。
在Python中修改headers要用到类urllib.request.Request

Request

urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

  • headers
    headers 必须是一个字典,可以通过两种途径设置

(1)直接设置一个字典,作为参数传给request

# 方法一:直接将浏览器中的User-Agent的值传入新建的字典,并作为参数传给request
import urllib.request
import urllib.parse # 用于解析
import json

content = input("请输入需要翻译的内容:")
url= 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'

head = {}
head['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'

data = {}
data ['i'] = content
data ['from'] = 'AUTO'
data ['to'] = 'AUTO'
data ['smartresult'] = 'dict'
data ['client'] = 'fanyideskweb'
data ['salt'] = '15795035731918'
data ['sign'] = 'f46e6f89190536f01015ca437b0d5157'
data ['ts'] = '1579503573191'
data ['bv'] = 'd7923968d7ea53caf8c2c98c014700ff'
data ['doctype'] = 'json'
data ['version'] = '2.1'
data ['keyfrom'] = 'fanyi.web'
data ['action'] = 'FY_BY_REALTlME'
data = urllib.parse.urlencode(data).encode('utf-8')

req = urllib.request.Request(url, data, head)

response = urllib.request.urlopen(req)
html = response.read().decode('utf-8')

target = json.loads(html)
target = target['translateResult'][0][0]['tgt']

print("翻译结果:", target)

在这里插入图片描述
(2)在request生成后,通过调用add_header()把它添加进去
Request.add_header(key, val)

# 方法二:
import urllib.request
import urllib.parse # 用于解析
import json

content = input("请输入需要翻译的内容:")
url= 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'

data = {}
data ['i'] = content
data ['from'] = 'AUTO'
data ['to'] = 'AUTO'
data ['smartresult'] = 'dict'
data ['client'] = 'fanyideskweb'
data ['salt'] = '15795035731918'
data ['sign'] = 'f46e6f89190536f01015ca437b0d5157'
data ['ts'] = '1579503573191'
data ['bv'] = 'd7923968d7ea53caf8c2c98c014700ff'
data ['doctype'] = 'json'
data ['version'] = '2.1'
data ['keyfrom'] = 'fanyi.web'
data ['action'] = 'FY_BY_REALTlME'
data = urllib.parse.urlencode(data).encode('utf-8')


req = urllib.request.Request(url, data)
req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')

response = urllib.request.urlopen(req)
html = response.read().decode('utf-8')

target = json.loads(html)
target = target['translateResult'][0][0]['tgt']

print("翻译结果:", target)

在这里插入图片描述
至此完成了服务器对访问对象的检测问题的处理,然而在实际情况中,代码访问服务器进行爬虫,通常情况下的持续访问的速度很快,导致服务器负载,因此服务器会将此ip进行屏蔽,或者返回一个验证码页面来进行非人类行为判断。
针对以上问题有两种解决方法:
(1)延迟提交时间
(2)使用代理

延迟提交时间

# 每次翻译后等5秒再次输入
import urllib.request
import urllib.parse # 用于解析
import json
import time

while True:
    
    content = input("请输入需要翻译的内容(输入“q!”退出程序):")
    if content == 'q!':
        break
    url= 'http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule'
    
    data = {}
    data ['i'] = content
    data ['from'] = 'AUTO'
    data ['to'] = 'AUTO'
    data ['smartresult'] = 'dict'
    data ['client'] = 'fanyideskweb'
    data ['salt'] = '15795035731918'
    data ['sign'] = 'f46e6f89190536f01015ca437b0d5157'
    data ['ts'] = '1579503573191'
    data ['bv'] = 'd7923968d7ea53caf8c2c98c014700ff'
    data ['doctype'] = 'json'
    data ['version'] = '2.1'
    data ['keyfrom'] = 'fanyi.web'
    data ['action'] = 'FY_BY_REALTlME'
    data = urllib.parse.urlencode(data).encode('utf-8')
    
    req = urllib.request.Request(url, data)
    req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')
    
    response = urllib.request.urlopen(req)
    html = response.read().decode('utf-8')
    
    target = json.loads(html)
    target = target['translateResult'][0][0]['tgt']
    
    print("翻译结果:", target)
    time.sleep(5)

代理

代理服务器是介于浏览器和Web服务器之间的一台服务器,浏览器向代理服务器发出请求,Request信号会先送到代理服务器,由代理服务器来取回浏览器所需要的信息并传送给浏览器。

  • 步骤
    (1)参数是一个字典{ ‘类型’ : ‘代理ip:端口号’ }
    proxy_support = urllib.request.ProxyHandler({})
    (2)定制、创建一个 opener
    opener = urllib.request.build_opener(proxy_support)
    可以定制opener来用代理ip进行访问
    (3)
    a.安装 opener【本方法用于替换掉默认的opener】
    urllib.request.install_opener(opener)
    b. 调用 opener【本方法用于临时或特殊情况下使用,不修改默认opener】
    opener.open(url)
import urllib.request

url = 'https://www.ipip.net/ip.html'
# 用于查询当前自己使用的ip

proxy_support = urllib.request.ProxyHandler({'https':'222.95.144.130:3000'})
# 从网络上查询到的免费代理ip和端口号
opener = urllib.request.build_opener(proxy_support)
opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')]

urllib.request.install_opener(opener)

response = urllib.request.urlopen(url)
html = response.read().decode('utf-8')

print(html)

在这里插入图片描述
还可以建立一个ip列表,把采集到的ip地址加进去,以供使用

import urllib.request
import random

url = 'https://www.ipip.net/ip.html'
# 用于查询当前自己使用的ip
iplist = ['58.212.40.126:9999', '113.128.9.171:9999', '61.145.48.94:9999']
proxy_support = urllib.request.ProxyHandler({'https':random.choice(iplist)})
# 从网络上查询到的免费代理ip和端口号
opener = urllib.request.build_opener(proxy_support)
opener.addheaders = [('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')]

urllib.request.install_opener(opener)

response = urllib.request.urlopen(url)
html = response.read().decode('utf-8')

print(html)

在这里插入图片描述


爬取网页上的图片

  • 要求:爬取 http://jandan.net/zoo/ 上的10页图片
  • 操作:打开网页后,进入开发者工具
    检查页码处代码
    在这里插入图片描述
    查看下一页的网页地址格式
    在这里插入图片描述
    检查图片处代码,查看图片处代码格式
    在这里插入图片描述
    由此写爬取代码
# =============================================================================
# 爬取网站上的图片
# =============================================================================
import urllib.request
import os

def url_open(url):
# =============================================================================
#     打开网址,爬取内容
# =============================================================================
    req = urllib.request.Request(url)
    req.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36')
    response = urllib.request.urlopen(url)
    html = response.read()  # 这里不解码是因为,如果解码了那么无法保存图片
    
    return html


def get_url(url, x):
# =============================================================================
#     查找下一页,这里的查找方式有局限性,只是单纯针对jandan网的页面设计
#     不具有可移植性,不同网站不同查找方式
# =============================================================================
    html = url_open(url).decode('utf-8')
    
    a = html.find(x) + 24
    b = html.find('#', a)
    
    return html[a:b]
  
    
def find_imgs(url): 
# =============================================================================
#     取当前页面的所有图片的链接,并保存到列表中
# =============================================================================
    html = url_open(url).decode('utf-8')
    img_addrs = [] # 存放图片地址的列表
    
    a = html.find('img src=')   # 找到图片的地址
    
    while a != -1:      # 循环找图
        b = html.find('.jpg', a, a+255)     # 找到.jpg结尾且不超过255个字符的(即不是jpg格式的图片跳过且返回-1)
        if b != -1:
            img_addrs.append('http:'+ html[a+9:b+4]) # 偏移即a的加(len('img src=')+1),b的加(len('.jpg'))
        else:
            b = a + 9   # 防止一直在一个地方循环          
        a = html.find('img src=', b)        # 从b开始继续找
# =============================================================================
#       测试打印列表
#     for each in img_addrs:
#          print(each)       
# =============================================================================
    return img_addrs


def save_imgs(folder, img_addrs):
# =============================================================================
#     保存图片到指定目录
# =============================================================================
    for each in img_addrs:
        filename = each.split('/')[-1]  # 切割出最后一个反斜杠后的字符作为名字
        with open(filename, 'wb') as f:
            img = url_open(each)
            f.write(img)


def download_pic(folder='img', page=10):
    os.mkdir(folder)    # 建立目录
    os.chdir(folder)    # 改变当前的工作目录到folder='img'
    
    url = "http://jandan.net/zoo/"  # 目标网站
   
    for i in range(10): # 爬取10页内容
        if i == 10 :
            break   
        page_url = url                  # 当前循环爬取的页面
        img_addrs = find_imgs(page_url)   
        save_imgs(folder, img_addrs)
        page_str_ed = get_url(url, 'Older Comments')    # 取下一页
        url = 'http://' + page_str_ed + '#comments'     # 保存下次循环地址
        
if __name__ == '__main__':
    download_pic()

运行结果
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!