Python之urllib库的用法

  参考:https://zhuanlan.zhihu.com/p/146016738

  urllib库的作用

  爬虫的第一个步骤是获取网页,urllib库是用来实现这个功能:想服务器发送请求,得到服务器响应,获取网页的内容。

  Python的强大在于提供了功能齐全的类库,来帮助我们完成这个请求,通过调用urllib库,我们不需要了解请求的数据结构,HTTP,TCP,IP层网络传输同学,以及服务器应答原理等。

  我们只需要关心以下三点,然后通过几行调用urllib库的代码,就能够获得我们想要的网页内容。

    • 请求的URL是什么
    • 传递的参数是什么
    • 如何设置可选的请求头

   urllib库的构成

  在python2中,曾经有urllib和urllib2两个库来实现请求的发送,但目前通用的python3版本中,两个库的功能已经合并成一个库,统一为urllib,它是python内置函数,不需要额外安装即可使用。

  urllib的四个模块

 

  

  【1】requset:HTTP请求模块,可以用来模拟发送请求,只需要传入URL及额外参数,就可以模拟浏览器访问网页的过程。

  【2】error:异常处理模块,检测请求是否报错,捕捉异常错误,进行重试或其他操作,保证程序不会终止。

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

  【4】robotparser:识别网站的robots.txt文件,判断哪些网站可以爬,哪些网站不可以爬,使用频率较少。

  发送请求

  urlopen是request模块中的方法,用于抓取网络。

  官方文档:https://docs.python.org/3/library/urllib.request.html

  我们以代码示例,我们抓取百度的网页

# 调用urllib库中的request模块
import urllib.request
# 发送请求获取百度网页的响应
response = urllib.request.urlopen("http://www.baidu.com")
# 打印响应内容
# read()是把响应对象内容全部读取出来,读取出来为bytes码
# decode('utf-8')把bytes码解码
print(response.read().decode('utf-8'))

  返回的结果比较多,随便截取其中一部分,可以看出是百度的网页HTML源代码。

 

   我们只用几行代码,就完成了百度的抓取,并打印了网页的源代码,接下来,我们看一看我们获得的响应内容response到底是什么?利用type()方法来输出响应的类型。

print(type(response))
# <class 'http.client.HTTPResponse'>

  它是一个HTTPResponse类型的对象

  包含方法:read()、readinto()、getheader()、getheaders()、fileno()等

  包含属性:msg、version、status、reason、debuglevel、closed等属性。

  通过调用以上的方法和属性,就能返回我们所需的信息。

  比如一开始我们打印获取网页内容时,就用到了read()方法。

  调用status属性则可以得到返回结果的状态码,200代表强求成功,404代表网页未找到等。

  再看一个代码示例加深理解:

# 打印响应状态码
print(response.status,"\n")
# 200
# 打印响应头信息
print(response.getheaders(),"\n")
# [('Bdpagetype', '1'), ('Bdqid', '0x90ae03e0000443e3'), ('Cache-Control', 'private'), ('Content-Type', 'text/html;charset=utf-8'), ('Date', 'Tue, 24 Aug 2021 01:18:50 GMT'), ('Expires', 'Tue, 24 Aug 2021 01:18:50 GMT'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND COM "'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND COM "'), ('Server', 'BWS/1.1'), ('Set-Cookie', 'BAIDUID=162948399648CA18CD24B2476E039F6B:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'BIDUPSID=162948399648CA18CD24B2476E039F6B; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'PSTM=1629767930; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'BAIDUID=162948399648CA18CA2A9672599961C8:FG=1; max-age=31536000; expires=Wed, 24-Aug-22 01:18:50 GMT; domain=.baidu.com; path=/; version=1; comment=bd'), ('Set-Cookie', 'BDSVRTM=17; path=/'), ('Set-Cookie', 'BD_HOME=1; path=/'), ('Set-Cookie', 'H_PS_PSSID=34437_34441_31253_34004_34092_26350_34390; path=/; domain=.baidu.com'), ('Traceid', '1629767930035569306610425274448017114083'), ('Vary', 'Accept-Encoding'), ('Vary', 'Accept-Encoding'), ('X-Frame-Options', 'sameorigin'), ('X-Ua-Compatible', 'IE=Edge,chrome=1'), ('Connection', 'close'), ('Transfer-Encoding', 'chunked')]
# 打印响应头的Server值
print(response.getheader('Server'),"\n")
# BWS/1.1

  在打印的三行代码中

  第一行输出了响应的状态码(200代表正常)

  第二行输出了响应的头信息

  第三行通过调用getheader()方法并传递参数Server,获取了第二行响应头信息中的Server对应的值BWS/1.1。

  参数

  利用基本的urlopen()方法可以完成最基本的简单网页GET方法抓取。

  如果想达成更复杂一些的任务,需要给链接传递一些参数,该如何实现。

  urlopen()的函数原型如下:

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None) 

  除了第一个参数传递URL之外,我们还可以传递其他参数,比如data(附加数据),timeout(超时时间)等。

  data参数

  data用来指明往服务器请求中的额外参数信息,data默认是None,此时以GET方式发送请求;当用户给出data参数的时候,改为POST方式发送请求。

  data参数是可选的,如果需要添加该参数,需要使用bytes()方法将参数转化为二进制数据。

  还是通过代码理解

  首先了解模块urllib.parse.urlencode的用法

  urllib.parse.urlencode用于把一个字典转换成对应的str

  举例说明

# urllib.parse.urlencode把字典转换成字符串str
# 如果字典包含多个元素则之间使用&分隔
import urllib.parse
print(urllib.parse.urlencode({"word":"hello"}))
# word=hello
print(urllib.parse.urlencode({"word":"hello","word1":"hello1"}))
# word=hello&word1=hello1

  下面代码演示POST方法

import urllib.parse
import urllib.request
# urllib.parse.urlencode({'word':'hello'})把字典转换为字符串 "word=hello"
# bytes("word=hello",encoding='utf-8')把字符串转换成二进制b"word=hello"
data = bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')
# 以下方法和上面转换的结果一致
# data = urllib.parse.urlencode({'word1':'hello'}).encode('utf-8')
response = urllib.request.urlopen("http://httpbin.org/post",data=data)
print(response.read().decode('utf-8'))

  首页这里请求的站点是,它是一个HTTP请求测试网站,记住它后面举例会经常用到它。

  这次我们要使用post方式请求,而不是get,因为post是需要携带表单信息的(类似登陆的用户名和密码)所以我们要在urlopen函数中传递data参数。

  前面讲了data参数必须是bytes型。

  bytes()这个方法第一个参数需要str(字符串类型),这里就需要用到urllib库的parse模块里的urlencode()方法来将参数字典转化为字符串。第二个参数是  指定编码格式,这里指定为utf-8。

  运行结果如下:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "word": "hello"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "10",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.6",
    "X-Amzn-Trace-Id": "Root=1-6124521e-0844c17c161a90e06ad543c0"
  },
  "json": null,
  "origin": "116.25.236.109",
  "url": "http://httpbin.org/post"
}

  该网页通过POST传递了表单数据,在form内输出一个字典格式,如果传递字典有多个元素则form下也对应多个元素。

  可以看到,我们传递的参数data中的字典键值对"word":"hello"出现在了form字段中,这表明了表单提交的方式,以POST方式传输数据。

  timeout参数

  timeout参数用于设置超时时间,单位为秒,意思是如果请求时间超过了设置的时间,还没有得到响应,就会抛出异常,在实际爬虫中,我们要对许多URL发起请求,中途肯定会出现爬取异常的URL,短时间无法获得响应,我们需要识别出这种异常,就需要用到timeout参数。

  如果不指定timeout参数,会使用全局默认时间。它支持HTTP、HTTPS、FTP请求。

  通过代码示例理解:

