Python 硒多处理

Posted

技术标签:

【中文标题】Python 硒多处理【英文标题】:Python selenium multiprocessing 【发布时间】:2019-04-27 18:30:56 【问题描述】:

我用 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://***.com/questions/tagged/web-scraping"
  ThreadPool(5).map(get_title,get_links(url))

【问题讨论】:

任何时候多处理开始发挥作用,它都会成为考虑从 selenium 切换到 headless chrome 的好机会。 @Qharr - 像 puppeteer 和 nightmarejs 这样的节点库比 selenium 更适合这样的事情。 Selenium 更受欢迎,因为它一直存在,但它有点像恐龙,主要适用于更简单的脚本。至少恕我直言。 不,实际上我是在建议从 python 切换到 node。 @pguardiario 谢谢。现在也在学习JS,这样很方便。 Selenium 是错误的网页抓取工具。请改用开源Scrapy Python 包。它开箱即用地进行多处理,很容易编写新脚本并将数据存储在文件或数据库中。 【参考方案1】:

我的问题:如何减少执行时间?

Selenium 似乎是网络抓取的错误工具 - 虽然我很欣赏 YMMV,特别是如果您需要模拟用户与网站的交互或存在一些 javascript 限制/要求。

对于没有太多交互的抓取任务,我使用开源的Scrapy Python包进行大规模的抓取任务取得了不错的效果。它开箱即用地进行多处理,很容易编写新脚本并将数据存储在文件或数据库中——而且它确实快速

当你的脚本作为一个完全并行的 Scrapy 蜘蛛实现时,你的脚本看起来像这样(注意我没有对此进行测试,参见 documentation on selectors)。

import scrapy
class BlogSpider(scrapy.Spider):
    name = 'blogspider'
    start_urls = ['https://***.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 website。

请注意,由于@SIM 的指针,Scrapy 还通过scrapy-splash 支持 JavaScript。到目前为止,我还没有接触过它,所以除了它看起来与 Scrapy 的工作方式很好地集成之外,我无法谈论它。

【讨论】:

看到我在 scrapy 上找到了解决方案,我感到非常震惊。我的帖子的标题还不够明确吗? Selenium ist the wrong tool for web scraping. Use the opensource Scrapy is it though? @robots.txt 不,您的问题是如何减少执行时间。正如我在之前的评论中所问的那样,请说明您的尝试 vv 期望的结果,您可能会得到更好的答案。 为什么 Selenium 是错误的工具?如何用scrapy处理javascript? Selenium 绝对不是从网站上抓取内容的错误工具,无论是否动态。但是,对于 scrapy,还有一个轻量级工具 splash 也可以解决问题。【参考方案2】:

当使用多处理运行时,如何使用 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)
  (...)

(...)

在我的系统上,这将时间从 1 分 7 秒缩短到 24.895 秒,提高了约 35%。要测试自己,请下载full script。

注意:ThreadPool 使用受 Python GIL 约束的线程。如果大部分任务是 I/O 绑定的,那没关系。根据您对抓取结果进行的后处理,您可能希望使用multiprocessing.Pool。这将启动作为一个组不受 GIL 约束的并行进程。其余代码保持不变。

【讨论】:

现在你的解决方案看起来很有前途@miraculixx。如果您愿意粘贴完整的脚本,我会很高兴接受您的回答,因为我非常怀疑自己能否实现它。这个答案绝对值得赞成。 @robots.txt 很高兴你喜欢它 :-) 作为要点添加的完整脚本链接(这样这里的答案可以保持简短,即只指出与你的脚本的差异) 只要赏金还在,我们就等着吧。已接受您的解决方案@miraculixx。 您为什么决定使用 threading.local 而不是 Queue? Thread-local data is data whose values are thread specific(来源:python3 docs)——这就是我们希望在这里为每个线程存储一次驱动程序实例的内容。队列无济于事,因为我们不想在多个进程之间传递数据,事实上恰恰相反。【参考方案3】:

我看到的一个聪明的每线程一个驱动程序答案的潜在问题是它省略了任何“退出”驱动程序的机制,从而留下了进程悬而未决的可能性。我会做出以下改变:

    改用 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".')
    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
    最后,在您不再使用ThreadPool 实例但在它终止之前,添加以下行以删除线程本地存储并强制调用Driver 实例的析构函数(希望如此):
del threadLocal
import gc
gc.collect() # a little extra insurance

【讨论】:

以上是关于Python 硒多处理的主要内容,如果未能解决你的问题,请参考以下文章

Python 图像处理 OpenCV :图像平滑(滤波)处理

Python 图像处理 OpenCV :图像平滑(滤波)处理

Python 图像处理 OpenCV :图像平滑(滤波)处理

python具体在文本处理上怎么用

python:PIL图像处理

python的异常处理