python网络爬虫教程(三):最全的请求库urllib详解与编程实战

前两章讲网页的构成以及爬虫的基本原理,如果您还不了解,推荐您看上一章python网络爬虫教程(二):网页基础。学习了这些以后,就可以开始写代码了。

学习爬虫,最初的操作就是模拟浏览器向服务器发出请求,幸运的是,python为我们提供了强大且便捷的类库来完成这些请求,本章我们先来详细了解一下python自带的urllib库,他是python内置的HTTP请求库,不需要额外安装即可使用。在python2中,有urllib和urllib2两个库来实现请求的发送,而在python3中统一为了urllib,作者使用的是python3.7,也就是以python3的urllib来说明。

urllib包含了4个模块:

request:它是最基本的http请求模块,用来模拟发送请求。

error:异常处理模块,如果出现错误可以捕获这些异常。

parse:一个工具模块,提供了许多URL处理方法,如:拆分、解析、合并等。

robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬。

1. urllib.request(构造请求)

urllib.request模块提供了最基本的构造HTTP请求的方法,其中比较重要的类为urllib…request.urlopen,以python官网为例,我们来体验一下它的强大之处:

from urllib.request import urlopen

response = urlopen('https://www.python.org')
print(response.read().decode())

运行结果如下:python网络爬虫教程(三):最全的请求库urllib详解与编程实战_第1张图片上述代码中,我们用urlopen来构造HTTP请求,返回值是一个HTTPRsponse对象,用read()方法读取网页内容之后,返回的结果是网页字节流,用decode()解码成unicode之后即可得到网页源代码。除此之外,HTTPRsponse对象还拥有如下属性和方法:

import urllib.request import urlopen

response=urlopen('https://www.python.org')  #请求站点获得一个HTTPResponse对象
print(response.read().decode('utf-8'))   #返回网页内容
print(response.getheader('server')) #返回响应头中的server值
print(response.getheaders()) #以列表元祖对的形式返回响应头信息
print(response.fileno()) #返回文件描述符
print(response.version)  #返回版本信息
print(response.status)  #返回状态码200404代表网页未找到
print(response.debuglevel) #返回调试等级
print(response.closed)  #返回对象是否关闭布尔值
print(response.geturl()) #返回检索的URL
print(response.info()) #返回网页的头信息
print(response.getcode()) #返回响应的HTTP状态码
print(response.msg)  #访问成功则返回ok
print(response.reason) #返回状态信息

urlopen参数信息如下:
urllib.request.urlopen(url,data=None,[timeout,],cafile=None,capath=None,cadefault=False,context=None)

data参数: 该参数是可选的,如果要添加该参数,需要将其内容编码为字节流格式即bytes类型,另外,如果传递了该参数,请求方式将使用POST请求:

from urllib.request import urlopen
import urllib.parse

dict= {'Hello': 'Word!'}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
response = urlopen('http://httpbin.org/post', data=data)
print(response.read().decode())

data需要字节类型的参数,用urllib.parse的urlencode()可以将参数字典转换为字符串,然后再用bytes()函数将其转化为字节流。这里请求的站点是httpbin.org,它可以提供HTTP请求测试,它可以输出一些请求信息,运行结果如下:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "Hello": "Word!"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "13",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.7",
    "X-Amzn-Trace-Id": "Root=1-5ecb5d2d-8ee3f56e31828d78e4a10158"
  },
  "json": null,
  "origin": "171.107.139.104",
  "url": "http://httpbin.org/post"
}

可以看到传递的参数出现在了form字段中,这表明该请求以POST方式传输数据。

timeout参数:timeout参数用于设置超时时间,单位为秒,如果请求超出了设置时间还未得到响应则抛出异常:

from urllib.request import urlopen

response = urlopen('http://httpbin.org/post', timeout=0.1)
print(response.read().decode())

运行结果如下:

urllib.error.URLError: 

我们可以使用error模块来捕获异常:

import urllib.request
import urllib.error
import socket
try:
    response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
    print(response.read())
except urllib.error.URLError as e:
    if isinstance(e.reason,socket.timeout): #判断对象是否为类的实例
        print(e.reason) #返回错误信息

运行结果如下:

timed out

我们捕获了URLError异常,接着判断异常是socket.timeout,从而得出它确实是由于超时而报错。

其他参数:context参数,她必须是ssl.SSLContext类型,用来指定SSL设置,此外,cafile和capath这两个参数分别指定CA证书和它的路径,会在https链接时用到。

urllib.request模块还有更为强大的Request()类,参数信息如下:
urllib.request.Request(url,data=None,headers={},origin_req_host=None,unverifiable=False,method=None)

url:请求的URL,必须传递的参数,其他都是可选参数

data:上传的数据,必须传bytes字节流类型的数据,如果它是字典,可以先用urllib.parse模块里的urlencode()编码。

headers:是一个字典,传递的是请求头数据。

origin_req_host:指请求方的host名称或者IP地址。

unverifiable:表示这个请求是否是无法验证的,默认为False,如我们请求一张图片如果没有权限获取图片那它的值就是true。

method:是一个字符串,用来指示请求使用的方法,如:GET,POST,PUT等。

通过实例来感受一下:

from urllib import request,parse

url='http://httpbin.org/post'
headers={
    'User-Agent':'Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)',
    'Host':'httpbin.org'
}  #定义头信息

dict={'name':'germey'}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url,data=data,headers=headers,method='POST')
response = request.urlopen(req) 
print(response.read().decode())

结果如下:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "name": "germey"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "11",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)",
    "X-Amzn-Trace-Id": "Root=1-5ecb645d-e062b9bcea50bcf9790167b3"
  },
  "json": null,
  "origin": "171.107.139.104",
  "url": "http://httpbin.org/post"
}

我们通过四个参数构造了一个请求,同时也可以发现我们依然用urlopen()来发送请求,不过这次请求的参数不再是URL,而是Request对象,通过这个数据结构,我们可以将请求独立成一个对象,也可以更加丰富和灵活地配置参数。

2. urllib.request的高级类

如果我们需要更加高级的操作,如Cookie处理、代理设置等,怎么办呢?这就需要更加强大的工具 Handler,在urllib.request模块里的BaseHandler类,他是所有其他Handler的父类,他是一个处理器,比如用它来处理登录验证,处理cookies,代理设置,重定向等。

Handler的子类包括:

HTTPDefaultErrorHandler:用来处理http响应错误,错误会抛出HTTPError类的异常

HTTPRedirectHandler:用于处理重定向

HTTPCookieProcessor:用于处理cookies

ProxyHandler:用于设置代理,默认代理为空

HTTPPasswordMgr:永远管理密码,它维护用户名和密码表

HTTPBasicAuthHandler:用户管理认证,如果一个链接打开时需要认证,可以使用它来实现验证功能

另一个比较重要的类是OpenerDirector,可以简称为opener,之前用过的urlopen()方法,实际上就是urllib为我们提供的一个Opener。我们需要用到更高级的功能,所以需要更深入一层进行配置,需要使用更底层的实例进行操作,所以这里就用到了Opener,我们需要利用Handler来构建Opener。

如果我们访问的页面会弹出提示框,提示输入用户名和密码,可以使用密码验证:

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError

username='18177459263'
passowrd='mimagaile9625'
url='https://passport.zhihuishu.com/login?service=https://onlineservice.zhihuishu.com/login/gologin'
p=HTTPPasswordMgrWithDefaultRealm() #构造密码管理实例
p.add_password(None,url,username,passowrd) #添加用户名和密码到实例中
auth_handler=HTTPBasicAuthHandler(p) #传递密码管理实例构建一个验证实例
opener=build_opener(auth_handler)  #构建一个Opener
try:
    result=opener.open(url)  #打开链接,完成验证,返回的结果是验证后的页面内容
    html=result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

设置代理:

from urllib.error import URLError
from urllib.request import ProxyHandler,build_opener

proxy_handler=ProxyHandler({
    'http':'http://127.0.0.1:8888',
    'https':'http://127.0.0.1:9999'
})
opener=build_opener(proxy_handler) #构造一个Opener
try:
    response=opener.open('https://www.baidu.com')
    print(response.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

使用Cookies:

import http.cookiejar,urllib.request
cookie=http.cookiejar.CookieJar() #实例化cookiejar对象
handler=urllib.request.HTTPCookieProcessor(cookie) #构建一个handler
opener=urllib.request.build_opener(handler) #构建Opener
response=opener.open('http://www.baidu.com') #请求
print(cookie)
for item in cookie:
    print(item.name+"="+item.value)

3. urllib.error(异常处理)

urllib的error模块定义了由request模块产生的异常,如果出现问题,request模块便会抛出error模块中定义的异常。其中有两个子类URLError和HTTPError。

1. URLError

URLError类来自urllib库的error模块,它继承自OSError类,是error异常模块的基类,由request模块产生的异常都可以通过捕获这个类来处理

它只有一个属性reason,即返回错误的原因:

from urllib import request, error

try:
    response = request.urlopen('https://jiangyvzang.org/index')
except error.URLError as e:
    print(e.reason)#如果网页不存在不会抛出异常,而是返回捕获的异常错误的原因(Not Found)

2. HTTPError

它是URLError的子类,专门用来处理HTTP请求错误,比如认证请求失败,它有3个属性:

code:返回HTTP的状态码,如404页面不存在,500服务器错误等

reason:同父类,返回错误的原因

headers:返回请求头

下面用一个实例来看看:

from urllib import request,error

try:
    response=request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:  #先捕获子类异常
    print(e.reason,e.code,e.headers,sep='\n')
except error.URLError as e:  #再捕获父类异常
    print(e.reason)
else:
    print('request successfully')

运行结果如下:

Not Found
404
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 25 May 2020 07:06:37 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
Set-Cookie: PHPSESSID=1dgv0fn7l6bttp08hhp9nse657; path=/
Pragma: no-cache
Vary: Cookie
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Link: ; rel="https://api.w.org/"

4. urllib.parse(解析链接)

urllib库提供了parse模块,它定义了处理URL的标准接口,如实现URL各部分的抽取,合并以及链接转换。

1. urlparse()

该方法可以实现URL的识别和分段:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)

