본문 바로가기
Back-end/Python

[크롤링] 무작정 시작하기 (4) - Selenium + Scrapy

by 허도치 2019. 11. 20.

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (1) - 패키지 선택

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (2) - 프로젝트 준비

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (3) - Spider

 

 

지난 포스트에서 네이버 뉴스를 크롤링하는 Spider를 작성해보았다. 이번 포스트에서는 Rueqest를 던질때 Selenium을 사용하도록 Middleware를 생성할할 것이다. 네이버 뉴스가 동적 웹페이지가 아니라서 크게 차이를 느끼기는 어렵지만 Middleware로 만들어 두는 것이기 때문에 필요에 따라서 사용하면 된다. 이번 포스트는 갑자기 난이도가 올라갈 수 있으니 차분히 따라오길 바란다.

 

 

1. Middeleware 생성 준비

   1-1. scrapy 프로젝트를 생성했을 때 기본으로 생성된 middlewares.py 확인.

   1-2. CrawlerDownloaderMiddleware 함수 라인별 정리.

          - 59 ln: 기본적으로 생성되어 있는 class, 함수가 정의만 되어있고 기능이 구현되어 있지는 않음.

          - 63 ln: Crawler가 실행될 때, Spider가 생성되면 Middleware의 spider_opened가 실행되도록 설정.

          - 66 ln: request를 던질 때 실행되는 함수, 여기서 Selenium을 사용.

          - 69 ln: response를 받을 때 실행되는 함수.

          - 72 ln: 오류가 발생했을 때 실행되는 함수.

          - 75 ln: Spider가 생성될 때 실행되는 함수, 여기서 Selenium을 정의.

          - 그 외에 spider_closed를 추가하여 Spider가 종료될 때 Selenium도 같이 종료되도록 설정하고, response와 exception은 미사용

 

   1-3. 지난 포스트에서 작성했던 Selenium 테스트 소스 및 WebDriver확인.

          - 이 소스를 그대로 활용할 수 있음.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
CHROMEDRIVER_PATH = './crawler/drivers/chromedriver' # Windows는 chromedriver.exe로 변경
WINDOW_SIZE = "1920,1080"
 
chrome_options = Options()
chrome_options.add_argument( "--headless" )     # 크롬창이 열리지 않음
chrome_options.add_argument( "--no-sandbox" )   # GUI를 사용할 수 없는 환경에서 설정, linux, docker 등
chrome_options.add_argument( "--disable-gpu" )  # GUI를 사용할 수 없는 환경에서 설정, linux, docker 등
chrome_options.add_argument(f"--window-size={ WINDOW_SIZE }")
chrome_options.add_argument('Content-Type=application/json; charset=utf-8')
 
driver = webdriver.Chrome( executable_path=CHROMEDRIVER_PATH, chrome_options=chrome_options )
driver.get( 'https://news.naver.com' )
cs

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (2) - 프로젝트 준비

 

 

2. Selenium Middleware 작성

    - crawler/middlewares.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from time import sleep
 
from scrapy import signals
from scrapy.http import HtmlResponse
from scrapy.utils.python import to_bytes
 
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
class SeleniumMiddleware(object):
 
    @classmethod
    def from_crawler(cls, crawler):
        middleware = cls()
        crawler.signals.connect(middleware.spider_opened, signals.spider_opened)
        crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
        return middleware
 
    def spider_opened(self, spider):
        CHROMEDRIVER_PATH = './crawler/drivers/chromedriver_78'
        WINDOW_SIZE = "1920,1080"
 
        chrome_options = Options()
        chrome_options.add_argument( "--headless" )
        chrome_options.add_argument( "--no-sandbox" )
        chrome_options.add_argument( "--disable-gpu" )
        chrome_options.add_argument( f"--window-size={ WINDOW_SIZE }" )
        
        driver = webdriver.Chrome( executable_path=CHROMEDRIVER_PATH, chrome_options=chrome_options )
        self.driver = driver
 
    def spider_closed(self, spider):
        self.driver.close()
 
    def process_request( self, request, spider ):
        self.driver.get( request.url )
 
        body = to_bytes( text=self.driver.page_source )
 
        sleep( 5 )
 
        return HtmlResponse( url=request.url, body=body, encoding='utf-8', request=request )
