异步pyppeteer:并发运行多个浏览器并收集结果
网上代码一大抄,居然网上讲pyppeteer异步的一大推,但运行起来都是await,并没有讲如何同时并发运行十几二个pyppeteer页面,那有个卵用呀,还不如开个多进程呢。
话不多说,上代码。
爬取目标是额,就夜幕吧,知名的爬虫论坛,没错,就肝它吧。
页面有44个,先用pyppeteer用await的方法一个一个爬,实际和同步用时一样,大概76秒爬完44个页面。
而用asyncio并发去跑,不控制信号量。就是让它跑满,实际用时12秒,快乐7倍有多。
# -*- coding: utf-8 -*- # @Time : 2021/4/13 18:44 # @File : crawler.py import asyncio from parsel import Selector import pyppeteer from read_config import * from pipeline.mongo import MongoDBCls from datetime import datetime class BaseCrawler(): def __init__(self): self.db_cls = MongoDBCls('db_technology','nightteam') # 如果不存数据,可以注释掉 async def browser(self): browser = await pyppeteer.launch( {'headless': HEADLESS, 'userDataDir': UserDataDir, # 'ignoreDefaultArgs': ignoreDefaultArgs, # 忽略内置参数 'args': ['--start-maximized'], # 最大化 } ) return browser async def crawl(self, **kwargs): browser = await self.browser() tasks=[] for i in range(1, 45): tasks.append(self.get_single_page(browser,i,**kwargs)) result =await asyncio.gather(*tasks) print(len(result)) await browser.close() return result async def start(self): result = await self.crawl(time=1000*600) for item in result: self.db_cls.add(item) async def get_single_page(self,browser,i,**kwargs): page = await browser.newPage() url = self.base_url.format(i) # print(url) await page.goto(url, **kwargs) content = await page.content() return self.parse(content) def parse(self, content): response = Selector(text=content) item_list = response.xpath('//div[@class="card-body card-p0"]/ul/li') result_list =[] for item in item_list: item_dict = dict() detail_url = item.xpath( './/div[@class="media-body"]/div[@class="subject break-all"]/a/@href').extract_first() title = item.xpath( './/div[@class="media-body"]/div[@class="subject break-all"]/a/text()').extract_first() detail_url = self.url_completion(detail_url) username = item.xpath( './/div[@class="d-flex justify-content-between small mt-2"]/div/span[@class="haya-post-info-username "]/span[1]/text()').extract_first() post_time = item.xpath( './/div[@class="d-flex justify-content-between small mt-2"]/div/span[@class="haya-post-info-username "]/span[2]/text()').extract_first() view_count = item.xpath( './/div[@class="d-flex justify-content-between small mt-2"]/div[@class="text-muted small"]/span[@class="ml-2"]/text()').extract_first() item_dict['detail_url'] = detail_url item_dict['title'] = title item_dict['username'] = username item_dict['post_time'] = post_time item_dict['crawltime'] = datetime.now() item_dict['view_count'] = int(view_count) if view_count else 0 # print(item_dict) result_list.append(item_dict) return result_list @property def base_url(self): base_url = 'https://bbs.nightteam.cn/index-{}.htm' return base_url
运行代码:
import asyncio import time def main(): start = time.time() spider = BaseCrawler() loop = asyncio.get_event_loop() loop.run_until_complete(spider.start()) print(f'time used {time.time()-start:.2} s') if __name__ == '__main__': main()
上面就是完整的代码,
这里为了展示效果,把获取到的字段打印出来即可。
这里重点是:
browser = await self.browser() tasks=[] for i in range(1, 45): tasks.append(self.get_single_page(browser,i,**kwargs)) result =await asyncio.gather(*tasks) print(len(result)) await browser.close() return result
如果把page传进去作为参数,得到的结果是全部都是一样的,是最后一页。
所以只能把browser传进去,让函数执行打开页面,而不能先把页面获取,再分配给函数。
tasks.append(self.get_single_page(browser,i,**kwargs))
这一句是把所有的协程放在一个列表里面,这个是代码的关键。
这时抓取动作还没有执行,等待一下条 await 阻塞语句出现是,这个时候,事件循环就开始分配任务,把这个44个页面的任务分配出去,并同时执行,最后等待最后一个页面执行完毕,所用耗时由最慢的一个页面决定。
最后把所有解析数据收集到返回给result,这是就可以拿数据取存储或者输出啦。
转自:https://zhuanlan.zhihu.com/p/364646776