0%

Scrapy와 Selenium을 연동하기

들어가기

scrapy만으로는 크롤링 결과물에 동적인 이벤트를 발생해서 그 결과물을 크롤링 할 수 없다.

따라서 scrapy에서 이런 동적 크롤링을 지원하는 selenium을 연동해서 사용할 필요가 있다.

이 글은 scrapy로 간단한 프로젝트를 생성하고 seleinum을 연동하여 크롤링하는 예제를 만드는 과정을 기록한다.

scrapy의 모든 기능을 담고 있지 않다.(item, pipeline)

실습

실습환경

  • windows 10(64)
  • anaconda
  • python 3.7
  • scrapy
  • selenium
  • chrome dirver
  • pycham

scrapy 프로젝트 생성

anaconda 프롬프트에서 다음 명령으로 예제 scrapy 프로젝트를 생성하자.

1
scrapy startproject scrapy_with_selenium

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled.png

별 오류 없이 명령어가 실행되면, 명령어를 실행한 경로에서 해당 프로젝트라 폴더로 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%201.png

해당 폴더를 pycham으로 열어보자.

아래구조와 같이 프로젝트가 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%202.png

scrapy로 프로젝트를 생성했으니, 실습에 사용할 간단한 spider를 생성하자.

크롤링할 대상 페이지는 아래와 같다.

https://auto.naver.com/bike/mainList.nhn

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%203.png

네이버의 오토바이 검색 페이지를 크롤링 실습으로 사용해 보겠다.

spider 생성

1
scrapy genspider naverbike auto.naver.com/bike/mainList.nhn

위 명령어로 네이버 오토바이 페이지를 크롤링할 spider를 생성하자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%204.png

명령어를 실행하면 아래처럼 spider 파일이 자동으로 생성된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%205.png

여기서 기초적일 설정이 필요하다.

settings.py 파일에서 ROBOTSTXT_OBEY = False로 변경하자.

해당설정은 웹 서버가 크롤링을 거부해도 강제로 진행하는 옵션이다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%206.png

그리고 앞서 생성한 naverbike.py 라는 스파이더의코드를 아래처럼 수정해주자.

scrapy 명령으로 생성한 spider 코드의 start_urls 리스트를 보면 url 맨 뒤에 슬래시가 있는데 아래처럼 제거해주자. 아래 이미지를 처럼, xxxxxx/mainList.nhn 으로 맨 뒤에 슬래시가 없어야 정상적으로 크롤링이 될 것이다.

그리고 parse 메소드에서 reposen.text를 출력해서 크롤링이 재대로 되는지 확인해보자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%207.png

터미널에서 아래 명령어로 해당 spider를 동작시켜 크롤링 결과가 출력되는지 확인해보자

1
scrapy crawl naverbike

아래처럼 크롤링 결과가 정상적으로 출력이 되는 것을 확인 할 수 있다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%208.png

지금 까지 한 작업은 순수하게 scrapy를 사용해서 크롤링을 해본 것이다.

이제 여기에 selenium을 연동시켜 보자.

scrapy에 seleinum연동하기

scrapy에 selenum을 연동하는 것은 생각 보다 간단하다.

scrapy 프로젝트를 생성할 때 같이 생성된 middleware 라는것에 적용하면 된다

요약

  • download middleware에 selenium설정과 selenium크롤링 코드를 작성한다.
  • spider에서 selenium을 설정한 download middleware를 사용한다고 옵션으로 지정해준다.
  • 해당 spider를 크롤링하게 되면, selenium으로 크롤링 하게 된다.

scrapy의 middleware란?

미들웨어란 보통 흐름, 처리 과정중 중간에 삽입되어 무언가를 처리하는 것을 말하는데, scrapy 의 middleware도 그런 역할이다.

앞서 생성한 scrapy 프로젝트의 디렉토리를 보면 아래처럼 middleware.py라는 파일이 자동으로 생성되어 있다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%209.png

파일 안을 보면 기본적으로 프로젝트명SpiderMiddleware라는 클래스가 있다.

그리고 좀 아래로 내려가다 보면 프로젝트명DownloadMiddleware라는 클래스가 있다.

이렇게 SpiderMiddleware, DownloadMiddleware라는 두개의 클래스가 디폴트로 생성된다.

일단 실습에 앞서 이 미들웨어들에 대해서 간단하게 알아보자.

scrapy의 미들웨어를 설명하기 전에 scrapy의 동작에 대해서 살짝 맛을 보자.

