如何使Selenium线程运行(每个线程都有自己的驱动程序)

时间:2019-07-15 14:02:57

标签: python-3.x multithreading selenium

我有一个python 3脚本,该脚本需要向多个不同的网站发出数千个请求,并检查其源代码是否通过了一些预定义的规则。

我正在使用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))

如您所见,首先我以为我只能拥有一个浏览器实例,所以我尝试至少在paralel中运行firefox和chrome,但这仍然只剩下一个线程来完成所有工作。

有时,即使驱动程序处于try / catch块中,该驱动程序仍会发狂并且该线程停止工作,因此,每次发生这种情况时,我都开始获取浏览器的新实例,但仍然无法正常工作。我还尝试在创建驱动程序的每个实例之间等待几秒钟,但仍然没有结果

这是其中一个日志文件的粘贴框:

https://pastebin.com/TsjZdRYf

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

感谢您的时间和帮助!

编辑:

[1]这是完整的代码:https://pastebin.com/fvVPwPVb

[2]自定义硒等待条件:https://pastebin.com/Zi7nbNFk

1 个答案:

答案 0 :(得分:0)

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

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)

此类的目的是让硒等待,因为JS可能正在加载其他DOM。

因此,此类使selenium等待一小会儿并检查代码,再次等待一会儿并再次检查代码。类重复此操作,直到源代码连续3次相同为止。

问题在于,有些页面包含js carrousel,因此源代码永远都不相同。我认为在这种情况下,WebDriverWait的第二个参数会使它崩溃并带有timeoutexception。我错了。