异步爬虫之协程
建议从代码部分开始阅读!!!不清楚的地方向上查看基本概念
异步编程大致流程:
1.事件循环:理解为一个死循环(去检测并执行某些代码)
#伪代码
任务列表={任务1,任务2,任务3.。。。}
while True:
可执行的任务列表,已完成的任务列表=去任务列表中检查所有的任务,将’可执行‘和’已完 成‘的任务返回
for 就绪任务 in 可执行的任务列表:
执行已就绪的任务
for 已完成的任务 in 已完成的任务列表;
在任务列表中移除,已完成的任务
如果 任务列表 中的任务都已完成,则终止循环
文章目录
基本概念介绍
1. 特殊的函数
- 如果一个函数的定义被async关键字修饰后,则该函数就变成了一个特殊的函数
- 特殊之处:
1. 该特殊的函数被调用后,内部的语句不会被立即执行
2. 该函数被调用后返回一个协程对象
2. 协程对象
- 一个对象,通过特殊的函数调用放回的一个协程对象。
所以,协程 = 特殊的函数 =一组指定的操作 => 协程= 一组特殊的函数
3. 任务对象
- 任务对象就是一个高级的协程对象(即对协程对象的进一步封装)
- 有task和future两种。本质上无区别。但是基于loop创建的任务对象的前提是得有loop。
如何创建一个任务对象:
· asyncio.ensure_future(协程对象)—(future)
第二种方法为基于事件循环对象的创建task,在后面介绍
- 任务对象的高级之处:
- 可以给任务对象绑定回调
task.add_done_callback(task_callback)- 回调函数的调用时机:
任务被执行结束后,才可以调用回调函数
-回调函数的参数只可以有一个;表示的就是该回调函数的调用者(任务对象)
-使用回调函数的参数调用result()返回的就是任务对象表示的特殊函数的返回值
4. 事件循环对象
作用:
-可以将多个任务对象注册/装载到任务循环对象中
-如果开启了事件循环,则其内部注册/装载的任务对象表示的就是指定操作就是任务对象
-创建方式:
-loop=asyncio.get_event_loop()
- 创建任务对象
- task=loop.creat_task(协程对象)
-注册且启动方式:
-loop.run_until_complete(task)
启动时不不能直接将task列表放入,要加asyncio.wait(tasks)修饰
wait()作用:可以将任务列表中的任务对象赋予可挂起权限。(每一个任务对象都可以被挂起)
任务对象的挂起:将当前挂起的任务对象交出CPU的使用权。只有当任务对象的CPU的使用权,
loop才可以使用CPU去执行下一个任务对象
-当loop在执行某一个任务对象时,遇到了阻塞操作,则loop会跳过阻塞操作执行下一个任务对象
-当loop在执行某一个任务对象时,前面一个任务对象的阻塞操作结束了,则loop会回头将该阻塞
结束的任务对象阻塞之后的操作进行执行
5. 注意事项!!!!!
-在特殊函数内部不可以出现不支持异步模块对应的代码,否则会中断整个异步效果,requests不支持异步
-await关键字
-在特殊函数内部,凡是阻塞操作前,都必须使用await进行修饰,await就可以保证阻塞
操作在异步执行过程中不会被跳过!!!
-aiohttp
-是一个支持异步的网络请求模块。
-使用代码:
1.写一个大致的架构
async def get_request(url):
#实例化好了一个请求对象
with aiohttp.ClientSession() as sess:
#调用get发起请求,返回一个响应对象
#get/post(url,headers,params/data,proxy=''http://ip:port)
with sess.get(url=url) as response:
#获取了字符串形式的响应数据
page_text=response.text()
return page_text
2.补充细节:
-在阻塞操作前加await关键字,在每个with前加上async关键字(爬虫中只有发送请求和获取响应数据是阻塞操作)
3。完整代码:
async def get_request(url):
#实例化好了一个请求对象
async with aiohttp.ClientSession() as sess:
#调用get发起请求,返回一个响应对象
#get/post(url,headers,params/data,proxy=''http://ip:port)
async with await sess.get(url=url) as response:
#text()获取了字符串形式的响应数据
#read()获取byte类型的响应数据
page_text=await response.text()
return page_text
代码部分
安装模块 : pip install asyncio
1. 基本协程的使用
import asyncio
# 定义协程函数
async def request(url):
print('正在请求:',url)
print('请求结束',url)
# async修饰的函数,调用后返回一个协程对象
c= request('www.baidu.com')
# 创建一个事件循环对象
loop=asyncio.get_event_loop()
# 将协程对象注册到loop中,然后启动loop
loop.run_until_complete(c)
2. 基于loop的任务对象的使用
import asyncio
async def request(url):
print('正在请求:',url)
print('请求结束',url)
# async修饰的函数,调用后返回一个协程对象
c= request('www.baidu.com')
# task的使用
#创建事件循环对象
loop=asyncio.get_event_loop()
# 基于loop创建的task对象
# 通过creat_task创建任务对象,将协程对象封装进去
task=loop.create_task(c)
print(task)
# 注册并执行任务
loop.run_until_complete(task)
print(task)
3. 任务对象future的使用
import asyncio
async def request(url):
print('正在请求:',url)
print('请求结束',url)
# async修饰的函数,调用后返回一个协程对象
c= request('www.baidu.com')
# future的使用
# 创建事件循环对象
loop=asyncio.get_event_loop()
# 创建future
future=asyncio.ensure_future(c)
print(future)
#注册并启动
loop.run_until_complete(future)
print(future)
状态与task相同
4. 回调函数的绑定和使用
import asyncio
async def request(url):
print('正在请求:',url)
print('请求结束',url)
# 返回,回调函数可以通过.result获取返回值
return url
# async修饰的函数,调用后返回一个协程对象
c= request('www.baidu.com')
#绑定回调
# 回调函数的创建无需关键字async,参数只能有一个,即回调函数的调用者,task对象
def callback_func(task):
print(task.result())
#创建事件循环对象
loop=asyncio.get_event_loop()
task=asyncio.ensure_future(c)
#将回调函数绑定到任务对象中
task.add_done_callback(callback_func)
#注册并启动任务
loop.run_until_complete(task)
5. 多任务协程
import asyncio
import time
#模拟请求发送
async def request(url):
print('正在请求',url)
time.sleep(2)
print("下载完毕",url)
# 请求列表
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']
# 任务列表:存放多个任务对象
tasks=[]
# 计时
start_time=time.time()
for url in urls:
c=request(url)
task=asyncio.ensure_future(c)
# 将创建的任务对象加入到任务列表中
tasks.append(task)
# 创建任务循环对象
loop=asyncio.get_event_loop()
#注册和启动时不能直接放入列表,具体详情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
原因:time是同步模块,在特殊函数内部不可以出现不支持异步模块对应的代码,否则会中断整个异步效果。 详情看注意事项
解决办法: 改用异步模块,await asyncio.sleep(2)
import asyncio
import time
#模拟请求发送
async def request(url):
print('正在请求',url)
# time.sleep(2)
# 使用异步模块,
await asyncio.sleep(2)
print("下载完毕",url)
# 请求列表
urls=['www.baidu.com','www.sougou.com','www.goubanjian.com']
# 任务列表:存放多个任务对象
tasks=[]
# 计时
start_time=time.time()
for url in urls:
c=request(url)
task=asyncio.ensure_future(c)
# 将创建的任务对象加入到任务列表中
tasks.append(task)
# 创建任务循环对象
loop=asyncio.get_event_loop()
#注册和启动时不能直接放入列表,具体详情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)
6. aiohttp的使用—实战
详情可看注意事项
import asyncio
import time
import aiohttp #使用该模块中的ClientSession进行网络请求发送
import requests
from lxml import etree
# 请求列表
urls=['https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
,'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD',
'https://www.baidu.com/s?ie=UTF-8&wd=%E6%9D%8E%E7%99%BD'
]
headers={
"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.67',
}
#请求发送
async def get_request(url):
#实例化一个session对象发送请求
async with aiohttp.ClientSession() as sess:
# 请求发送,手动挂起
async with await sess.get(url=url,headers=headers) as response:
#text()方法可以返回字符串形式的响应数据
#read()方法可以返回二进制形式的数据
#json()方法返回的就是json对象
# 注意在获取响应数据操作之前要使用await进行手动挂起
page_text=await response.text()
return page_text
# 解析响应数据
def parse(task):
page_text=task.result()
tree=etree.HTML(page_text)
title=tree.xpath('/html/head/title/text()')[0]
print(title)
# 任务列表:存放多个任务对象
tasks=[]
# 计时
start_time=time.time()
for url in urls:
c=get_request(url)
task=asyncio.ensure_future(c)
#绑定回调
task.add_done_callback(parse)
# 将创建的任务对象加入到任务列表中
tasks.append(task)
# 创建任务循环对象
loop=asyncio.get_event_loop()
#注册和启动时不能直接放入列表,具体详情看基本概念 4
loop.run_until_complete(asyncio.wait(tasks))
print(time.time()-start_time)