【网络爬虫 -06】使用 urllib-发送请求

一.使用 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()
利用 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 搭建的。
  • urlopen() 函数的 API
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 参数
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 类型的对象。通过构造这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可更加丰富和灵活地配置参数。
  • Request的API
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 - 发送请求

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值