Python全栈开发:数据分析
上QQ阅读APP看书,第一时间看更新

1.3.1 urllib库

urllib库是Python内置的网络请求库,无须安装即可使用,urllib库包含了4个模块,即parse模块、request模块、error模块和robotparser模块。

1.parse模块

该模块为工具模块,定义了处理URL的标准接口,如实现URL各部分的抽取、合并及链接转换等,其常用的函数如下。

1)urlparse()函数

该函数用于URL的识别,并返回一个ParseResult对象,其语法格式如下:

     urlparse(url)

其中,参数url表示URL,示例代码如下:

     #资源包\Code\chapter1\1.3\0101.py
     import urllib.parse
     url='http://www.oldxia.com/xzd/upload/forum.php?mod=forumdisplay&fid=2'
     result=urllib.parse.urlparse(url)
     #输出结果为ParseResult(scheme='http',netloc='www.oldxia.com',path='/xzd/upload/forum.
     php',params='',query='mod=forumdisplay&fid=2',fragment='')
     print(result)
     #可以通过ParseResult对象的下标或属性名进行访问
     #输出结果为http www.oldxia.com
     print(result[0],result.netloc)

这里需要注意的是,ParseResult对象包含6个属性,分别为属性scheme(协议)、属性netloc(域名)、属性path(访问路径)、属性params(参数)、属性query(查询条件)和属性fragment(锚点)。

2)urlunparse()函数

该函数用于将协议、域名、访问路径、参数、查询条件及锚点拼接成URL,其语法格式如下:

     urlunparse(components)

其中,参数components表示协议、域名、访问路径、参数、查询条件及锚点所组成的序列,示例代码如下:

     #资源包\Code\chapter1\1.3\0102.py
     import urllib.parse
     data=['http','www.oldxia.com','/xzd/upload/forum.php','oldxia','wd=python','python']
     result=urllib.parse.urlunparse(data)
     #输出结果为
     http://www.oldxia.com/xzd/upload/forum.php;oldxia?wd=python#python
     print(result)
3)urlsplit()函数

该函数用于URL的识别,并返回一个SplitResult对象,其语法格式如下:

     urlsplit(url)

其中,参数url表示URL,示例代码如下:

     #资源包\Code\chapter1\1.3\0103.py
     import urllib.parse
     url='http://www.oldxia.com/xzd/upload/forum.php?mod=forumdisplay&fid=2'
     result=urllib.parse.urlsplit(url)
     #输出结果为SplitResult(scheme='http',netloc='www.oldxia.com',path='/xzd/upload/forum.
     php',query='mod=forumdisplay&fid=2',fragment='')
     print(result)
     #可以通过ParseResult对象的下标或属性名进行访
     #输出结果为http www.oldxia.com
     print(result[0],result.netloc)

这里需要注意的是,SplitResult对象包含5个属性,分别为属性scheme(协议)、属性netloc(域名)、属性path(访问路径和参数)、属性query(查询条件)和属性fragment(锚点)。

4)urlunsplit()函数

该函数用于对协议、域名、访问路径和参数、查询条件及锚点拼接成URL,其语法格式如下:

     urlunsplit(components)

其中,参数components表示协议、域名、访问路径和参数、查询条件及锚点所组成的序列,示例代码如下:

     #资源包\Code\chapter1\1.3\0104.py
     import urllib.parse
     data=['http','www.oldxia.com','/xzd/upload/forum.php;oldxia','wd=python','python']
     result=urllib.parse.urlunsplit(data)
     #输出结果为
     http://www.oldxia.com/xzd/upload/forum.php;oldxia?wd=python#python
     print(result)
5)urljoin()函数

该函数用于将基本路径和相对路径连接成绝对路径,其语法格式如下:

     urljoin(base,url)

其中,参数base表示基本路径;参数url表示相对路径,示例代码如下:

     #资源包\Code\chapter1\1.3\0105.py
     import urllib.parse
     result=urllib.parse.urljoin("http://www.oldxia.com","/index.html")
     #输出结果为http://www.oldxia.com/index.html
     print(result)
