1. 基本介绍
前面介绍的 asyncio 实现了模块内部的正确性 TCP、UDP、SSL 协议的异步操作,但对于 HTTP 就请求而言,我们需要使用它 aiohttp 来实现了。
aiohttp 是一个基于 asyncio 的异步 HTTP 提供服务端和客户端的网络模块。其中,我们可以用服务器构建支持异步处理的服务器,用于处理请求并返回响应,类似于 Django、Flask、Tornado 等一些 Web 服务器。而客户端可以用来发起请求,类似于使用 requests 发起一个 HTTP 请求然后得到响应,但是 requests 同步同步网络请求,aiohttp 异步。
本节主要了解 aiohttp 使用客户端部分。
2. 基本实例
首先,让我们来看看个基本的 aiohttp 请求案例代码如下:
import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text(), response.status async def main(): async with aiohttp.ClientSession() as session: html, status = await fetch(session, 'https://cuiqingcai.com') print(f'html: {html[:100]}...') print(f'status: {status}') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main())
我们在这里使用 aiohttp 爬上我的个人博客,获取源代码和响应状态代码并输出,运行结果如下:
html: <!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> <meta name="baidu-tc-verification" content=... status: 200
这里的网页源代码太长,只截取部分输出。可以看出,我们成功获取了网页的源代码和响应状态代码 200,基本完成一次 HTTP 请求是我们成功使用的 aiohttp 网页爬行是通过异步进行的。当然,这个操作用之前说过 requests 也可以做到。
可见,其请求方法的定义与以往有明显的不同,主要有以下几点:
- 首先,在导入仓库时,我们必须引入它 aiohttp 除了这个库,还必须引入 asyncio 这个库。为了实现异步爬行,需要启动协程,协程需要帮助 asyncio 执行内部的事件循环。除事件循环外,asyncio 它还提供了许多基本的异步操作。
- 异步爬行方法的定义与以前不同,应在每个异步方法前统一添加 async 来修饰。
- 句子的前面也需要添加 来修饰。在 Python 中,with as 声明上下文管理器可以帮助我们自动分配和释放资源。在异步方法中, 前面加上 代表声明支持异步的上下文管理器。
- 对于一些返回 前面需要添加的操作 装饰 调用 方法,查询 API 可以发现,其返回的是 对象,所以前面要加 ;对于状态码,其返回值是一种数值类型,因此无需在前面添加 。因此,这里可以根据实际情况进行处理,参考官方文件说明,查看相应的返回值类型,然后决定是否添加 就可以了。
- 最后,定义爬行方法后,实际上是 方法调用了 方法。要运行,必须启用事件循环,需要使用事件循环 asyncio 然后使用 操作方法。
注意:在 Python 3.7 在以后的版本中,我们可以使用它 替换最终启动操作,无需显示声明事件循环, 该方法将自动启动事件循环。但这里有更多的兼容性 Python 版本仍然显式地声明了事件循环。
3. URL 参数设置
对于 URL 我们可以借助设置参数 将参数传入字典如下:
import aiohttp import asyncio async def main(): params = {'name': 'germey', 'age': 25} async with aiohttp.ClientSession() as session: async with session.get('https://httpbin.org/get', params=params) as response: print(await response.text()) if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main())
运行结果如下:
{ "args": { "age": "25", "name": "germey" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "Python/3.7 aiohttp/3.6.2", "X-Amzn-Trace-Id": "Root=1-5e85eed2-d240ac90f4dddf40b4723ef0" }, "origin": "17.20.255.122", "url": "https://httpbin.org/get?name=germey&age=25" }
这里可以看到实际请求 URL 为 httpbin.org/get?name=ge… URL 请求参数对应 params 的内容。
4. 其它类型的请求
另外,aiohttp 还支持其他类型的请求,如 POST、PUT、DELETE 等,这和 requests 例如,使用方法有点相似:
session.post('http://httpbin.org/post', data=b'data') session.put('http://httpbin.org/put', data=b'data') session.delete('http://httpbin.org/delete') session.head('http://httpbin.org/get') session.options('http://httpbin.org/get') session.patch('http://httpbin.org/patch', data=b'data')
只需替换相应的方法和参数即可使用这些方法。
5. POST 请求
对于 POST 表单提交,其对应的请求头的 为 ,代码示例如下:
import aiohttp import asyncio async def main(): data = {'name': 'germey', 'age': 25} async with aiohttp.ClientSession() as session: async with session.post('https://httpbin.org/post', data=data) as response: print(await response.text()) if __name__ == '__main__': asyncio.get_event_loop().run_until_complete(main())
运行结果如下:
{ "args": {}, "data": "", "files": {}, "form": { "age": "25", "name": "germey" }, "headrs": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "18",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python/3.7 aiohttp/3.6.2",
"X-Amzn-Trace-Id": "Root=1-5e85f0b2-9017ea603a68dc285e0552d0"
},
"json": null,
"origin": "17.20.255.58",
"url": "https://httpbin.org/post"
}
对于 POST JSON 数据提交,其对应的请求头的 为 ,我们只需要将 方法的 参数改成 即可,代码示例如下:
async def main():
data = {'name': 'germey', 'age': 25}
async with aiohttp.ClientSession() as session:
async with session.post('https://httpbin.org/post', json=data) as response:
print(await response.text())
运行结果如下:
{
"args": {},
"data": "{\"name\": \"germey\", \"age\": 25}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "29",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Python/3.7 aiohttp/3.6.2",
"X-Amzn-Trace-Id": "Root=1-5e85f03e-c91c9a20c79b9780dbed7540"
},
"json": {
"age": 25,
"name": "germey"
},
"origin": "17.20.255.58",
"url": "https://httpbin.org/post"
}
可以发现,其实现也和 requests 非常像,不同的参数支持不同类型的请求内容。
6. 响应
对于响应来说,我们可以用如下方法分别获取响应的状态码、响应头、响应体、响应体二进制内容、响应体 JSON 结果,示例如下:
import aiohttp
import asyncio
async def main():
data = {'name': 'germey', 'age': 25}
async with aiohttp.ClientSession() as session:
async with session.post('https://httpbin.org/post', data=data) as response:
print('status:', response.status)
print('headers:', response.headers)
print('body:', await response.text())
print('bytes:', await response.read())
print('json:', await response.json())
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
运行结果如下:
status: 200
headers: <CIMultiDictProxy('Date': 'Thu, 02 Apr 2020 14:13:05 GMT', 'Content-Type': 'application/json', 'Content-Length': '503', 'Connection': 'keep-alive', 'Server': 'gunicorn/19.9.0', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': 'true')>
body: {
"args": {},
"data": "",
"files": {},
"form": {
"age": "25",
"name": "germey"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "18",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python/3.7 aiohttp/3.6.2",
"X-Amzn-Trace-Id": "Root=1-5e85f2f1-f55326ff5800b15886c8e029"
},
"json": null,
"origin": "17.20.255.58",
"url": "https://httpbin.org/post"
}
bytes: b'{\n "args": {}, \n "data": "", \n "files": {}, \n "form": {\n "age": "25", \n "name": "germey"\n }, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Content-Length": "18", \n "Content-Type": "application/x-www-form-urlencoded", \n "Host": "httpbin.org", \n "User-Agent": "Python/3.7 aiohttp/3.6.2", \n "X-Amzn-Trace-Id": "Root=1-5e85f2f1-f55326ff5800b15886c8e029"\n }, \n "json": null, \n "origin": "17.20.255.58", \n "url": "https://httpbin.org/post"\n}\n'
json: {'args': {}, 'data': '', 'files': {}, 'form': {'age': '25', 'name': 'germey'}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '18', 'Content-Type': 'application/x-www-form-urlencoded', 'Host': 'httpbin.org', 'User-Agent': 'Python/3.7 aiohttp/3.6.2', 'X-Amzn-Trace-Id': 'Root=1-5e85f2f1-f55326ff5800b15886c8e029'}, 'json': None, 'origin': '17.20.255.58', 'url': 'https://httpbin.org/post'}
这里我们可以看到有些字段前面需要加 ,有的则不需要。其原则是,如果它返回的是一个 对象(如 修饰的方法),那么前面就要加 ,具体可以看 aiohttp 的 API,其链接为:docs.aiohttp.org/en/stable/c…
7. 超时设置
对于超时设置,我们可以借助 ClientTimeout 对象,比如这里要设置 1 秒的超时,可以这么实现:
import aiohttp
import asyncio
async def main():
timeout = aiohttp.ClientTimeout(total=1)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get('https://httpbin.org/get') as response:
print('status:', response.status)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
如果在 1 秒之内成功获取响应的话,运行结果如下:
200
如果超时的话,会抛出 异常,其类型为 ,我们再进行异常捕获即可。
另外,声明 对象时还有其他参数,如 等,详细可以参考官方文档:docs.aiohttp.org/en/stable/c…
8. 并发限制
由于 aiohttp 可以支持非常大的并发,比如上万、十万、百万都是能做到的,但对于这么大的并发量,目标网站很可能在短时间内无法响应,而且很可能瞬时间将目标网站爬挂掉,所以我们需要控制一下爬取的并发量。
一般情况下,我们可以借助于 asyncio 的 来控制并发量,示例如下:
import asyncio
import aiohttp
CONCURRENCY = 5
URL = 'https://www.baidu.com'
semaphore = asyncio.Semaphore(CONCURRENCY)
session = None
async def scrape_api():
async with semaphore:
print('scraping', URL)
async with session.get(URL) as response:
await asyncio.sleep(1)
return await response.text()
async def main():
global session
session = aiohttp.ClientSession()
scrape_index_tasks = [asyncio.ensure_future(scrape_api()) for _ in range(10000)]
await asyncio.gather(*scrape_index_tasks)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
这里我们声明了 (代表爬取的最大并发量)为 5,同时声明爬取的目标 URL 为百度。接着,我们借助于 创建了一个信号量对象,将其赋值为 ,这样我们就可以用它来控制最大并发量了。怎么使用呢?这里我们把它直接放置在对应的爬取方法里面,使用 语句将 作为上下文对象即可。这样的话,信号量可以控制进入爬取的最大协程数量,即我们声明的 的值。
在 方法里面,我们声明了 10000 个 ,将其传递给 方法运行。倘若不加以限制,这 10000 个 会被同时执行,并发数量太大。但有了信号量的控制之后,同时运行的 task 的数量最大会被控制在 5 个,这样就能给 aiohttp 限制速度了。
9. 总结
本节我们了解了 aiohttp 的基本使用方法,更详细的内容还是推荐大家到官方文档查阅,详见 docs.aiohttp.org/。 本节代码:github.com/Python3WebS…
关于Python技术储备
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(文末获取!)
二、Python必备开发工具
三、精品Python学习书籍
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、Python练习题
检查学习结果。
七、面试资料
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。