python学习笔记分享(三十三)网络爬虫(1)

IT Xiao Ang Zai     2019年3月7号

版本:python3.7

编程软件:Pycharm,Sublime Text 3

作者说明:之前向大家介绍过有关网络爬虫的东西,但知识比较分散,内容不算多,却写了很多文章。现在看来,有必要重新进行网络爬虫的总结了,我还会在之后配合python其他的有关知识以及js等网页内容的讲解,python大佬好多都是js大佬,两者有很多相关的地方。本篇文章,是对之前文章的重新讲解,希望大家有任何宝贵的意见,都可以提出来。

一:网页简单模块urllib模块爬取网页源代码

1.网络爬虫:简单讲,网络爬虫(又称为网页蜘蛛),就是可以在互联网上爬来爬去,捕获我们所需要的资源,并存储起来配合其他地方使用。

2.urllib模块简介

  (1)这个模块是URl和lib两个单词共同构成的:URL就是网页的地址,lib是library(库)的缩写。可以把这个模块理解为网页地址库。

  (2)URL的一般格式为(带方括号[]的为可选项):protocol://hostname[port]/path/[;parameters][?query]#fragment。

  (3)URL的组成部分:

    a.协议部分(protocol):该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符。

    b.域名部分(hostname[port]):存放资源的服务器的域名系统(DNS)主机名(也可以使用IP地址作为域名使用)。

    c.端口部分(post):跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口80。

     d.虚拟目录部分(path):从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。

      e.文件名部分[;parameters?]:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名。

      f.锚部分(fragment):从“#”开始到最后,都是锚部分。锚部分也不是一个URL必须的部分。

      g.参数部分[query]:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分,不包括?。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。

  (4)python3版本把以前版本的urllib2模块(对urllib的补充)等模块合并成为urllib,调用有时看到urllib2类似的代码,可以用urllib模块中的函数代替。

  (5)其实urllib里面有很多模块,相当于一个包。

  (6)我们代开python的参考文档可以看到:

    

这里可以看到,urllib是一个包,里边总共有四个模块。一般第一个模块是最复杂与最重要的,它包含了对服务器请求的发出,跳转,代理和安全。

3.urllib模块爬取网页源码

我们先来爬取一下网页源码,用urllib.request.urlopen()函数就可以访问网页了:


import urllib.request
response = urllib.request.urlopen("https://baike.baidu.com/item/%E4%B8%8A%E5%8F%A4%E5%8D%B7%E8%BD%B45%EF%BC%9A%E5%A4%A9%E9%99%85/353892?fromtitle=%E4%B8%8A%E5%8F%A4%E5%8D%B7%E8%BD%B45&fromid=290703&fr=aladdin")
html = response.read()
print(html)

效果如下:

我们发现它并不是HTML代码,其实python爬取的内容是以utf-8编码的bytes对象,上面有个b,表示这是一个bytes对象,要还原为带中文的html代码,需要对其进行解码,将它变成Unicode编码:

html = html.decode("utf-8")
print(html)

效果如下:

4.实例讲解

(1)下载一只猫

网站:http://placekitten.com/这个网站在后面附上宽度和高度,就可以得到一张对应的图片:

import urllib.request
response = urllib.request.urlopen("http://placekitten.com/408/287")
cat = response.read()
with open('E:\\小猫.jpg',"wb") as f:
    f.write(cat)

我们可以发现这张图片被下载下来了:

其实,urlopen()的参数既可以是一个字符串也可以是Request对象,先把传入的字符串转换为Request对象,然后再传给urlopen函数。因此代码也可以这样写:

req= urllib.request.Request("http://placekitten.com/408/287")
response = urllib.request.urlopen(req)

然后,urlopen()实际上是返回的一个类文件对象,可以用read()读取内容。

(2)文档告诉我们以下三个函数可能以后会用到:

  geturl() -------返回请求的url。

  info()    -------返回一个httplib.HTTPMessage对象,包括远程服务器返回的头信息。

  getcode() -------返回HTTP状态码。

(3)我们用类似的功能爬取其他图片

代码如下:

import urllib.request
req= urllib.request.Request("http://img1.gtimg.com/comic/pics/hv1/229/116/2084/135541909.jpg")
response = urllib.request.urlopen(req)
xiao = response.read()
with open('E:\\小美女.jpg',"wb") as f:
    f.write(xiao)

效果如下:

二:编码简介

中文编码问题一直是程序员头疼的问题,现在我们简介一下字符编码。

要彻底解决字符编码的问题就不能不去了解到底什么是字符编码。计算机从本质上来说只认识二进制中的0和。

