Многопроцессорная обработка Python Selen

Я написал скрипт на python в сочетании с селеном, чтобы очистить ссылки различных постов с его целевой страницы и, наконец, получить заголовок каждого поста, отслеживая URL, ведущий к его внутренней странице. Хотя содержимое, которое я здесь проанализировал, является статическим, я использовал селен, чтобы увидеть, как он работает в многопроцессорной среде.

Тем не менее, мое намерение состоит в том, чтобы выполнить очистку с использованием многопроцессорной обработки. Пока я знаю, что селен не поддерживает многопроцессорность, но, похоже, я ошибался.

Мой вопрос: как я могу сократить время выполнения, используя селен, когда он выполняется для многопроцессорной обработки?

This is my try (it 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/info/tagged/web-scraping"
  ThreadPool(5).map(get_title,get_links(url))

Ответы

Ответ 1

Как я могу сократить время выполнения с помощью селена, когда он сделан для запуска с использованием многопроцессорной

В вашем решении много времени тратится на запуск веб-драйвера для каждого URL. Вы можете сократить это время, запустив драйвер только один раз для каждого потока:

(... 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%. Чтобы проверить себя, загрузите полный скрипт.

Примечание: ThreadPool использует потоки, которые ограничены Python GIL. Это нормально, если по большей части задача связана с вводом/выводом. В зависимости от последующей обработки, которую вы выполняете со соскребенными результатами, вы можете вместо этого использовать multiprocessing.Pool. Это запускает параллельные процессы, которые как группа не ограничены GIL. Остальная часть кода остается прежней.

Ответ 2

Мой вопрос: как я могу сократить время выполнения?

Selenium кажется неподходящим инструментом для просмотра веб-страниц - хотя я ценю YMMV, особенно если вам нужно смоделировать взаимодействие пользователя с веб-сайтом или есть некоторые ограничения/требования JavaScript.

Для задач очистки без особого взаимодействия у меня были хорошие результаты при использовании пакета Python с открытым исходным кодом Scrapy для крупномасштабных задач очистки. Он выполняет многопроцессорную обработку из коробки, легко писать новые сценарии и сохранять данные в файлах или базе данных - и это действительно быстро.

Ваш сценарий будет выглядеть примерно так при реализации в виде полностью параллельного паука Scrapy (обратите внимание, я не проверял это, см. Документацию по селекторам).

import scrapy
class BlogSpider(scrapy.Spider):
    name = 'blogspider'
    start_urls = ['https://stackoverflow.com/info/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 для полного учебника.

Обратите внимание, что Scrapy также поддерживает JavaScript посредством scrapy-splash, благодаря указателю @SIM. До сих пор я не сталкивался с этим, поэтому не могу говорить об этом, кроме того, что он хорошо интегрирован с тем, как работает Scrapy.