[Python] 파이썬 웹 크롤링 기초 2-2 : Scrapy

[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)


 

참고사이트 2 : https://youtu.be/LQS6QfDQ9RA