0%

scrapy 크롤링 실습

들어가기

저번에 anaconda에 scrapy를 설치 했었다. (windows와 호환성에 문제가 있는지 좀 고생을 했지만…

이번엔 scrapy로 간단한 프로젝트를 만들고 크롤링 실습을 해볼까 한다.

scrapy는 단순한 라이브러리가 아니라, 프레임 워크이다.

scrapy를 사용하기 위해서는 scrapy 기반 프로젝트를 생성해야 한다.

프레임워크 답게 cli 명령어로 프로젝트 생성, spider 생성, 크롤링 등을 지원한다.

실습

이 실습에서는 scrapy 프로젝트를 생성하고 간단한 spider를 생성해서 크롤링을 진행 하겠다.

실습환경

  • windows10(64)
  • anaconda(32)
  • conda 버전 4.8.3
  • scrapy 버전 1.6.0

scrapy로 프로젝트를 생성하자.

일단 conda 프롬프트를 열어서 아래 명령어로 scrapy 프로젝트를 생성한다.

1
2
scrapy startproject scrapy_example
문법: scrapy startproject 프로젝트명

주의 할점은 프로젝트 명으로 하이픈을 사용할 수 없다. (언더바는 사용이 가능)

scrapy%20952e11b67d354d7689155512461fde77/Untitled.png

위처럼 간단하게 scrapy 프로젝트를 생성 할 수 있다.

프로젝트가 생성되면 파이참으로 해당 프로젝트를 열어보자.

나는 개발도구로 파이참을 사용하겠다.

아래처럼 자동으로 프로젝트 폴더구조가 생성된 것을 확인 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%201.png

여기서 잠깐 파이참의 장점을 말하자면, 일반 cmd, gitbash에서는 conda 명령어가 path로 지정되지 않으면 conda 명령어를 사용 할 수 없지만, 파이참에서 인터프리터로 conda를 지정할 경우 파이참의 터미널에서 별도의 path 설정없이 conda 명령을 사용 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%202.png

다시 돌아와서, scrapy로 생성된 프로젝트의 폴더를 살펴보자.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%203.png

특의한 점은 프로젝트 루트 경로 아래, 프로젝트명과 동일한 폴더가 하나 더 추가로 생성되고, 그 아래 아래 디렉토리에 scrapy 프레임워크 디렉토리가 자동으로 생성된다.

  • spiders 폴더 : spider란 scrapy프레임워크에서 주체적으로 크롤링을 하는 일종의 봇과 같은 ㄴ녀석이다. 예를 들어서 내가 구글을 크롤링하고 싶으면 구글 스파이더를 생성해서 크롤링을 시키고, 또 네이버를 크롤링 하고 싶다면 네이버 스파이더를 생성해서 크롤링을 시키는 구조가 된다.
  • init.py : 엔트리 포인트
  • items.py : 크롤링한 데이터 어떻게 정형화 하고 저장할지에 대한 코드.
  • middelwares.py : node express 미들웨어처럼 크롤링 과정에서, 다양한 부가기능을 미들웨어라는 형태로 사용한다.
  • pipelines.py : 크롤링 이후 검색된 데이터를 어떻게 처리할지 코드.
  • settings.py : 프로젝트 설정 파일

Spider 를 만들자.

scrapy 에서 크롤링을 하는 실제 크롤러는 이 spider이다.

테스트로 https://auto.naver.com/bike/mainList.nhn 네이버 바이크 페이지를 크롤링 해도록 하겠다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%204.png

1
2
scrapy genspider naverbike auto.naver.com/bike/mainList.nhn
문법: scrapy genspider 스파이더이름 크롤링할url(http, https 프로토콜은 생략하자)

터미널에서 위 명령어롤 사용해서 naverbike라는 스파이더를 생성하자.

아래처럼 spiders 폴더에 naverbike.py 라는 파일이 자동으로 생겼다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%205.png

1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
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/']

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

스파이더 소스를 보면 생성할때 입력된 url이 크롤링 대상으로 셋팅이 되어 있다.

parse 메소드가 실제 해당 스파이더가 동작하면서, 크롤링한 결과를 파싱해주는 메소드이다

위처럼 기존의 pass를 주석처리하고 print문으로 response.text를 출력하게 코드를 수정하자.

그리고 다음 명령어로 해당 스파이더를 동작 시키자.

1
2
scrapy crawl naverbike
문법: scrapy crawl 스파이이더명

내가 예상하기로는 해당 웹 페이지의 html 태그들이 주르륵 나와야 되는데 이상한, 로그 정보만 찍힌다.

내용을 보니 naver의 robot.txt에 의해서 응답을 못 받은것 같다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%206.png

scrapy crawl 명령어 리턴 결과

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
2020-06-17 21:14:41 [scrapy.utils.log] INFO: Scrapy 1.6.0 started (bot: freerider_crawler)
2020-06-17 21:14:41 [scrapy.utils.log] INFO: Versions: lxml 4.5.0.0, libxml2 2.9.9, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 20.3.0, Python 3.7.6 (default, Jan 8 2020, 16:
21:45) [MSC v.1916 32 bit (Intel)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d 10 Sep 2019), cryptography 2.8, Platform Windows-10-10.0.18362-SP0
2020-06-17 21:14:41 [scrapy.crawler] INFO: Overridden settings: {'BOT_NAME': 'freerider_crawler', 'NEWSPIDER_MODULE': 'freerider_crawler.spiders', 'ROBOTSTXT_OBEY': True, 'SPIDER_MODUL
ES': ['freerider_crawler.spiders']}
2020-06-17 21:14:41 [scrapy.extensions.telnet] INFO: Telnet Password: 683edf7aaadefaed
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.logstats.LogStats']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-06-17 21:14:41 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-06-17 21:14:41 [scrapy.core.engine] INFO: Spider opened
2020-06-17 21:14:41 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-06-17 21:14:41 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-06-17 21:14:42 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (301) to <GET https://auto.naver.com/robots.txt> from <GET http://auto.naver.com/robots.txt>
2020-06-17 21:14:42 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://auto.naver.com/robots.txt> (referer: None)
2020-06-17 21:14:42 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET http://auto.naver.com/bike/mainList.nhn/>
2020-06-17 21:14:42 [scrapy.core.engine] INFO: Closing spider (finished)
2020-06-17 21:14:42 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
'downloader/request_bytes': 446,
'downloader/request_count': 2,
'downloader/request_method_count/GET': 2,
'downloader/response_bytes': 500,
'downloader/response_count': 2,
'downloader/response_status_count/200': 1,
'downloader/response_status_count/301': 1,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2020, 6, 17, 12, 14, 42, 285564),
'log_count/DEBUG': 3,
'log_count/INFO': 9,
'response_received_count': 1,
'robotstxt/forbidden': 1,
'robotstxt/request_count': 1,
'robotstxt/response_count': 1,
'robotstxt/response_status_count/200': 1,
'scheduler/dequeued': 1,
'scheduler/dequeued/memory': 1,
'scheduler/enqueued': 1,
'scheduler/enqueued/memory': 1,
'start_time': datetime.datetime(2020, 6, 17, 12, 14, 41, 909219)}
2020-06-17 21:14:42 [scrapy.core.engine] INFO: Spider closed (finished)