结果如下


ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

可以看到输出结果是一个ParseResult类型对象,它包含6部分,分别是scheme(协议)、netloc(域名)、path(访问路径)、params(参数)、query(条件)和fragment(锚点),由此我们可以得到一个标准的链接格式:
scheme://netloc/path;params?query#fragment
一个标准的URL都会符合这个格式,利用urlparse可以将它们拆分开来。

接下来我们看看它的API用法:
urllib.parse.urlparse(urlstring,scheme=’’,allow_fragments=True)

urlstring:待解析的URL,字符串

scheme:它是默认的协议,如http或者https,URL如果不带http协议,可以通过scheme来指定,如果URL中制定了http协议则URL中生效

allow_fragments:是否忽略fragment即锚点,如果设置为False,fragment部分会被忽略,反之不忽略,通过一个实例来看一下:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(type(result))
print(result)

运行结果如下:


ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')

假如URL中不包含params和query:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(type(result))
print(result)

运行结果如下:


ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')

结果表明,如果URL中不包含params和query,fragment便会被解析为path的一部分。

返回结果实际上是一个元组,我们可以通过索引来获取,也可以通过属性名来获取:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(result.scheme, result[0], result.netloc, result[1], sep='\n')

结果如下:

https
https
www.baidu.com
www.baidu.com

2. urlunparse()

有了urlparse(),自然也有它的对立方法urlunparse(),它接受一个可迭代对象,但它的长度必须为6,否则会抛出数量不足或者过多的问题:

from urllib.parse import urlunparse

url = ['https', 'www.baidu.com', 'index.html', 'user', 'id=6', 'comment']
print(urlunparse(url))

结果如下:

https://www.baidu.com/index.html;user?id=6#comment

3. urlsplit()

这个方法和urlparse()类似,不过它不再单独解析params这一部分,只返回5个结果,params会合并到path中:

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index.html;user?id=6#comment')
print(type(result))
print(result)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')

结果如下:


SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=6', fragment='comment')
https
https
www.baidu.com
www.baidu.com

从结果可以看到返回值是一个SplitResult类型,并且既可以用属性获取值,也支持索引。

4. urlunsplit()

与urlunparse()类似,略。

5. urljoin()

有了urlunparse()和urlunsplit()方法,可以完成链接的合并,不过前提是必须要有特定长度的对象。此外,生成链接还有另外一种方法,那就是urljoin(),我们可以提供一个base_url(基础链接)作为第一个参数,将新的链接作为第二个参数,该方法会分析base_url的scheme、netloc和path这3个内容并对新的链接缺失的部分进行补充:

from urllib.parse import urljoin

print(urljoin('http://www.baidu.com','index.html'))
print(urljoin('http://www.baidu.com','http://cdblogs.com/index.html'))
print(urljoin('http://www.baidu.com/home.html','https://cnblog.com/index.html'))
print(urljoin('http://www.baidu.com?id=3','https://cnblog.com/index.html?id=6'))
print(urljoin('http://www.baidu.com','?id=2#comment'))
print(urljoin('www.baidu.com','https://cnblog.com/index.html?id=6'))

运行结果如下:

http://www.baidu.com/index.html
http://cdblogs.com/index.html
https://cnblog.com/index.html
https://cnblog.com/index.html?id=6
http://www.baidu.com?id=2#comment
https://cnblog.com/index.html?id=6

通过urljoin()方法,可以轻松实现链接的解析、拼合与生成。

6. urlencode()

urlencode()在构造GET请求参数时很有用,它可以将字典转化为GET请求参数:

from urllib.parse import urlencode

