美文网首页
在scrapy中使用selenium

在scrapy中使用selenium

作者: kingron | 来源:发表于2019-10-15 08:51 被阅读0次

在scrapy中使用selenium

scrapy是个好工具,selenium也是一个好工具,但是两者一结合,就不那么好了。因为往一个非阻塞程序中塞入一段阻塞的代码,不能不令人抓狂。但即便如此,还是有不少需求需要在scrapy中使用selenium(往往是因为JavaScript搞不定)。既然如此,不妨来试一下怎样更好的利用scraoy特性使用selenium。大概思路如下:

  1. 编写专属的SeleniumRequest类用来封装selenium的相关操作;
  2. 编写下载中间件,用于启动浏览器,并根据SeleniumReuqest的相关属性进行进一步操作。

OK,思路很清晰,接下来就撸起袖子干吧。

编写SeleniumRequest

毫无疑问这个类要继承自Scrapy.Reuqest,同时我们希望这个类能保存一些属性用于对浏览器的操作。大概如下:

  1. 首先是wait_until,用来保存浏览器等待到我们想要的条件加载出来为止;
  2. script,用来保存js脚本,用于在加载后执行该脚本;
  3. handler,该属性为一个函数,接收一个driver参数,当网页加载完成后调用它。

代码如下:

class SeleniumRequest(scrapy.Request):
    """Selenium Request
    
    :param wait_until: 等待条件
        结构: {by: condition}
        其中 by 的可指定类型可查看selenium.webdriver.common.by.By 
        如: By.ID, By.XPATH 等(仅支持指定条件出现)   
        :type wait_until: dict
    
    :param wait_time: 等待时间
    :type wait_time: int
    
    :param script: 需要执行的js脚本
        执行的结果会存储到 meta 中,字段为 js_result

    :param handler: 处理driver实例的函数
        该函数不需要返回值
    """
    def __init__(self, url, callback=None,
                 wait_until=None, wait_time=None,
                 script=None, handler=None, **kwargs):
        self.wait_until = wait_until
        self.script = script
        self.wait_time = wait_time
        self.handler = handler
        super().__init__(url, callback, **kwargs)

到此请求类就写完了,接下来开始写下载中间件。

编写下载中间件

下载中间件负责接收SeleniumReuqest并实际调用浏览器和操作浏览器,最后将浏览器获取到的网页源码封装为HtmlResponse返回。因此它要做的事相对多一点。下面一步步来写:

  1. 第一步还是要先定义一下类,构造函数中我们需要一个项目设置实例,因为我们要从配置文件中获取Webdriver的启动路径和其它设置信息(规定它必须被配置在scrapy项目的配置文件中,以保持使用上的统一),需要的设置分别为SELENIUM_DRIVER_PATHSELENIUM_HEADLESS,分别表示路径和是否显示浏览器界面。
# 引入下面所有代码需要的模块和方法
import logging

from scrapy import signals
from scrapy.http import HtmlResponse

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

logger = logging.getLogger(__name__)