1.ASCII

现在我们如何让英文,数字和符号被计算机认识。我们以英文为例,英文中有英文字母(大小写)、标点符号、特殊符号。如果我们将这些字母与符号给予固定的编号,然后将这些编号转变为二进制,那么计算机明显就能够正确读取这些符号,同时通过这些编号,计算机也能够将二进制转化为编号对应的字符再显示给人类去阅读。由此产生了我们最熟知的ASCII码。ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。这样在大部分情况下,英文与二进制的转换就变得容易多了。

2.GB2312

然而,虽然计算机是美国人发明的,但是全世界的人都在使用计算机。现在出现了另一个问题:如何让中文被计算机理解?ASCII 码显然没办法解决这个问题,为了解决这个问题,中国国家标准总局1980年发布《信息交换用汉字编码字符集》提出了GB2312编码,用于解决汉字处理的问题。1995年又颁布了《汉字编码扩展规范》(GBK)。GBK与GB 2312—1980国家标准所对应的内码标准兼容,同时在字汇一级支持ISO/IEC10646—1和GB 13000—1的全部中、日、韩(CJK)汉字,共计20902字。这样我们就解决了计算机处理汉字的问题了。

3.Unicode

现在英文和中文问题被解决了,但新的问题又出现了。全球有那么多的国家不仅有英文、中文还有阿拉伯语、西班牙语、日语、韩语等等。难不成每种语言都做一种编码?基于这种情况一种新的编码诞生了:Unicode。Unicode又被称为统一码、万国码;它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。Unicode支持欧洲、非洲、中东、亚洲(包括统一标准的东亚象形汉字和韩国表音文字)。这样不管你使用的是英文或者中文,日语或者韩语,在Unicode编码中都有收录,且对应唯一的二进制编码。这样大家都开心了,只要大家都用Unicode编码,那就不存在这些转码的问题了,什么样的字符都能够解析了。

4.UTF-8

但是,由于Unicode收录了更多的字符,可想而知它的解析效率相比ASCII码和GB2312的速度要大大降低,而且由于Unicode通过增加一个高字节对ISO Latin-1字符集进行扩展,当这些高字节位为0时,低字节就是ISO Latin-1字符。对可以用ASCII表示的字符使用Unicode并不高效,因为Unicode比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,即UTF(Unicode Transformation Format)。而我们最常用的UTF-8就是这些转换格式中的一种。在这里我们不去研究UTF-8到底是如何提高效率的,你只需要知道他们之间的关系即可。

5.decode()方法将其他编码字符转化为Unicode编码字符。 
encode()方法将Unicode编码字符转化为其他编码字符。

总结:

1.处理英文字符,产生了ASCII码。 
2.处理中文字符,产生了GB2312。 
3.处理各国字符,产生了Unicode。 
4.提高Unicode存储和传输性能,产生了UTF-8,它是Unicode的一种实现形式。

三:查看网页的编码方式

我们在爬取网页时需要该网页是由什么编码的,那么怎么查看网页的编码方式呢,下面有几种方法。

(1)使用浏览器审查元素,找到head标签->chareset,就知道网页是采用何种方式编码的。

(2)自动获取网页编码,可以导入第三方库chardet,用chardet.detect()方法就可以判断网页的编码了。

四:urlopen简介

urlopen有两个重要参数

1.urlopen的参数url

  (1)url不仅可以是一个字符串,也可以是一个Request对象,这就需要我们先定义一个Request对象,然后将这个Request对象作为urlopen的参数使用。不过大部分直接调用urlopen()函数。

(2)urlopen()返回的对象,可以使用read()进行读取,同样也有geturl()、info()、getcode()方法。

2.url的data参数

(1)我们可以使用data参数,向服务器发送数据。根据HTTP规范,GET用于信息获取,POST是向服务器提交数据的一种请求。

可以理解为:

    从客户端向服务器提交数据使用POST;

    从服务器获得数据到客户端使用GET(GET也可以提交,暂不考虑)。

    如果没有设置urlopen()函数的data参数,HTTP请求采用GET方式,也就是我们从服务器获取信息,如果我们设置data参数,HTTP请求采用POST方式,也就是我们向服务器传递数据。

(2)urllib.parse.urlencode()函数

data参数有自己的格式,它是一个基于application/x-www.form-urlencoded的格式,具体格式我们不用了解, 我们可以使用urllib.parse.urlencode()函数将字符串自动转换成上面所说的格式。

五:有道翻译爬虫实例

1.我们先打开有道翻译页面,按照下面步骤:

(1)右键打开审查元素->Network

我们可以看到Name下面出现的内容,我们点击一个并记住它的Request URL:

我们把Request URL和Form Data的内容记录下来,一会要用到。

注:需要删除真实请求地址Request URL中的_o。

(2)输入下面代码

# -*- coding: UTF-8 -*-
from urllib import request
from urllib import parse
import json
 
if __name__ == "__main__":
    Request_URL = 'http://fanyi.youdao.com/translate?smar'
    #创建Form_Data字典,存储上图的Form Data
    Form_Data = {}
    Form_Data['i'] = '小朋友'
    Form_Data['from'] = 'AUTO'
    Form_Data['to'] = 'AUTO'
    Form_Data['smartresult'] = 'dict'
    Form_Data['client'] = 'fanyideskweb'
    Form_Data['salt'] = '1536154689630'
    Form_Data['sign'] = 'cee2be9a62335b781133afa5d2f62c91'
    Form_Data['doctype'] = 'json'
    Form_Data['version'] = '2.1'
    Form_Data['keyfrom'] = 'fanyi.web'
    Form_Data['action'] = 'FY_BY_CLICKBUTTION'
    
    #使用urlencode方法转换标准格式
    data = parse.urlencode(Form_Data).encode('utf-8')
    #传递Request对象和转换完格式的数据
    response = request.urlopen(Request_URL,data)
    #读取信息并解码
    html = response.read().decode('utf-8')
    #使用JSON
    translate_results = json.loads(html)
    #找到翻译结果
    translate_results = translate_results['translateResult'][0][0]['tgt']
    #打印翻译信息
    print("翻译的结果是:%s" % translate_results)

结果如下:

注:JSON是一种数据交换格式,我们如果可以从爬取到的内容中找到JSON格式的数据,是我们想要的,我们将得到的JSON格式的结果进行解析,就可以得到我们想要的数据。

2.网页审查元素的使用(一)

(1)我们先进入有道词典的官网:

我们使用浏览器的"审查元素"功能或检查(qq浏览器)功能,切换到Network窗口,此时下面什么都还没有:

此时我们尝试一下,输入西瓜,我们会发现会拦截到许多文件,这些就是浏览器和客户端的通信内容:

在客户端和服务器之间进行请求-响应时,有两种最常用到的方法是GET和POST。GET是从指定的服务器请求数据,而POST是向指定的服务器提交要被处理的数据(有时也用GET提交数据给服务器)。

(2)我们现在重新输入西瓜

单击有translate的选项,会出现如图所示的页面:

我们先选择Headers选项卡,我们进一步分析里面的内容:

在General下面有五个部分,是关于客户端发出的请求(也称为Request),下面是对一些关键名词的解释:

Request URL:请求的链接地址

Request Method:请求的方法,这里是POST

Status Code:状态码,200表示响应正常

Remote Address:服务器ip地址和端口

Referrer Policy:referrer常被用于分析用户来源等信息。但是也有成为用户的一个不安全因素,比如有些网站直接将 sessionid 或是 token 放在地址栏里传递的,会原样不动地当作 Referrer 报头的内容传递给第三方网站。

所以就有了Referrer Policy ,用于过滤 Referrer 报头内容,目前是一个候选标准,不过已经有部分浏览器支持该标准


(3)在General下面我们还可以发现其他信息:

我们详细讲解一下:
a.Request Headers是客户端发送请求的Headers,这个常常被服务器用来判断是否来自"非人类"访问。就指的是我们用程序来访问,那样服务器的压力就会增大,因此一般服务器不欢迎"非人类"访问。

一般服务器是通过这个User-Agent来识别,普通浏览器会通过该内容向访问网站提供你所使用的浏览器类型等信息。

User-Agent:

Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5558.400 QQBrowser/10.1.1695.400

而使用python访问的话,User-Agent会被定义为Python-urllib/3.4。但是python是智能的,我们可以自定义它来让浏览器不能判断出我们是非人类访问。

b.有的朋友可能会发现,下面还有个Form Data,这个就是POST提交的内容,我们可以发现,里面有我们刚才向服务器提交的西瓜:

我们就有了一个疑问,如何用python提交POST表单呢?其实是urlopen()函数有一个data参数,如果该这个参数赋值,那么HTTP的请求就是使用POST方式;如果data的值是NULL,也就是默认值,那么HTTP的请求就是使用GET方式。

c.有了以上两个知识,我们尝试来写代码:

import urllib.request
import urllib.parse
 