6)urlencode()函数

该函数用于对字典类型的请求参数进行编码,其语法格式如下:

     urlencode(query)

其中,参数query表示字典类型的请求参数,示例代码如下:

     #资源包\Code\chapter1\1.3\0106.py
     import urllib.parse
     data={'name':'夏正东','age':35}
     url_parse=urllib.parse.urlencode(data)
     #输出结果为name=%E5%A4%8F%E6%AD%A3%E4%B8%9C&age=35
     print(url_parse)
7)parse_qs()函数

该函数用于将字符串类型的请求参数转换为字典类型的请求参数,其语法格式如下:

     parse_qs(qs)

其中,参数qs表示字符串类型的请求参数,示例代码如下:

     #资源包\Code\chapter1\1.3\0107.py
     import urllib.parse
     qs='name=夏正东&age=35'
     result=urllib.parse.parse_qs(qs)
     #输出结果为{'name':['夏正东'],'age':['35']}
     print(result)
8)parse_qsl()函数

该函数用于将字符串类型的请求参数转换为元组组成的列表类型的请求参数,其语法格式如下:

     parse_qsl(qs)

其中,参数qs表示字符串类型的请求参数,示例代码如下:

     #资源包\Code\chapter1\1.3\0108.py
     import urllib.parse
     qs='name=夏正东&age=35'
     result=urllib.parse.parse_qsl(qs)
     #输出结果为[('name','夏正东'),('age','35')]
     print(result)
9)quote()函数

该函数用于对字符串进行编码,其语法格式如下:

     quote(string)

其中,参数string表示字符串,示例代码如下:

     #资源包\Code\chapter1\1.3\0109.py
     import urllib.parse
     keyword='夏正东'
     result='http://www.oldxia.com/'+urllib.parse.quote(keyword)
     #输出结果为http://www.oldxia.com/%E5%A4%8F%E6%AD%A3%E4%B8%9C
     print(result)
10)unquote()函数

该函数用于对编码后的字符串进行解码,其语法格式如下:

     unquote(string)

其中,参数string表示编码后的字符串,示例代码如下:

     #资源包\Code\chapter1\1.3\0110.py
     import urllib.parse
     url='http://www.oldxia.com/%E5%A4%8F%E6%AD%A3%E4%B8%9C'
     result=urllib.parse.unquote(url)
     #输出结果为http://www.oldxia.com/夏正东
     print(result)

2.request模块

该模块是最基本的HTTP请求模块,用来模拟发送HTTP请求。

1)发送HTTP请求

可以通过urlopen()函数发送HTTP请求,并返回HTTPResponse对象,其语法格式如下:

     urlopen(url,data,timeout)

其中,参数url表示字符串类型的URL网址,也可以是一个Request对象(该内容将在后续为读者详细讲解);参数data为可选参数,表示Bytes类型的数据,如果省略该参数,则表示使用GET方法发送HTTP请求,否则表示使用POST方法发送HTTP请求;参数timeout为可选参数,表示网站的访问超时时间。

此外,通过HTTPResponse对象的相关属性和方法可以获取HTTP请求过程中的相关信息,具体如表1-8所示。

表1-8 HTTPResponse对象的相关属性和方法

以下为发送HTTP请求的相关示例代码。

(1)爬取百度首页的相关数据,示例代码如下:

     #资源包\Code\chapter1\1.3\0111.py
     import urllib.request
     url="http://www.baidu.com/"
     #使用GET方法发送请求
     response=urllib.request.urlopen(url)
     print(response.getheader('Server'))
     print('===================')
     print(response.getheaders())
     print('===================')
     print(response.fileno())
     print('===================')
     print(response.geturl())
     print('===================')
     print(response.info())
     print('===================')
     print(response.getcode())
     print('===================')
     print(response.status)
     print('===================')
     print(response.version)
     print('===================')
     print(response.msg)
     print('===================')
     print(response.reason)
     print('===================')
     print(response.Debuglevel)
     print('===================')
     response.close()
     print('===================')
     res=response.read().decode("utf-8")
     with open("baidu.html","w",encoding="utf-8")as f:
         f.write(res)

