조용한 담장

Scrapy : python web crawler 본문

python

Scrapy : python web crawler

iosroid 2019. 10. 1. 17:20

Scrapy

파이썬으로 구현된 웹 클롤러 이다.

기본 구조와 동작을 이해하면 다양하게 활용하기 좋은 오픈소스 소프트웨어다.

 

Scrapy

Github

구조 간단히 보기

Architecture

architecture

사이트 주소를 가지고 (1)Request 하면 (4)Downloader 가 다운받아 (5)Response 를 생성해주고, (6)Response 에서 필요한 (7)Items 을 뽑아 (8)Item PIpelines 을 통해 결과를 얻어내면 된다.

SchedulerEngine이 있다. 자세한건 공식문서를 보자.

command line tool 로 테스트 해보기

Command line tool

Scrapy shell

ㅃㅃ 커뮤니티 사이트의 게시판 목록을 읽어보자.

$ scrapy shell http://www.ppomppu.co.kr/zboard/zboard.php\?id\=ppomppu4\&category\=3
2019-10-01 11:11:46 [scrapy.utils.log] INFO: Scrapy 1.7.3 started (bot: scrapybot)
...
2019-10-01 15:14:24 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.ppomppu.co.kr/robots.txt> (referer: None)
2019-10-01 15:14:24 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu4&category=3> (referer: None)
[s] Available Scrapy objects:
...
[s]   request    <GET http://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu4&category=3>
[s]   response   <200 http://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu4&category=3>
...

robots.txt 가 있으면 [s] response 가 없다.

웹브라우저의 개발자 도구를 사용해서 게시글 제목의 CSS 정보를 찾아본다.

 

페이지 소스를 보면 아래처럼 되어있다.

<font class=list_title>[Amazon] Logitech G29 Gaming Racing Wheel  ($183.01/무료)</font>

selectors

css selector 를 사용해서 값을 얻어보자.

>>> response.css('font.list_title')
[<Selector xpath="descendant-or-self::font[@class and contains(concat(' ', normalize-space(@class), ' '), ' list_title ')]" data='<font class="list_title">[Amazon] Log...'>,  <Selector xpath="descendant-or-self::font[@class and contains(concat(' ', normalize-space(@class), ' '), ' list_title ')]" data='<font class="list_title">[aliexpress]...'>
...]

list_title 을 뽑아준거 같은데 가공이 필요하다.

text 값을 얻기위해 getall() 를 써보자.

>>> response.css('font.list_title').getall()
['<font class="list_title">[Amazon] Logitech G29 Gaming Racing Wheel  ($183.01/무료)</font>', '<font class="list_title">[aliexpress] 레노버 S340 15api 랩탑용 키보드 스킨 (1.79$ / Free)</font>'
...]

좀 더 정확하게 텍스트만 가져오자.

>>> response.css('font.list_title::text').getall()
['[Amazon] Logitech G29 Gaming Racing Wheel  ($183.01/무료)', '[aliexpress] 레노버 S340 15api 랩탑용 키보드 스킨 (1.79$ / Free)'
...]

일단 잘 얻어진다.

이정도만 코드로 구현해보자.

Spider 구현

새 프로젝트를 만든다.

$ scrapy startproject ppboard
New Scrapy project 'ppboard', using template directory '/scrapy/venv/lib/python3.6/site-packages/scrapy/templates/project', created in:
    /scrapy/ppboard

You can start your first spider with:
    cd ppboard
    scrapy genspider example example.com

spider 를 만든다.

name 은 spider 의 이름이다.

spider 의 최종 리턴값의 형태는 Request, BaseItem, dict or None 이다.

import scrapy

class PpboardSpider(scrapy.Spider):
    name = "ppb3"

    def start_requests(self):
        urls = [
            'http://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu4&category=3',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        list_titles = response.css('font.list_title::text').getall()
        yield { 'list titles': list_titles }

spider를 실행한다.

$ scrapy crawl bbp3
2019-10-01 15:37:52 [scrapy.utils.log] INFO: Scrapy 1.7.3 started (bot: ppboard)
...
2019-10-01 15:37:53 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.ppomppu.co.kr/zboard/zboard.php?id=ppomppu4&category=3> (referer: None)
['[Amazon] Logitech G29 Gaming Racing Wheel  ($183.01/무료)', '[aliexpress] 레노버 S340 15api 랩탑용 키보드 스킨 (1.79$ / Free)', 
...

scrapy shell 에서 실행한 것과 같은 결과가 콘솔에서 잘 확인된다.

이제 콘솔 말고 파일로 저장하자.

Pipeline 사용

pipeline

settings.py 파일에서 아래 항목을 활성화 해주면 기본적으로 생성되어 있는 pipeline 이 실행된다.

기본 코드 PpboardPipeline 아무것도 안한다. 구현을 해야 한다.

공식문서의 json 저장 예제인 JsonWriterPipeline 를 적용해 보자.

# ppboard/pipelines.py

import json

class PpboardPipeline(object):
    def process_item(self, item, spider):
        return item

class JsonWriterPipeline(object):

    def open_spider(self, spider):
        self.file = open('items.jl', 'w')

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(dict(item)) + "\n"
        self.file.write(line)
        return item

pipeline 에서는 Item class 를 가지고 데이터를 처리한다.

settings.py 에 추가한다.

# ppboard/settings.py

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'ppboard.pipelines.PpboardPipeline': 300,
    'ppboard.pipelines.JsonWriterPipeline': 400,
}

XXXPipeline() 을 여러개 만들어 위 settings.py 의 ITEM_PIPELINES 에 추가하여 실행시킬 수 있다.

300 숫자는 여러개의 pipeline 의 실행 우선순위이다.

이곳에서 spider 가 넘긴 데이터를 pipeline 내에서 정제, 가공하면 되겠다.

Feed exports

Feed exports 를 사용하면 Scrapy 가 지원하는 형식으로 쉽게 저장할 수 있다.

$ scrapy crawl ppb3 -o data.json
$ scrapy crawl ppb3 -o data.csv
$ scrapy crawl ppb3 -o data.xml
$ scrapy crawl ppb3 -s FEED_URI='export.csv' -s FEED_FORMAT=csv 
$ scrapy crawl ppb3 -s FEED_URI='export.json' -s FEED_FORMAT=json
Comments