url = "http://fanyi.youdao.com/translate_o?smar"
data = {}
data['i'] = "西瓜"
data['from'] = "AUTO"
data['to'] = "AUTO"
data['smartresult'] = "dict"
data['client'] = "fanyideskweb"
data['salt'] = "1536413873985"
data['sign'] = "1ecd1c28f34d5ec21ab7352d60572d01"
data['doctype'] = "json"
data['version'] = "2.1"
data['keyfrom'] = "fanyi.web"
data['action'] = "FY_BY_CLICKBUTTION"
data['typoResult'] = "false"
data = urllib.parse.urlencode(data).encode('utf-8')
response = urllib.request.urlopen(url,data)
html = response.read().decode('utf-8')
print(html)

我们可以发现得到了如下的信息:

字符串在python内部的表示是Unicode编码,因此,需要做一下编码转换,通常需要以Unicode作为中间编码,即先将返回的bytes对象的数据解码(decode)成Unicode,再以Unicode编码(encode)成另一种编码。

其实返回的是一个JOSN格式的字符串(JSON是一种轻量级的数据交换格式,我们可以理解成用字符串把Pythpn的数据结构封装起来),下面只需要解析这个JOSN格式的字符串即可。
下面我们来完善一下代码:

import urllib.request
import urllib.parse
import json
 
findText = input("请输入翻译的内容:")
url = "http://fanyi.youdao.com/translate?smar"
data = {}
data['i'] = findText
data['from'] = "AUTO"
data['to'] = "AUTO"
data['smartresult'] = "dict"
data['client'] = "fanyideskweb"
data['salt'] = "1536413873985"
data['sign'] = "1ecd1c28f34d5ec21ab7352d60572d01"
data['doctype'] = "json"
data['version'] = "2.1"
data['keyfrom'] = "fanyi.web"
data['action'] = "FY_BY_CLICKBUTTION"
data['typoResult'] = "false"
data = urllib.parse.urlencode(data).encode('utf-8')
response = urllib.request.urlopen(url,data)
html = response.read().decode('utf-8')
result = json.loads(html)
print("%s翻译为英文为:%s" % (findText,(result['translateResult'][0][0]['tgt'])))

效果如下:

当使用的次数多时,服务端会识别出该来源是非人类,会把你屏蔽,在大批量爬取时,不建议采取该方法。

六:反爬虫处理

有些网站不喜欢被程序访问,它们会检查访问的来源,如果识别出来访问的来源不是正常的路径,就会屏蔽你的访问,但是我们可以改进我们的代码,让浏览器误认为我们是正常的访问。

下面是一些方法:

1.修改User-Agent

我们查看User-Agent的文档可以发现,里面详细讲解了Request有一个headers参数,通过设置这个参数,我们可以误让浏览器认为我们是正常的访问。设置这个参数有两种途径:实例化Request对象的时候将headers参数传入进去,或者通过add_header()方法向Request对象添加headers。
(1)第一种方法是实例化Request对象的时候将headers参数传入进去,它要求headers必须是一个字典的形式

代码如下:

import urllib.request
import urllib.parse
import json
 
findText = input("请输入翻译的内容:")
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
head = {}
head['Referer'] = 'http://fanyi.youdao.com'
head['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
                     'Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5558.400 QQBrowser/10.1.1695.400'
data = {}
data['i'] = findText
data['from'] = "AUTO"
data['to'] = "AUTO"
data['smartresult'] = "dict"
data['client'] = "fanyideskweb"
data['salt'] = "1536413873985"
data['sign'] = "45d4bc7902bf704c8adc6d27f5081a55"
data['doctype'] = "json"
data['version'] = "2.1"
data['keyfrom'] = "fanyi.web"
data['action'] = "FY_BY_CLICKBUTTION"
data['typoResult'] = "false"
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')
result = json.loads(html)
print("%s翻译为英文为:%s" % (findText,(result['translateResult'][0][0]['tgt'])))
print(req.headers)

(2)还有另外一种方法,就是在Request对象生成之后,用add_header()方法追加进去

代码如下:

import urllib.request
import urllib.parse
import json
 
findText = input("请输入翻译的内容:")
url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
data = {}
data['i'] = findText
data['from'] = "AUTO"
data['to'] = "AUTO"
data['smartresult'] = "dict"
data['client'] = "fanyideskweb"
data['salt'] = "1536413873985"
data['sign'] = "45d4bc7902bf704c8adc6d27f5081a55"
data['doctype'] = "json"
data['version'] = "2.1"
data['keyfrom'] = "fanyi.web"
data['action'] = "FY_BY_CLICKBUTTION"
data['typoResult'] = "false"
data = urllib.parse.urlencode(data).encode('utf-8')
req = urllib.request.Request(url,data)
req.add_header('Referer','http://fanyi.youdao.com')
req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                            'Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5558.400 QQBrowser/10.1.1695.400')
response = urllib.request.urlopen(req)
response = urllib.request.urlopen(req)
html = response.read().decode('utf-8')
result = json.loads(html)
print("%s翻译为英文为:%s" % (findText,(result['translateResult'][0][0]['tgt'])))
print(req.headers)