cs

           - 1 ln: Request를 던질 때, 대기시간을 걸어서 한번에 많은 요청을 하지못하도록 방지.

           - 4 ln: process_request 함수의 반환 타입이 HtmlResponse이며, Selenium의 결과값을 HtmlResponse로 만들어주기위해 사용.

           - 5 ln: HtmlResponse에서 body를 담아서 보내야하는데 이때 bytes 타입을 입력 받도록 되어있으므로 형변환할 때 사용.

           - 7 ln: Selenium WebDriver를 생성할 때 사용.

           - 8 ln: Selenium WebDriver의 옵션을 정의할 때 사용.

           - 15 ln: Crawler가 실행될 때, Spider가 생성되면 middleware의 spider_opened를 실행되도록 설정.

           - 16 ln: Crawler가 실행될 때, Spider가 종료되면 middleware의 spider_closed를 실행되도록 설정.

           - 19~30 ln: Spider가 생성될 때, Selenium WebDriver를 생성.

           - 32~33 ln: Spider가 종료될 때, Selenium WebDriver를 종료.

           - 35 ln: Spider가 start_requests에서 Request를 던지면 이 함수로 넘어옴.

           - 36 ln: 요청한 주소를 다시 Selenium으로 Reqeust를 던짐.

           - 38 ln: process_request는 HtmlResponse를 반환하는데 이때 body가 'bytes' 타입이여야 함.

           - 42 ln: Selenium 으로 요청한 결과로 새로운 Response를 생성하여 반환.

 

 

3. Spider 소스 수정

    - Middleware를 적용하는 방법은 여러가지가 있는데 우선 2가지만 소개하겠음.

 

    3-1. settings.py에 선언.

1
2
3
DOWNLOADER_MIDDLEWARES = {
    'crawler.middlewares.SeleniumMiddleware'100 
}
cs

           - 이렇게 Middleware를 적용하면, NavernewsSpider 외에 다른 Spider들도 이 Middleware를 사용하게 됨.

           - 모든 Spider를 Selenium으로 Request를 던지려면 이 방식을 추천함.

 

   3-2. NavernewsSpider에 선언.

1
2
3
4
5
6
7
8
class NavernewsSpider(scrapy.Spider):
    name = 'navernews'
    allowed_domains = ['news.naver.com']
    custom_settings= {
        'DOWNLOADER_MIDDLEWARES': { 
            'crawler.middlewares.SeleniumMiddleware'100 
        }
    }
cs

           - 이렇게 Middleware를 적용하며, Spider마다 필요한 Middleware를 적용할 수 있음.

           - Spider마다 다양하게 커스터마이징하고 싶다면 이 방법을 추천함.

 

 

4. 크롤러 실행

    - 지금까지 잘 따라왔다면 문제없이 결과값을 얻을 수 있을 것이다. 기본 모듈로 Request를 던졌을 때보다는 다소 속도는 늦어지지만 좀더 확장성 있는 Request를 던질 수 있을 것이다.

 

 

  지금까지 Scrapy와 Selenium을 연결해서 크롤링을 하는 방법을 알아보았다. 이 포스트에서는 Selenium에 대해서 많이 다루지는 않았지만, 원하는 Element가 로드 될때까지 기다리는 기능이나 로그인을 하는 등 다양한 방식으로 활용할 수 있다. 필자도 Selenium은 접한지 얼마되지 않았기 때문에 점차 공부를 해나가고 있는 중이다. 나중에 정리가되면 포스트로 다루어보도록 하겠다.

  다음 포스트에서는 Parse의 결과를 Dict이 아닌 Item Object로 반환하도록 적용하도록 하겠다.

 

댓글