异步爬虫(五)----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
posted @ 2021-01-21 21:53  理工—王栋轩  阅读(202)  评论(0编辑  收藏  举报