Scrapy 사용법과 Jupyter 에서 Scrapy 실행하기
포스트
취소

Scrapy 사용법과 Jupyter 에서 Scrapy 실행하기

Scrapy 사용법을 소개하고, Jupyter 에서 오류 없이 실행하기 위한 방법을 설명합니다.

1. Scrapy

스크래파이는 웹사이트를 크롤링하고 구조적인 데이터를 추출하는 애플리케이션 프레임워크이다. 데이터 마이닝, 정보처리 또는 히스토리 적재 등 넓은 사용 범위를 가진다.

1) Scrapy 구조

Scrapy - Data flow
Scrapy - Data flow
  • Engine
    • Spiders 와 Downloader, Pipelines, Scheduler 를 실행하고
    • 개체와 개체간에 입출력 이벤트를 연결
  • Spider
    • reqeust 가 시작되고
    • response 를 파싱하여 items 출력
  • Scheduler
    • request 의 처리 순서를 조정 (작업큐)
  • Downloader
    • request 에 대한 HTML 을 다운로드
  • Item Pipelines
    • item 에 대한 검증 또는 변형, 저장 등을 처리

2) Scrapy 장점

  • css 또는 xpath 표현식을 사용하여 html 데이터 추출 가능
    • 이게 효과가 상당합니다. 코드량을 엄청나게 줄여줍니다.
  • selector 표현식을 시험해 볼 수 있는 shell 기능 제공
  • json/csv/xml 등 여러 포맷으로 export 할 수 있는 기능 제공

2) Scrapy 와 BS4(BeautifulSoup) 비교

출처: Stackoverflow

Scrapy is a multitool(다목적칼). BS4 is a penknife(주머니칼).

Scrapy 특징

  • 무겁다
  • 종속성 설치 문제가 발생할 수 있다
  • 학습 난이도가 있다
  • 최신 문서와 활발한 커뮤니티의 도움을 받을 수 있다.
  • 추출 속도가 빠르다
  • 큰 작업에 적합
  • Scrapy spiders 를 위한 클라우드 서비스가 있다
  • 세부적인 설정과 추가기능 확장이 가능하다
  • 매우 구조적인 코드 작성이 가능하다
  • 프록시 서비스와 IP 로테이션 미들웨어와 통합이 쉽다
  • 상태 조회 인터페이스와 디버깅이 편리하다.

BS4 특징

  • 가볍다
  • 설치가 빠르다
  • 배우기도 쉽다
  • 코드를 빠르게 작성할 수 있지만 지저분해진다.
  • 간단한 작업에 적합
  • 사이트를 테스트하거나 가설 검증에 적합
  • Chrome 개발도구에서 curl 을 사용하여 쿠키 종속사이트 또는 복잡한 post 요청에 대한 결과를 사용할 수 있다.

요약

  • 웹스크래핑을 입문하는 경우, 단순 작업일 경우 BS4 를 사용하라
  • 대규모 데이터, 긴 작업, 전문적인 스크래핑 작업에는 Scrapy 를 사용하라

2. Scrapy 사용법

1) 프로젝트 생성 및 실행

1
2
3
4
5
6
7
$ scrapy startproject <project_name> [project_dir]

$ cd [project_dir]
$ scrapy list
<scrapy_module>

$ scrapy crawl <scrapy_module>

2) 프로젝트의 파일들

  • {PROJECT_ROOT}
    • scrapy.cfg : scrapy 모듈 배포를 위한 설정
    • [naverstocks] : 실질적인 프로젝트 루트
      • settings.py : 기본 설정 및 커스텀 설정
      • pipelines.py : parse 처리된 item 들을 개별 처리하는 단계
      • items.py : export 를 위한 데이터 모델
      • exporters.py : 커스텀 exporter 기능을 설정
      • extensions.py : scrapy 실행 단계별 hook 에 실행할 확장 기능 정의
      • middlewares.py : spider, downloader 실행을 대체하는 커스텀 기능 정의
      • [spiders] : 독립적인 scrapy 실행 단위
        • stocks.py : spider 또는 crawler 클래스를 상속받아 request 와 response, parse 를 정의

3) scrapy 설정 파일

(1) settings.py

스파이더의 기본 설정 항목 또는 공통적으로 사용되는 항목들을 설정

  • spider 인스턴스가 생성된 이후 settings 참조가 가능하다
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
BOT_NAME = "naver-stocks-collector"

# 스파이더 모듈 paths (load 대상)
SPIDER_MODULES = ["naverstocks.spiders"]
# 스파이더 생성자 모듈
NEWSPIDER_MODULE = "naverstocks.spiders"

# 맥북 크롬 브라우져
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"

# True 이면 로봇 정책 때문에 읽지 않음
ROBOTSTXT_OBEY = False

