本章将告诉你该如何去对 request 模块进行二次封装,暂时并不会告诉你 HTTP 协议及原理、URL 等相关。当然你会使用然后在来阅读此文章一定会另有所获。我已经迫不及待要告诉你这个小秘密,以及想与你交流了。没时间解释了,快来一起和我一起探讨相关的内容吧
官方文档 对 requests 的定义为:Requests 唯一的一个非转基因 的 Python HTTP 库,人类可以安全享用。
使用 Python 写做爬虫的小伙伴一定使用过 requests 这个模块,初入爬虫的小伙伴也一定写过 N 个重复的 requests,这是你的疑问。当然也一直伴随着我,最近在想对 requests 如何进行封装一下,让他支持支持通用的函数。若需要使用,直接调用即可。
那么问题来了,如果要写个供自己使用通用的请求函数将会有几个问题
requests 的请求方式 (GET\POST\INPUT 等等)
智能识别网站的编码,避免出现乱码
支持文本、二进制 (图片、视频等为二进制内容)
以及还需要傻瓜一点,那就是网站的 Ua (Ua:User-Agent,基本上网站都会验证接受到请求的 Ua。来初步判断是爬虫还是用户)
那么咱们就针对以上问题开干吧
Requests 的安装
在确保 python 环境搭建完成后直接使用 pip 或者 conda 命令进行安装,安装命令如下:
1 2 3 4 5
pip install requests conda install requests pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple/
安装完成后,效果图如下:
初探 requests 基本使用
HTTP 中最常见的请求之一就是 GET 请求,下面我们来详细了解利用 requests 库构建 GET 请求的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
import requestsresponse = requests.get('http://httpbin.org/get' ) print("response.status_code:" , response.status_code) print("response.headers:" , response.headers) print("response.request.headers:" , response.request.headers) print("response.content:" , response.content) print("response.text" , response.text) response.status_code: 200 response.headers: {'Date' : 'Thu, 12 Nov 2020 13:38:05 GMT' , 'Content-Type' : 'application/json' , 'Content-Length' : '306' , 'Connection' : 'keep-alive' , 'Server' : 'gunicorn/19.9.0' , 'Access-Control-Allow-Origin' : '*' , 'Access-Control-Allow-Credentials' : 'true' } response.request.headers: {'User-Agent' : 'python-requests/2.24.0' , 'Accept-Encoding' : 'gzip, deflate' , 'Accept' : '*/*' , 'Connection' : 'keep-alive' } response.content: b'{\n "args": {}, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Host": "httpbin.org", \n "User-Agent": "python-requests/2.24.0", \n "X-Amzn-Trace-Id": "Root=1-5fad3abd-7516d60b3e951824687a50d8"\n }, \n "origin": "116.162.2.166", \n "url": "http://httpbin.org/get"\n}\n' { "args" : {}, "headers" : { "Accept" : "*/*" , "Accept-Encoding" : "gzip, deflate" , "Host" : "httpbin.org" , "User-Agent" : "python-requests/2.24.0" , "X-Amzn-Trace-Id" : "Root=1-5fad3abd-7516d60b3e951824687a50d8" }, "origin" : "116.162.2.166" , "url" : "http://httpbin.org/get" }
requests 基本使用已经经过简单的测试了,是否有一点点 feel 呢?接下来我们直接将它封装为一个函数以供随时调用
示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import requestsurls = 'http://httpbin.org/get' def downloader (url, headers=None) : response = requests.get(url, headers=headers) return response print("downloader.status_code:" , downloader(url=urls).status_code) print("downloader.headers:" , downloader(url=urls).headers) print("downloader.request.headers:" , downloader(url=urls).request.headers) print("downloader.content:" , downloader(url=urls).content) print("downloader.text" , downloader(url=urls).text)
以上我们就把,请求方法封装成了一个函数。将基本的 url,headers 以形参的方式暴露出来,我们只需传入需要请求的 url 即可发起请求,至此一个简单可复用的请求方法咱们就完成啦。
完~~~
以上照顾新手的就基本完成了,接下来我们搞点真家伙。
二次封装
请求函数的封装
由于请求方式并不一定 (有可能是 GET 也有可能是 POST),所以我们并不能智能
的确定它是什么方式发送请求的。
Requests 中 request 方法以及帮我们实现了这个方法。我们将他的请求方式暴露出来,写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
urls = 'http://httpbin.org/get' def downloader (url, method=None, headers=None) : _method = "GET" if not method else method response = requests.request(url, method=_method, headers=headers) return response print("downloader.status_code:" , downloader(url=urls).status_code) print("downloader.headers:" , downloader(url=urls).headers) print("downloader.request.headers:" , downloader(url=urls).request.headers) print("downloader.content:" , downloader(url=urls).content) print("downloader.text" , downloader(url=urls).text)
由于大部分都是 GET 方法,所以我们定义了一个默认的请求方式。如果需要修改请求方式,只需在调用时传入相对应的方法即可。例如我们可以这样
1
downloader(urls, method="POST" )
文本编码问题
解决由于 request 的误差判断而造成解码错误,而得到乱码。
此误差造成的原因是可能是响应头的 Accept-Encoding
,另一个是识别错误
此时我们需要借用 Python 中 C 语言编写的 cchardet
这个包来识别响应文本的编码。安装它
1 2
pip install cchardet -i https://pypi.tuna.tsinghua.edu.cn/simple/
1 2 3 4 5 6 7 8 9 10
encoding = cchardet.detect(response.content)['encoding' ] def downloader (url, method=None, headers=None) : _method = "GET" if not method else method response = requests.request(url, method=_method, headers=headers) encoding = cchardet.detect(response.content)['encoding' ] return response.content.decode(encoding)
区分二进制与文本的解析
在下载图片、视频等需获取到其二进制内容。而下载网页文本需要进行 encode。
同理,我们只需要将一个标志传进去,从而达到分辨的的效果。例如这样
1 2 3 4 5
def downloader (url, method=None, headers=None, binary=False) : _method = "GET" if not method else method response = requests.request(url, method=_method, headers=headers) encoding = cchardet.detect(response.content)['encoding' ] return response.content if binary else response.content.decode(encoding)
默认 Ua
在很多时候,我们拿 ua 又是复制。又是加引号构建 key-value 格式。这样有时候仅仅用 requests 做个测试。就搞的麻烦的很。而且请求过多了,直接就被封 IP 了。没有自己的 ip 代理,没有钱又时候还真有点感觉玩不起爬虫。
为了减少被封禁 IP 的概率什么的,我们添加个自己的 Ua 池。Ua 池的原理很简单,内部就是采用随机的 Ua,从而减少被发现的概率
. 至于为什么可以达到这这样的效果,在这里仅作简单介绍。详细可能要从计算机网络原理说起。
结论就是你一个公司里大多采用的都是同一个外网
ip 去访问目标网址。那么就意味着可能你们公司有 N 个人使用同一个 ip 去访问目标网址。而封禁做区分的一般由 ip 访问频率和浏览器的指纹和在一起的什么鬼东东。简单理解为 Ua+ip 访问频率达到峰值,你 IP 就对方关小黑屋了。
构建自己的 ua 池,去添加默认的请求头,
Ua 有很多,这里就不放出来了,如果有兴趣可以直接去源码 里面拿。直接说原理:构造很多个 Ua,然后随机取用。从而降低这个同一访问频率:同时也暴露端口方便你自己传入 header
1 2 3 4 5 6 7 8 9
from powerspider.tools.Ua import uaimport requestsdef downloader (url, method=None, header=None, binary=False) : _headers = header if header else {'User-Agent' : ua()} _method = "GET" if not method else method response = requests.request(url, method=_method, headers=_headers) encoding = cchardet.detect(response.content)['encoding' ] return response.content if binary else response.content.decode(encoding)
那么基本的文件都已经解决了,不过还不完美。异常处理,错误重试,日志什么都没。这怎么行呢。活既然干了,那就干的漂漂亮亮的。
来让我们加入进来这些东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import cchardetfrom retrying import retryfrom powerspider import loggerfrom powerspider.tools.Ua import uafrom requests import request, RequestException@retry(stop_max_attempt_number=3, retry_on_result=lambda x: x is None, wait_fixed=2000) def downloader (url, method=None, header=None, timeout=None, binary=False, **kwargs) : logger.info(f'Scraping {url} ' ) _header = {'User-Agent' : ua()} _maxTimeout = timeout if timeout else 5 _headers = header if header else _header _method = "GET" if not method else method try : response = request(method=_method, url=url, headers=_headers, **kwargs) encoding = cchardet.detect(response.content)['encoding' ] if response.status_code == 200 : return response.content if binary else response.content.decode(encoding) elif 200 < response.status_code < 400 : logger.info(f"Redirect_URL: {response.url} " ) logger.error('Get invalid status code %s while scraping %s' , response.status_code, url) except RequestException as e: logger.error(f'Error occurred while scraping {url} , Msg: {e} ' , exc_info=True ) if __name__ == '__main__' : print(downloader("https://www.baidu.com/" , "GET" ))
至此,我们的对 Requests 二次封装,构造通用的请求函数就已经完成了。
源码地址:https://github.com/PowerSpider/PowerSpider/tree/dev
期待下次再见