异步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

posted @ 2022-11-15 16:11  石衣_2014  阅读(278)  评论(0编辑  收藏  举报