将硒驱动程序传递给scrapy

Posted

技术标签:

【中文标题】将硒驱动程序传递给scrapy【英文标题】:Passing selenium driver to scrapy 【发布时间】:2018-08-27 22:45:55 【问题描述】:

我花了很长时间试图弄清楚这一点无济于事。我已经阅读了很多关于传回 htmlResponse 和使用 selenium 中间件的内容,但一直难以理解如何构建代码并在我的解决方案中实现。

这是我的蜘蛛代码:

import scrapy
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep

count = 0

class ContractSpider(scrapy.Spider):

name = "contracts"

def start_requests(self):
    urls = [
        'https://www.contractsfinder.service.gov.uk/Search/Results',
    ]
    for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

def __init__(self):
    self.driver = webdriver.Firefox()
    self.driver.get("https://www.contractsfinder.service.gov.uk/Search/Results")
    elem2 = self.driver.find_element_by_name("open")
    elem2.click()
    sleep(5)
    elem = self.driver.find_element_by_name("awarded")
    elem.click()
    sleep(5)
    elem3 = self.driver.find_element_by_id("awarded_date")
    elem3.click()
    sleep(5)
    elem4 = self.driver.find_element_by_name("awarded_from")
    elem4.send_keys("01/03/2018")
    elem4.send_keys(Keys.RETURN)
    sleep(5)
    elem5 = self.driver.find_element_by_name("awarded_to")
    elem5.send_keys("16/03/2018")
    elem5.send_keys(Keys.RETURN)
    sleep(5)
    elem6 = self.driver.find_element_by_name("adv_search")
    self.driver.execute_script("arguments[0].scrollIntoView(true);", elem6)
    elem6.send_keys(Keys.RETURN)

def parse(self, response):
    global count
    count += 1
    strcount = str(count)
    page = self.driver.get(response.url)
    filename = strcount+'quotes-%s.html' % page
    with open(filename, 'wb') as f:
        f.write(response.body)
    self.log('Saved file %s' % filename)

    for a in response.css('a.standard-paginate-next'):
        yield response.follow(a, callback=self.parse)

selenium 部分在调用 firefox 时工作,正在发生各种 java 交互并加载最后一页结果。

代码的scrapy部分似乎正在工作(因为它找到了加载硒的firefox webdriver的下一个按钮并点击 - 我可以通过观察webdriver firefox本身来看到这一点) - 但是,实际的抓取发生了(将 HTML 保存到我的 c:\ 驱动器上)正在单独抓取 URL 'https://www.contractsfinder.service.gov.uk/Search/Results',并且没有来自 firefox webdriver 的 selenium 诱导的 java 交互。

我想我理解为什么它不能按我想要的那样工作的一些原因,例如在 start_requests 我指的是原始 URL,这意味着硒加载的页面没有被蜘蛛,但是每次我尝试通过阅读***的各种不同方法从webdriver创建响应时,我都会遇到各种错误,因为我的理解还不够好——我想我会发布一个selenium 和 scrapy 元素正在做某事的版本,但请有人解释并向我展示将 2 个元素链接在一起的最佳方法,即,一旦 selenium 完成 - 使用 firefox webdriver 加载页面并将其传递给 scrapy 做它的东西?非常感谢任何反馈。

【问题讨论】:

【参考方案1】:

正如您所说,scrapy 打开您的初始网址,而不是 Selenium 修改的页面。

如果你想从 Selenium 获取页面,你应该使用 driver.page_source.encode('utf-8') (编码不是强制性的)。你也可以将它与 scrapy Selector 一起使用:

response = Selector(text=driver.page_source.encode('utf-8'))

在它像以前一样使用响应之后。

编辑:

我会尝试这样的事情(注意,我还没有测试过代码):

import scrapy
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from time import sleep

count = 0

class ContractSpider(scrapy.Spider):

    name = "contracts"

    def start_requests(self):
        urls = [
            'https://www.contractsfinder.service.gov.uk/Search/Results',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def __init__(self):
        driver = webdriver.Firefox()
        # An implicit wait tells WebDriver to poll the DOM for a certain amount of time when trying to find any element
        # (or elements) not immediately available.
        driver.implicitly_wait(5)

    @staticmethod
    def get__response(url):
        self.driver.get("url")
        elem2 = self.driver.find_element_by_name("open")
        elem2.click()
        elem = self.driver.find_element_by_name("awarded")
        elem.click()
        elem3 = self.driver.find_element_by_id("awarded_date")
        elem3.click()
        elem4 = self.driver.find_element_by_name("awarded_from")
        elem4.send_keys("01/03/2018")
        elem4.send_keys(Keys.RETURN)
        elem5 = self.driver.find_element_by_name("awarded_to")
        elem5.send_keys("16/03/2018")
        elem5.send_keys(Keys.RETURN)
        elem6 = self.driver.find_element_by_name("adv_search")
        self.driver.execute_script("arguments[0].scrollIntoView(true);", elem6)
        elem6.send_keys(Keys.RETURN)
        return self.driver.page_source.encode('utf-8')

    def parse(self, response):
        global count
        count += 1
        strcount = str(count)
        # Here you got response from webdriver
        # you can use selectors to extract data from it
        selenium_response = Selector(text=self.get_selenium_response(response.url))
    ...

【讨论】:

感谢 Alex - 几个 q's - 1) 该代码在我当前的蜘蛛中应该放在哪里,2) 我的 start_requests 应该是什么样子/我是否还需要包含 start_requests? alex 说的是对的,我建议你在代码中使用 wait.until 而不是很多 sleep ;) Alex - 再次感谢您提供代码,经过数小时的编辑和尝试,我仍然无法让脚本正常工作。只是重申我正在尝试开始工作的过程-我想加载firefox webdriver,转到URL,应用所有java-加载最后一页-将其发送到scrapy并通过过滤(通过selenium)结果进行分页-不知道我是否说清楚了。我很难理解 - 我可以解析但不断收到错误,例如未从 selenium_response = Selector(..) 定义的 'url' 你能尝试运行你的代码来调试吗?谢谢【参考方案2】:

结合@Alex K和其他人的解决方案,这是我测试过的代码:

import scrapy
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait

...

def __init__(self, name=None, **kwargs):
    super(MySpider, self).__init__(name, **kwargs)
    self.driver = webdriver.Chrome()

@staticmethod
def get_selenium_response(driver, url):
    driver.get(url)
    # in case of explicit amount of time
    # time.sleep(5) 
    # in case of wait until element been found
    try:
        def find(driver):
            table_el = driver.find_element_by_xpath('//*[@id="table_el"]')
            if table_el:
                return table_el
            else:
                return False
        element = WebDriverWait(driver, 5).until(find)
        return driver.page_source.encode('utf-8')
    except:
        driver.quit()

def parse(self, response):
    response = scrapy.Selector(
        text=self.get_selenium_response(self.driver, response.url))
    # ...parse response as normally

【讨论】:

以上是关于将硒驱动程序传递给scrapy的主要内容,如果未能解决你的问题,请参考以下文章

仅在将参数传递给程序时使用 openMP

Robot Framework - 将 Appium 驱动程序传递给 python 脚本

如何将自定义参数传递给事件处理程序

将完成处理程序传递给 NSTimer

将额外参数传递给事件处理程序?

如何将事件和其他参数传递给单击处理程序