1. 基本介绍

前面介绍的 asyncio 模块内部实现了对 TCP、UDP、SSL 协议的异步操作,但是对于 HTTP 请求来说,我们就需要用到 aiohttp 来实现了。

aiohttp 是一个基于 asyncio 的异步 HTTP 网络模块,它既提供了服务端,又提供了客户端。其中我们用服务端可以搭建一个支持异步处理的服务器,就是用来处理请求并返回响应的,类似于 Django、Flask、Tornado 等一些 Web 服务器。而客户端可以用来发起请求,类似于使用 requests 发起一个 HTTP 请求然后获得响应,但 requests 发起的是同步的网络请求,aiohttp 则是异步的。

本节中,我们主要了解一下 aiohttp 客户端部分的用法。

2. 基本实例

首先,我们来看一个基本的 aiohttp 请求案例,代码如下:

import aiohttp
import asyncioasync def fetch(session, url):async with session.get(url) as response:return await response.text(), response.statusasync 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 来修饰。
  • with as 语句前面同样需要加 async 来修饰。在 Python 中,with as 语句用于声明一个上下文管理器,能够帮我们自动分配和释放资源。而在异步方法中,with as 前面加上 async 代表声明一个支持异步的上下文管理器。
  • 对于一些返回 coroutine 的操作,前面需要加 await 来修饰。比如 response 调用 text 方法,查询 API 可以发现,其返回的是 coroutine 对象,那么前面就要加 await;而对于状态码来说,其返回值就是一个数值类型,那么前面就不需要加 await。所以,这里可以按照实际情况处理,参考官方文档说明,看看其对应的返回值是怎样的类型,然后决定加不加 await 就可以了。
  • 最后,定义完爬取方法之后,实际上是 main 方法调用了 fetch 方法。要运行的话,必须要启用事件循环,而事件循环就需要使用 asyncio 库,然后使用 run_until_complete 方法来运行。

注意:在 Python 3.7 及以后的版本中,我们可以使用 asyncio.run(main()) 来代替最后的启动操作,不需要显示声明事件循环,run 方法内部会自动启动一个事件循环。但这里为了兼容更多的 Python 版本,依然还是显式声明了事件循环。

3. URL 参数设置

对于 URL 参数的设置,我们可以借助于 params 参数,传入一个字典即可,示例如下:

import aiohttp
import asyncioasync 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 表单提交,其对应的请求头的 Content-Typeapplication/x-www-form-urlencoded,我们可以用如下方式来实现,代码示例如下:

import aiohttp
import asyncioasync 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"},"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-5e85f0b2-9017ea603a68dc285e0552d0"},"json": null,"origin": "17.20.255.58","url": "https://httpbin.org/post"
}

对于 POST JSON 数据提交,其对应的请求头的 Content-Typeapplication/json,我们只需要将 post 方法的 data 参数改成 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 asyncioasync 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'}

这里我们可以看到有些字段前面需要加 await,有的则不需要。其原则是,如果它返回的是一个 coroutine 对象(如 async 修饰的方法),那么前面就要加 await,具体可以看 aiohttp 的 API,其链接为:docs.aiohttp.org/en/stable/c…

7. 超时设置

对于超时设置,我们可以借助 ClientTimeout 对象,比如这里要设置 1 秒的超时,可以这么实现:

import aiohttp
import asyncioasync 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

如果超时的话,会抛出 TimeoutError 异常,其类型为 asyncio.TimeoutError,我们再进行异常捕获即可。

另外,声明 ClientTimeout 对象时还有其他参数,如 connect、socket_connect 等,详细可以参考官方文档:docs.aiohttp.org/en/stable/c…

8. 并发限制

由于 aiohttp 可以支持非常大的并发,比如上万、十万、百万都是能做到的,但对于这么大的并发量,目标网站很可能在短时间内无法响应,而且很可能瞬时间将目标网站爬挂掉,所以我们需要控制一下爬取的并发量。

一般情况下,我们可以借助于 asyncio 的 Semaphore 来控制并发量,示例如下:

import asyncio
import aiohttpCONCURRENCY = 5
URL = 'https://www.baidu.com'semaphore = asyncio.Semaphore(CONCURRENCY)
session = Noneasync 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 sessionsession = 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())

这里我们声明了 CONCURRENCY(代表爬取的最大并发量)为 5,同时声明爬取的目标 URL 为百度。接着,我们借助于 Semaphore 创建了一个信号量对象,将其赋值为 semaphore,这样我们就可以用它来控制最大并发量了。怎么使用呢?这里我们把它直接放置在对应的爬取方法里面,使用 async with 语句将 semaphore 作为上下文对象即可。这样的话,信号量可以控制进入爬取的最大协程数量,即我们声明的 CONCURRENCY 的值。

main 方法里面,我们声明了 10000 个 task,将其传递给 gather 方法运行。倘若不加以限制,这 10000 个 task 会被同时执行,并发数量太大。但有了信号量的控制之后,同时运行的 task 的数量最大会被控制在 5 个,这样就能给 aiohttp 限制速度了。

9. 总结

本节我们了解了 aiohttp 的基本使用方法,更详细的内容还是推荐大家到官方文档查阅,详见 docs.aiohttp.org/。
本节代码:github.com/Python3WebS…

关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(文末获取!)

温馨提示:篇幅有限,已打包文件夹,获取方式在“文末”!!!

