异步爬虫(五)----aiohttp
开始学着利用协程做异步爬虫,直接上代码:
先自己弄一个服务器,别问我代码什么意思,我自己都不知道,视频copy过来的。。
from flask import Flask import time app = Flask(__name__) @app.route('/tom') def index_tom(): time.sleep(1) return 'hello tom' if __name__ == '__main__': app.run(threaded = True)
访问http://127.0.0.1:5000/tom,返回一个‘hello tom’,中间等待1s。
import requests print(requests.get(url='http://127.0.0.1:5000/tom').text)
输出:
hello tom
来一个利用协程爬虫的代码:
import aiohttp import asyncio import time import requests urls = ['http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom' ] async def request(url): responses = requests.get(url=url) print(responses.text) async def main(): tasks = [] for u in urls: task = asyncio.create_task(request(u)) tasks.append(task) if __name__ == '__main__': start_time = time.time() asyncio.run(main()) end_time = time.time() print(end_time-start_time)
输出 :
hello tom
hello tom
hello tom
3.0251729488372803
很明显,利用协程做了,但是没有实现并发,时间还是3s多一点。
问题在哪呢?在requests模块是不支持异步的,异步要用到aiohttp。同样cmd,pip install aiohttp下载该模块。
import aiohttp import asyncio import time import requests urls = ['http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom' ] async def request(url): async with aiohttp.ClientSession() as session: async with session.get(url = url) as response: ret = await response.text() return ret async def main(): tasks = [] for u in urls: task = asyncio.create_task(request(u)) tasks.append(task) done,pending = await asyncio.wait(tasks) for i in done: print(i.result()) if __name__ == '__main__': start_time = time.time() asyncio.run(main()) end_time = time.time() print(end_time-start_time)
输出:
hello tom
hello tom
hello tom
1.0150578022003174
下边解释下代码:
async def request(url):
async with aiohttp.ClientSession() as session:
async with session.get(url = url) as response:
ret = await response.text()
return ret
这个协程函数的功能是创建协程对象,访问一个形参url,返回的是响应结果。
async with aiohttp.ClientSession() as session:
async with 固定语法,用于异步协程,with…as……是一个上下文切换器,具体的可以百度相关资料。aiohttp.ClientSession() 创建一个session对象,调用session的方法get可以访问url。如果是get请求,就是调用get方法,如果是post请求,就调用post方法,和之前的requests模块一样。get、post方法中参数可以有headers、params/data和requests模块一样,代理用proxy = ‘http:// + IP:端口号’,传入的是字符串,和requests中proxy={},传入字典不一样。
获取响应结果与requests模块略有不同:
|
aiohttp |
requests |
返回字符串 |
res.text() |
res.text |
返回二进制数据(图像、音频) |
Res.read() |
res.content |
Json |
res.json()
|
res.json()
|
这个函数可以分为两步理解,第一步是申请访问,第二步是返回,返回的过程是一个IO过程,是比较耗时的,我们利用协程做爬虫的目的是利用这个返回的时间,对其他的url申请访问。所以返回时一个阻塞操作,应该用await关键字挂起他,这时候程序会切换到其他协程对象执行。
async def main():
tasks = []
for u in urls:
task = asyncio.create_task(request(u))
tasks.append(task)
done,pending = await asyncio.wait(tasks)
for i in done:
print(i.result())
这个函数的功能是为协程对象创建task对象,并添加到循环事件中,并执行这个循环事件。tasks = []tasks是一个列表,每一个元素是创建的task对象。asyncio.create_task()前边提到过,是创建task对象。done,pending = await asyncio.wait(tasks),这个就是执行这个循环事件,返回的结果以集合形式在done中,所以后边的for循环就是获取每个task的结果。也可以使用回调机制,具体可参考 : https://www.jianshu.com/p/b5e347b3a17c
所以我们可以在for循环这里中持久化存储,也就是将返回结果保存在本地。这个过程是一个CPU繁忙型任务,所以用协程提高效率的意义不大,直接用串行就可以。
if __name__ == '__main__':
start_time = time.time()
asyncio.run(main())
end_time = time.time()
print(end_time-start_time)
这个主函数就不用说了,关键是asyncio.run(main())运行mian()协程函数,前边的main()函数定义的也是协程函数,记住就可以。
有一个问题容易被忽略,我们接受到的响应数据在没有持久化存储之前去哪了?答案是在内存中,所以说如果下载的是视频、音频这些大数据时,内存会很吃劲,尤其是并发数据量非常大时,那怎么办呢?可以根据实际电脑配置限制并发的协程数量。
例子:
import aiohttp import asyncio import time import requests urls = ['http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom', 'http://127.0.0.1:5000/tom' ] async def request(url,semaphore): async with semaphore: async with aiohttp.ClientSession() as session: async with session.get(url = url) as response: ret = await response.text() return ret async def main(): semaphore = asyncio.Semaphore(2) tasks = [] for u in urls: task = asyncio.create_task(request(u,semaphore)) tasks.append(task) done,pending = await asyncio.wait(tasks) for i in done: print(i.result()) if __name__ == '__main__': start_time = time.time() asyncio.run(main()) end_time = time.time() print(end_time-start_time)
添加的部分就是红色字体部分,不需要管啥意思,一个固定用法而已,参考: https://www.cnblogs.com/shenh/p/9090586.html
输出:
hello tom
hello tom
hello tom
hello tom
2.061117649078369
结果很明显了,4个协程对象,2个2个并发的。
分享一个写的非常好,非常全的博客: https://www.cnblogs.com/ssyfj/p/9222342.html
this's all 下一步,做一个爬取糗百或者百思不得姐的视频,那种大批量的。
end_time