【问题标题】:Is selenium thread safe for scraping with Python?使用 Python 抓取硒线程是否安全?
【发布时间】:2014-11-17 11:53:18
【问题描述】:

我正在使用 Threading 执行 Python 脚本,在给定我放入队列的“查询”术语的情况下,我使用查询参数创建 url,设置 cookie 并解析网页以返回产品和 url那些产品。这是脚本。

任务:对于给定的一组查询,将前 20 个产品 ID 存储在一个文件中,如果查询返回的结果较少,则将 # 降低。

我记得读过 Selenium 不是线程安全的。只是想确保由于该限制而发生此问题,有没有办法让它在并发线程中工作?主要问题是脚本受 I/O 限制,因此抓取大约 3000 个 url 非常慢。

from pyvirtualdisplay import Display
from data_mining.scraping import scraping_conf as sf #custom file with rules for scraping
import Queue
import threading
import urllib2
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By

num_threads=5
COOKIES=sf.__MERCHANT_PARAMS[merchant_domain]['COOKIES']
query_args =sf.__MERCHANT_PARAMS[merchant_domain]['QUERY_ARGS']


class ThreadUrl(threading.Thread):
    """Threaded Url Grab"""
    def __init__(self, queue, out_queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.out_queue = out_queue

    def url_from_query(self,query):
        for key,val in query_args.items():
            if query_args[key]=='query' :
                query_args[key]=query
                print "query", query
            try :
                url = base_url+urllib.urlencode(query_args)
                print "url"
                return url
            except Exception as e:
                log()
                return None


    def init_driver_and_scrape(self,base_url,query,url):
        # Will use Pyvirtual display later 
        #display = Display(visible=0, size=(1024, 768))
        #display.start()
        fp = webdriver.FirefoxProfile()
        fp.set_preference("browser.download.folderList",2)
        fp.set_preference("javascript.enabled", True)
        driver = webdriver.Firefox(firefox_profile=fp)
        driver.delete_all_cookies()
        driver.get(base_url)
        for key,val in COOKIES[exp].items():
            driver.add_cookie({'name':key,'value':val,'path':'/','domain': merchant_domain,'secure':False,'expiry':None})
        print "printing cookie name & value"
        for cookie in driver.get_cookies():
            if cookie['name'] in COOKIES[exp].keys():
                print cookie['name'],"-->", cookie['value']
        driver.get(base_url+'search=junk') # To counter any refresh issues
        driver.implicitly_wait(20)
        driver.execute_script("window.scrollTo(0, 2000)")
        print "url inside scrape", url
        if url is not None :
            flag = True
            i=-1
            row_data,row_res=(),()
            while flag :
                i=i+1
                try :
                    driver.get(url)
                    key=sf.__MERCHANT_PARAMS[merchant_domain]['GET_ITEM_BY_ID']+str(i)
                    print key
                    item=driver.find_element_by_id(key)
                    href=item.get_attribute("href")
                    prod_id=eval(sf.__MERCHANT_PARAMS[merchant_domain]['PRODUCTID_EVAL_FUNC'])
                    row_res=row_res+(prod_id,)
                    print url,row_res
                except Exception as e:
                    log()
                    flag =False
            driver.delete_all_cookies()
            driver.close()

            return query+"|"+str(row_res)+"\n"  #  row_data, row_res
        else :
            return  [query+"|"+"None"]+"\n"
    def run(self):
        while True:
            #grabs host from queue
            query = self.queue.get()
            url=self.url_from_query(query)
            print "query, url", query, url
            data=self.init_driver_and_scrape(base_url,query,url)
            self.out_queue.put(data)

            #signals to queue job is done
            self.queue.task_done()


class DatamineThread(threading.Thread):
    """Threaded Url Grab"""
    def __init__(self, out_queue):
        threading.Thread.__init__(self)
        self.out_queue = out_queue

    def run(self):
        while True:
            #grabs host from queue
            data = self.out_queue.get()
            fh.write(str(data)+"\n")
            #signals to queue job is done
            self.out_queue.task_done()

start = time.time()

def log():
    logging_hndl=logging.getLogger("get_results_url")
    logging_hndl.exception("Stacktrace from "+"get_results_url")


df=pd.read_csv(fh_query, sep='|',skiprows=0,header=0,usecols=None,error_bad_lines=False) # read all queries
query_list=list(df['query'].values)[0:3]

def main():
    exp="Control"
    #spawn a pool of threads, and pass them queue instance
    for i in range(num_threads):
        t = ThreadUrl(queue, out_queue)
        t.setDaemon(True)
        t.start()

    #populate queue with data
    print query_list
    for query in query_list:
        queue.put(query)

    for i in range(num_threads):
        dt = DatamineThread(out_queue)
        dt.setDaemon(True)
        dt.start()


    #wait on the queue until everything has been processed
    queue.join()
    out_queue.join()

main()
print "Elapsed Time: %s" % (time.time() - start)

虽然我应该得到来自每个 url 页面的所有搜索结果,但我只得到第一个 i=0 搜索卡,并且这不会对所有查询/url 执行。我究竟做错了什么 ?

我的期望 -

url inside scrape http://<masked>/search=nike+costume
searchResultsItem0
url inside scrape http://<masked>/search=red+tops
searchResultsItem0
url inside scrape http://<masked>/search=halloween+costumes
searchResultsItem0
and more searchResultsItem(s) , like searchResultsItem1,searchResultsItem2 and so on..

我得到了什么

url inside scrape http://<masked>/search=nike+costume
searchResultsItem0
url inside scrape http://<masked>/search=nike+costume
searchResultsItem0
url inside scrape http://<masked>/search=nike+costume
searchResultsItem0

骨架代码取自

http://www.ibm.com/developerworks/aix/library/au-threadingpython/

此外,当我使用 Pyvirtual 显示时,它是否也适用于线程?我还使用了具有相同 Selenium 代码的进程,它给出了相同的错误。 基本上它会打开 3 个 Firefox 浏览器,并带有确切的 url,而它应该从队列中的不同项目打开它们。在这里,我将规则存储在将作为 sf 导入的文件中,该文件具有 Base Domain 的所有自定义属性。

由于设置 cookie 是我脚本的一个组成部分,我不能使用 dryscrape。

编辑: 我试图定位错误,这就是我发现的 - 在自定义规则文件中,我在上面调用“sf”,我已经定义,QUERY_ARGS 为

__MERCHANT_PARAMS = {
  "some_domain.com" :
  {
    COOKIES: { <a dict of dict, masked here>
              },
    ... more such rules
    QUERY_ARGS:{'search':'query'}
  }

所以真正发生的是,在调用时,

query_args =sf.__MERCHANT_PARAMS[merchant_domain]['QUERY_ARGS'] - 这应该返回字典 {'search':'query'},当它返回时,

AttributeError: 'module' 对象没有属性 '_ThreadUrl__MERCHANT_PARAMS'

这是我不明白线程如何传递“_ThreadUrl__”的地方我还尝试在 url_from_query 方法中重新初始化 query_args,但这不起作用。

任何指针,我做错了什么?

【问题讨论】:

    标签: multithreading python-2.7 selenium cookies thread-safety


    【解决方案1】:

    我可能回复得很晚。但是,我在 python2.7 中对其进行了测试,多线程和多进程选项都适用于 selenium,并且它正在打开两个单独的浏览器。

    【讨论】:

      猜你喜欢
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      • 1970-01-01
      相关资源
      最近更新 更多