00 Scrapy Tutorial

게시글 링크

시작하며

  • 안녕하세요, TaeBbong 입니다.
  • 이번 글에서는 Scrapy 공식 Document의 Tutorial을 번역 & 정리해보겠습니다.
  • 완벽한 번역보다는 개인적으로 공부한 것을 정리하는 느낌으로 작성할 것이며, 정확한 원문은 다음링크에서 확인할 수 있습니다.

Scrapy Tutorial

  • 이 글은 Scrapy 공식 Document Tutorial을 참고합니다.

  • 이 튜토리얼은 Scrapy가 설치되어있음을 전제로 진행됩니다. 설치 가이드는 다음 링크에서 확인하실 수 있습니다.

  • 이 튜토리얼에서는 quotes.toscrape.com에서 유명 작가들의 문장들을 스크랩할 예정입니다.

  • 본 튜토리얼에서 진행할 일들은 다음과 같습니다:

  1. Scrapy 프로젝트 만들기
  2. 사이트를 크롤링하고 데이터를 추출할 Spider 생성하기
  3. Scrape한 데이터를 내보내기
  4. 링크를 따라 재귀적으로 Spider 작동시키기
  5. Spider Argument 사용하기

Project 생성하기!

  • 프로젝트가 저장될 디렉토리에 터미널 등으로 접근하여 다음 명령어로 Scrapy 프로젝트를 생성합니다.
1
$ scrapy startproject tutorial
  • 이 명령어를 통해 tutorial이라는 폴더가 생성되었습니다. 이 폴더는 다음과 같은 구조를 가지고 있습니다.
1
2
3
4
5
6
7
8
9
10
tutorial/
scrapy.cfg # 배포 설정 파일
tutorial/ # 프로젝트의 파이썬 코드들이 존재하는 디렉토리
__init__.py
items.py # items 정의 파일
middlewares.py # middleware 정의 파일
pipelines.py # pipelines 정의 파일
settings.py # setting 파일
spiders/ # Spider를 작성하는 디렉토리
__init__.py
  • (역자) 위의 디렉토리 구조에서 spiders만 주로 작성하게 되며, 다른 파일들은 거의 수정하지 않습니다.

첫번째 Spider 만들기

  • Spiders는 Scrapy가 타겟 웹사이트(들)로부터 정보를 scrape할 수 있도록 우리가 정의하는 클래스입니다.
  • 이는 scrapy.Spider의 subclass 형태이며, 초기 요청을 정의해야하고, 선택적으로 어떤 링크를 따라 진행할지, 혹은 어떻게 데이터를 파싱해서 가져올지에 대해 정의할 수 있습니다.
  • quotes_spider.py라는 이름의 파이썬 파일을 다음과 같이 작성하여 spiders/ 디렉토리에 생성합니다.
  • (역자) 주석으로 설명을 달았습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scrapy

class QuotesSpider(scrapy.Spider):
name = "quotes" # scrapy 실행을 위한 spider의 고유 이름

def start_requests(self): # 일련한 requests을 return, spider의 시작점 설정 부분으로 세팅한 urls에 따라 자동으로 크롤링 시작
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse) # parse를 콜백으로 부름으로, 매 request마다 발생하는 response를 처리

def parse(self, response): # request마다 발생한 response를 처리하는 부분
page = response.url.split('/')[-2]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)

(추가) starts_requests 생략하기

  • starts_requests 메소드는 다음과 같은 형태로 축약하여 사용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import scrapy

class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]

def parse(self, response): # request마다 발생한 response를 처리하는 부분
page = response.url.split('/')[-2]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)
  • (역자) 위와 같은 형태로 축약하면, spider 클래스에서는 자동으로 starts_urls라는 클래스 구성요소를 인식하여 start_requests 메소드를 기본값으로 선언, 작동하게 됩니다.
  • (역자) parse 또한 마찬가지로 각 url의 request에 따라 콜백으로 지정이 되는데, 이 역시 parse가 scrapy의 기본 콜백 메소드이기 때문이며, 따로 지정하지 않아도 실행됩니다.

Scrapy 실행하기

  • 프로젝트의 가장 상위 디렉토리, 즉 scrapy.cfg 파일이 있는 디렉토리에서 다음 명령어를 입력해 실행합니다.
