网站做好了怎么上线,广告设计创意培训,在centos上搭建wordpress,怎么制作网页视频教学在上一篇博客我们介绍了异步爬虫的基本原理和 asyncio 的基本用法#xff0c;并且在最后简单提及了使用aiohttp 实现网页爬取的过程。本篇博客我们介绍一下 aiohttp 的常见用法。
基本介绍
前面介绍的 asyncio模块#xff0c;其内部实现了对 TCP、UDP、SSL协议的异步操作并且在最后简单提及了使用aiohttp 实现网页爬取的过程。本篇博客我们介绍一下 aiohttp 的常见用法。
基本介绍
前面介绍的 asyncio模块其内部实现了对 TCP、UDP、SSL协议的异步操作但是对于 HTTP 请求来说就需要用 aiohttp 实现了。
aiohttp 是一个基于 asyncio 的异步 HTTP 网络模块它既提供了服务端又提供了客户端。其中我们用服务端可以搭建一个支持异步处理的服务器这个服务器就是用来处理请求并返回响应的类似于 Django、Flask、Tormado 等一些 Web服务器。而客户端可以用来发起请求,类似于使用 requests 发起一个 HTTP 请求然后获得响应但 requests 发起的是同步的网络请求aiohttp 则是异步的。
本篇博客我们主要了解一下 aiohttp 客户端部分的用法。
基本实例
我们来看一个基本的 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(fhtml:{html[:100]}...)print(fstatus:{status})if __name__ __main__:loop asyncio.get_event_loop()loop.run_until_complete(main())这里使用 aiohttp 爬取了本书作者的个人博客获得了源码和响应状态码并打印出来运行结果如下:
html:!DOCTYPE html
html langzh-CNheadmeta charsetUTF-8meta nameviewport content...
status:200由于网页源码过长这里只截取了输出的一部分。可以看到我们成功获取了网页的源代码及响应状态码 200也就是完成了一次基本的 HTTP请求即我们成功使用 aiohttp 通过异步的方式完成了网页爬取。当然这个操作用之前讲的 requests 也可以做到。
能够发现aiohttp 的请求方法的定义和之前有明显的区别主要包括如下几点。
首先在导人库的时候除了必须引人 aiohttp这个库还必须引人asyncio 库。因为要实现异步爬取需要启动协程而协程则需要借助于asyncio 里面的事件循环才能执行。除了事件循环asyncio 里面也提供了很多基础的异步操作。异步爬取方法的定义和之前有所不同每个异步方法的前面都要统一加 async 来修饰。with as 语句前面同样需要加 async 来修饰。在 Pvthon 中with as 语句用于声明一个上下文管理器能够帮我们自动分配和释放资源。而在异步方法中withas前面加上async代表声明一个支持异步的上下文管理器。对于一些返回协程对象的操作前面需要加 await 来修饰。例如 response 调用 text 方法查询 API可以发现其返回的是协程对象那么前面就要加 await;而对于状态码来说其返回值就是一个数值因此前面不需要加 await。所以这里可以按照实际情况做处理参考官方文档说明看看其对应的返回值是怎样的类型然后决定加不加await 就可以了。最后定义完爬取方法之后实际上是 main 方法调用了 fetch 方法。要运行的话必须启用事件循环而事件循环需要使用 asyncio库然后调用 run_until_complete 方法来运行。 注意在 Python 3.7 及以后的版本中我们可以使用 asyncio.run(main())代替最后的启动操作不需要显示声明事件循环run 方法内部会自动启动一个事件循环。但这里为了兼容更多的Python 版本依然显式声明了事件循环。 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://www.httpbin.org/get, paramsparams) 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: www.httpbin.org, User-Agent: Python/3.9 aiohttp/3.11.11, X-Amzn-Trace-Id: Root1-677217f5-3bc6954d23f245465ccbb806}, origin: 111.18.92.1, url: https://www.httpbin.org/get?namegermeyage25
}这里可以看到实际请求的 URL 为 https://www.httpbin.org/get?namegermeyage-25其中的参数对应于 params 的内容。
其他请求类型
aiohttp 还支持其他请求类型如 POST、PUT、DELETE 等这些和 requests 的使用方式有点类 似实例如下:
session.post(http://www.httpbin.org/post,databdata)
session.put(http://www.httpbin.org/put, databdata)
session.delete(http://www.httpbin.org/delete)
session.head(http://www.httpbin.org/get)
session.options(http://www.httpbin.org/get)
session.patch(http://www.httpbin.org/patch, databdata)要使用这些方法只需要把对应的方法和参数替换一下。
POST请求
对于 POST表单提交,其对应的请求头中的Content-Type为application/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://www.httpbin.org/post, datadata) 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: www.httpbin.org, User-Agent: Python/3.9 aiohttp/3.11.11, X-Amzn-Trace-Id: Root1-67721981-6891ead2152d4e70567cd9ab}, json: null, origin: 111.18.92.1, url: https://www.httpbin.org/post
}对于 POST JSON 数据提交其对应的请求头中的 Content-Type 为 application/json我们只需要将 post 方法里的 data 参数改成 json 即可实例代码如下:
import aiohttp
import asyncioasync def main():data {name: germey, age: 25}async with aiohttp.ClientSession() as session:async with session.post(https://www.httpbin.org/post, jsondata) as response:print(await response.text())if __name__ __main__:asyncio.get_event_loop().run_until_complete(main())运行结果如下:
{args: {}, data: {\name\: \germey\, \age\: 25}, files: {}, form: {}, headers: {Accept: */*, Accept-Encoding: gzip, deflate, Content-Length: 29, Content-Type: application/json, Host: www.httpbin.org, User-Agent: Python/3.9 aiohttp/3.11.11, X-Amzn-Trace-Id: Root1-67721ce2-0439155d5dce282c3803cca6}, json: {age: 25, name: germey}, origin: 111.18.92.1, url: https://www.httpbin.org/post
}可以发现其实现也和requests 非常像不同的参数支持不同类型的请求内容。
响应
对于响应来说我们可以用如下方法分别获取其中的状态码、响应头、响应体、响应体二进制内容、响应体 JSON 结果实例代码如下:
import aiohttp
import asyncioasync def main():data {name: germey, age: 25}async with aiohttp.ClientSession() as session:async with session.post(https://www.httpbin.org/post, datadata) 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: Mon, 30 Dec 2024 04:40:46 GMT, Content-Type: application/json, Content-Length: 510, 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: www.httpbin.org, User-Agent: Python/3.9 aiohttp/3.11.11, X-Amzn-Trace-Id: Root1-6772244e-191cbb78229cbd5412777470}, json: null, origin: 111.18.92.1, url: https://www.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: www.httpbin.org, \n User-Agent: Python/3.9 aiohttp/3.11.11, \n X-Amzn-Trace-Id: Root1-6772244e-191cbb78229cbd5412777470\n }, \n json: null, \n origin: 111.18.92.1, \n url: https://www.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: www.httpbin.org, User-Agent: Python/3.9 aiohttp/3.11.11, X-Amzn-Trace-Id: Root1-6772244e-191cbb78229cbd5412777470}, json: None, origin: 111.18.92.1, url: https://www.httpbin.org/post}可以看到这里有些字段前面需要加 await有些则不需要。其原则是如果返回的是一个协程对象(如 async修饰的方法),那么前面就要加 await,具体可以看 aiohttp的API,其链接为:https://docs.aiohttp.org/en/stable/client_reference.html.
超时设置
我们可以借助 clientTimeout 对象设置超时例如要设置1秒的超时时间可以这么实现:
import aiohttp
import asyncioasync def main():timeout aiohttp.ClientTimeout(total1)async with aiohttp.ClientSession(timeouttimeout) as session:async with session.get(https://www.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等详细可以参考官方文档:https://docs.aiohttp.org/en/stable/client_quickstart.html#timeouts。
并发限制
由于 aiohttp 可以支持非常高的并发量如几万、十万、百万都是能做到的但面对如此高的并发量目标网站很可能无法在短时间内响应而且有瞬间将目标网站爬挂掉的危险这提示我们需要控制一下爬取的并发量。
一般情况下可以借助 asyncio 的 Semaphore 来控制并发量实例代码如下:
import aiohttp
import asyncioCONCURRENCY 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())参考文献
https://docs.aiohttp.org/