(2)访问老夏学院的HTTP请求测试页面,示例代码如下:

     #资源包\Code\chapter1\1.3\0112.py
     import urllib.request
     import urllib.parse
     #构造POST方法的请求参数
     data=Bytes(urllib.parse.urlencode({'name':'夏正东','age':35}),encoding='utf-8')
     #使用POST方法发送请求
     response=urllib.request.urlopen('http://www.oldxia.com/http_test/post.php',data=data)
     print(response.read().decode('utf-8'))
2)构造HTTP请求对象

为了防止反爬虫技术阻止网络爬虫爬取数据,通常在使用网络爬虫向服务器发送HTTP请求时,需要构造HTTP请求对象,以便于在其内部设置请求头等相关信息,达到模拟浏览器进行HTTP请求的目的。

可以通过Request()函数构造请求对象,其语法格式如下:

     Request(url,data,headers)

其中,参数url表示URL;参数data为可选参数,表示Bytes类型的数据,如果省略该参数,则表示使用GET方法发送HTTP请求,否则表示使用POST方法发送HTTP请求;参数headers表示传递的请求头数据,其类型为字典。

以下为构造HTTP请求对象的相关示例代码。

(1)访问老夏学院的HTTP请求测试页面,示例代码如下:

     #资源包\Code\chapter1\1.3\0113.py
     import urllib.request
     import urllib.parse
     url='http://www.oldxia.com/http_test/get.php?name=xzd&age=35'
     headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36
     Core/1.70.3868.400 QQBrowser/10.8.4394.400"}
     #使用GET方法发送请求
     req=urllib.request.Request(url=url,headers=headers)
     response=urllib.request.urlopen(req)
     print(response.read().decode('utf-8'))

(2)爬取百度首页的源代码,示例代码如下:

     #资源包\Code\chapter1\1.3\0114.py
     import urllib.request
     url="http://www.baidu.com/"
     headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36
     Core/1.70.3868.400 QQBrowser/10.8.4394.400"}
     #使用GET方法发送请求
     req=urllib.request.Request(url=url,headers=headers)
     response=urllib.request.urlopen(req)
     res=response.read().decode("utf-8")
     with open("baidu.html","w",encoding="utf-8")as f:
         f.write(res)

(3)爬取豆瓣电影分类排行榜中动作片的封面图片,并将相关信息保存至JSON文件中,示例代码如下:

     #资源包\Code\chapter1\1.3\0115.py
     from urllib import request,parse
     import json
     #请求URL,如图1-4所示
     url="https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&"
     headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36
     Core/1.70.3741.400 QQBrowser/10.5.3863.400"}
     start=input("请输入开始采集的位置:")
     limit=input("请输入要采集的数量:")
     #GET方法的请求参数,如图1-5所示
     form={"start":start,"limit":limit}
     url_parse=parse.urlencode(form)
     #HTTP请求的URL
     full_url=url+url_parse
     #使用GET方法发送请求
     req=request.Request(url=full_url,headers=headers)
     response=request.urlopen(req)
     res=response.read().decode("utf-8")
     results=json.loads(res)
     items=[]
     for result in results:
         dict={}
         #获取电影名称
         title=result["title"]
         #获取电影评分
         score=result["score"]
         #获取电影封面图片URL
         img_link=result["cover_url"]
         dict["title"]=title
         dict["score"]=score
         dict["img_link"]=img_link
         items.append(dict)
         print(f"正在下载:{title}...")
         request.urlretrieve(img_link,f"data/{title}.jpg")
         print(f"{title}下载完毕!")
     json.dump(items,open("douban.json","w",encoding="utf-8"),ensure_ascii=False,indent=4)

图1-4 请求URL

图1-5 GET方法的请求参数

