어떻게 scrapy 에서 selenium 을 통합 하여 웹 페이지 를 오 르 는 방법 입 니까?

1.배경
4.567917.우 리 는 웹 페이지 를 오 를 때 보통 세 개의 파충류 창고 에 사용 된다.requests,scrapy,selenium.requests 는 보통 소형 파충류 에 사용 되 며,scrapy 는 큰 파충류 프로젝트 를 구축 하 는 데 사용 되 며,selenium 은 주로 담당 페이지(복잡 한 js 렌 더 링 페이지,구조 가 매우 어렵 거나 구조 방식 이 자주 변화 함)에 대응 합 니 다4.567917.우리 가 대형 파충류 프로젝트 에 직면 했 을 때 반드시 scrapy 프레임 워 크 를 선택 하여 개발 할 것 이다.그러나 복잡 한 JS 렌 더 링 페이지 를 분석 할 때 매우 번거롭다.비록 selenium 브 라 우 저 렌 더 링 을 사용 하여 이러한 페이지 를 캡 처 하 는 것 이 편리 하지만 이런 방식 에서 우 리 는 페이지 배경 에 어떤 요청 이 발생 했 는 지 에 관심 을 가 질 필요 가 없고 전체 페이지 의 렌 더 링 과정 을 분석 할 필요 가 없다.우 리 는 페이지 의 최종 결과 에 만 관심 을 가 져 야 한다.이 를 통 해 알 수 있 듯 이 올 라 갈 수 있 지만 selenium 의 효율 은 너무 낮다4.567917.그래서 만약 에 scrapy 에서 selenium 을 통합 하여 selenium 에 게 복잡 한 페이지 의 기어 오 르 는 것 을 책임 지게 할 수 있다 면 이런 파충 류 는 무적 이 고 모든 사 이 트 를 기어 올 라 갈 수 있다.
 2.환경
  • python 3.6.1
  • 시스템:win 7
  • IDE:pycharm
  • chrome 브 라 우 저 설치
    chromedriver(환경 변 수 를 설정)
  • selenium 3.7.0
  • scrapy 1.4.0
  • 3.원리 분석
    3.1.request 요청 의 절 차 를 분석 합 니 다.
    우선 scrapy 의 최신 구조 도 를 살 펴 보 자.
    这里写图片描述
    부분 흐름:
    첫째:파충류 엔진 생 성 requests 요청,scheduler 스케줄 러 모듈 로 보 내 고 대기 대기 열 에 들 어가 스케줄 을 기다 리 고 있 습 니 다.
    두 번 째:scheduler 모듈 은 이러한 requests 를 스케줄 링 하고 팀 을 나 와 파충류 엔진 으로 보 내기 시작 했다.
    셋째,파충류 엔진 은 이러한 requests 를 다운로드 미들웨어(여러 개,예 를 들 어 header,대리,사용자 정의 등)로 보 내 처리 합 니 다.
    넷 째:처리 후 다운 로 더 모듈 로 보 내 다운로드 합 니 다.이 처리 과정 을 보면 돌파 구 는 미들웨어 부분 을 다운로드 하고 selenium 으로 request 요청 을 직접 처리 합 니 다.
    3.2.requests 와 response 중간 처리 부품 소스 코드 분석
    관련 코드 위치:
    这里写图片描述
    원본 분석:
    
    #   :E:\Miniconda\Lib\site-packages\scrapy\core\downloader\middleware.py
    """
    Downloader Middleware manager
    
    See documentation in docs/topics/downloader-middleware.rst
    """
    import six
    
    from twisted.internet import defer
    
    from scrapy.http import Request, Response
    from scrapy.middleware import MiddlewareManager
    from scrapy.utils.defer import mustbe_deferred
    from scrapy.utils.conf import build_component_list
    
    
    class DownloaderMiddlewareManager(MiddlewareManager):
    
      component_name = 'downloader middleware'
    
      @classmethod
      def _get_mwlist_from_settings(cls, settings):
        #  settings.py  custom_setting       Middleware   
        '''
        'DOWNLOADER_MIDDLEWARES': {
          'mySpider.middlewares.ProxiesMiddleware': 400,
          # SeleniumMiddleware
          'mySpider.middlewares.SeleniumMiddleware': 543,
          'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
        },
        '''
        return build_component_list(
          settings.getwithbase('DOWNLOADER_MIDDLEWARES'))
    
      #       Middleware              methods   
      def _add_middleware(self, mw):
        if hasattr(mw, 'process_request'):
          self.methods['process_request'].append(mw.process_request)
        if hasattr(mw, 'process_response'):
          self.methods['process_response'].insert(0, mw.process_response)
        if hasattr(mw, 'process_exception'):
          self.methods['process_exception'].insert(0, mw.process_exception)
    
      #       
      def download(self, download_func, request, spider):
        @defer.inlineCallbacks
        def process_request(request):
          #   request  ,         Middleware    process_request  ,      list 
          for method in self.methods['process_request']:
            response = yield method(request=request, spider=spider)
            assert response is None or isinstance(response, (Response, Request)), \
                'Middleware %s.process_request must return None, Response or Request, got %s' % \
                (six.get_method_self(method).__class__.__name__, response.__class__.__name__)
            #       
            #      Middleware    process_request      ,     response  
            #         response return   ,    ,       process_request
            #      header,proxy   ,     user-agent,  proxy,     return 
            #        :    return    Response  
            #        HtmlResponse  Response     
            if response:
              defer.returnValue(response)
          #         process_request ,       Response    
          #   ,        Request  download_func,    ,       Response  
          #         Middleware    process_response      ,  
          defer.returnValue((yield download_func(request=request,spider=spider)))
    
        @defer.inlineCallbacks
        def process_response(response):
          assert response is not None, 'Received None in process_response'
          if isinstance(response, Request):
            defer.returnValue(response)
    
          for method in self.methods['process_response']:
            response = yield method(request=request, response=response,
                        spider=spider)
            assert isinstance(response, (Response, Request)), \
              'Middleware %s.process_response must return Response or Request, got %s' % \
              (six.get_method_self(method).__class__.__name__, type(response))
            if isinstance(response, Request):
              defer.returnValue(response)
          defer.returnValue(response)
    
        @defer.inlineCallbacks
        def process_exception(_failure):
          exception = _failure.value
          for method in self.methods['process_exception']:
            response = yield method(request=request, exception=exception,
                        spider=spider)
            assert response is None or isinstance(response, (Response, Request)), \
              'Middleware %s.process_exception must return None, Response or Request, got %s' % \
              (six.get_method_self(method).__class__.__name__, type(response))
            if response:
              defer.returnValue(response)
          defer.returnValue(_failure)
    
        deferred = mustbe_deferred(process_request, request)
        deferred.addErrback(process_exception)
        deferred.addCallback(process_response)
        return deferred
    
    4.코드
    settings.py 에서 selenium 인 자 를 설정 합 니 다:
    
    #   settings.py 
    
    # ----------- selenium     -------------
    SELENIUM_TIMEOUT = 25      # selenium        ,   
    LOAD_IMAGE = True        #       
    WINDOW_HEIGHT = 900       #        
    WINDOW_WIDTH = 900
    spider 에서 request 를 생 성 할 때 어떤 요청 을 selenium 으로 다운로드 해 야 하 는 지 표시 합 니 다.
    
    #   mySpider.py 
    class mySpider(CrawlSpider):
      name = "mySpiderAmazon"
      allowed_domains = ['amazon.com']
    
      custom_settings = {
        'LOG_LEVEL':'INFO',
        'DOWNLOAD_DELAY': 0,
        'COOKIES_ENABLED': False, # enabled by default
        'DOWNLOADER_MIDDLEWARES': {
          #      
          'mySpider.middlewares.ProxiesMiddleware': 400,
          # SeleniumMiddleware    
          'mySpider.middlewares.SeleniumMiddleware': 543,
          #  scrapy   user-agent     
          'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
        },
    
    #.....................      .......................
    #   request ,     selenium     ,   meta 
    yield Request(
      url = "https://www.amazon.com/",
      meta = {'usedSelenium': True, 'dont_redirect': True},
      callback = self.parseIndexPage,
      errback = self.error
    )
    중간 부품 middlewares.py 를 다운로드 할 때 selenium 으로 페이지 캡 처(핵심 부분)
    
    # -*- coding: utf-8 -*-
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.keys import Keys
    from scrapy.http import HtmlResponse
    from logging import getLogger
    import time
    
    class SeleniumMiddleware():
      #      pipeline        settings   ,    scrapy.crawler.Crawler.settings  
      @classmethod
      def from_crawler(cls, crawler):
        #  settings.py ,  selenium    ,    
        return cls(timeout=crawler.settings.get('SELENIUM_TIMEOUT'),
              isLoadImage=crawler.settings.get('LOAD_IMAGE'),
              windowHeight=crawler.settings.get('WINDOW_HEIGHT'),
              windowWidth=crawler.settings.get('WINDOW_WIDTH')
              )
    
      def __init__(self, timeout=30, isLoadImage=True, windowHeight=None, windowWidth=None):
        self.logger = getLogger(__name__)
        self.timeout = timeout
        self.isLoadImage = isLoadImage
        #           browser,         ,        chrome   
        #   ,      Request        browser
        self.browser = webdriver.Chrome()
        if windowHeight and windowWidth:
          self.browser.set_window_size(900, 900)
        self.browser.set_page_load_timeout(self.timeout)    #         
        self.wait = WebDriverWait(self.browser, 25)       #           
    
        def process_request(self, request, spider):
        '''
         chrome    
        :param request: Request    
        :param spider: Spider  
        :return: HtmlResponse  
        '''
        # self.logger.debug('chrome is getting page')
        print(f"chrome is getting page")
        #   meta    ,         selenium   
        usedSelenium = request.meta.get('usedSelenium', False)
        if usedSelenium:
          try:
            self.browser.get(request.url)
            #        
            input = self.wait.until(
              EC.presence_of_element_located((By.XPATH, "//div[@class='nav-search-field ']/input"))
            )
            time.sleep(2)
            input.clear()
            input.send_keys("iphone 7s")
            #  enter ,     
            input.send_keys(Keys.RETURN)
            #           
            searchRes = self.wait.until(
              EC.presence_of_element_located((By.XPATH, "//div[@id='resultsCol']"))
            )
          except Exception as e:
            # self.logger.debug(f'chrome getting page error, Exception = {e}')
            print(f"chrome getting page error, Exception = {e}")
            return HtmlResponse(url=request.url, status=500, request=request)
          else:
            time.sleep(3)
            return HtmlResponse(url=request.url,
                      body=self.browser.page_source,
                      request=request,
                      #              
                      encoding='utf-8',
                      status=200)
    
    5.실행 결과
    这里写图片描述  
    这里写图片描述
    6.존재 하 는 문제점
    6.1.Spider 가 닫 혔 습 니 다.chrome 은 종료 되 지 않 았 습 니 다.
    2018-04-04 09:26:18 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
    {'downloader/response_bytes': 2092766,
    'downloader/response_count': 2,
    'downloader/response_status_count/200': 2,
    'finish_reason': 'finished',
    'finish_time': datetime.datetime(2018, 4, 4, 1, 26, 16, 763602),
    'log_count/INFO': 7,
    'request_depth_max': 1,
    'response_received_count': 2,
    'scheduler/dequeued': 2,
    'scheduler/dequeued/memory': 2,
    'scheduler/enqueued': 2,
    'scheduler/enqueued/memory': 2,
    'start_time': datetime.datetime(2018, 4, 4, 1, 25, 48, 301602)}
    2018-04-04 09:26:18 [scrapy.core.engine] INFO: Spider closed (finished)
    위,우 리 는 브 라 우 저 대상 을 Middleware 미들웨어 미들웨어 에 넣 었 습 니 다.process 만 할 수 있 습 니 다.request 와 processresponse,중간 부품 에서 scrapy 를 어떻게 호출 하 는 close 방법 을 소개 하지 않 았 습 니 다.
    솔 루 션:신 호 량 을 이용 하여 spider 를 받 으 면closed 신호 시 browser.quit()호출
    6.2.한 프로젝트 가 여러 개의 spider 를 동시에 시작 하면 Middleware 의 selenium 을 함께 사용 하여 동시 다발 에 불리 합 니 다.
    scrapy+selenium 방식 으로 일부,심지어 일부 페이지 만 chrome 을 사용 하기 때 문 입 니 다.chrome 을 Middleware 에 넣 는 데 이렇게 많은 제한 이 있 는데 왜 chrome 을 spider 에 넣 을 수 없 습 니까?이러한 장점 은 모든 spider 는 자신의 chrome 을 가지 고 있 습 니 다.이렇게 여러 개의 spider 를 시작 할 때 여러 개의 chrome 이 있 습 니 다.모든 spider 가 하나의 chrome 을 공유 하 는 것 이 아니 라 우리 의 병발 에 좋 습 니 다.
    솔 루 션:chrome 의 초기 화 를 spider 에 넣 고 모든 spider 가 자신의 chrome 을 독점 합 니 다.
     7.개정판 코드
    settings.py 에서 selenium 인 자 를 설정 합 니 다:
    
    #   settings.py 
    
    # ----------- selenium     -------------
    SELENIUM_TIMEOUT = 25      # selenium        ,   
    LOAD_IMAGE = True        #       
    WINDOW_HEIGHT = 900       #        
    WINDOW_WIDTH = 900
    spider 에서 request 를 생 성 할 때 어떤 요청 을 selenium 으로 다운로드 해 야 하 는 지 표시 합 니 다.
    
    #   mySpider.py 
    # selenium   
    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    
    # scrapy      
    from scrapy.utils.project import get_project_settings
    #       ,    ,    
    # from scrapy.xlib.pydispatch import dispatcher
    from scrapy import signals
    # scrapy       
    from pydispatch import dispatcher
    
    class mySpider(CrawlSpider):
      name = "mySpiderAmazon"
      allowed_domains = ['amazon.com']
    
      custom_settings = {
        'LOG_LEVEL':'INFO',
        'DOWNLOAD_DELAY': 0,
        'COOKIES_ENABLED': False, # enabled by default
        'DOWNLOADER_MIDDLEWARES': {
          #      
          'mySpider.middlewares.ProxiesMiddleware': 400,
          # SeleniumMiddleware    
          'mySpider.middlewares.SeleniumMiddleware': 543,
          #  scrapy   user-agent     
          'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
        },
    
      #  chrome     spider ,  spider    
      def __init__(self, timeout=30, isLoadImage=True, windowHeight=None, windowWidth=None):
        #  settings.py       
        self.mySetting = get_project_settings()
        self.timeout = self.mySetting['SELENIUM_TIMEOUT']
        self.isLoadImage = self.mySetting['LOAD_IMAGE']
        self.windowHeight = self.mySetting['WINDOW_HEIGHT']
        self.windowWidth = self.mySetting['windowWidth']
        #    chrome  
        self.browser = webdriver.Chrome()
        if self.windowHeight and self.windowWidth:
          self.browser.set_window_size(900, 900)
        self.browser.set_page_load_timeout(self.timeout)    #         
        self.wait = WebDriverWait(self.browser, 25)       #           
        super(mySpider, self).__init__()
        #      ,   spider_closed   ,  mySpiderCloseHandle  ,  chrome
        dispatcher.connect(receiver = self.mySpiderCloseHandle,
                  signal = signals.spider_closed
                  )
    
      #        :  chrome   
      def mySpiderCloseHandle(self, spider):
        print(f"mySpiderCloseHandle: enter ")
        self.browser.quit()
    
    #.....................      .......................
    #   request ,     selenium     ,   meta 
    yield Request(
      url = "https://www.amazon.com/",
      meta = {'usedSelenium': True, 'dont_redirect': True},
      callback = self.parseIndexPage,
      errback = self.error
    )
    미들웨어 를 다운로드 할 때 selenium 으로 페이지 를 캡 처 합 니 다.
    
    # -*- coding: utf-8 -*-
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.keys import Keys
    from scrapy.http import HtmlResponse
    from logging import getLogger
    import time
    
    class SeleniumMiddleware():
      # Middleware        spider,      spider  ,      __init__  chrome    
      def process_request(self, request, spider):
        '''
         chrome    
        :param request: Request    
        :param spider: Spider  
        :return: HtmlResponse  
        '''
        print(f"chrome is getting page")
        #   meta    ,         selenium   
        usedSelenium = request.meta.get('usedSelenium', False)
        if usedSelenium:
          try:
            spider.browser.get(request.url)
            #        
            input = spider.wait.until(
              EC.presence_of_element_located((By.XPATH, "//div[@class='nav-search-field ']/input"))
            )
            time.sleep(2)
            input.clear()
            input.send_keys("iphone 7s")
            #  enter ,     
            input.send_keys(Keys.RETURN)
            #           
            searchRes = spider.wait.until(
              EC.presence_of_element_located((By.XPATH, "//div[@id='resultsCol']"))
            )
          except Exception as e:
            print(f"chrome getting page error, Exception = {e}")
            return HtmlResponse(url=request.url, status=500, request=request)
          else:
            time.sleep(3)
            #       ,       Response  (HtmlResponse     )
            return HtmlResponse(url=request.url,
                      body=spider.browser.page_source,
                      request=request,
                      #              
                      encoding='utf-8',
                      status=200)
    
    실행 결과(spider 종료,mySpiderCloseHandle 실행 으로 chrome 브 라 우 저 닫 기):
    ['categorySelectorAmazon1.pipelines.MongoPipeline']
    2018-04-04 11:56:21 [scrapy.core.engine] INFO: Spider opened
    2018-04-04 11:56:21 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
    chrome is getting page
    parseProductDetail url = https://www.amazon.com/, status = 200, meta = {'usedSelenium': True, 'dont_redirect': True, 'download_timeout': 25.0, 'proxy': 'http://H37XPSB6V57VU96D:[email protected]:9020', 'depth': 0}
    chrome is getting page
    2018-04-04 11:56:54 [scrapy.core.engine] INFO: Closing spider (finished)
    mySpiderCloseHandle: enter
    2018-04-04 11:56:59 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
    {'downloader/response_bytes': 1938619,
    'downloader/response_count': 2,
    'downloader/response_status_count/200': 2,
    'finish_reason': 'finished',
    'finish_time': datetime.datetime(2018, 4, 4, 3, 56, 54, 301602),
    'log_count/INFO': 7,
    'request_depth_max': 1,
    'response_received_count': 2,
    'scheduler/dequeued': 2,
    'scheduler/dequeued/memory': 2,
    'scheduler/enqueued': 2,
    'scheduler/enqueued/memory': 2,
    'start_time': datetime.datetime(2018, 4, 4, 3, 56, 21, 642602)}
    2018-04-04 11:56:59 [scrapy.core.engine] INFO: Spider closed (finished)
    scrapy 에 selenium 을 통합 하여 웹 페이지 를 오 르 는 방법 에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 scrapy 통합 selenium 이 웹 페이지 를 오 르 는 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 조회 하 시기 바 랍 니 다.앞으로 많은 응원 바 랍 니 다!

    좋은 웹페이지 즐겨찾기