Python请求标准库 urllib 与 urllib3

学习一时爽,一直学习一直爽!

  大家好,我是 Connor,一个从无到有的技术小白。上一次我们说到了什么是HTTP协议,那么这一次我们就要动手,来真正的了解如何使用Python访问一个网站了。今天我们要说的是Python自带的标准库,Urllib与Urllib3。

1.urllib库

  urllib`是Python中请求url连接的官方标准库,在Python2中主要为urllib和urllib2,在Python3中整合成了urllib。urllib中一共有四个模块,分别是 request,error,parse,robotparser,下面我们就来看一下urllib怎么使用吧。


  urllib.request模块定义了在复杂世界中帮助打开URLs (主要是HTTP )的函数和类——基本和摘要认证、重定向、cookies等等,这个模块主要负责构造和发起网络请求,并在其中加入Headers、Proxy等。

  如果你想打开一个网页,那么使urlopen()方法来发起请求无疑是正确的选择,你可以这样做:

from urllib import request

resp = reqeust.urlopen('https://www.baidu.com')
print(resp.read().decode())

  在urlopen()方法中,直接写入要访问的url地址字符串,该方法就会主动的访问目标网址,然后返回访问结果,返回的访问结果是一个 http.client.HTTPResponse对象,使用该对象的read()方法即可获取访问网页获取的数据,这个数据是二进制格式的,所以我们还需要使用decode()方法来将获取的二进制数据进行解码,转换成我们可以看的懂得字符串。

  在urlopen()方法中,urlopen()默认的访问方式是GET,当在urlopen()方法中传入data参数时,则会发起POST请求。注意:传递的data数据需要为bytes格式,你可以这样做:

from urllib import reuqest

resp = request.urlopen('http://httpbin.org/post', data=b'word=hello')
print(resp.read().decode())

  在urlopen()中不止可以传入字符串格式的url,也可以传入Request对象来实现功能的拓展,Request对象如下所示:

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

  当我们需要模拟一些其他的参数的时候,简单的urlopen() 方法已经无法满足我们的需求了,这个时候我们就需要使用urllib.request中的Request对象来帮助我们实现一些其它参数的模拟,这些需要模拟的其它参数有如下几种:

  • Headers:

  通过urllib发起请求的时候会有一个默认的Headers,这个Headers是"User-Agent": “Python-urllib/3.x”,如果网站设有UA验证,那么我们的程序无法访问成功,这个时候我们就需要伪装UA来进行访问,直接使用Request对象来添加Headers即可:

from urllib import request
  
  url = 'http://httpbin.org/get'
  headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'}
  
  # 需要使用url和headers生成一个Request对象,然后将其传入urlopen方法中
  req = request.Request(url, headers=headers)
  resp = request.urlopen(req)
  print(resp.read().decode())
  • Cookie:

  有时候当我们访问一些网站的时候需要进行翻页或者跳转等其它操作,为了防止无法访问我们想要的数据,需要让网站识别我们是同一个用户。这个时候我们就需要带上cookie进行访问。

  在设置cookie的时候由于urllib并没有很好的处理cookie的对象,所以在这里我们需要用到一个别的库,即http库,并使用里面的cookiejar来进行cookie的管理:

from http import cookiejar
  from urllib import request
  
  url = 'https://www.baidu.com'
  # 创建一个cookiejar对象
  cookie = cookiejar.CookieJar()
  # 使用HTTPCookieProcessor创建cookie处理器
  cookies = request.HTTPCookieProcessor(cookie)
  # 并以它为参数创建Opener对象
  opener = request.build_opener(cookies)
  # 使用这个opener来发起请求
  resp = opener.open(url)
  
  # 查看之前的cookie对象,则可以看到访问百度获得的cookie
  for i in cookie:
      print(i)


  在上述的方法中我们还用到了一个新的东西,即request.build_opener()方法,其实urlopen()就是一个构造好了的opener对象,在这里我们使用request.build_opener()方法重构了一个opener()对象,我们可以通过这个对象实现urlopen()实现的任何东西。

  当然,如果把这个生成的opener使用install_opener方法来设置为全局的,那么这个opener就是全局的,之后的每次访问都会带上这个cookie。

# 将这个opener设置为全局的opener
  request.install_opener(opener)
  resp = request.urlopen(url)
  • Proxy:

  使用爬虫来爬取数据的时候,如果过于频繁的访问,而且网站还设有限制的话,很有可能会禁封我们的ip地址,这个时候就需要设置代理,来隐藏我们的真实IP,代理需要创建一个代理处理器,使用request.ProxyHandler来生成一个代理处理器,你应该这样做:

from urllib import request
  
  url = 'http://httpbin.org/ip'
  proxy = {'http': '218.18.232.26:80', 'https': '218.18.232.26:80'}
  proxies = request.ProxyHandler(proxy)  # 创建代理处理器
  opener = request.build_opener(proxies)  # 创建opener对象
  
  resp = opener.open(url)
  print(resp.read().decode())
  1. decode()方法中,如果你不写解码格式,那么它会自动的将二进制格式转码成当前编辑器环境的格式,通常为UTF-8,当然,有些网站的编码格式并不会使用utf-8格式来编写网站,这个时候就需要你指定解码格式,如果你不清楚网站的解码格式的话,可以尝试查看网页源代码,ctrl + F来查找charset属性,这时候直接指定解码格式即可,当然如果网站中并没有写明charset属性的话,那就只有多试几次了…
  2. urlopen(),opener的open()中的data数据是二进制的,一定要先将它们用encode()方法转换成二进制数据中再进行发送。

  在使用urlopen()方法或者opener的open()方法发起请求后,获得的结果是一个response对象。

  这个对象有一些方法和属性,可以让我们对请求返回的结果进行一些处理。

  • read()
    获取响应返回的数据,只能使用一次。
  • getcode()
    获取服务器返回的状态码。
  • getheaders()
    获取返回响应的响应报头。
  • geturl()
    获取访问的url。

  urllib.parse`是urllib中用来解析各种数据格式的模块。这之中有我们常用的两个方法,所以我们只着重说两个方法:

  再url中,只能使用ASCII中包含的字符,也就是说,ASCII不包含特殊字符,包括中文,韩语,日语等各种字符,这些字符都无法在url中进行使用,而我们有的时候又需要将一些中文字符加入到url中,例如百度的搜索:

https://www.baidu.com/s?wd=底特律

  ?之后的wd参数,就是我们搜索的关键词。我们实现的方法就是将特殊字符进行url编码,转换成可以使用url传输的格式,urlib中可以使用quote()方法来实现这个功能。

In [1]: from urllib import parse

In [2]: parse.quote('底特律')
Out[2]: '%E5%BA%95%E7%89%B9%E5%BE%8B'

  这样我们就把中文字符串转换成了url能够识别的形式了,但是有的时候你会发现你明明进行了转码,但是访问的时候还是会无法访问,原因在于你的编码格式和网站指定的编码格式并不相符。

urllib.parse.quote(string, safe=’/’, encoding=None, errors=None)

  在上面的方法中不难看到,quote拥有多个参数,你可以通过指定编码格式的方法来确保你转码后获得的数据是正确的。

In [1]: from urllib import parse
    
In [2]: parse.quote("底特律", encoding='utf-8')
Out[2]: '%E5%BA%95%E7%89%B9%E5%BE%8B'
    
In [3]: parse.quote("底特律", encoding='gbk')
Out[3]: '%B5%D7%CC%D8%C2%C9'

  效果还是很明显的,明显的指定不同格式后的字符串编码是不同的。当然,如过你需要解码的时候,你也可以使用unquote()方法来解码:

In [1]: from urllib import parse

In [2]: parse.unquote('%E5%BA%95%E7%89%B9%E5%BE%8B',encoding='utf-8')
Out[2]: '底特律'

  在访问url时,我们常常需要传递很多的url参数,而如果用字符串的方法去拼接url的话,会比较麻烦,所以urllib中提供了urlencode这个方法来拼接url参数,该方法同样支持指定编码格式。

In [1]: from urllib import parse

In [2]: paramers = {'address': '底特律', 'phone': '123456789', 'name': 'Connor'}

In [3]: parse.urlencode(paramers)
Out[3]: 'address=%E5%BA%95%E7%89%B9%E5%BE%8B&phone=123456789&name=Connor'

In [4]: parse.urlencode(paramers,encoding='utf-8')
Out[4]: 'address=%E5%BA%95%E7%89%B9%E5%BE%8B&phone=123456789&name=Connor'

In [5]: parse.urlencode(paramers,encoding='gbk')
Out[5]: 'address=%B5%D7%CC%D8%C2%C9&phone=123456789&name=Connor'

  在urllib中主要设置了两个异常,一个是URLError,一个是HTTPErrorHTTPErrorURLError的子类。

HTTPError还包含了三个属性:

  • code:请求的状态码
  • reason:错误的原因
  • headers:响应的报头

例子:

In [1]: from urllib.error import HTTPError

In [2]: try:
   ...:     request.urlopen('https://www.jianshu.com')
   ...: except HTTPError as e:
   ...:     print(e.code)

403

2.urllib3

  其实我原本放弃写urllib3的打算了,因为相比requests来说,urllib和urllib3都显得太繁琐了,乱七八糟的功能导致几乎没有人写urlib3的说明,现有的urllib3几乎都是出自一人之手,大家相互抄都抄烂了… 我就简单的写一点吧,不见得比抄烂了的好,大家见谅吧。

urllib3是一个功能强大,对SAP 健全的 HTTP客户端。许多Python生态系统已经使用了urllib3,你也应该这样做。                     ——来自官方的自述

  urllib一共有两个子包和八个模块,包几乎用不到,八个模块中并不是所有的都是我们常用的东西,大概有六个左右是我们经常用的,下面我们来看一下这几个模块吧


  如果你想通过urllib3访问一个网页,那么你必须先构造一个PoolManager对象,然后通过PoolMagent中的request方法来访问一个网页。