(4)访问老夏学院的HTTP请求测试页面,示例代码如下:

     #资源包\Code\chapter1\1.3\0116.py
     import urllib.request
     import urllib.parse
     import json
     url='http://www.oldxia.com/http_test/post.php'
     headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36
     Core/1.70.3868.400 QQBrowser/10.8.4394.400"}
     data=Bytes(urllib.parse.urlencode({'name':'夏正东','age':35}),encoding='utf-8')
     #使用POST方法发送请求
     req=urllib.request.Request(url=url,data=data,headers=headers)
     response=urllib.request.urlopen(req)
     print(response.read().decode('utf-8'))
     print('=========================')
     #JSON数据
     json_str={'name':'于萍','age':65}
     #使用POST方法发送请求
     req=urllib.request.Request(url=url,data=Bytes(json.dumps(json_str),'utf8'),headers=
     headers)
     response=urllib.request.urlopen(req)
     print(response.read().decode('utf-8'))

(5)百度翻译API,示例代码如下:

     #资源包\Code\chapter1\1.3\0117.py
     import urllib.request
     import urllib.parse
     import json
     #请求URL,如图1-6所示
     url="https://fanyi.baidu.com/sug"
     word=input("请输入要翻译的单词:")
     headers={"User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.25 Safari/537.36
     Core/1.70.3868.400 QQBrowser/10.8.4394.400"}
     #POST方法的请求参数,如图1-7所示
     data={"kw":word}
     #使用POST方法发送请求
     data=Bytes(urllib.parse.urlencode(data),encoding='utf-8')
     req=urllib.request.Request(url=url,headers=headers,data=data)
     response=urllib.request.urlopen(req)
     res=response.read().decode("utf-8")
     #百度翻译返回的数据格式为JSON格式,需要进行转换
     result=json.loads(res)
     print(result["data"][0]['v'])

图1-6 请求URL

图1-7 POST方法的请求参数

3)模拟登录

在访问网站的过程中,经常会遇到需要登录的情况,这就给编写网络爬虫增加了一定的难度,即某些需要爬取的页面必须在登录网站后才能正常爬取,而此时,就可以使用模拟登录技术,进而获取指定页面的内容。

在学习模拟登录的相关技术之前,首先了解一下什么是会话。

会话指的是客户端与服务器之间的一系列交互动作。理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆。例如,用户A在超市购买的任何商品都应该放在用户A的购物车内,并且无论用户A在何时购买的商品,也都应该放入用户A的购物车内,而不能放入其他用户的购物车内,因为这都属于用户A的购物行为。

那么,如何使多个会话不发生混淆呢?这就需要进行会话跟踪。

但是,通过之前的学习得知HTTP是一种无状态协议,一旦数据交换完毕,客户端与服务器的连接就会关闭,再次交换数据则需要建立新的连接,也就是说服务器无法保存客户端的状态,这就意味着服务器无法从连接上进行会话跟踪。例如,用户A在超市购买了1件商品并放入购物车内后,当用户A再次购买商品时,超市就已经无法判断该购物行为是否属于用户A。

所以进行会话跟踪必须引入一种特殊的机制,而Cookie和Session就是用来进行会话跟踪的特殊机制。

(1)Cookie是一种客户端会话跟踪的机制。是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求时都会带上这些特殊的信息,即当用户使用浏览器访问一个支持Cookie的网站时,用户会提供包括用户名在内的相关信息,并提交至服务器,然后,如果服务器需要记录该用户的状态,则在向客户端返回相应数据的同时也会返回这些信息,但是这些信息并不是存放在响应体中,而是存放于响应头的首部字段Set-Cookie中,表示在客户端建立一个Cookie;最后,当客户端接收到来自服务器的响应后,客户端会将这些信息存放于一个统一的位置;自此,客户端再向服务器发送请求时,都会将相应的Cookie再次发送至服务器,而此次的Cookie信息则存放于请求头的首部字段Cookie中。

