用asyncio和aiohttp异步协程爬虫

协程的概念

协程是在一个线程内切换任务,所需要的资源更少,切换速度更快。
爬虫任务非常适合用协程来实现,原因是请求目标网站的时候需要等待返回,这段时间就可以切换到其他任务(例如解析已经请求成功的页面等)。

应用实例

aiohttp是发起异步请求的库,async修饰的函数表明该函数是异步函数,里面存在异步操作。
目标网站为: 中央结算系统持股记录查询服务
请求页面的代码如下:

async def get_page(day, code):
# 发起post请求需要的参数
    data = {
        "txtStockCode": code,
        "txtShareholdingDate": day.replace('-',''),
        "__EVENTTARGET": "btnSearch",
        "sortBy":"shareholding",
        "sortDirection":"desc",
    }
    html = None
    try:
        with(await sem): # 控制并发的信号量
        # 用aiohttp发起异步请求
            async with aiohttp.ClientSession() as session:
            # 如果请求失败,最大重试次数
                maxretry = 3
                while(maxretry>0):
                    maxretry = maxretry - 1
                    async with session.post(url = url, headers=headers, data=data,timeout=20) as response:
                        if response.status==200:
                        # await 表示异步等待
                            html = await response.text()
                            break
                        else:
                            asyncio.sleep(5)
                if maxretry<0:
                # 请求失败的股票代码,需要做后续处理
                    failed_codes.append(code)
                    print("failed code :",code)
    except Exception as e:
        failed_codes.append(code)
        print(code,e)
    return (day, code, html) # 异步函数返回的结果

上面的请求和普通的请求有什么区别呢?
首先,函数的定义用 async修饰表明该函数为异步操作函数,其中有异步等待的内容,请求成功之后,需要返回页面,这里用await修饰,表明异步等待(意思是可以先做别的事情,等页面返回成功之后再从此处继续执行)

请求成功之后,还需要解析页面,解析函数如下:

# 请求结束之后的回调函数
def parse_page(task):
    global datas # 存储解析数据的全局变量
    # 获得请求成功侯的页面
    day,code,html = task.result()
    if html is None:
        return
    soup = BeautifulSoup(html,'lxml')
    # 解析过程略
    if len(datas)>5000:
    # 写入数据
        write_to_csv(day,datas)
        datas = []

然后将两者结合起来:

def run(day,codes):
    """爬取某天,指定股票代码的数据,
    Args:
        day (str): 2022-10-10
        codes (list): stock_code, 为香港5位数代码
    """
    global failed_codes
    global datas
    failed_codes = [] # 清空上一次失败代码
    datas = [] # 清空上一次的数据残留
    tasks = []
    for code in codes:
    # 构造协程任务列表
        task = asyncio.ensure_future(get_page(day,code))
        # 任务结束之后的回调函数:请求成功之后,立即解析页面
        task.add_done_callback(parse_page)
        tasks.append(task)
    # 获取事件循环
    loop = asyncio.get_event_loop()
    # 启动任务
    loop.run_until_complete(asyncio.wait(tasks))
    write_to_csv(day,datas) # 最后剩余数据也要写入

框架总结

如果读者对上面的应用实例不理解,那么就无法应用到自己的爬虫任务种,这里总结一般爬虫任务种使用协程的代码框架:

import aiohttp
import asyncio
# 1. 异步发起请求,以post请求为例
async def get_page(param):
# 发起post请求需要的参数
    data = {
    	param(自行构造请求需要的参数)
    }
    html = None
    try:
        with(await sem): # 控制并发的信号量
        # 用aiohttp发起异步请求
            async with aiohttp.ClientSession() as session:
                    async with session.post(url = url, headers=headers, data=data,timeout=20) as response:
                        if response.status==200:
                        	# await 表示异步等待
                            html = await response.text()
   

    except Exception as e:
    	print(e)
    return html # 异步函数返回的结果

# 2.解析页面数据,这里传入的参数是协程任务
def parse_page(task):
    # 获得请求成功侯的页面
    html = task.result()
    # 利用bs4等进行解析操作
    result = parse(html)
    # 将解析的数据持久化保存
    writ_to_local(result)

# 3.构造协程任务列表
def run(params):
    tasks = []
    for param in params:
    # 构造协程任务列表
        task = asyncio.ensure_future(get_page(param))
        # 任务结束之后的回调函数:请求成功之后,立即解析页面
        task.add_done_callback(parse_page)
        tasks.append(task)
    # 获取事件循环
    loop = asyncio.get_event_loop()
    # 启动任务
    loop.run_until_complete(asyncio.wait(tasks))

注意:代码中的参数param,params,parse函数,writ_to_local函数等都是示意,具体情况需要具体分析,自己写对应的函数。

总结

可以看到,最为关键的是最后的run函数,读者只需要根据自己的任务,构造合适的请求函数和解析函数,应用到run函数中,即可启动协程任务。

并发信号量 sem是一个全局变量,用来控制协程的数量,或者说爬虫的速度。

还可以参考笔者另一篇文章 用asyncio和aiohttp异步协程爬取披露易网站港资持股数据

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值