Python 3.x 如何使selenium线程运行(每个线程都有自己的驱动程序)

Python 3.x 如何使selenium线程运行(每个线程都有自己的驱动程序),python-3.x,multithreading,selenium,Python 3.x,Multithreading,Selenium,我有一个Python3脚本,它需要向多个不同的网站发出数千个请求,并检查它们的源代码是否传递了一些预定义的规则 我使用selenium发出请求,因为我需要在JS完成它的执行后获取源代码,但由于需要检查大量URL,我尝试让它同时运行多个线程。每个线程创建并维护一个webdriver实例以发出请求。问题是,过了一段时间,所有线程都停止运行,只剩下一个线程完成所有工作。以下是我的代码的相关部分: def get_browser(use_firefox = True): if use_firef

我有一个Python3脚本,它需要向多个不同的网站发出数千个请求,并检查它们的源代码是否传递了一些预定义的规则

我使用selenium发出请求,因为我需要在JS完成它的执行后获取源代码,但由于需要检查大量URL,我尝试让它同时运行多个线程。每个线程创建并维护一个webdriver实例以发出请求。问题是,过了一段时间,所有线程都停止运行,只剩下一个线程完成所有工作。以下是我的代码的相关部分:

def get_browser(use_firefox = True):
    if use_firefox:
        options = FirefoxOptions()
        options.headless = True
        browser = webdriver.Firefox(options = options)
        browser.implicitly_wait(4)
        return browser
    else:
        chrome_options = ChromeOptions()
        chrome_options.add_argument("--headless")
        browser = webdriver.Chrome(chrome_options=chrome_options)
        browser.implicitly_wait(4)
        return browser

def start_validations(urls, rules, results, thread_id):
    try:
        log("thread %s started" % thread_id, thread_id)
        browser = get_browser(thread_id % 2 == 1)
        while not urls.empty():
            url = "http://%s" % urls.get()
            try:
                log("starting %s" % url, thread_id)
                browser.get(url)
                time.sleep(0.5)
                WebDriverWait(browser, 6).until(selenium_wait_reload(4))
                html = browser.page_source
                result = check_url(html, rules)

                original_domain = url.split("://")[1].split("/")[0].replace("www.","")
                tested_domain = browser.current_url.split("://")[1].split("/")[0].replace("www.","")
                redirected_url = "" if tested_domain == original_domain else browser.current_url

                results.append({"Category":result, "URL":url, "Redirected":redirected_url})
                log("finished %s" % url, thread_id)
            except Exception as e:
                log("couldn't test url %s" % url, thread_id )
                log(str(e), thread_id)
                results.append({"Category":"Connection Error", "URL":url, "Redirected":""})
                browser.quit()
                time.sleep(2)
                browser = get_browser(thread_id % 2 == 1)
    except Exception as e:
        log(str(e), thread_id)
    finally:
        log("closing thread", thread_id)
        browser.quit()

def calculate_progress(urls):
    progress_folder ="%sprogress/" % WEBROOT
    if not os.path.exists(progress_folder):
        os.makedirs(progress_folder)

    initial_size = urls.qsize()
    while not urls.empty():
        current_size = urls.qsize()
        on_queue = initial_size - current_size
        progress = '{0:.0f}'.format((on_queue / initial_size * 100))
        for progress_file in os.listdir(progress_folder):
            file_path = os.path.join(progress_folder, progress_file)
            if os.path.isfile(file_path) and not file_path.endswith(".csv"):
                os.unlink(file_path)
        os.mknod("%s%s" % (progress_folder, progress))
        time.sleep(1)



if __name__ == '__main__':
    while True:
        try:
            log("scraper started")
            if os.path.isfile(OUTPUT_FILE):
                os.unlink(OUTPUT_FILE)

            manager = Manager()

            rules = fetch_rules()
            urls = manager.Queue()
            fetch_urls()
            results = manager.list()

            jobs = []
            p = Process(target=calculate_progress, args=(urls,))
            jobs.append(p)
            p.start()
            for i in range(THREAD_POOL_SIZE):
                log("spawning thread with id %s" % i)
                p = Process(target=start_validations, args=(urls, rules, results, i))
                jobs.append(p)
                p.start()
                time.sleep(2)
            for j in jobs:
                j.join()

            save_results(results, OUTPUT_FILE)
            log("scraper finished")
        except Exception as e:
            log(str(e))
正如你所看到的,首先我认为我只能有一个浏览器实例,所以我尝试至少并行运行firefox和chrome,但这仍然只剩下一个线程来完成所有工作

有时,驱动程序崩溃,线程停止工作,即使它在try/catch块中,所以每次发生这种情况时,我都开始获取浏览器的新实例,但它仍然不工作。我还尝试在创建驱动程序的每个实例之间等待几秒钟,但仍然没有结果

以下是其中一个日志文件的粘贴箱:

我注意到的一件奇怪的事情是,几乎每次唯一保持运行的线程都是最后一个生成的线程(id为3)

谢谢你的时间和帮助

编辑:

[1] 以下是完整的代码:


[2] 自定义selenium等待条件:

允许我诅咒SO吗?我解决了这个问题,我认为这个答案不应该存在,因为没有其他人会从中受益。问题是我创建的自定义等待条件。此类位于编辑2中添加的粘贴箱中,但为了方便起见,我也将其添加到此处:

import time
class selenium_wait_reload:
    def __init__(self, desired_repeating_sources):
        self.desired_repeating_sources = desired_repeating_sources
        self.repeated_pages = 0
        self.previous_source = None

    def __call__(self, driver):
        while True:
            current_source = driver.page_source
            if current_source == self.previous_source:
                self.repeated_pages = self.repeated_pages +1
                if self.repeated_pages >= self.desired_repeating_sources:
                    return True
            else:
                self.previous_source = current_source
                self.repeated_pages = 0
            time.sleep(0.3)
这个类的目标是让selenium等待,因为JS可能正在加载额外的DOM

所以,这个类让selenium等待一小段时间并检查代码,再等待一小段时间并再次检查代码。该类重复此操作,直到源代码连续三次相同


问题是有些页面有js carrousel,所以源代码从来都不一样。我认为在这样的情况下,WebDriverWait第二个参数会使它崩溃,并出现timeoutexception。我错了。

看起来你在操纵URL。你必须使用硒吗?我这样问是因为他们打开了浏览器,这会消耗大量内存和其他开销。你能改用请求库吗?是的,很遗憾我不得不。。。我的第一次尝试只是使用请求,但我需要在页面中的JS完成对DOM的操作(即发出ajax请求以显示某些内容,或重定向到另一个页面)后获取源代码。您可以在请求库中允许重定向。还可以发出ajax请求。是的,如果我知道有什么要求,这是可能的。还有一些网站会测试是否启用了JS,如果禁用了JS,则会为您提供不同的页面。但是,如果您可以共享一个片段来复制数千个不同网站发出的请求,我很乐意使用该解决方案,那么您可以将完整代码托管在我们可以看到的地方吗?我想用iTunes把我的手弄脏WebDriverWait@CoreyGoldberg你对预期的情况有什么建议吗?尽管我做了很多研究,但人们都说这不是一项琐碎的任务,我尝试的一切(包括滚动我自己的自定义等待条件)都失败了,所以我决定等待一个固定的X秒……你的答案与所问的问题完全无关。不是,也许你误解了什么。问题基本上是这样的:“为什么这个脚本直到最后才运行它的所有线程?”答案是“因为线程运行的代码让它永远等待。这个代码就是答案中的类”您的解决方案是对问题中没有的代码的修复。