有了Cookie这样的技术,服务器在接收到来自客户端的请求后,就能通过分析存放于请求头中的Cookie获得客户端的特有信息,从而动态地生成与该客户端相对应的内容,以此来辨认用户的状态,进而可以轻松实现登录等需要会话跟踪的页面。

(2)随着网络技术的不断发展,Session应运而生。Session是一种服务器会话跟踪机制,其在使用上比Cookie更简单,并且安全性较Cookie也有很大的提升,但是相应地也增加了服务器的存储压力。

Session是服务器为客户端所开辟的内存空间,其中保存的信息用于会话跟踪。当客户端访问服务器时,会在服务器开辟一块内存,这块内存就叫作Session;与此同时,服务器会为该Session生成唯一的sessionID,而这个sessionID将在本次响应中返回客户端,并可以通过3种方式进行保存,一是借助Cookie机制,即把sessionID放在Cookie中,其名称类似于SESSIONID,二是URL重写,三是表单隐藏字段;最后,当客户端再次发送请求时,会将sessionID一并发送,服务器在接收到该请求后,会根据sessionID找到相应的Session,从而再次使用。

下面通过一个例子,以使读者深入了解Cookie和Session之间的区别与联系。

当顾客去超市购物时,一般情况下,超市会提供会员卡,即当顾客购买商品时出示会员卡就会获得相应的优惠待遇,接下来就以此为例具体分析一下:

一是该超市的收银员很厉害,能记住每位办过会员卡的顾客,每当顾客结账时,收银员凭借其记忆就可以知道该顾客是否为会员,进而判断该顾客是否可以享受相应的优惠待遇。这种方式就是协议本身支持会话跟踪。

二是该超市发给顾客一张实体的会员卡,并且该实体会员卡一般还会具有有效期限,而超市并不掌握所有发放的实体会员卡的明细,所以顾客在每次结账时,必须携带该实体会员卡,并需要出示实体会员卡,这样才可以享受相应的优惠待遇。这种方式就是客户端会话跟踪的机制,即Cookie。

三是该超市发给顾客一张实体的会员卡,并且超市掌握所有发放的实体会员卡的明细,所以顾客在每次结账时,不必携带该实体会员卡,而是只需告知收银员实体会员卡的卡号,便可以享受相应的优惠待遇。这种做法就是服务器会话跟踪的机制,即Session。

此外,关于Session还有一种误解,即“只要关闭浏览器,Session就会消失”。其实经过之前的学习,这个误解已经很好解释了,读者完全可以从上述的例子中得出答案,即除非顾客主动向超市提出注销实体会员卡,否则超市绝对不会轻易删除顾客的实体会员卡信息。对于Session来讲也是一样的,除非客户端通知服务器删除一个Session,否则服务器会一直保留该Session,然而,客户端的浏览器从不会主动地在关闭之前通知服务器其将要关闭,因此服务器根本没有机会得知客户端的浏览器已经关闭,所以服务器也就不会删除Session,但之所以会有这种误解,是因为大部分Session是借助Cookie机制来保存sessionID的,即关闭客户端的浏览器后,随着Cookie的消失,导致sessionID被删除,所以当再次连接服务器时,也就无法找到原来的Session,而如果服务器在客户端所建立的Cookie被保存到硬盘上,或者使用某种方式改写客户端发出的请求头,将原来的sessionID发送给服务器,则再次打开客户端的浏览器时,仍然可以找到原来的Session。此外,由于关闭客户端的浏览器并不会导致服务器的Session被删除,所以服务器会为Session设置一个失效时间,即当距离客户端上次使用Session的时间超过该失效时间时,服务器就可以认为客户端已经停止了活动,从而会删除该Session,以达到节省存储空间的目的。

在学习完会话、会话跟踪等知识点后,接着来学习一下如何进行模拟登录。

模拟登录可以分为两种方式,即手动添加Cookie和自动保存Cookie。

(1)手动添加Cookie,该方法的应用过程很简单。首先,需要在登录页面手动输入正确的登录账号和登录密码,然后,获取登录成功后待爬取页面的Cookie并手动保存;最后,将该Cookie添加至网络爬虫程序请求头的首部字段Cookie中,这样便可以在不输入登录账号和登录密码的情况下,再次访问需要爬取的页面。

