2 votes

Lors de l'utilisation de Pool.map à partir de la multiprocessing intégrée de Python, le programme fonctionne de plus en plus lentement

Voici une question similaire Pourquoi le script de multiprocessing Python ralentit-il après un certain temps?

Exemple de code utilisant Pool:

from multiprocessing import Pool
Pool(processes=6).map(some_func, array)

Après quelques itérations, le programme ralentit et finit par devenir encore plus lent qu'avec le multiprocessing. Peut-être que le problème vient de la fonction liée à Selenium? Voici le code complet:

# bibliothèques
import os
from time import sleep
from bs4 import BeautifulSoup
from selenium import webdriver
from multiprocessing import Pool

#  
url = "https://eldorado.ua/"
directory = os.path.dirname(os.path.realpath(__file__))
env_path = directory + "\chromedriver"
chromedriver_path = env_path + "\chromedriver.exe"

dict1 = {"  ": "https://eldorado.ua/node/c1038944/",
         "  ": "https://eldorado.ua/node/c1038957/",
         ",   ": "https://eldorado.ua/node/c1038958/",
         "  ": "https://eldorado.ua/node/c1088594/",
         "  ": "https://eldorado.ua/node/c1088603/",
         " ": "https://eldorado.ua/node/c1285101/",
         "  ": "https://eldorado.ua/node/c1215257/",
         "": "https://eldorado.ua/node/c1039055/",
         "  ": "https://eldorado.ua/node/c1038960/",
         "  ": "https://eldorado.ua/node/c1178596/",
         "  ": "https://eldorado.ua/node/c1284654/",
         "  ": "https://eldorado.ua/node/c1218544/",
         "    ": "https://eldorado.ua/node/c1285161/",
         "  ": "https://eldorado.ua/node/c1085100/"}

def openChrome_headless(url1, name):
    options = webdriver.ChromeOptions()
    options.headless = True
    options.add_experimental_option("excludeSwitches", ['enable-automation'])
    options.add_argument(
        '--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"')
    driver = webdriver.Chrome(executable_path=chromedriver_path, options=options)
    driver.get(url=url1)
    sleep(1)
    try:
        with open(name + ".html", "w", encoding="utf-8") as file:
            file.write(driver.page_source)
    except Exception as ex:
        print(ex)
    finally:
        driver.close()
        driver.quit()

def processing_goods_pages(name):
    for n in os.listdir(f"brand_pages\\{name}"):
        with open(f"{directory}\\brand_pages\\{name}\\{n}", encoding="utf-8") as file:
            soup = BeautifulSoup(file.read(), "lxml")

        if not os.path.exists(f"{directory}\\goods_pages\\{name}\\{n[:-5]}"):
            if not os.path.exists(f"{directory}\\goods_pages\\{name}"):
                os.mkdir(f"{directory}\\goods_pages\\{name}")
            os.mkdir(f"{directory}\\goods_pages\\{name}\\{n[:-5]}")

        links = soup.find_all("header", class_="good-description")
        for li in links:
            ref = url + li.find('a').get('href')
            print(li.text)
            openChrome_headless(ref, f"{directory}\\goods_pages\\{name}\\{n[:-5]}\\{li.text}")

if __name__ == "__main__":
    ar2 = []
    for k, v in dict1.items():
        ar2.append(k)
    Pool(processes=6).map(processing_goods_pages, ar2)

2voto

Ronald Aaronson Points 200

Vous créez 6 processus pour traiter 14 URL - jusqu'ici tout va bien. Mais ensuite, chaque processus dans le pool lancera un navigateur Chrome headless une fois pour chaque lien extrait d'un fichier pour cette URL. Je ne sais pas combien de liens en moyenne il traite pour chaque URL et je ne peux pas dire que l'ouverture et la fermeture de Chrome autant de fois est la cause du ralentissement éventuel. Mais il me semble que si vous voulez un niveau de multiprocessus de 6, alors vous ne devriez jamais avoir plus de 6 sessions Chrome démarrées. Cependant, cela nécessite un peu de refactoring du code.

La première chose que je noterais est que ce travail pourrait probablement aussi bien utiliser du multithreading au lieu du multiprocessus. Il y a un travail intensif en CPU réalisé par BeautifulSoup et l'analyseur lxml, mais je soupçonne que cela est insignifiant par rapport au lancement de Chrome 6 fois et à la récupération des pages URL, surtout que vous avez un délai codé en dur de 1 seconde après la récupération de l'URL (nous en reparlerons plus tard).