scrapy의 동작 과정설명

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2010.png

아래는 document의 원문이다. 내 짧은 영어로 발번역을 첨부한다.

  1. The Engine gets the initial Requests to crawl from the Spider.
    [우리가 spider에게 크롤링을 명령하면(ex:scpay crawl naverbike) spider는 Engine에게 request를 보낸다. ]
  2. The Engine schedules the Requests in the Scheduler and asks for the next Requests to crawl.
    [Engine은 spider로 부터 받은 최초의 request를 받아서 scheduler에 전달한다. scheduler는 일종의 크롤링 request를 저장하고 여유가 되면 동작시키는 일종의 이벤트큐와 같은 역할을 하는 것 같다. ]
  3. The Scheduler returns the next Requests to the Engine.
    [Scheduler가 다시 request를 Engine에게 전달한다.]
  4. The Engine sends the Requests to the Downloader, passing through the Downloader Middlewares (see [process_request()](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#scrapy.downloadermiddlewares.DownloaderMiddleware.process_request)).
    [Engine이 Request를 Downloader Middlewares를 거쳐, Downloader에게 전달한다. ]
  5. Once the page finishes downloading the Downloader generates a Response (with that page) and sends it to the Engine, passing through the Downloader Middlewares (see [process_response()](https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#scrapy.downloadermiddlewares.DownloaderMiddleware.process_response)).
    [Downloader는 크롤링할 웹 페이지를 가져와서 그것을 Response을 생성한다. 그리고 이걸 다시 Downloader Middleware를 거쳐 Engine에게 전달한다.]
  6. The Engine receives the Response from the Downloader and sends it to the Spider for processing, passing through the Spider Middleware (see [process_spider_input()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_input)).
    [Engine이 Downloader로 부터 크롤링의 결과물을 담은 Response를 받는다. 그리고 이걸 다시 Spider Middleware를 거쳐서, Spider에게 보낸다.]
  7. The Spider processes the Response and returns scraped items and new Requests (to follow) to the Engine, passing through the Spider Middleware (see [process_spider_output()](https://docs.scrapy.org/en/latest/topics/spider-middleware.html#scrapy.spidermiddlewares.SpiderMiddleware.process_spider_output)).
    [Spider는 engine으로 부터 받은 크롤링 결과물 Response를 처리하고, Engine에게 보낼 새로운 Request(새로운 크롤링을 위한)와 추출된 item(크롤링 결과물을 의미있는 데이터 단위로 추출)]
  8. The Engine sends processed items to Item Pipelines, then send processed Requests to the Scheduler and asks for possible next Requests to crawl.
    [Engine은 추출된 아이템(크롤링에서 추출한 데이터)를 item PipeLine에게 보낸다. Pipeline은 크롤링된 데이터를 후반 처리하는 녀석이다. 여기서 일단 크롤링 한번이 완료된 것이다.
    Engine은 spider에게 받은 Request를 다시 scheduler에제 전달하고 또 다른 크롤링의 절차가 시작된다.
    ]
  9. The process repeats (from step 1) until there are no more requests from the Scheduler.

위 scrapy의 크롤링 동작 과정 중 SpiderMiddleware와 DownloadMiddleware가 언급 되었다.

scrapy에 selenium을 연동하기 위해서는 크롤링할 웹 페이지에서 크롤링 결과를 받아올 때, 크롤링 방법에 개입을 해야 한다. 그 부분이 Download Middleware 이다.

아래 DownloadMiddleware에 selenium 을 적용해보자.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2011.png

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
from time import sleep

class ScrapyWithSeleniumSpiderMiddleware(object):

@classmethod
def from_crawler(cls, crawler):
print("this is ScrapyWithSeleniumSpiderMiddleware from_crawler ===================")
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s

def process_spider_input(self, response, spider):
return None

def process_spider_output(self, response, result, spider):
for i in result:
yield i

def process_spider_exception(self, response, exception, spider):
pass

def process_start_requests(self, start_requests, spider):
for r in start_requests:
yield r

def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)

class ScrapyWithSeleniumDownloaderMiddleware(object):

@classmethod
def from_crawler(cls, crawler):
print("this is ScrapyWithSeleniumSpiderMiddleware from_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 = 'C:\dev_python\Webdriver\chromedriver.exe'
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)

# scrapy에서 셀레니움을 연동해서 사용할경우. 셀레니움의 동적인 크롤링 코드는 여기 미들웨어에서 작성해야 할것 같다.
# headless 옵션을 끄고 아래 결과가 동작하는지 보자. 동작을 확인했다.
bikeCompanyAllBtn = self.driver.find_element_by_css_selector(
"#container > div.spot_main > div.spot_aside > div.tit > a")
bikeCompanyAllBtn.click()

body = to_bytes(text=self.driver.page_source)
sleep(5)
return HtmlResponse(url=request.url, body=body, encoding='utf-8', request=request)


def process_response(self, request, response, spider):
return response

def process_exception(self, request, exception, spider):
pass

위 코드에서 ScrapyWithSeleniumDownloaderMiddleware 클래스에 Selenium을 적용하는 코드들이 추가 되었다. ScrapyWithSeleniumSpiderMiddleware는 최초 생성된 상태로 수정하지 않았으니 신경쓰지 않아도 된다.

ScrapyWithSeleniumDownloaderMiddleware 에서 중점적으로 볼 부분은, spider_openedprocess_request 메소드이다.

  • spider_opened : Selenium의 기초 설정을 한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2012.png

  • process_request: Downloader로 받은 크롤링의 결과물을 Selenuim으로 구체적인 크롤링 처리를 한다. 위 예제에서 특정 앵커 태그를 찾아서 click 이벤트를 발생 시켰다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2013.png

그리고 아래 부분은 selenium에서 크롤링한 결과물을 바이트로 변환해서 return 하게 된다. 이 결과물은 결과적으로 spider로 보내지게 된다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2014.png

spider 코드에서 해당 위의 download middleware를 사용하겠다고 설정을 해주자.

naverbike.py (spider)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import scrapy

class NaverbikeSpider(scrapy.Spider):
name = 'naverbike'
allowed_domains = ['auto.naver.com/bike/mainList.nhn']
start_urls = ['http://auto.naver.com/bike/mainList.nhn']
custom_settings = {
'DOWNLOADER_MIDDLEWARES': {
'scrapy_with_selenium.middlewares.ScrapyWithSeleniumDownloaderMiddleware': 100
}
}

def parse(self, response):
print(response.text)

custom_settings 부분이 DOWNLOADER_MIDDLEWARES를 어떤 것을 사용할지 지정하는 부분이다.

parse 부분에서는 간단하게 전달받은 respose의 내용물을 출력한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2015.png

scrapy에 selenium을 연동하는 셋팅은 모두 끝이 났다.

이제 naverbike.py spider로 크롤링을 해보자.

위 download middleware에 selenium의 동작은 아래와 같다.

아래 페이지를 일단 크롤링 하고 전체 제조사 엘리멘트를 찾아서 클릭한다.

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2016.png

클릭한 다음에 나오는 페이지가 결론적으로 크롤링 되는 것이다.(아래화면)

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2017.png

scrapy프로젝트에서 터미널에서 아래 명령어를 실행하자.

1
scrapy crawl naverbike

아래처럼 selenium이 동작하면서, chrome web driver가 동작하고 크롤링이 진행되는 것을 확인 할 수 있다.(일부로 제대로 동작하는지 확인을 위해 selenium headless 옵션을 제거했다.)

Scrapy%20Selenium%20921481638b2f47b8addcc35b00f25926/Untitled%2018.png

재밌는 점은 크롤링이 종료 되면 자동으로 chrome web dirver창이 종료된다.(selenium에서는 별도의 quit 명령어를 호출하지만, scrapy에서는 그럴 필요가 없었다.)

끝!!!

마무리

scrapy를 정확히 모르는 입장에서, 단순히 scrapy에 selenium을 끼얹어 크롤링을 하는 예제를 만들어 보았다.

인터넷을 찾아보니 애시당초 scrapy와 selenium을 합쳐서 만든 프로젝트도 있는걸 보니, 그런것들을 활용해보는 것도 좋아 보인다.

위 예제 소스 github 주소

https://github.com/blog-examples/scrapy-with-selenium

참고자료

위 예제의 근간

https://heodolf.tistory.com/13

middelware 와 download middelware 의 차이점

https://stackoverflow.com/questions/17872753/what-is-the-difference-between-scrapys-spider-middleware-and-downloader-middlew

scrapy 와 selenium을 연동한 프로젝트가 존재한다.

https://github.com/clemfromspace/scrapy-selenium

scrapy 아키텍쳐 번역

http://sooyoung32.github.io/dev/2016/02/06/scrapy-architecture.html

scrapy structure

https://docs.scrapy.org/en/latest/topics/architecture.html