params = {
    'name': 'germey',
    'age': '22'
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

运行结果如下:

https://www.baidu.com?name=germey&age=22

7. parse_qs()

parse_qs()与urlencode()正好相反,它是用来反序列化的,如将GET参数转换回字典格式:

from urllib.parse import parse_qs

query = 'name=germey&age=22'
print(parse_qs(query))

运行结果如下:

{'name': ['germey'], 'age': ['22']}

8. parse_qsl()

另外还有一个parse_qsl()方法,用来将参数转化为元组组成的列表:

from urllib.parse import parse_qsl

query = 'name=germey&age=22'
print(parse_qsl(query))

运行结果如下:

[('name', 'germey'), ('age', '22')]

9. quote()

该方法可以将内容转化为URL编码的格式,当URL中含有中文参数时,可能会导致乱码的问题,此时用这个方法可以将中文参数转化为URL编码:

from urllib.parse import quote

keyword = '壁纸'
base_url = 'https://www.baidu.com/s?wd='
url = base_url + quote(keyword)
print(url)

运行结果如下:

https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8

10. unquote()

有了quote()方法自然就有unquote()方法,它可以进行URL解码:

from urllib.parse import unquote

url = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))

运行结果如下:

https://www.baidu.com/s?wd=壁纸

5. 分析Robots协议

利用urllib的robotparser模块,我们可以实现网站Robots协议的分析

1. Robots协议

Robots协议也称为爬虫协议、机器人协议,它的全名叫做网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些网页可以抓取,哪些不可以抓取,它通常是一个robots.txt的文本文件,一般放在网站的根目录下。

当搜索爬虫访问一个站点时,它首先会检查这个站点根目录下是否存在robots.txt文件,如果存在,搜索爬虫会根据其中定义的爬去范围来爬取,如果没有找到,搜索爬虫会访问所有可直接访问的页面

我们来看下robots.txt的样例:

User-agent: *
Disallow: /
Allow: /public/

它实现了对所有搜索爬虫只允许爬取public目录的功能,将上述内容保存为robots.txt文件放在网站根目录下,和网站的入口文件(index.html)放在一起

User-agent描述了搜索爬虫的名称,将其设置为*则代表协议对任何爬虫有效,如设置为Baiduspider则代表规则对百度爬虫有效,如果有多条则对多个爬虫受到限制,但至少需要指定一条

一些常见的搜索爬虫名称:

BaiduSpider  百度爬虫 www.baidu.com

Googlebot  Google爬虫 www.google.com

360Spider  360爬虫 www.so.com

YodaoBot  有道爬虫 www.youdao.com

ia_archiver  Alexa爬虫 www.alexa.cn

Scooter  altavista爬虫 www.altavista.com

Disallow指定了不允许抓取的目录,如上例中设置的/则代表不允许抓取所有的页面

Allow一般和Disallow一起使用,用来排除单独的某些限制,如上例中设置为/public/则表示所有页面不允许抓取,但可以抓取public目录

设置示例:

#禁止所有爬虫
User-agent: *
Disallow: /

#允许所有爬虫访问任何目录,另外把文件留空也可以
User-agent: *
Disallow:

#禁止所有爬虫访问某那些目录
User-agent: *
Disallow: /home/
Disallow: /tmp/

#只允许某一个爬虫访问
User-agent: BaiduSpider
Disallow:
User-agent: *
Disallow: /

2. robotparser

rebotparser模块用来解析robots.txt,该模块提供了一个类RobotFileParser,它可以根据某网站的robots.txt文件来判断一个抓取爬虫时都有权限来抓取这个网页

urllib.robotparser.RobotFileParser(url=’’)

robotparser类常用的方法:

set_url():用来设置robots.txt文件的连接,如果在创建RobotFileParser对象是传入了连接,就不需要在使用这个方法设置了

read():读取reobts.txt文件并进行分析,它不会返回任何内容,但执行那个了读取和分析操作

parse():用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,并安装语法规则来分析内容

can_fetch():该方法传入两个参数,第一个是User-agent,第二个是要抓取的URL,返回的内容是该搜索引擎是否可以抓取这个url,结果为True或False

mtime():返回上次抓取和分析robots.txt的时间

modified():将当前时间设置为上次抓取和分析robots.txt的时间

from urllib.robotparser import RobotFileParser
rp = RobotFileParser()  #创建对象
rp.set_url('https://www.cnblogs.com/robots.txt') #设置robots.txt连接,也可以在创建对象时指定
rp.read()  #读取和解析文件
print(rp.can_fetch('*','https://i.cnblogs.com/EditPosts.aspx?postid=9170312&update=1')) #坚持链接是否可以被抓取

通过的学习我们可以使用urllib库来进行网页请求、资源的获取,urllib库功能强大,但使用起来也相对繁琐,下一章我们可以学习功能同样强大,却简单实用的requests库,可以直接点击如下链接跳转:python网络爬虫教程(四):详解requests库。

你可能感兴趣的:(python网络爬虫,教程,学习记录)