下面以爬取快代理的账户管理页面为例,讲解如何通过手动添加Cookie的方式进行模拟登录。

当在不使用Cookie的情况下,使用网络爬虫爬取快代理的账户管理页面(https://www.kuaidaili.com/usercenter/)时,会被强制跳转至登录页面,因为该页面只有在正常登录后才可以访问。此时,就可以通过手动添加Cookie的方式进行模拟登录,以爬取账户管理页面的内容,其步骤如下:

第1步,进入快代理的登录页面,如图1-8所示,输入正确的登录账号和登录密码,并单击“登录”按钮。

第2步,成功登录后,在当前页面打开开发者工具,如图1-9所示,并单击右上角的账户名,即可进入账户管理页面。

图1-8 登录页面

图1-9 成功登录后的界面

第3步,进入账户管理页面后,在开发者工具中手动保存当前页面的Cookie,如图1-10所示。

第4步,将该Cookie添加到网络爬虫请求头的首部字段Cookie中即可。

示例代码如下:

     #资源包\Code\chapter1\1.3\0118.py
     import urllib.request
     #请求URL
     url="https://www.kuaidaili.com/usercenter/overview"
     headers={
        "User-Agent":"Mozilla/5.0(Windows NT 10.0;WOW64)AppleWebKit/537.36(KHTML,like
     Gecko)Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3741.400 QQBrowser/10.5.3863.400",
     #Cookie,如图1-26所示
        "Cookie":"channelid=bdtg_a10_a10a1;sid=1654743338035518;_gcl_au=1.1.662463456.
     1654743338;_ga=GA1.2.228310457.1654743339;_gid=GA1.2.541324931.1654743339;Hm_lvt_
     7ed65b1cc4b810e9fd37959c9bb51b31      =      1654743339,     1654754105;      sessionid     =
     c29d392cbcdeddaa8bf12e3940514a51;Hm_lpvt_7ed65b1cc4b810e9fd37959c9bb51b31=1654754192"
     }
     req=urllib.request.Request(url=url,headers=headers)
     response=urllib.request.urlopen(req)
     html_code=response.read().decode('utf-8')
     with open("kuaidaili.html","w",encoding="utf-8")as f:
          f.write(html_code)

图1-10 当前页面的Cookie

(2)自动保存Cookie,该方法的应用过程与手动添加Cookie登录相比更加复杂,因为该过程需要应用Handler处理器。

下面就以爬取快代理的账户管理页面为例,讲解一下如何通过自动保存Cookie的方式进行模拟登录。

当在不使用Cookie的情况下,使用网络爬虫爬取快代理的账户管理页面(https://www.kuaidaili.com/usercenter/)时,会被强制跳转至登录页面,因为该页面只有在正常登录后才可以访问。此时,就可以通过自动保存Cookie的方式进行模拟登录,以爬取账户管理页面的内容,其步骤如下:

第1步,使用http.cookiejar模块中的CookieJar()方法自动保存在登录页面登录成功后的Cookie。

第2步,使用BaseHandler类的子类HTTPCookieProcessor创建HTTPCookieProcessor对象。

第3步,使用request模块中的build_opener()方法创建opener对象,因为该对象可以自动保存Cookie,并且可以实现会话保持。

第4步,对需要爬取的页面发送包含上述已经自动保存Cookie的请求头的HTTP请求,这里需要注意的是,不能直接使用urlopen()方法进行发送,而是需要使用opener对象的open()方法进行发送。

示例代码如下:

4)使用代理IP

在运行网络爬虫时,经常会遇到这样的情况,即刚开始网络爬虫运行一切正常,并且可以正常抓取所需要的数据,然而随着时间的推移就会出现错误,例如“403 Forbidden”,而出现这种错误的原因就是爬取的网站采取了一些反爬虫措施,即网站会检测访问IP在单位时间内的请求次数,如果超出了网站设定的阈值,就会拒绝为该请求提供服务,并返回相关的错误信息,而这种情况常常称为“封禁IP”。