1
$ scrapy crawl quotes
  • 이 명령이 실행되면 우리가 지정한 spider의 name인 ‘quotes’가 실행되며, request를 타겟 url에 대해 보내게 됩니다.

  • 선언한 로그가 정상적으로 print되면 잘 작동한 것입니다.

  • 2개의 html 파일이 저장되면 정상적으로 작동한 것입니다.

데이터 추출하기

  • bash 환경에서 데이터 추출하는 연습을 해볼 수 있습니다.
  • 터미널 등에서 다음 명령어를 입력해보세요. (단, url에 & 등의 문자로 인자가 담긴 경우 bash 환경에서 잘 작동하지 않습니다.)
1
$ scrapy shell 'http://quotes.toscrape.com/page/1/'
  • 다음과 같이 실행됨을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[ ... Scrapy log here ... ]
2016-09-19 12:09:27 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s] crawler <scrapy.crawler.Crawler object at 0x7fa91d888c90>
[s] item {}
[s] request <GET http://quotes.toscrape.com/page/1/>
[s] response <200 http://quotes.toscrape.com/page/1/>
[s] settings <scrapy.settings.Settings object at 0x7fa91d888c10>
[s] spider <DefaultSpider 'default' at 0x7fa91c8af990>
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
  • (역자) 위에 보시면 Available Scrapy objects라고 되어있는데, 이 부분에서 선택할 수 있는 Scrapy 객체들이 적혀있습니다. 이를 활용해 여러가지 기능을 확인해볼 수 있습니다.

  • CSS를 기반으로 response object에서의 element를 골라볼 수 있습니다.

1
2
>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
1
2
>>> response.css('title::text')[0].get()
'Quotes to Scrape'
  • (역자) 원문에서는 정규표현식 등으로 파싱하는 내용도 기술되어있지만, 이 부분은 생략하겠습니다.

  • XPath로도 당연히 element를 골라볼 수 있습니다.

1
2
>>> response.xpath('//title/text()').get()
'Quotes to Scrape'
  • (역자) 이하 파싱을 활용해 각종 데이터를 추출, 정리하는 내용이 포함되어있습니다. 진행이 어려우신 초보 분들은 원문에서 코드를 확인해보세요!

Spider 코드에서 데이터 추출하기

  • 여태까지 Shell을 활용해 데이터를 추출해봤으며, 앞서 작성했던 Spider 객체 내에서도 데이터 추출을 적용해볼 수 있습니다.
  • Scrapy Spider는 일반적으로 페이지에서 추출한 데이터들을 포함하고 있는 많은 Dictionary를 생성합니다. 이를 위해 yield 키워드를 활용할 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import scrapy


class QuotesSpider(scrapy.Spider):
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]

def parse(self, response):
for quote in response.css('div.quote'):
yield {
'text': quote.css('span.text::text').get(),
'author': quote.css('small.author::text').get(),
'tags': quote.css('div.tags a.tag::text').getall(),
}
  • 다음 결과를 얻을 수 있습니다.
1
2
3
4
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['life', 'love'], 'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”'}
2016-09-19 18:57:19 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'tags': ['edison', 'failure', 'inspirational', 'paraphrased'], 'author': 'Thomas A. Edison', 'text': "“I have not failed. I've just found 10,000 ways that won't work.”"}

스크랩한 데이터 저장하기

  • Feed exports 방식이 데이터 저장할 때 가장 간단합니다. 다음 명령으로 간단하게 저장할 수 있습니다.
1
$ scrapy crawl quotes -o quotes.json
  • Scrapy는 인자로 들어온 파일에 덮어 쓰는 것이 아닌, 해당 파일에 append하기 때문에, 저 명령어를 기존 파일을 지우지 않고 여러번 쓰면 JSON 파일이 손상되니 주의해야 합니다.

  • JSON Lines와 같은 다른 포맷도 사용할 수 있습니다.

  • 작은 프로젝트에서는 데이터를 그냥 저장해도 괜찮지만, 좀 더 복잡한 프로젝트를 수행할때는 Item Pipeline을 쓰게 됩니다.

  • 이는 project 내의 tutorial/pipelines.py 에서 설정할 수 있습니다.

  • (역자) 이에 대한 자세한 내용은 추후 추가될 Item Pipeline 포스트에서 확인할 수 있습니다.

Author

TaeBbong Kwon

Posted on

2019-04-01

Updated on

2022-08-06

Licensed under

Comments