추축하건데, 아마도 네이버에서 scrapy라는 크롤러의 크롤요청을 파악하고 거부 한듯 하다.

크롤링 하려는 아래 페이지의 robot.txt 를 확인해보자.

auto.naver.com/bike/mainList.nhn

역시 모든 경로가 Disallow 이다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%207.png

어떻게 이것을 무시하고 크롤링 할 수 있을까?

scrapy로 robot.txt를 무시하고 크롤링 하는 방법

방법1, 방법2 가 있다 원하는 방법을 골라서 설정 하면된다.

방법1. scrapy crawl 명령시 옵션 추가

1
scrapy crawl --set=ROBOTSTXT_OBEY='False' naverbike

방법2. settings.py 에서 ROBOTSTXT_OBEY 설정을 False로 변경

scrapy%20952e11b67d354d7689155512461fde77/Untitled%208.png

방법1을 사용하며 크롤링 할때마다 옵션을 넣어 주어야 하지만, 방법2를 사용하면 프로젝트 전체 설정으로 적용 되기 때문에 scrapy crawl naverbike 명령을 사용하면 된다.

나는 방법 1을 사용해서 다시 크롤링 해 보았다.

하지만 이상하게 역시 크롤링이 재대로 되지 않았다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%209.png

이 문제 때문에 한참 해맸었는데, 문제는 아래와 같다

start_urls 맨 뒤에 슬래시가 있는데, 저 슬래시가 문제였다. 슬래시를 지우고 다시 크롤링 해보자.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2010.png

아래처럼 spider가 정상적으로 크롤링을 하는 것을 확인 할 수 있다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2011.png

위처럼 html이 크롤링 되는 것을 터미널에서 확인 할 수 있다.

음 스파이더가 자동으로 생성해준 코드라서, 설마 start_url의 마지막 슬래시가 크롤링이 안되는 원인이 될 것이라고는 생각조차 하지 못해 한참을 헤맸다.

scrapy shell을 이용해서 프롬프트에서 scrapy 크롤링을 할 수도 있다.

직접 코딩을 하기전에 테스트용도로 활용하면 좋을 방법이다.

1
scrapy shell 'https://auto.naver.com/bike/mainList.nhn'

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2012.png

위처럼 크롤링이 진행되다가 아래처럼 프롬프트가 멈춘다. 해당 부분에서 크롤링한 response를 가지고 shell에서 주피터노트북 처럼 테스트 코드를 작성해서 돌려 볼 수 있다.

그리고 빨간부분을 보면 해당 scrapy shell에서 호출 가능한 명령어 들을 보여준다.

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2013.png

response 객체를 이용해서 크롤링 된 데이터에 접근 할 수 있다.

response 객체 사용법

1
2
3
response.css(css 셀렉터 양식).get()
response.css('head > title').get() # 하나만 가져오기
response.css('head > title').getall() # 일치하는 거 다 가져오기

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2014.png

1
response.css('head > title::text').get()     # 하나만 가져오기, 태그는 제외

scrapy%20952e11b67d354d7689155512461fde77/Untitled%2015.png

scrapy shell에서 위처럼 크롤링의 결과물인 response 객체를 이용해서 다양한 방법으로 크롤링 결과물을 간추리는 작업을 테스트 해 볼 수 있다.

네이버 자동차 바이크 페이지에서 전체 바이크 제조사 정보를 가져오고 싶은데, 마우스 클릭이 필요하다.

scrapy 만으로는 크롤링한 페이지에 마우스 클릭할 수 없다.

하지만 scrapy 크롤링 결과물에 셀레니움을 연동해서 처리가 가능해보인다.

https://stackoverflow.com/questions/36874494/simulating-a-javascript-button-click-with-scrapy

끝!!!

github 예제소스경로

https://github.com/blog-examples/python-scrapy-startproject/tree/master