# Custom exporters
FEED_EXPORTERS = {
    # 분리 기호 또는 따옴표("") 사용방식 등의 csv 옵션 변경
    "csv": "naverstocks.exporters.QuoteAllCsvItemExporter",
}

# request 사이의 delay 설정 (기본값 0초)
# - 까다로운 사이트의 경우 3초 이상을 주어야 할 수도 있음
DOWNLOAD_DELAY = 0.25

# 스파이더 Hook 클래스에 커스텀 기능 구현
# https://docs.scrapy.org/en/latest/topics/extensions.html#sample-extension
MYEXT_ENABLED = True  # enable/disable the extension
MYEXT_ITEMCOUNT = 100  # how many items per log message

(2) spiders 클래스 내부에서 커스텀 설정

spider 인스턴스 생성시 개별적으로 참조되는 항목들을 설정

1
2
3
4
5
6
7
# spiders/stock_categories.py
custom_settings = {
    "ITEM_PIPELINES": {StockGroupPipeline: 100},
    "FEEDS": {"output/stock-categories.csv": {"format": "csv", "overwrite": True}},
    # 사용자 정의 설정
    "ITEM_LIST_OUTPUT": "output/category-groups.jl",  # json list
}

3. Jupyter Notebook 에서 Scrapy 실행하기

정석적인 python 프로젝트 실행 외에, Jupyter 에서 간단히 작업하려면 Crawler 프로세스를 직접 실행시켜야 한다. 이 때 Jypyter 메인 스레드 위에서 동작되므로, 정상적으로 Crawler 프로세스를 동작시키려면 Thread 처리에 대한 설정이 필요하다.

출처: Run Scrapy code from Jupyter Notebook without issues

1) CrawlerProcess 사용시 Reactor 재실행 오류

  • 첫번째는 잘 실행되었지만
  • 두번째 실행부터는 ReactorNotRestartable 발생

해결책은 커널 재시작뿐!!

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
# scrape webpage
import scrapy
from scrapy.crawler import CrawlerProcess

# text cleaning
import re

class QuotesToCsv(scrapy.Spider):
    name = "MJKQuotesToCsv"
    start_urls = [
        'https://en.wikiquote.org/wiki/Maynard_James_Keenan',
    ]
    custom_settings = {
        'ITEM_PIPELINES': {
            '__main__.ExtractFirstLine': 1
        },
        'FEEDS': {
            'quotes.csv': {
                'format': 'csv',
                'overwrite': True
            }
        }
    }

    def parse(self, response):
        for quote in response.css('div.mw-parser-output > ul > li'):
            yield {'quote': quote.extract()}

            
class ExtractFirstLine(object):
    def process_item(self, item, spider):
        lines = dict(item)["quote"].splitlines()
        first_line = self.__remove_html_tags__(lines[0])
        return {'quote': first_line}

    def __remove_html_tags__(self, text):
        """remove html tags from string"""
        html_tags = re.compile('<.*?>')
        return re.sub(html_tags, '', text)


# 처음에는 잘 작동했지만
# 두번째 실행부터는 ReactorNotRestartable 발생
process = CrawlerProcess()
process.crawl(QuotesToCsv)
process.start()

2) Reactor 재실행을 위해 crochet 을 사용

  • setup 과정에서 Reactor의 thread 상태에 대한 observer 를 등록
  • wait_for 데코레이터를 통해 timeout 이후 thread 를 종료시킨다
    • 실행이 끝난 spider 작업에 대해 Reactor 를 해제시킴
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
# scrape webpage
import scrapy
from scrapy.crawler import CrawlerRunner

# Reactor restart 를 위해 임포트
from crochet import setup, wait_for

# crawler 실행 이전에 한번만 실행
setup()


# 코드 동일
class QuotesToCsv(scrapy.Spider):
    pass

# 코드 동일
class ExtractFirstLine(object):    
    pass  


# 여기서부터 다름
@wait_for(10)  # 10초 후 timeout
def run_spider():
    """run spider with MJKQuotesToCsv"""
    crawler = CrawlerRunner()
    d = crawler.crawl(QuotesToCsv)
    return d


# 여러번 실행해도 정상적으로 수행됨
run_spider()

9. Review

  • 데이터 수집/분석을 위해 크롤링은 핵심 기술이 되었다.
    • 가난한 서비스가 데이터 서비스를 하려면 크롤링뿐
    • 거대 플랫폼들이 데이터를 독식하면서 기생할 수 밖에 없다.
  • Linkedin 사이트 등을 크롤링 하려면 프록시 API 와 IP 로테이션 서비스가 필수다.
    • 여러번 크롤링 하다보면 IP가 통채로 block 당한다
  • 로그인이라던지, 동적 페이지라던지, 자바스크립트 처리 등이 고급과정

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.