网络爬虫之异步协程
引言:异步协程本质就是一条线程中多个任务遇到阻塞操作就自动挂起并继续执行下一个任务,等待阻塞操作完成之后再回去执行完剩余的操作。涉及的模块:aiohttp,asyncio。
协程的作用:
减轻了操作系统的负担
用来规避IO操作,就达到了我们将一条线程中的io操作降到最低的目的
一条线程如果开了多个协程,那么给操作系统的印象是线程很忙,这样能多争取一些时间片时间来被CPU执行,程序的效率就提高了
下面来看一个例子:
import aiohttp import asyncio import time headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } urls = ['https://www.baidu.com/', 'https://www.hao123.com/‘, 'https://www.sohu.com/‘] async def get_response(url):#声明一个协程函数 conn = aiohttp.TCPConnector(verify_ssl=False) #跳过证书认证 async with aiohttp.ClientSession(connector=conn) as sess: async with await sess.get(url=url,headers=headers) as response: # await asyncio.sleep(2) page_text = await response.text() return page_text def parse(t):#回调函数 page_text = t.result() print(page_text) start_time = time.time() tasks = [] #创建一个任务列表 for url in urls: c = get_response(url) task = asyncio.ensure_future(c) #创建任务 task.add_done_callback(parse) #绑定回调函数 tasks.append(task) #向任务列表中添加任务 loop = asyncio.get_event_loop() #创建事件循环 loop.run_until_complete(asyncio.wait(tasks))#执行 print(f'总耗时:{time.time()-start_time}')
这里async的作用是声明一个协程函数,为什么用到with呢?with可以自动关闭。协程函数内的阻塞操作前面一点需要加上await表示阻塞之后自动挂起让出cup去执行另一个任务,协程函数内用到的方法必须是基于异步的而python原生的requests模块不支持所以这里使用了aiohttp一个基于异步的请求模块。
下面来看一个实例:
import aiohttp import asyncio from lxml import etree import time urls_list = [f'https://www.qiushibaike.com/text/page/{i}/' for i in range(1,101)] headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36' } async def get_page(url): conn = aiohttp.TCPConnector(verify_ssl=False) async with aiohttp.ClientSession(connector=conn) as session: async with session.get(url=url,headers=headers) as response: page = await response.text() await asyncio.sleep(2) return page # def parms(t): #回调函数 page = t.result() tree = etree.HTML(page) div_list = tree.xpath('//*[@id="content"]/div/div[2]/div') for div in div_list: content = div.xpath('./a[1]/div[1]/span//text()')[0] print(content) takes = [] start_time = time.time() for url in urls_list: c = get_page(url) take = asyncio.ensure_future(c) take.add_done_callback(parms) #绑定回调函数 takes.append(take) loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(takes)) print(f'总耗时:{time.time()-start_time}')
目标网站是糗事百科,首先用列表推倒式构建前100页的url列表,然后声明一个协程函数。之后创建事件循环并提交任务。
结果如下:
可见速度还是非常快的,为什么这里不使用多线程的方式?因为GIL锁导致了在CPython解释器下只有一个线程真正的被cpu调度,使用多线程就是让每一个线程轮流执行一个时间片然后让出cpu给下一个线程并没有规避掉IO。而协程是遇到IO就自动挂起然后让出cpu,之后等待阻塞操作完成之后再继续回去执行完剩余操作。这样大大规避了IO操作,节省了时间。