# 调用urllib库中的request模块
import urllib.request
response = urllib.request.urlopen("http://httpbin.org/get",timeout=0.01)
print(response.read().decode('utf-8'))

  这里我们把timeout参数设成0.01秒,网页反应时间是没这么快的,所以一定会报错,我们来看一下报错的信息:

urllib.error.URLError: <urlopen error timed out>

  显示异常属于urllib.error模块,错误原因是超时。

  在实际爬虫中,我们可以用过设置这个超时时间来控制一个网页在长时间未响应时,就跳过它的抓取。这可以利用try except 语句来实现。

  代码示例:

import socket
import urllib.request

try:
    response = urllib.request.urlopen("http://httpbin.org/get",timeout=0.01)
    print(response.read().decode('utf-8'))
except urllib.error.URLError as e :
    if isinstance(e.reason,socket.timeout):
        print("time out !")

  在代码中加入try except 语句后,如果响应超时,便会跳过抓取,我们捕获了URLError异常后,接着判断异常是socket.timeout类型(意思就是超时异常),于是打印出了time out !

  输出结果如下:

time out !

  其他参数(少用)

  context参数:它必须是ssl.SSLContext类型,用来指定SSL设置,实现SSL加密传输。

  cafile、capath:分别指定CA证书和它的路径,用于实现可信任的CA证书的HTTP请求。

  cadefault:已经弃用,默认False。

  2,Request

   上文已经讲解了如何利用urlopen()方法实现最基本的请求发起。但这几个简单的参数并不足以构建一个完整的请求(过于简单的请求会被浏览器识别为爬虫而拒绝响应)。如果请求中需要加入Headers等信息,就需要用到Requset类来构建。

  同样通过实例展示Request用法:

# 展示Request
import urllib.request

request = urllib.request.Request('http://httpbin.org/get')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

  我们依然是用urlopen()方法来发送请求,只是这次的参数不在是url,而是一个Request类型对象。通过构造这个数据类型,我们可以将请求独立为一个对象,并且更灵活的配置参数。

  Reque的函数原型如下:

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

  下面来看它的具体参数:

  url:用于请求的URL,这是毕传参数,其他都是可选参数

  data:必须是bytes类型,如果它是字典,可以先用urllib.parse模块里的urlencode方法编码(用于POST请求)

  headers:是一个字典,它就是请求头,我们可以在构造请求时,通过headers参数直接构造,也可以通过调用请求示例的add_header()方法添加

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

  unverifiable:表示这个请求是否无法验证,默认为False,意思是说用户没有足够权限来选择接收这个请求的结果。比如我们请求一个HTML文档中的图片,但是我们没有自助抓取图象的权限,这是unverifiable的值就是True。

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

# Request请求多个参数
import urllib.request,urllib.parse

url = "http://httpbin.org/post"
headers = {
 "User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0",
 "Host":"httpbin.org" 
}

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

  在这个示例中,我们通过4个参数构造了一个请求,我们加入了url、data、headers、method,其中最重要的是把头信息(包含User-Agent和Host)写入了  Request,让请求变得更完整。

  运行结果如下:

{
  "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 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0",
    "X-Amzn-Trace-Id": "Root=1-61246460-1f4d152f11557e9f6670f33a"
  },
  "json": null,
  "origin": "116.25.236.109",
  "url": "http://httpbin.org/post"
}

  可以看到,我们成功通过新建的参数,设置了data、headers 和 method。

  另外之前提到的headers也可以用add_header()方法添加,示例代码如下:

req = urllib.request.Request(url=url,data=data,method='POST')
req.add_header('User-Agent',"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0")

  传递参数格式为key value格式

posted @ 2021-08-24 11:36  minseo  阅读(713)  评论(0编辑  收藏  举报