乐趣区

Python 爬虫十六式 – 第二式 :urllib 与 urllib3

学习一时爽,一直学习一直爽!
  大家好,我是 Connor,上一次我们说到了什么是 HTTP 协议,那么这一次我们就要动手,来真正的了解如何使用 Python 访问一个网站了。今天我们要说的是 Python 自带的标准库,Urllib 与 Urllib3。
<center> Python 请求标准库 urllib 与 urllib3</center>
1.urllib 库
 1.1urllib 的简介
​  urllib` 是 Python 中请求 url 连接的官方标准库,在 Python2 中主要为 urllib 和 urllib2,在 Python3 中整合成了 urllib。urllib 中一共有四个模块,分别是 request,error,parse,robotparser,下面我们就来看一下 urllib 怎么使用吧。

 1.2 urllib.request 库
​  urllib.request 模块定义了在复杂世界中帮助打开 URLs (主要是 HTTP) 的函数和类——基本和摘要认证、重定向、cookies 等等,这个模块主要负责构造和发起网络请求,并在其中加入 Headers、Proxy 等。
  1.2.1 发起 GET 请求
​  如果你想打开一个网页,那么使 urlopen() 方法来发起请求无疑是正确的选择,你可以这样做:
from urllib import request

resp = reqeust.urlopen(‘https://www.baidu.com’)
print(resp.read().decode())
​  在 urlopen() 方法中,直接写入要访问的 url 地址字符串,该方法就会主动的访问目标网址,然后返回访问结果,返回的访问结果是一个 http.client.HTTPResponse 对象,使用该对象的 read() 方法即可获取访问网页获取的数据,这个数据是二进制格式的,所以我们还需要使用 decode() 方法来将获取的二进制数据进行解码,转换成我们可以看的懂得字符串。
  1.2.2 发起 POST 请求
​  在 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())
  1.2.3 Request 对象
  在 urlopen() 中不止可以传入字符串格式的 url,也可以传入 Request 对象来实现功能的拓展,Request 对象如下所示:
class urllib.request.Request(url, data=None, headers={},
origin_req_host=None,
unverifiable=False, method=None)
  1.2.4 其它参数
​  当我们需要模拟一些其他的参数的时候,简单的 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.2.5 请求中需要注意的事情

decode() 方法中,如果你不写解码格式,那么它会自动的将二进制格式转码成当前编辑器环境的格式,通常为 UTF-8,当然,有些网站的编码格式并不会使用 utf- 8 格式来编写网站,这个时候就需要你指定解码格式,如果你不清楚网站的解码格式的话,可以尝试查看网页源代码,ctrl + F 来查找 charset 属性,这时候直接指定解码格式即可,当然如果网站中并没有写明 charset 属性的话,那就只有多试几次了 …

urlopen(),opener 的 open() 中的 data 数据是二进制的,一定要先将它们用 encode() 方法转换成二进制数据中再进行发送。

 1.3 urllib.response
  在使用 urlopen() 方法或者 opener 的 open() 方法发起请求后,获得的结果是一个 response 对象。
  这个对象有一些方法和属性,可以让我们对请求返回的结果进行一些处理。

read() 获取响应返回的数据,只能使用一次。

getcode() 获取服务器返回的状态码。

getheaders() 获取返回响应的响应报头。

geturl()
获取访问的 url。

 1.4 urllib.parse
  urllib.parse` 是 urllib 中用来解析各种数据格式的模块。这之中有我们常用的两个方法,所以我们只着重说两个方法:
  1.4.1 urllib.parse.quote
  再 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]: ‘ 底特律 ’
  1.4.2 urllib.parse.urlencode
  在访问 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’

 1.5 urllib.error
  在 urllib 中主要设置了两个异常,一个是 URLError,一个是 HTTPError,HTTPError 是 URLError 的子类。
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 几乎都是出自一人之手,大家相互抄都抄烂了 … 我就简单的写一点吧,不见得比抄烂了的好,大家见谅吧。
 2.1 urllib3 简介
​ urllib3 是一个功能强大,对 SAP 健全的 HTTP 客户端。许多 Python 生态系统已经使用了 urllib3,你也应该这样做。——来自官方的自述
  urllib 一共有两个子包和八个模块,包几乎用不到,八个模块中并不是所有的都是我们常用的东西,大概有六个左右是我们经常用的,下面我们来看一下这几个模块吧

 2.2 urllib3 PoolManager 对象
  2.2.1 urllib3.PoolMagent
  如果你想通过 urllib3 访问一个网页,那么你必须先构造一个 PoolManager 对象,然后通过 PoolMagent 中的 request 方法来访问一个网页。
class urllib3.poolmanager.PoolManager(num_pools = 10,headers = None,** connection_pool_kw)
​ 这是生成一个 PoolMaager 所需要的参数:

num_pools 代表了缓存的池的个数,如果访问的个数大于 num_pools,将按顺序丢弃最初始的缓存,将缓存的个数维持在池的大小。

headers 代表了请求头的信息,如果在初始化 PoolManager 的时候制定了 headers,那么之后每次使用 PoolManager 来进行访问的时候,都将使用该 headers 来进行访问。

** connection_pool_kw 是基于 connection_pool 来生成的其它设置,这里我们不细述

  2.2.2 request() 和 urlopen() 方法
  当生成了一个 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 中

  2.2.3 request() 和 urllib() 的区别
  我个人还是比较建议使用 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 两个参数,有的人一定会问我说:“明明多了两个参数,为什么推荐哪个参数少的?”别急,我们来分析一下:我们的这些访问都是基于生成了 PoolManager 之后,如果我们在生成 PoolManager 的时候就指定了 Header,那么这两个方法的访问是几乎完全没有区别的。
  或许你会问,那那个 fields 呢?虽然 urlopen() 没有 fielder,但这并不代表它不能进行 POST 访问,相反,两个方法都有一个 body 属性,这个属性就是我们平时 POST 请求时所熟知的 data 参数,只不过你需要将 data 字典先转换成 JSON 格式再进行传输。fielder 属性其实也是用来传输 data 的, 但是它的功能是可以直接将字典对象传入,无需再进行 json 格式转换了。如果你用的是 GET 方式的话,那 fielder 可以帮你进行 url 拼接,如果是 POST 方式的话,filder 可以帮你主动转换成 JSON 格式。不过特别要声明的一点是 fielder 和 body 中只能存在一个,这个功能说实话,个人觉得写这两个特别类似的方法 … Emmn… 总是有点鸡肋的感觉。

 2.2 urllib3 ProxyManager
  如果你需要使用代理来访问某个网站的话, 那么你可以使用 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 的文章啦,恭喜你终于读完了这篇文章,希望你可以把他们全部掌握,加油吧!
学习一时爽,一直学习一直爽

退出移动版