2.通过修改User-Agen实现隐藏,这是最简单的一种可以"欺骗浏览器"的方法。但是浏览器还是会识别出我们的程序,它用一个记录每个IP的访问频率的方法,在单位时间内,如果访问频率超过了一个阈值,便认为我们可能是用爬虫来进行的,这时它会要求用户填写一个验证码,我们的爬虫程序当然不能够写验证码,于是就被屏蔽了。

我们针对这种情况,有两种容易实现的方法。

(1)延迟提交数据

我们可以延迟提交数据,用time模块来实现:

import urllib.request
import urllib.parse
import json
import time
 
while True:
    findText = input("请输入yes/no:")
    if findText == 'no':
        break
    else:
        findText = input("请输入要翻译的内容:")
        url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
        data = {}
        data['i'] = findText
        data['from'] = "AUTO"
        data['to'] = "AUTO"
        data['smartresult'] = "dict"
        data['client'] = "fanyideskweb"
        data['salt'] = "1536413873985"
        data['sign'] = "45d4bc7902bf704c8adc6d27f5081a55"
        data['doctype'] = "json"
        data['version'] = "2.1"
        data['keyfrom'] = "fanyi.web"
        data['action'] = "FY_BY_CLICKBUTTION"
        data['typoResult'] = "false"
        data = urllib.parse.urlencode(data).encode('utf-8')
        req = urllib.request.Request(url,data)
        req.add_header('Referer','http://fanyi.youdao.com')
        req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                'Chrome/63.0.3239.26 Safari/537.36 Core/1.63.5558.400 QQBrowser/10.1.1695.400')
        response = urllib.request.urlopen(req)
        response = urllib.request.urlopen(req)
        html = response.read().decode('utf-8')
        result = json.loads(html)
        print("%s翻译为英文为:%s" % (findText,(result['translateResult'][0][0]['tgt'])))
        time.sleep(10)

但是这样做会使程序的工作效率大大降低。

(2)使用代理

我们可以把需要访问的网址告诉代理,让代理替你访问,然后再转给你。服务器看到的是代理的IP地址,不是你的IP地址,这样,就会让服务器不能识别出是否是人类访问。

我们可以把需要访问的网址告诉代理,让代理替你访问,然后再转给你。服务器看到的是代理的IP地址,不是你的IP地址,这样,就会让服务器不能识别出是否是人类访问。

使用代理的步骤如下:

(1)proxy_support = urllib.request.ProxyHandler({})

它的参数是一个字典,字典的键是代理的类型,例如http,ftp或https,字典的值就是代理的IP地址和对应的端口号。

(2)opener = urllib.request.build_opener(proxy_support)

我们可以把这个opener看作是一个私人定制,当使用urlopen()函数打开一个网页的时候,就是使用默认的opener在工作。

而这个opener是可以定制的,我们可以给它定制特殊的headers,或者给它定制指定的代理IP,用build_opener()函数创建我们的私人定制。

(3)urllib.request.install_opener(opener)

我们可以用这个方法把我们定制好的opener安装到系统中。之后,我们只需要用普通的urlopen()函数,就可以使用我们定制好的opener进行工作,如果你不想替换我们默认的opener,你可以在每次有特殊需要的时候,用opener.open()方法打开网页。

我们搜索"代理IP"可以获得一个免费的代理IP地址(这里我们使用211.138.121.38:80),通过访问http://www.whatismyip.com.tw可以查看当前的IP。

代码如下:

import urllib.request
 
url = "http://www.whatismyip.com.tw/"
proxy_support = urllib.request.ProxyHandler({'http':'211.138.121.38:80'})
opener = urllib.request.build_opener(proxy_support)
urllib.request.install_opener(opener)
response = urllib.request.urlopen(url)
html = response.read().decode('utf-8')
print(html)

在之后的文章中会详细讲解有关爬虫的正则表达式的内容,欢迎大家评论与交流。

猜你喜欢

转载自blog.csdn.net/ITxiaoangzai/article/details/88294533