簡體   English   中英

Python 硒多處理

[英]Python selenium multiprocessing

我在 python 中結合 selenium 編寫了一個腳本,以從其登錄頁面抓取不同帖子的鏈接,最后通過跟蹤通向其內頁的 url 來獲取每個帖子的標題。 雖然我這里解析的內容是靜態的,但我使用了 selenium 來看看它在多處理中是如何工作的。

但是,我的目的是使用多處理進行抓取。 到目前為止,我知道 selenium 不支持多處理,但似乎我錯了。

我的問題:當使用多處理運行時,如何使用 selenium 減少執行時間?

This is my try (it's a working one)

import requests
from urllib.parse import urljoin
from multiprocessing.pool import ThreadPool
from bs4 import BeautifulSoup
from selenium import webdriver

def get_links(link):
  res = requests.get(link)
  soup = BeautifulSoup(res.text,"lxml")
  titles = [urljoin(url,items.get("href")) for items in soup.select(".summary .question-hyperlink")]
  return titles

def get_title(url):
  chromeOptions = webdriver.ChromeOptions()
  chromeOptions.add_argument("--headless")
  driver = webdriver.Chrome(chrome_options=chromeOptions)
  driver.get(url)
  sauce = BeautifulSoup(driver.page_source,"lxml")
  item = sauce.select_one("h1 a").text
  print(item)

if __name__ == '__main__':
  url = "https://stackoverflow.com/questions/tagged/web-scraping"
  ThreadPool(5).map(get_title,get_links(url))

當使用多處理運行時,如何使用 selenium 減少執行時間

解決方案中的大量時間都花在為每個 URL 啟動 webdriver 上。 您可以通過每個線程僅啟動一次驅動程序來減少此時間:

(... skipped for brevity ...)

threadLocal = threading.local()

def get_driver():
  driver = getattr(threadLocal, 'driver', None)
  if driver is None:
    chromeOptions = webdriver.ChromeOptions()
    chromeOptions.add_argument("--headless")
    driver = webdriver.Chrome(chrome_options=chromeOptions)
    setattr(threadLocal, 'driver', driver)
  return driver


def get_title(url):
  driver = get_driver()
  driver.get(url)
  (...)

(...)

在我的系統上,這將時間從 1m7s 減少到 24.895s,改進了約 35%。 要測試自己,請下載完整的腳本

注意: ThreadPool使用受 Python GIL 約束的線程。 如果大部分任務是 I/O 綁定的,那沒關系。 根據您對抓取結果進行的后處理,您可能希望改用multiprocessing.Pool 這將啟動作為一個組不受 GIL 約束的並行進程。 其余代碼保持不變。

我看到一個聰明的每線程一個驅動程序答案的一個潛在問題是它省略了任何“退出”驅動程序的機制,從而留下了進程掛起的可能性。 我將進行以下更改:

  1. 使用類Driver來創建驅動程序實例並將其存儲在線程本地存儲上,但也有一個析構函數,當線程本地存儲被刪除時,它將quit驅動程序:
class Driver:
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.add_argument("--headless")
        self.driver = webdriver.Chrome(options=options)

    def __del__(self):
        self.driver.quit() # clean up driver when we are cleaned up
        #print('The driver has been "quitted".')
  1. create_driver現在變成:
threadLocal = threading.local()

def create_driver():
    the_driver = getattr(threadLocal, 'the_driver', None)
    if the_driver is None:
        the_driver = Driver()
        setattr(threadLocal, 'the_driver', the_driver)
    return the_driver.driver
  1. 最后,在您不再使用ThreadPool實例但在它終止之前,添加以下行以刪除線程本地存儲並強制調用Driver實例的析構函數(希望如此):
del threadLocal
import gc
gc.collect() # a little extra insurance

我的問題:如何減少執行時間?

Selenium 似乎是網絡抓取的錯誤工具 - 盡管我很欣賞 YMMV,特別是如果您需要模擬用戶與網站的交互或存在一些 JavaScript 限制/要求。

對於沒有太多交互的抓取任務,我使用開源Scrapy Python 包進行大規模抓取任務取得了不錯的效果。 它開箱即用地進行多處理,很容易編寫新腳本並將數據存儲在文件或數據庫中——而且速度非常

當作為完全並行的 Scrapy 蜘蛛實現時,您的腳本看起來像這樣(請注意,我沒有對此進行測試,請參閱有關選擇器的文檔)。

import scrapy
class BlogSpider(scrapy.Spider):
    name = 'blogspider'
    start_urls = ['https://stackoverflow.com/questions/tagged/web-scraping']

    def parse(self, response):
        for title in response.css('.summary .question-hyperlink'):
            yield title.get('href')

要運行將其放入blogspider.py並運行

$ scrapy runspider blogspider.py

有關完整教程,請參閱Scrapy 網站

請注意,由於@SIM 的指針,Scrapy 還通過scrapy-splash支持 JavaScript。 到目前為止,我沒有任何接觸,所以除了它看起來與 Scrapy 的工作方式很好地集成之外,無法談論它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM