web-dev-qa-db-fra.com

Python Multiprocessing Selenium

J'ai écrit un script en python en combinaison avec Selenium pour gratter les liens des différents articles de sa page de destination et enfin obtenir le titre de chaque article en suivant l'url menant à sa page intérieure. Bien que le contenu que j'ai analysé ici soit statique, j'ai utilisé Selenium pour voir comment il fonctionne en multitraitement.

Cependant, mon intention est de faire le grattage en utilisant le multitraitement. Jusqu'à présent, je sais que Selenium ne prend pas en charge le multitraitement, mais il semble que j'avais tort.

Ma question: comment puis-je réduire le temps d'exécution en utilisant Selenium quand il est fait pour fonctionner en utilisant le multitraitement?

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))
16
robots.txt

comment puis-je réduire le temps d'exécution en utilisant Selenium quand il est fait pour fonctionner en utilisant le multitraitement

Votre solution consacre beaucoup de temps au lancement du pilote Web pour chaque URL. Vous pouvez réduire ce temps en lançant le pilote une seule fois par thread:

(... 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)
  (...)

(...)

Sur mon système, cela réduit le temps de 1m7s à seulement 24.895s, une amélioration de ~ 35%. Pour vous tester, téléchargez le script complet .

Remarque: ThreadPool utilise des threads, qui sont contraints par le Python GIL. C'est ok si la tâche est pour la plupart liée aux E/S. Selon le post-traitement que vous faire avec les résultats grattés, vous pouvez utiliser un multiprocessing.Pool au lieu. Cela lance des processus parallèles qui, en tant que groupe, ne sont pas limités par le GIL. Le reste du code reste le même.

9
miraculixx

Ma question: comment puis-je réduire le temps d'exécution?

Le sélénium semble le mauvais outil pour le web scraping - bien que j'apprécie YMMV, en particulier si vous devez simuler l'interaction de l'utilisateur avec le site Web ou s'il existe une limitation/exigence JavaScript.

Pour les tâches de grattage sans grande interaction, j'ai obtenu de bons résultats en utilisant l'openource Scrapy Python pour les tâches de grattage à grande échelle. Il fait du multitraitement hors de la boîte, il est facile d'écrire de nouveaux scripts et de stocker les données dans des fichiers ou une base de données - et c'est vraiment rapide .

Votre script ressemblerait à quelque chose comme ceci lorsqu'il est implémenté comme une araignée Scrapy entièrement parallèle (notez que je n'ai pas testé cela, voir documentation sur les sélecteurs ).

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')

Pour exécuter, mettez ceci dans blogspider.py et courir

$ scrapy runspider blogspider.py

Voir le site Web Scrapy pour un tutoriel complet.

Notez que Scrapy prend également en charge JavaScript via scrapy-splash , grâce au pointeur de @SIM. Je n'ai pas été exposé à cela jusqu'à présent, je ne peux donc pas en parler autrement que cela semble bien intégré au fonctionnement de Scrapy.

4
miraculixx