一.使用 urllib
request | 它是最基本的 HTTP 请求模块,可以用来模拟发送请求。就像在浏览器里输入网址然后回车一样,只需要给库方法传入 URL 以及额外的参数,就可以模拟实现这个过程了。 |
---|
error | 异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后进行重试或其他操作以保证程序不会意外终止。 |
parse | 一个工具模块,提供了许多 URL 处理方法,比如拆分、解析、合并等。 |
robotparser | 主要是用来识别网站的 robots.txt 文件,然后判断哪些网站可以爬,哪些网站不可以爬,它其实用得比较少。 |
(一).request 模块
urllib.request 模块提供了最基本的构造 HTTP 请求的方法,利用它可以模拟浏览器的一个请求发起过程,同时它还带有处理授权验证(authenticaton)、重定向(redirection)、浏览器 Cookies 以及其他内容。 |
---|
1.发送请求
1.1 urlopen()
这里以 Python 官网为例,我们来把这个网页抓下来:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
利用 type() 方法输出响应的类型:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(type(response))
<class 'http.client.HTTPResponse'>
它主要包含 read()、readinto()、getheader(name)、getheaders()、fileno() 等方法,以及 msg、version、status、reason、debuglevel、closed 等属性。 |
---|
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))
200
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('Age', '2473'), ('Connection', 'close'), ('X-Served-By', 'cache-lcy1125-LCY'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '23'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
nginx
|
---|
前两个输出分别输出了响应的状态码和响应的头信息,最后一个输出通过调用 getheader() 方法并传递一个参数 Server 获取了响应头中的 Server 值,结果是 nginx,意思是服务器是用 Nginx 搭建的。 |
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
- data 参数
data 参数是可选的。如果要添加该参数,并且如果它是字节流编码格式的内容,即 bytes 类型,则需要通过 bytes() 方法转化。另外,如果传递了这个参数,则它的请求方式就不再是 GET 方式,而是 POST 方式。
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())
|
---|
这里我们传递了一个参数 word,值是 hello。它需要被转码成 bytes(字节流)类型。其中转字节流采用了 bytes() 方法,该方法的第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode() 方法来将参数字典转化为字符串;第二个参数指定编码格式,这里指定为 utf8。 |
这里请求的站点是 httpbin.org,它可以提供 HTTP 请求测试。本次我们请求的 URL 为 http://httpbin.org/post,这个链接可以用来测试 POST 请求,它可以输出请求的一些信息,其中包含我们传递的 data 参数。 |
{
"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.5"
},
"json": null,
"origin": "123.124.23.253",
"url": "http://httpbin.org/post"
}
|
---|
传递的参数出现在了 form 字段中,这表明是模拟了表单提交的方式,以 POST 方式传输数据。 |
timeout 参数用于设置超时时间,单位为秒,意思就是如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。如果不指定该参数,就会使用全局默认时间。它支持 HTTP、HTTPS、FTP 请求。 |
---|
import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())
Traceback (most recent call last):
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 1349, in do_open
encode_chunked=req.has_header('Transfer-encoding'))
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 1287, in request
self._send_request(method, url, body, headers, encode_chunked)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 1333, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 1282, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 1042, in _send_output
self.send(msg)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 980, in send
self.connect()
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\http\client.py", line 952, in connect
(self.host,self.port), self.timeout, self.source_address)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\socket.py", line 724, in create_connection
raise err
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\socket.py", line 713, in create_connection
sock.connect(sa)
socket.timeout: timed out
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "D:/English_Name/pythonProject/3.py", line 19, in <module>
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 223, in urlopen
return opener.open(url, data, timeout)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 526, in open
response = self._open(req, data)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 544, in _open
'_open', req)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 504, in _call_chain
result = func(*args)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 1377, in http_open
return self.do_open(http.client.HTTPConnection, req)
File "D:\English_Name\Anaconda3\envs\python36_pachong\lib\urllib\request.py", line 1351, in do_open
raise URLError(err)
urllib.error.URLError: <urlopen error timed out>
|
---|
这里我们设置超时时间是 1 秒。程序 1 秒过后,服务器依然没有响应,于是抛出了 URLError 异常。该异常属于 urllib.error 模块,错误原因是超时。 |
因此,可以通过设置这个超时时间来控制一个网页如果长时间未响应,就跳过它的抓取。这可以利用 try except 语句来实现,相关代码如下: |
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
TIME OUT
|
---|
context 参数,它必须是 ssl.SSLContext 类型,用来指定 SSL 设置。 |
cafile 和 capath 这两个参数分别指定 CA 证书和它的路径,这个在请求 HTTPS 链接时会有用。 |
cadefault 参数现在已经弃用了,其默认值为 False。 |
1.2 Request
- 我们知道利用 urlopen() 方法可以实现最基本请求的发起,但这几个简单的参数并不足以构建一个完整的请求。如果请求中需要加入 Headers 等信息,就可以利用更强大的 Request 类来构建。
import urllib.request
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
|
---|
依然是用 urlopen() 方法来发送这个请求,只不过这次该方法的参数不再是 URL,而是一个 Request 类型的对象。通过构造这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可更加丰富和灵活地配置参数。 |
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
url | 用于请求 URL,这是必传参数,其他都是可选参数。 |
---|
data | 如果要传,必须传 bytes(字节流)类型的。如果它是字典,可以先用 urllib.parse 模块里的 urlencode() 编码。 |
headers | 它就是请求头,我们可以在构造请求时通过 headers 参数直接构造,也可以通过调用请求实例的 add_header() 方法添加。 添加请求头最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。 |
origin_req_host | 请求方的 host 名称或者 IP 地址。 |
unverifiable | 表示这个请求是否是无法验证的,默认是 False,意思就是说用户没有足够权限来选择接收这个请求的结果。例如,我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True`。 |
method | 是一个字符串,用来指示请求使用的方法,比如 GET、POST 和 PUT 等。 |
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {
'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
|
---|
其中 url 即请求 URL,headers 中指定了 User-Agent 和 Host,参数 data 用 urlencode() 和 bytes() 方法转成字节流。另外,指定了请求方式为 POST。 |
{
"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/4.0 (compatible; MSIE 5.5; Windows NT)",
"X-Amzn-Trace-Id": "Root=1-601e89c3-7ead3a893f82bf424f898f5a"
},
"json": null,
"origin": "183.167.28.2",
"url": "http://httpbin.org/post"
}
|
---|
headers 也可以用 add_header() 方法来添加: |
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
1.3 高级用法
- 在上面的过程中,我们虽然可以构造请求,但是对于一些更高级的操作(比如 Cookies 处理、代理设置等),我们该怎么办呢?
- 接下来,就需要更强大的工具 Handler 登场了。简而言之,我们可以把它理解为各种处理器,有专门处理登录验证的,有处理 Cookies的,有处理代理设置的。利用它们,我们几乎可以做到 HTTP 请求中所有的事情。
- 首先,介绍一下 urllib.request 模块里的 BaseHandler 类,它是所有其他 Handler的父类,它提供了最基本的方法,例如 default_open()、protocol_request() 等。
- 接下来,就有各种 Handler 子类继承这个 BaseHandler 类,举例如下。
| |
---|
HTTPDefaultErrorHandler | 用于处理 HTTP 响应错误,错误都会抛出 HTTPError 类型的异常。 |
HTTPRedirectHandler | 用于处理重定向。 |
HTTPCookieProcessor | 用于处理 Cookies |
ProxyHandler | 用于设置代理,默认代理为空。 |
HTTPPasswordMgr | 用于管理密码,它维护了用户名和密码的表。 |
HTTPBasicAuthHandler | 用于管理认证,如果一个链接打开时需要认证,那么可以用它来解决认证问题。 |
比较重要的类就是 OpenerDirector,我们可以称为 Opener。我们之前用过 urlopen() 这个方法,实际上它就是 urllib 为我们提供的一个 Opener。 |
---|
为什么要引入 Opener 呢?因为需要实现更高级的功能。之前使用的 Request 和 urlopen() 相当于类库为你封装好了极其常用的请求方法,利用它们可以完成基本的请求,但是现在不一样了,我们需要实现更高级的功能,所以需要深入一层进行配置,使用更底层的实例来完成操作,所以这里就用到了 Opener。 |
Opener 可以使用 open() 方法,返回的类型和 urlopen() 如出一辙。那么,它和 Handler 有什么关系呢?简而言之,就是利用 Handler 来构建 Opener。 |
- 验证
有些网站在打开时就会弹出提示框,直接提示你输入用户名和密码,验证成功后才能查看页面。那么,如果要请求这样的页面,借助 HTTPBasicAuthHandler 就可以完成。
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
username = 'username'
password = 'password'
url = 'http://localhost:5000/'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)
try:
result = opener.open(url)
html = result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)
|
---|
这里首先实例化 HTTPBasicAuthHandler 对象,其参数是 HTTPPasswordMgrWithDefaultRealm 对象,它利用 add_password() 添加进去用户名和密码,这样就建立了一个处理验证的 Handler。 |
接下来,利用这个 Handler 并使用 build_opener() 方法构建一个 Opener,这个 Opener 在发送请求时就相当于已经验证成功了。 |
接下来,利用 Opener 的 open() 方法打开链接,就可以完成验证了。这里获取到的结果就是验证后的页面源码内容。 |
//自己尝试果然报错了,之后再看吧
[WinError 10061] 由于目标计算机积极拒绝,无法连接。
- 代理
在做爬虫的时候,免不了要使用代理,如果要添加代理,可以这样做:
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = ProxyHandler({
'http': 'http://127.0.0.1:9743',
'https': 'https://127.0.0.1:9743'
})
opener = build_opener(proxy_handler)
try:
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)
|
---|
在本地搭建了一个代理,它运行在 9743 端口上。 |
这里使用了 ProxyHandler,其参数是一个字典,键名是协议类型(比如 HTTP 或者 HTTPS 等),键值是代理链接,可以添加多个代理。 |
然后,利用这个 Handler 及 build_opener() 方法构造一个 Opener,之后发送请求即可。 |
出错
[WinError 10061] 由于目标计算机积极拒绝,无法连接。
- 解决办法:设置IP代理错误
因为未找到合适的IP:PORT(梦回计网),直接用自己的电脑
//OK
<html>
<head>
<script>
location.replace(location.href.replace("https://","http://"));
</script>
</head>
<body>
<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>
</body>
</html>
- Cookies 将网站的 Cookies 获取下来
import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
print(item.name+"="+item.value)
|
---|
首先,我们必须声明一个 CookieJar 对象。接下来,就需要利用 HTTPCookieProcessor 来构建一个 Handler,最后利用 build_opener() 方法构建出 Opener,执行 open() 函数即可。 |
BAIDUID=2E65A683F8A8BA3DF521469DF8EFF1E1:FG=1
BIDUPSID=2E65A683F8A8BA3DF521469DF8EFF1E1
H_PS_PSSID=20987_1421_18282_17949_21122_17001_21227_21189_21161_20927
PSTM=1474900615
BDSVRTM=0
BD_HOME=0
|
---|
不过既然能输出,那可不可以输出成文件格式呢? Cookies 实际上也是以文本形式保存的。 |
filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
|
---|
这时 CookieJar 就需要换成 MozillaCookieJar,它在生成文件时会用到,是 CookieJar 的子类,可以用来处理 Cookies 和文件相关的事件,比如读取和保存 Cookies,可以将 Cookies 保存成 Mozilla 型浏览器的 Cookies 格式。 |
|
---|
另外,LWPCookieJar 同样可以读取和保存 Cookies,但是保存的格式和 MozillaCookieJar 不一样,它会保存成 libwww-perl (LWP) 格式的 Cookies 文件。 |
cookie = http.cookiejar.LWPCookieJar(filename)
|
---|
那么,生成了 Cookies 文件后,怎样从文件中读取并利用呢? |
下面我们以 LWPCookieJar 格式为例来看一下: |
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookies.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))
|
---|
这里调用 load() 方法来读取本地的 Cookies 文件,获取到了 Cookies 的内容。不过前提是我们首先生成了 LWPCookieJar 格式的 Cookies,并保存成文件,然后读取 Cookies 之后使用同样的方法构建 Handler 和 Opener 即可完成操作。 |
运行结果正常的话,会输出百度网页的源代码。 |
转载
作者: 崔庆才
来源:[Python3 网络爬虫开发实战] 3.1.1 - 发送请求