[Python] 파이썬 웹 크롤링 기초 2-2 : Scrapy
지난글에 이어서 파이썬 크롤링 라이브러리 스크래피(Scrapy) 설명을 보충한다.
지난글
https://blog.naver.com/bb_/222619390736
1. 스크래피 셀렉터(selector)
html 문서의 어떤 요소를 가져오기 위해서는 selector를 사용해야 한다.
스크래피는 xpath 셀렉터와 css 셀렉터 두 종류를 지원한다.
html 문서 예
<html> <head> <base href=’http://example.com/‘ /> <title>Example website</title> </head> <body> <div id=’images’> <a href=’image1.html’>Name: My image 1 <br /><img src=’image1_thumb.jpg’ /></a> <a href=’image2.html’>Name: My image 2 <br /><img src=’image2_thumb.jpg’ /></a> <a href=’image3.html’>Name: My image 3 <br /><img src=’image3_thumb.jpg’ /></a> <a href=’image4.html’>Name: My image 4 <br /><img src=’image4_thumb.jpg’ /></a> <a href=’image5.html’>Name: My image 5 <br /><img src=’image5_thumb.jpg’ /></a> </div> </body> </html> |
xpath 셀렉터와 css 셀렉터 중 편한 방법을 사용하면 된다.
아래는 동일한 요소를 가져오는 세트이다.
response.xpath(‘//title/text()’)
response.css(‘title::text’)
response.xpath(‘//base/@href’).extract()
response.css(‘base::attr(href)’).extract()
response.xpath(‘//a[contains(@href, “image”)]/@href’).extract()
response.css(‘a[href*=image]::attr(href)’).extract()
response.xpath(‘//a[contains(@href, “image”)]/img/@src’).extract()
response.css(‘a[href*=image] img::attr(src)’).extract()
2. 스크래피 프로젝트 구조
먼저 스크래피 프로젝트를 생성한다.
# 스크래피 프로젝트 생성
scrapy startproject [프로젝트명]
ex) scrapy startproject community
이어서 파이썬 IDE인 파이참(pycharm)으로 해당 프로젝트 열기한다.
좌측 트리를 보면 스크래피 구조가 보인다.
(1) spiders 폴더 : 이 폴더 안에 실질적으로 크롤링 하는 로직 파일이 들어가게 된다.
예륻 들어 community_spider.py 라는 파일을 생성했다면, html 의 특정한 데이터들을 선택(select)해서 아이템으로 만드는 로직을 해당 파일에 구현하면 된다.
(2) items.py : 크롤링 대상을 아이템이라는 단위로 클래스화 가능하다.
(3) pipelines.py : 크롤링 시 파이프라인에서 데이터를 DB에 반영하거나, 데이터를 필터링할 수 있다.
데이터의 유효성 검사, 데이터 중복 체크, 데이터베이스에 아이템 저장, 필터링 등을 여기서 처리하면 된다.
(4) settings.py : 전체 프로젝트에 대한 설정 가능함.
파이프라인 순서를 결정할 수 있고, 로그 파일 지정하고 로그 파일의 레벨을 지정 가능하다.
아래와 같이 파이프라인을 지정할 수 있고, 여러개 써넣을 수도 있다.
실행순서를 숫자를 통해 지정가능한데 낮은 숫자가 먼저 실행된다.
ITEM_PIPELINES = { ‘community.pipelines.CommunityPipeline’: 300, } |
로그 파일명과 로그 레벨을 설정할 수 있다.
로그레벨은 5개가 있다. CRITICAL, ERROR, WARNING, INFO, DEBUG.
아래처럼 내용 추가하면 로그가 터미널에 쌓이지 않고 해당 파일에 떨어진다.
LOG_FILE = ‘scrapy.log’ LOG_LEVEL = logging.DEBUG |
(5) scrapy.cfg : 전체 프로젝트를 배포할 때의 설정이다.
3. 스크래피 프로젝트 예제
스크래피 프로젝트를 만들었으면, 먼저 아이템을 정의한다. (items.py 파일 수정)
spiders 폴더 안에 새 파일을 추가해서 파싱 로직을 넣으면 된다. (ex : community_spider.py)
대상 url을 지정하는 방법은 크게 두 가지가 있다.
첫번째로 start_urls 이라는 변수명으로 스트링 리스트를 생성하는 방법이 있고,
두번째로 start_requests 함수를 정의해서 특정한 url에 대해서 콜백함수를 지정하는 방법이 있다. 각 url에 대해서 콜백함수를 지정해야 하는데, 보통 parse라는 이름으로 콜백함수를 만든다.
아래 예제에서는 두번째 방법을 사용한다.
(1) community_spider.py (spiders 폴더 하위에 새 파일 작성)
import scrapy from community.items import CommunityItem # scrapy.Spider 를 상속하는 CommunitySpider 클래스 생성 class CommunitySpider(scrapy.Spider): name = “communityCrawler” def start_requests(self): # 1페이지 ~ 2페이지 크롤링 for i in range(1, 3, 1): # 1페이지 호출 시와 그렇지 않은 경우 url 체계가 달라서 분기처리함 if i == 1: yield scrapy.Request(“http://it-archives.com/“, self.parse_wordpress) else: yield scrapy.Request(“http://it-archives.com/page/%d/” % i, self.parse_wordpress) yield scrapy.Request(“https://gall.dcinside.com/board/lists/?id=dcbest&page=%d” % i, self.parse_dcinside) def parse_wordpress(self, response): for sel in response.xpath(‘//article’): item = CommunityItem() item[‘source’] = ‘흑곰의 유익한 블로그 2호점’ item[‘category’] = ‘wordpress’ item[‘title’] = sel.xpath(‘header/h3[@class=”entry-title”]/a/text()’).extract()[0] item[‘url’] = sel.xpath(‘header/h3[@class=”entry-title”]/a/@href’).extract()[0] # yield : 아이템을 한 개씩 차곡차곡 쌓기 yield item def parse_dcinside(self, response): for sel in response.xpath(‘//tr[@class=”ub-content us-post”]’): item = CommunityItem() item[‘source’] = ‘디시인사이드 실시간베스트’ item[‘category’] = ‘dcinside’ item[‘title’] = sel.xpath(‘td/a/text()’).extract()[0] item[‘url’] = ‘https://gall.dcinside.com/‘ + sel.xpath(‘td/a/@href’).extract()[0] # yield : 아이템을 한 개씩 차곡차곡 쌓기 yield item |
for문이 한 번 돌때마다 scrapy.Request 를 두 번 수행하도록 작성되었다.
첫번째 요청은 흑곰의 유익한 블로그 2호점, 두번째 요청은 디시인사이드 실시간베스트 게시판을 파싱한다.
각각의 콜백함수는 parse_wordpress 함수와 parse_dcinside 함수를 만들어서 구현했다.
(2) items.py
import scrapy class CommunityItem(scrapy.Item): source = scrapy.Field() category = scrapy.Field() title = scrapy.Field() url = scrapy.Field() pass |
아이템의 필드를 정의한다.
(3) pipelines.py
from scrapy.exceptions import DropItem class CommunityPipeline: words_to_filter = [‘아이폰’, ‘안드로이드’] def process_item(self, item, spider): for word in self.words_to_filter: if word in item[‘title’]: raise DropItem(“Contains forbidden word: %s” % word) else: return item |
아이폰, 안드로이드라는 텍스트가 포함되는 경우 아이템 추가하지 않도록 DropItem 이벤트를 발생시킨다.
이렇게 하면 나중에 “scrapy crawl communityCrawler -o output.json” 과 같은 명령어로 실행했을 때, 해당하는 아이템은 결과파일인 output.json 에서 제외된다.
(4) settings.py
# Configure item pipelines # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { ‘community.pipelines.CommunityPipeline’: 300, } |
파일 내 ITEM_PIPELINES 부분을 검색해서 위와 같이 주석해제 및 수정한다.
scrapy crawl [크롤러명] 명령어로 크롤링을 실행해본다. (ex : scrapy crawl communityCrawler)