#知识复盘
要实现异步机制的爬虫,离不开协程!
一、协程的基本概念:
协程(单线程),英文叫coroutine,又称微线程、纤程,是一种运行在用户状态的轻量级线程。它拥有自己的寄存器上下文和栈,在调度切换时,将寄存器上下文和栈保存到其他地方,等切回来时,再恢复到先前保存的寄存器上下文和栈。因此,协程能保留上一次调用时的状态,所有局部状态的一个特定组合,每次过程重入,就相当于进入上一次调用的状态。
1. event_loop:事件循环,相当于一个无限循环,我们可以把一个函数注册到这个事件循环上,当满足发生条件的时候,就调用对应的处理方法。
2. coroutine:协程,指代协程对象类型,我们可以将协程对象注册到事件循环中,它会被事件循环调用。我们可以使用async关键字来定义一个方法,这个方法再调用时不会被立即执行,而是返回一个协程对象
3. task:任务,这是协程对象的进一步封装,包含协程的各个状态
4. future:代表将来执行或者没有执行的任务的结果,实际上和task没有本质区别
二、准备工作
安装asyncio和aiphttp,相关命令如下:
pip install asyncio
pip install aiphttp
三、简单运用asyncio和aiohttp
假如爬取一个网站,同时执行10个请求,每个请求6s,若是按照顺次执行的方式,则花费总时间:大约60s左右。若是按照异步爬取,则只需大约6的时间,具体代码如下:
# 协程爬取
import asyncio
import aiohttp
import time
start = time.time()
# aiohttp的client封装
async def get(url):
session = aiohttp.ClientSession()
response = await session.get(url)
await response.text()
await session.close()
return response
# 网络请求方法
async def request():
url = "https://www.httpbin.org/delay/5"
print("等待:", url)
response = await get(url)
print("得到返回结果:", response)
tasks = [asyncio.ensure_future(request()) for _ in range(10)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print("总时间:", end - start)
结果演示
四、aiohttp的其他使用
aiohttp是一个基于asyncio的异步http网络模块,它既提供服务端,又提供客户端。其中,我们服务端可以搭建一个支持异步处理的服务器,这个服务器就是用来处理请求并返回响应的,类似于Django、Flask、Tornado等一些web服务器。而客户端可以用来发起请求,类似于使用requests发起一个Http请求然后获得响应,但requests发起的是同步的网络请求,aiphttp则是异步的。
1. 基本实例:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text(), response.status
async def main():
async with aiohttp.ClientSession() as session:
html, status = await fetch(session, "https://www.baidu.com")
print(f"html:{html[:100]}")
print(f"状态:{status}")
if __name__ == "__main__":
# 兼容python3.7以下的版本
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# python版本3.7及以上
# asyncio.run(main())
注意事项:aiphttp的写法和requests的写法区别
1. 首先是导入的库,除了导入aiohttp这个库,还必须引入asyncio库,因为要实现异步爬取,需要启动线程,而协程则需要借助于asyncio里面的事件循环才能执行。
2. 异步爬取方法的定义和requests的定义不同,每个异步方法的前面都要统一加async来修饰
3. with as 语句前面同样需要加async来修饰。在python中,with as 语句用于声明一个上下文管理器,能够帮我们自动分配和释放资源。而在异步方法中,with as 前面加上async代表声明一个支持异步的上下文管理器
4. 对于一些返回协程对象的操作,前面加await来修饰
5. 在python3.7及以后的版本中,我们可以使用asyncio.run(main())代替最后的启动操作,不需要显示声明事件循环,run方法内部会自动启动一个事件循环。
2. URL参数设置
import asyncio
import aiohttp
async def main():
param = {'name': 'germey', 'age': 23}
async with aiohttp.ClientSession() as session:
async with session.get("https://www.baidu.com", params=param) as response:
print(f"html:{response.text}")
print(f"状态:{response.status}")
if __name__ == "__main__":
# 兼容python3.7以下的版本
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# python版本3.7及以上
# asyncio.run(main())
3. 其他请求类型
aiohttp还支持其他请求类型,如POST,PUT,DELETE等
post请求代码如下:
import asyncio
import aiohttp
async def main():
data = {'name': 'germey', 'age': 23}
async with aiohttp.ClientSession() as session:
async with session.post("https://www.httpbin.org/post", data=data) as response:
print(await response.text())
if __name__ == "__main__":
# 兼容python3.7以下的版本
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# python版本3.7及以上
# asyncio.run(main())
4. 超时设置:
我们可以借助ClientTimeout对象设置超时,例如设置1s超时:
import asyncio
import aiohttp
async def main():
timeout = aiohttp.ClientTimeout(total=1) # 设置超时1秒
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post("https://www.baidu.com") as response:
print(f"html:{response.text}")
print(f"状态:{response.status}")
if __name__ == "__main__":
# 兼容python3.7以下的版本
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# python版本3.7及以上
# asyncio.run(main())
5. 并发限制
由于aiohttp可以支持高并发量,但面对如此高的并发量,目标网站可能无法短时间内进行响应,甚者会导致网站挂掉,所以需要主动控制以下并发量。一般情况下,我们使用asyncio的Semaphore来控制并发量,如下:
import asyncio
import aiohttp
MAX_NUM = 5 # 最大并发量
URL = "https://www.baidu.com"
semaphore = asyncio.Semaphore(MAX_NUM)
session = None
async def scrape_api():
async with semaphore:
print("地址:", URL)
async with session.get(URL) as response:
await asyncio.sleep(1)
return await response.text()
async def main():
global session
session = aiohttp.ClientSession()
scrape_index_tasks = [asyncio.ensure_future(scrape_api()) for _ in range(1000)]
await asyncio.gather(*scrape_index_tasks)
if __name__ == "__main__":
# 兼容python3.7以下的版本
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
# python版本3.7及以上
# asyncio.run(main())
以上就是python异步爬虫的基本使用,其他功能可在这基础上进行拓展!