L'idée est de stocker dans le stockage local du thread le pilote Chrome actuellement ouvert pour chaque thread dans le pool de multithreading et de ne jamais quitter le pilote avant la fin du programme. La logique qui était dans la fonction openChrome_headless doit maintenant être déplacée vers une nouvelle fonction spéciale create_driver qui peut être appelée par processing_goods_pages pour obtenir le pilote Chrome actuel pour le thread actuel (ou en créer un s'il n'y en a pas actuellement). Mais cela signifie que le code spécifique à l'URL qui se trouvait dans openChrome_headlesss doit maintenant être déplacé vers processing_goods_pages.

Enfin, le stockage local du thread est supprimé et le ramasse-miettes est exécuté pour garantir que le destructeur de toutes les instances de la classe Driver est exécuté pour garantir que toutes les instances de pilote Chrome sont "quittées".

Étant donné que je n'ai pas accès à vos fichiers, cela n'a évidemment pas pu être testé de manière approfondie, il pourrait donc y avoir une erreur de frappe ou dix. Bonne chance.

Une autre remarque : Au lieu de faire un appel à sleep(1) après l'appel à driver.get(ref), vous devriez envisager de faire un appel à driver.implicitly_wait(1) suivi d'un appel de pilote pour localiser un élément dont la présence assure que tout ce dont vous avez besoin sur la page pour l'écriture est chargé, si cela est possible. De cette façon, vous n'attendez que le temps minimum nécessaire pour que les liens soient présents. Bien sûr, si le DOM n'est pas modifié après le chargement initial de la page via des appels AJAX, il n'est pas nécessaire de dormir du tout.

import os
from time import sleep
from bs4 import BeautifulSoup
from selenium import webdriver
# Utiliser le multithreading au lieu du multiprocessus
from multiprocessing.pool import ThreadPool
import threading

#  
url = "https://eldorado.ua/"
directory = os.path.dirname(os.path.realpath(__file__))
env_path = directory + "\chromedriver"
chromedriver_path = env_path + "\chromedriver.exe"

class Driver:
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.headless = True
        options.add_experimental_option("excludeSwitches", ['enable-automation'])
        options.add_argument(
            '--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"')
        self.driver = webdriver.Chrome(executable_path=chromedriver_path, options=options)

    def __del__(self):
        self.driver.quit() # nettoyer le pilote lorsque nous sommes nettoyés
        #print('Le pilote a été "quitté".')

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

dict1 = {"  ": "https://eldorado.ua/node/c1038944/",
         "  ": "https://eldorado.ua/node/c1038957/",
         ",   ": "https://eldorado.ua/node/c1038958/",
         "  ": "https://eldorado.ua/node/c1088594/",
         "  ": "https://eldorado.ua/node/c1088603/",
         " ": "https://eldorado.ua/node/c1285101/",
         "  ": "https://eldorado.ua/node/c1215257/",
         "": "https://eldorado.ua/node/c1039055/",
         "  ": "https://eldorado.ua/node/c1038960/",
         "  ": "https://eldorado.ua/node/c1178596/",
         "  ": "https://eldorado.ua/node/c1284654/",
         "  ": "https://eldorado.ua/node/c1218544/",
         "    ": "https://eldorado.ua/node/c1285161/",
         "  ": "https://eldorado.ua/node/c1085100/"}

def processing_goods_pages(name):
    for n in os.listdir(f"brand_pages\\{name}"):
        with open(f"{directory}\\brand_pages\\{name}\\{n}", encoding="utf-8") as file:
            soup = BeautifulSoup(file.read(), "lxml")

        if not os.path.exists(f"{directory}\\goods_pages\\{name}\\{n[:-5]}"):
            if not os.path.exists(f"{directory}\\goods_pages\\{name}"):
                os.mkdir(f"{directory}\\goods_pages\\{name}")
            os.mkdir(f"{directory}\\goods_pages\\{name}\\{n[:-5]}")

        links = soup.find_all("header", class_="good-description")
        driver = create_driver()
        for li in links:
            ref = url + li.find('a').get('href')
            print(li.text)
            driver.get(ref)
            sleep(1)
            name = f"{directory}\\goods_pages\\{name}\\{n[:-5]}\\{li.text}"
            try:
                with open(name + ".html", "w", encoding="utf-8") as file:
                    file.write(driver.page_source)
            except Exception as ex:
                print(ex)

if __name__ == "__main__":
    ThreadPool(processes=6).map(processing_goods_pages, dict1.keys())
    # Quitter tous les pilotes Selenium :
    del threadLocal
    import gc
    gc.collect() # une petite assurance supplémentaire

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X