class urllib3.poolmanager.PoolManager(num_pools = 10,headers = None,** connection_pool_kw )

这是生成一个PoolMaager所需要的参数:

  1. num_pools 代表了缓存的池的个数,如果访问的个数大于num_pools,将按顺序丢弃最初始的缓存,将缓存的个数维持在池的大小。
  2. headers 代表了请求头的信息,如果在初始化PoolManager的时候制定了headers,那么之后每次使用PoolManager来进行访问的时候,都将使用该headers来进行访问。
  3. ** connection_pool_kw 是基于connection_pool 来生成的其它设置,这里我们不细述

  当生成了一个PoolManager之后,我们就可以开始访问我们需要访问的网页了。

  你可以通过 urlopen()方法或者request()方法来访问一个网页,当你访问完成之后,将会返回一个HTTPREsopnse对象给你,你可以通过如下的方法来读取获取的响应内容:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data)
print(resp1.data.decode())

  当然,你也可以使用urlopen()方法来打开一个网页,他们两者几乎没有任何区别:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp2 = http.urlopen('GET', 'http://www.baidu.com', body=data)
print(resp2.data.decode())


  除了普通的 GET 请求之外,你还可以使用request()进行 POST 请求:

import urllib3
import json

data = json.dumps({'abc': '123'})
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('POST', 'http://www.httpbin.org/post', body=data)
print(resp1.data.decode())


  当然,request() 能做的东西,urlopen()都能做:

import urllib3
import json

data = json.dumps({'abc': '123'})
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp2 = http.urlopen('POST', 'http://www.httpbin.org/post', body=data)
print(resp1.data.decode())


  你还可以为他们设置超时时间,只需要添上timeout参数即可:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data, timeout=5)
print(resp1.data.decode())
import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.urlopen('GET', 'http://www.baidu.com', body=data, timeout=5)
print(resp1.data.decode())


  你也可以使用retries设置重试次数:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data, timeout=5, retries=5)
print(resp1.data.decode())
import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.urlopen('GET', 'http://www.baidu.com', body=data, timeout=5, retries=5)
print(resp1.data.decode())

注意事项

  • urllib3 并没有办法单独设置cookie,所以如果你想使用cookie的话,可以将cookie放入到headers中

  我个人还是比较建议使用request()来进行访问的,因为相比之下,使用request()来进行访问,原因很简单,我们可以看一下ruquest()urlopen()之间的差距:

def request(self, method, url, fields=None, headers=None, **urlopen_kw):
def urlopen(self, method, url, redirect=True, **kw):

  如果你用的是 request() 方法的话,那你还可以通过这种方式来直接进行post请求,不需要将 data参数转换成JSON格式

import urllib3
import json

data = {'abc': '123'}
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('POST', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())

  如果你用的是 request() 方法的话,那你还可以通过这种方式来直接进行GET请求,不需要自己拼接url参数

import urllib3
import json

data = {'abc': '123'}
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())

  差距还是很明显的,urlopen()request()有三个参数是不一样的,你会发现request()多了fields,headers两个参数。而且相比之下 reuqest() 方法还是比较符合人们的使用方式的,所以更多的也就使用 request() 方法了。

  虽然urlopen()没有fielder,但这并不代表它不能进行POST访问,相反,两个方法都有一个body属性,这个属性就是我们平时POST请求时所熟知的data参数,只不过你需要将data字典先转换成JSON格式再进行传输。fielder属性其实也是用来传输data的,但是它的功能是可以直接将字典对象传入,无需再进行json格式转换了。如果你用的是GET方式的话,那fielder可以帮你进行url拼接,如果是POST方式的话,filder可以帮你主动转换成JSON格式。不过特别要声明的一点是 fielder 和 body 中只能存在一个,这个功能说实话,个人觉得写这两个特别类似的方法… Emmn… 总是有点鸡肋的感觉。


  如果你需要使用代理来访问某个网站的话, 那么你可以使用 ProxyManager 对象来进行设置

def __init__(self, proxy_url, num_pools=10, headers=None,
             proxy_headers=None, **connection_pool_kw):

ProxyManager和PoolManager的方法基本完全相同,这里举个简单的小例子,就不再赘述了:

import urllib3
import json

data = {'abc': '123'}
proxy = urllib3.ProxyManager('http://50.233.137.33:80', headers={'connection': 'keep-alive'})
resp1 = proxy.request('POST', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())

  好啦,这就是全部的urllib和urllib3的文章啦,恭喜你终于读完了这篇文章,不知道今天你又学会了多少呢?
  我是Connor,一个从无到有的技术小白,这一次我们就讲到这里啦,我们下期再见!

学习一时爽,一直学习一直爽