二、Python必备开发工具

三、精品Python学习书籍

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

四、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

五、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

六、Python练习题

检查学习结果。

七、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

Python3 爬虫教程 - aiohttp 的基本使用相关推荐

  1. 廖雪峰python3爬虫教程_Scrapy爬虫框架入门教程(1)——爬取廖雪峰老师的博客...

    最近一直在学习scrapy,但是网上关于scrapy的教程实在是太少,能找到的教程大多都是基于py2.7/scrapy0.2以下,甚至很多教程都是互相抄袭,连代码都抄漏了好多,更别提各种缩进错误.变量 ...

  2. python3爬虫入门教程-Python3爬虫教程基础篇之一:什么是爬虫

    爬虫的定义 爬虫,全名为网络爬虫(英文:web crawler),是一种请求网络资源并提取保存的计算机自动化程序. 最典型的爬虫是百度爬虫.它通过第一时间收集互联网的最新资源并建立索引,使得用户可以在 ...

  3. python爬取豆瓣电影top250_【Python3爬虫教程】Scrapy爬取豆瓣电影TOP250

    今天要实现的就是使用是scrapy爬取豆瓣电影TOP250榜单上的电影信息. 步骤如下: 一.爬取单页信息 首先是建立一个scrapy项目,在文件夹中按住shift然后点击鼠标右键,选择在此处打开命令 ...

  4. python3爬虫入门教程-有什么好的python3爬虫入门教程或书籍吗?

    2018.2.23 更新.现在书也来了,经过一段时间的学习,我把我的爬虫经验写成了一本书,名字叫<Python3网络爬虫开发实战> /> 本书通过多个实战案例详细介绍了 Python ...

  5. python3爬虫入门教程-Python3爬虫学习入门教程

    本文实例讲述了Python3爬虫相关入门知识.分享给大家供大家参考,具体如下: 在网上看到大多数爬虫教程都是Python2的,但Python3才是未来的趋势,许多初学者看了Python2的教程学Pyt ...

  6. python3爬虫系列教程-Python3爬虫视频学习教程

    下面是转发博客内容,挺有用的 大家好哈,现在呢静觅博客已经两年多啦,可能大家过来更多看到的是爬虫方面的博文,首先非常感谢大家的支持,希望我的博文对大家有帮助! 之前我写了一些Python爬虫方面的文章 ...

  7. Python3.x爬虫教程:爬网页、爬图片、自己主动登录

    林炳文Evankaka原创作品. 转载请注明出处http://blog.csdn.net/evankaka 摘要:本文将使用Python3.4爬网页.爬图片.自己主动登录.并对HTTP协议做了一个简单 ...

  8. Python3.x爬虫教程:爬网页、爬图片、自动登录

    林炳文Evankaka原创作品.转载请注明出处 http://blog.csdn.net/evankaka 摘要:本文将使用Python3.4爬网页.爬图片.自动登录.并对HTTP协议做了一个简单的介 ...

  9. Python3网络爬虫教程8——有道在线翻译项目(JS加密)

    上接: Python3网络爬虫教程7--SSL数字证书 https://blog.csdn.net/u011318077/article/details/86538116 6. js加密(有道在线翻译 ...

最新文章

  1. 苹果遭火速打脸:照片扫描AI被逆向工程,英特尔工程师发现漏洞
  2. 29.CSS3边框图片效果
  3. play 连接mysql_Play framework 2.x 连接mysql | 学步园
  4. 11 Django组件-分页器
  5. numpy的增删改查操作
  6. dumpsys命令用法
  7. 看《大明王朝1566》聊分布式中的CAP和BASE理论
  8. 字节跳动日常实习一二三面面经(后端开发)
  9. CMake Error: Could not find a package configuration file provided by “serial“
  10. 记一次淘宝开放平台开通电商软件服务商ISV
  11. AI-常见机器学习算法介绍
  12. python用循环打出阶梯图形,matplotlib阶梯图的实现(step())
  13. 尚未领取号牌和行驶证的机动车需要临时上道路行驶的,怎么办?
  14. i.MX6ULL终结者Linux I2C驱动实验IMX6ULL的I2C总线驱动分析
  15. onenote | 下载安装使用
  16. 谁说菜鸟不会数据分析-入门篇
  17. 可控硅和场效应管区别
  18. 【2022项目复盘】无位置传感器的无刷直流电机驱动设计
  19. 《烈焰龙城》隐私政策
  20. 计算机科学与技术专业软考考什么,为什么大家都考软件水平考试_为什么大家都考软考_软考到底有什么用_课课家...

热门文章

  1. 分析业务模型 - 类图 新书《火球 UML大战需求分析》试读 第3章
  2. 日本:缺陷美女颠覆风潮
  3. css样式优先级规则总结
  4. Java动态代理中报java.lang.IllegalArgumentException非法参数异常
  5. Excel的简单Word报告批量生成 vba笔记 (一)
  6. android 记录美剧观看进度,TVShow Time app-最爱的美剧回归了,你一定还需要这个#iOS #Android...
  7. git 在 commit 提交之后怎么撤回本地提交
  8. c语言数组实现环形缓冲区,[嵌入式开发模块]环形缓冲区/循环队列 C语言实现
  9. android 换机 iphone8,iPhone 8屏幕大升级 给了安卓用户一个换机理由
  10. Flink CDC 系列(7)—— 从 MySQL 到 ElasticSearch