为了应对该种反爬虫措施,需要将IP进行伪装,即需要使用代理IP,其根据匿名程度,可以分为以下3种:

(1)高匿名代理IP,该代理IP会将数据包原封不动地进行转发,这样服务器就会认为其是一个普通的客户端在进行访问,但其记录的IP是代理服务器的IP。

(2)普通匿名代理IP,该代理IP会在数据包上进行一定的改动,因此服务器有可能发现其使用的是代理服务器,也有一定概率追查到客户端的真实IP。

(3)透明代理IP,该代理IP不但改动了数据包,而且会告知服务器其客户端的真实IP,所以这种代理IP除了能用缓存技术提高浏览速度,以及使用内容过滤提高安全性之外,并无其他显著作用。

再来学习一下设置代理IP的3种常用的方式,其包括以下3种:

(1)使用免费代理服务,该方式提供的代理IP质量整体较差,建议从中选取高匿名代理IP,但是由于该种方式是免费的,所以实际上其所提供的可用高匿名代理IP并不多,并且稳定性不佳,使用前需要进行筛选。

(2)使用付费代理服务,该方式提供的代理IP质量较免费代理服务要高出很多,并且种类繁多,便于用户根据具体的项目需求进行选择。

(3)ADSL拨号,该种方式应用了ADSL拨号的特性,即拨一次号,其IP也随之更换,其特点就是稳定性较高。

下面就以使用百度查询当前IP为例,讲解一下如何使用代理IP。

由于网络中可用的高质量免费代理IP不多,并且其稳定性较差,所以本例使用快代理提供的收费代理IP进行访问,其步骤如下:

第1步,登录快代理,并购买“私密代理”服务。

第2步,进入“我的私密代理”,如图1-11所示,并单击“提取代理”。

图1-11 我的私密代理

第3步,在“提取私密代理”页面(如图1-12所示)中单击“立即提取”,这样就可以看到所提取出来的代理IP,如图1-13所示。

图1-12 提取私密代理

图1-13 私密代理提取结果

第4步,使用BaseHandler类的子类ProxyHandler创建ProxyHandler对象,其语法格式如下:

     ProxyHandler(proxies)

其中,参数proxies表示代理IP,其可分为两种,即免费代理IP和收费代理IP,具体格式如表1-9所示。

表1-9 参数proxies的格式

第5步,使用request模块中的build_opener()方法创建opener对象,并使用opener对象的open()方法发送HTTP请求即可。

示例代码如下:

     #资源包\Code\chapter1\1.3\0120.py
     from urllib import request
     url='https://www.baidu.com/s?wd=ip'
     #免费代理IP
     #proxy={
     #"http":"http://42.56.238.40:3000",
     #"https":"http://42.56.238.40:3000"
     #}
     #收费代理IP
     proxy={
         "http":"http://xiazhengdong:3p0h090r@27.158.237.186:19674",
         "https":"http://xiazhengdong:3p0h090r@27.158.237.186:19674"
     }
     headers={
         "User-Agent":"Mozilla/5.0(Windows NT 10.0;Win64;x64)
     AppleWebKit/537.36(KHTML,like Gecko)Chrome/74.0.3729.169 Safari/537.36",
     }
     handler=request.ProxyHandler(proxies=proxy)
     opener=request.build_opener(handler)
     req=request.Request(url=url,headers=headers)
     new_res=opener.open(req)
     html_code=new_res.read().decode('utf-8')
     with open("baidu.html","w",encoding="utf-8")as f:
          f.write(html_code)

此时,运行baidu.html文件,其显示的IP为“27.158.237.186福建省漳州市电信”,如图1-14所示,而笔者的真实IP为“113.234.4.216辽宁省大连市甘井子区联通”,如图1-15所示。

3.error模块

该模块为异常处理模块,主要用于处理由request模块产生的异常。

该模块中常用的类如下。

