網上代碼一大抄,居然網上講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