class SeleniumDownloadMiddleWare(object):

    def __init__(self, settings):
        driver_path = settings['SELENIUM_DRIVER_PATH']
        headless = settings.getbool('SELENIUM_HEADLESS', True)
        
        # 目前就只支持 Chrome 了
        options = webdriver.ChromeOptions()
        options.headless = headless

        # User-Agent与项目配置保持一致
        # 否则可能会导致在某些根据该请求头设定cookies的网站上出现意想不到的情况
        ua = settings['DEFAULT_REQUEST_HEADERS']['User-Agent']
        options.add_argument(f'user-agent={ua}')
        self._options = options
        self._driver_path = driver_path
        self._driver = None
  1. 接下来定义类方法from_crawler用来实例化类。在这里,还要绑定一个爬虫结束的信号,以保证当爬虫结束时测试浏览器被正常关闭。
    @classmethod
    def from_crawler(cls, crawler):
        dm = cls(crawler.settings)
        crawler.signals.connect(dm.close, signal=signals.spider_closed)
        return dm
  1. 于是马上就轮到close方法了:
    def closed(self):
        if self._driver is not None:
            self._driver.quit()
            logger.debug('Selenium closed')
  1. 写一个driver属性方便调用:
    @property
    def driver(self):
        if self._driver is None:
            self._driver = webdriver.Chrome(
                executable_path=self._driver_path, options=self._options
            )
        return self._driver
  1. 终于来到了最后的环节,当然就是写一个process_request方法了,我们将通过该方法处理SeleniumRequest
    def process_request(self, request, spider):
        if not isinstance(request, SeleniumRequest):
            return

        self.driver.get(request.url)

        # 处理等待条件
        if request.wait_until:
            for k, v in request.wait_until.items():
                condition = EC.presence_of_element_located((k, v))
                WebDriverWait(self.driver, request.wait_time).until(
                    condition
                )

        # 处理js脚本
        if request.script:
            result = self.driver.execute_script(request.script)
            if result is not None:
                request.meta['js_result'] = result

        # 调用处理函数
        if request.handler is not None:
            request.handler(self.driver)

        # 传递Cookies
        for cookie_name, cookie_value in request.cookies.items():
            self.driver.add_cookie(
                {
                    'name': cookie_name,
                    'value': cookie_value
                }
            )
        request.cookies = self.driver.get_cookies()
        request.meta['browser'] = self.driver

        # 返回 Response对象
        body = str.encode(self.driver.page_source)
        return HtmlResponse(
            self.driver.current_url,
            body=body,
            encoding='utf-8',
            request=request
        )

到此就写完了,接下来在项目的配置中配置该中间件就可以使用了。完整代码如下:

import logging

from scrapy import signals
from scrapy.http import HtmlResponse

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 这里需要修改为SeleniumRequest的定义处
from utils.selenium import SeleniumRequest

logger = logging.getLogger(__name__)


class SeleniumDownloadMiddleWare(object):

    def __init__(self, settings):
        driver_path = settings['SELENIUM_DRIVER_PATH']
        headless = settings.getbool('SELENIUM_HEADLESS', True)
        ua = settings['DEFAULT_REQUEST_HEADERS']['User-Agent']
        options = webdriver.ChromeOptions()
        options.headless = headless
        options.add_argument(f'user-agent={ua}')
        self._options = options
        self._driver_path = driver_path
        self._driver = None

    @property
    def driver(self):
        if self._driver is None:
            self._driver = webdriver.Chrome(
                executable_path=self._driver_path, options=self._options
            )
        return self._driver

    @classmethod
    def from_crawler(cls, crawler):
        dm = cls(crawler.settings)
        crawler.signals.connect(dm.close, signal=signals.spider_closed)
        return dm

    def process_request(self, request, spider):
        if not isinstance(request, SeleniumRequest):
            return

        self.driver.get(request.url)

        # 处理等待条件
        if request.wait_until:
            for k, v in request.wait_until.items():
                condition = EC.presence_of_element_located((k, v))
                WebDriverWait(self.driver, request.wait_time).until(
                    condition
                )

        # 处理js脚本
        if request.script:
            result = self.driver.execute_script(request.script)
            if result is not None:
                request.meta['js_result'] = result

        # 调用处理函数
        if request.handler is not None:
            request.handler(self.driver)

        # 传递Cookies
        for cookie_name, cookie_value in request.cookies.items():
            self.driver.add_cookie(
                {
                    'name': cookie_name,
                    'value': cookie_value
                }
            )
        request.cookies = self.driver.get_cookies()
        request.meta['browser'] = self.driver

        # 返回 Response对象
        body = str.encode(self.driver.page_source)
        return HtmlResponse(
            self.driver.current_url,
            body=body,
            encoding='utf-8',
            request=request
        )

    def close(self):
        if self._driver is not None:
            self._driver.quit()
            logger.debug('Selenium closed')

相关文章

网友评论

      本文标题:在scrapy中使用selenium

      本文链接:https://www.haomeiwen.com/subject/iksjmctx.html