1)URLError类

该类继承于OSError类,request模块产生的所有异常都可以由该类进行处理。

该类的实例对象具有1个属性,即属性reason,用于表示产生异常的原因,示例代码如下:

图1-14 使用代理IP后所查询到的IP

图1-15 真实IP

     #资源包\Code\chapter1\1.3\0121.py
     import urllib.error
     import urllib.request
     try:
     #请求一个不存在的URL
         responses=urllib.request.urlopen('http://www.oldxia.com/python')
     except urllib.error.urlError as e:
         print(type(e))
         print(e.reason)
2)HTTPError类

该类继承于URLError类,专门用于处理HTTP请求的异常。

该类的实例对象具有3个属性,即属性reason、属性code和属性headers,分别用于表示产生异常的原因、响应状态码和请求头的信息,示例代码如下:

     #资源包\Code\chapter1\1.3\0122.py
     import urllib.error
     import urllib.request
     try:
     #请求一个不存在的URL
         responses=urllib.request.urlopen('http://www.oldxia.com/python')
     except urllib.error.HTTPError as e:
         print(f'异常原因:{e.reason}')
         print(f'响应状态码:{e.code}')
         print(f'请求头:\n{e.headers}')
     except urllib.error.urlError as e:
         print(e.reason)

4.robotparser模块

该模块主要用于实现网站Robots协议的分析,并判断网站是否可以被爬取。

在正式开始学习robotparser模块之前,先来了解一下Robots协议。

Robots协议(也称为爬虫协议、机器人协议等)的全称是网络爬虫排除标准(Robots Exclusion Protocol)。

Robots协议是国际互联网界通行的道德规范,其基于以下原则建立:一是搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权;二是网站有义务保护其使用者的个人信息和隐私不被侵犯。

Robots协议有以下4点作用:第一,告知搜索引擎哪些页面可以爬取,哪些页面不可以爬取;第二,可以屏蔽一些网站中比较大的文件,例如图片、音乐、视频等,节省服务器带宽;第三,可以屏蔽站点的一些死链接,以便于搜索引擎抓取网站内容;第四,设置网站地图链接,方便引导网络爬虫爬取页面。

Robots协议通常是以robots.txt的文件形式存在的,并且一般存放于网站的根目录下。

以下就是一个robots.txt文件,其内容如下:

     User-agent:*
     Allow:/xzd/upload/
     Disallow:/

其中,User-agent表示对Robots协议有效的搜索引擎;Disallow表示不允许爬取的目录;Allow表示允许爬取的目录,一般与Disallow一起使用。

可以通过robotparser模块中的RobotFileParser类对robots.txt文件进行解析,其常用的方法如下。

1)set_url()方法

该方法用于设置robots.txt文件的URL,其语法格式如下:

     set_url(url)

其中,参数url表示URL。

2)read()方法

该方法用于读取robots.txt文件,其语法格式如下:

     read()
3)can_fetch()方法

该方法用于获取搜索引擎是否可以爬取指定的URL,其语法格式如下:

     can_fetch(useragent,url)

其中,参数ueseragent表示搜索引擎;参数url表示URL。

4)mtime()方法

该方法用于获取上次爬取和分析Robots协议的时间,其语法格式如下:

     mtime()

示例代码如下:

     #资源包\Code\chapter1\1.3\0123.py
     import urllib.robotparser
     import datetime
     rfp=urllib.robotparser.RobotFileParser()
     rfp.set_url('http://www.oldxia.com/robotparser/robots.txt')
     rfp.read()
     res1=rfp.can_fetch('*','http://www.oldxia.com/xzd/upload/')
     #True
     print(res1)
     res2=rfp.can_fetch('*','http://www.oldxia.com/xzd/upload/index.html')
     #True
     print(res2)
     res3=rfp.can_fetch('*','http://www.oldxia.com/xzd/')
     #False
     print(res3)
     print(f'上次爬取和分析Robots协议的时间为{datetime.date.fromtimestamp(rfp.mtime())}')