本文主要讲述如何基于python3.x的urllib库(官方标准库),实现自定义的Handler处理器,代码中实现的效果可以用其他简单的方式实现,这里只是举例
语言版本:Python 3.7
测试链接:http://httpbin.org
通常我们请求一个链接的python代码写法是:
import requests
url = "http://httpbin.org/get"
res = requests.get(url)
print(res.content)
运行后得到如下返回结果
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.22.0"
},
"origin": "x.x.x.x, x.x.x.x",
"url": "https://httpbin.org/get"
}
但我们这里不打算使用第三方请求库requests
,所以用urllib库的实现版本如下,得到的结果不同之处只在于默认的User-Agent
字段变成了urllib
.
import urllib.request as urlreq
url = "http://httpbin.org/get"
res = urlreq.urlopen(url)
print(res.read().decode('utf-8'))
目前一切OK,只是如果我们打算使用代理IP进行请求,urllib
的基本请求方式便不能满足,于是引入了opener
和Handler
的用法:
import urllib.request as urlreq
url = "http://httpbin.org/get"
proxy = {'http':'112.87.68.202:9999'}#此处我就随便写一个错误的代理IP
proxyhandler = urlreq.ProxyHandler(proxies=proxy)
opener = urlreq.build_opener(proxyhandler)
res = opener.open(url)
print(res.read().decode('utf-8'))
我们会发现orgin字段已经变成我们使用的代理IP的地址
"origin": "112.87.68.202, 112.87.68.202",
urllib
库本身已经实现了一些Handler,比如上面使用的ProxyHandler
,但功能上的需求因人而异,比如我希望在请求某个链接时只允许这个过程发生三次重定向,然后这三次重定向每次的user-agent都是不一样的。那么模块已经提供的handler便无法满足这些功能,需要我们自定义Handler.
在前面的请求例子中,我们发现user-agent字段一直是模块默认的字段,显得并不像一个浏览器该有的user-agent,简单的python代码是:
import requests
url = '...'
headers = {"User-Agent":"......"}
res = requests.get(url,headers=headers)
或者是
import urllib.request as urlreq
url = '...'
headers = {"User-Agent":'.....'}
req = urlreq.Request(url,headers=headers)
res = urlreq.urlopen(req)
而如果是自定义的Handler版本则是:
from fake_useragent import UserAgent
import urllib.request as request
ua = UserAgent()
url = 'http://httpbin.org/get'
class HeaderHandler(request.BaseHandler):
def __init__(self,headers=None):
if headers is None:
headers = ua.random
self.headers = headers
def http_request(self,req):
req.headers['User-Agent'] = self.headers
return req
https_request = http_request
# 使用handler请求*****************
proxyhandler = request.ProxyHandler(proxies={'http':'163.204.242.73:9999'})
headerhandler = HeaderHandler()
handlers = [proxyhandler,headerhandler]
opener = request.build_opener(*handlers)
req_2 = request.Request(url)
res_2 = opener.open(req_2)
print(res_2.read().decode('utf-8'))
这个例子显然比前两种实现方式复杂,有点大材小用,但如果是更为复杂的功能需求时,它的可定制化就展现出其强大。
fake_useragent
,该模块主要用于提供user-agent
字段,读者可以直接复制一个user-agent
字段.proxyhandler
和headerhandler
,一个完成IP代理功能,一个完成请求头字段修改的功能.HTTPRedirectHandler
)的基础上实现,那可以继承自它BaseHandler
BaseHandler提供的几个函数的功能,不得不联想Scrapy爬虫框架中的中间件是不是基于此实现。在考虑如何子类化这个类前,有必要了解一个opener,即OpenerDirector对象,在处理一个url或Request对象时经历的三个时期:
Request预处理时期。这个时期主要是对Request对象在进行urlopen或者open操作之前的再加工,这个时期opener会根据我们的handler的顺序(我们是有多个Handler的,如这个例子看得到的
就有两个),依次调用他们的protocol_request()方法,这里的protocol是指http或者https,如你的重写函数是http_request()。这个方法接受一个Request对象并返回一个Request对象,这个对象继续传给下一个Handler的protocol_request方法加工,或者做其他操作。
Request处理时期。这个时期是opener真正去向网络请求的时期,要对这个时期进行影响则需要对BaseHandler的继承类实现*_open方法。*_open方法由三个方法(default_open()、protocol_open()、unknown_open())组成,但对每一个BaseHandler子类,不要求都实现他们或者实现他们其中一个。未完待续
Response处理时期。未完待续
下面的代码例子实现了限制重定向次数的功能,测试网站使用requests库作者的另一个开源项目http://httpbin.org,网站提供了一个测试重定向的功能,在这里的用法是
http://httpbin.org/absolute-redirect/{n}
修改n,就可以决定请求过程中发生的重定向次数,比如重定向3次,则
http://httpbin.org/absolute-redirect/3
在这里我没有像第一个例子一样继承基类BaseHandler,而是继承自其子类HTTPRedirectHandler,并且直接复制其中的源代码进行修改。那么为什么我知道要这样修改呢 ?
import urllib.request as urlreq
from urllib.error import HTTPError
from urllib.parse import urlencode
class RedirectTimesOutError(Exception):
def __init__(self,expression,message):
self.expression = expression
self.message = message
class RedirectError(Exception):
def __init__(self,expression,message):
self.expression = expression
self.message = message
class MyHTTPRedirectHandler(urlreq.HTTPRedirectHandler):
def __init__(self,timeslimit=3):
self.limit = timeslimit
self.flag = 1
def redirect_request(self,req,fp,code,msg,headers,newurl):
m = req.get_method()
if (code != 302) and (m != 'GET'):
raise RedirectError('','Must 302 redirect from GET Method')
# raise HTTPError(req.full_url,code,msg,headers,fp)
print('newurl:',newurl)
print('-'*8)
newurl = newurl.replace(' ','%20')
CONTENT_HEADERS = ("content-length","content-type")
newheaders = {k:v for k,v in req.headers.items()
if k.lower() not in CONTENT_HEADERS}
if self.flag > self.limit:
self.flag = 1
raise RedirectTimesOutError(expression='32',message='Redirect times out!')
else:
self.flag +=1
return urlreq.Request(newurl,
headers=newheaders,
origin_req_host=req.origin_req_host,
unverifiable=True)
# def http_error_302(self,req,fp,code,msg,hdrs):
# print('call here')
# pass
url = 'http://httpbin.org/absolute-redirect/4' #GET请求
data = None
myhandler = MyHTTPRedirectHandler(4)
opener = urlreq.build_opener(myhandler)
try:
res_2 = opener.open(url,data=data)
except RedirectTimesOutError as e:
print(e.message)
else:
print(res_2.getcode())
# print(res_2.read().decode('utf-8'))
代码复制了源码urllib模块的request.py文件中的HTTPRedirectHandler类的redirect_request函数,并修改部分:
通过设定self.limit
值来限定对于一个链接的访问中间允许有多少次重定向发生,而利用self.flag
记录第几次进行处理该重定向,最后需要在抛出异常前重置self.flag
的值,否则如果该Handler继续处理一次新的请求的重定向时,受到前一次请求得干扰。
在官方API对这个Handler有这样一段介绍:
HTTPRedirectHandler.redirect_request(req, fp, code, msg, hdrs, newurl)
Return a Request or None in response to a redirect.
This is called by the default implementations of the http_error_30*() methods when a redirection is received from the server.
If a redirection should take place, return a new Request to allow http_error_30*() to perform the redirect to newurl. Otherwise, raise HTTPError if no other handler should try to handle this URL, or return None if you can’t but another handler might.
这段话主要有这样两则信息:
redirect_request
函数用于处理重定向,如果这个Handler能处理这个重定向,那么返回Request对象;如果这个Handler不能,那么返回None,把处理权转给下一个能处理它的Handler.http_error_30*
函数,这个函数进而触发redirect_request
函数进行处理.而读者可能会有下面两个疑问:
http_error_30*
,我们上面的例子没有实现该方法,但为什么被触发了,而且优先于系统默认的处理方案被执行?http_error_30*
,执行顺序会是怎样的呢?这些问题在下一篇Handler的主题里解释,这里需要结合源码进行解释其行为