繁体   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