Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

파이썬 - 웹 페이지 데이터 수집을 위한 scrapy Crawler 사용법 요약

윈도우 사용자라고 해도, 간단한 실습에 불과하니 쓸데없이 ^^ 디렉터리를 어지럽히지 말고 WSL에 맡기면 좋습니다. 게다가 파이썬 환경이니 virtualenv로 한 번 더 격리를 하면 좋겠지요. ^^

$ cd ~
~$ mkdir pyenv
~$ cd pyenv

~/pyenv$ virtualenv scraptest

~/pyenv$ source scraptest/bin/activate

(scraptest) testusr@TESTPC:~/pyenv$

scrapy를 설치하고,

(scraptest) testusr@TESTPC:~/pyenv$ pip install scrapy

(scraptest) testusr@TESTPC:~/pyenv$ python --version
Python 3.8.10


(scraptest) testusr@TESTPC:~/pyenv$ scrapy --version
Scrapy 2.5.0 - no active project

Usage:
  scrapy <command> [options] [args]

Available commands:
  bench         Run quick benchmark test
  commands
  fetch         Fetch a URL using the Scrapy downloader
  genspider     Generate new spider using pre-defined templates
  runspider     Run a self-contained spider (without creating a project)
  settings      Get settings values
  shell         Interactive scraping console
  startproject  Create new project
  version       Print Scrapy version
  view          Open URL in browser, as seen by Scrapy

  [ more ]      More commands available when run from project directory

Use "scrapy <command> -h" to see more info about a command

프로젝트를 하나 만듭니다.

(scraptest) testusr@TESTPC:~/pyenv$ cd scraptest/
(scraptest) testusr@TESTPC:~/pyenv/scraptest$

(scraptest) testusr@TESTPC:~/pyenv/scraptest$ scrapy startproject sample1
New Scrapy project 'sample1', using template directory '/home/testusr/pyenv/scraptest/lib/python3.8/site-packages/scrapy/templates/project', created in:
    /home/testusr/pyenv/scraptest/sample1

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

(scraptest) testusr@TESTPC:~/pyenv/scraptest$ cd sample1/
(scraptest) testusr@TESTPC:~/pyenv/scraptest/sample1$

스파이더를 생성하고,

(scraptest) testusr@TESTPC:~/pyenv/scraptest/sample1$ scrapy genspider sysnet sysnet.pe.kr
Created spider 'sysnet' using template 'basic' in module:
  sample1.spiders.sysnet

(scraptest) testusr@TESTPC:~/pyenv/scraptest/sample1$ tree
.
├── sample1
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-38.pyc
│   │   └── settings.cpython-38.pyc
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       ├── __pycache__
│       │   └── __init__.cpython-38.pyc
│       └── sysnet.py
└── scrapy.cfg

(scraptest) testusr@TESTPC:~/pyenv/scraptest/sample1$ cat sample1/spiders/sysnet.py
import scrapy

class SysnetSpider(scrapy.Spider):
    name = 'sysnet'
    allowed_domains = ['sysnet.pe.kr']
    start_urls = ['http://sysnet.pe.kr/']

    def parse(self, response):
        pass

여기서, scrap 관련한 코드를 원하는 목적에 맞게 수정해야 합니다. 가령 제 경우에는 제 웹 사이트에 있는 글의 "제목"을 스크립하고 싶은데요, 다음과 같은 식으로 위의 코드를 변경해야 합니다.

# items.py의 기본 내용을 다음과 같이 변경
import scrapy

class Sample1Item(scrapy.Item):
    title = scrapy.Field()

# ./spiders/sysnet.py의 기본 내용을 다음과 같이 변경

import scrapy
from ..items import Sample1Item

class SysnetSpider(scrapy.Spider):
    name = 'sysnet'
    allowed_domains = ['www.sysnet.pe.kr']
    start_urls = ['https://www.sysnet.pe.kr/']

    def start_requests(self):
        maxPage = 5

        for i in range(0, maxPage):
            yield scrapy.Request(self.start_urls[0] + "Default.aspx?mode=2&sub=0&pageno={0}".format(i), self.parse)

    def parse(self, response):
        items = []

        # CSS 예제 (1)
        # for article in response.css('#contentPane > table.postlist > tr'):
        #     item = Sample1Item()
        #     item['title'] = article.css("td:nth-child(5) > a::text").extract()[0]
        #     items.append(item)

        # CSS 예제 (1)
        # for article in response.css('#contentPane > table.postlist > tr > td:nth-child(5) > a::text'):
        #     item = Sample1Item()
        #     item['title'] = article.extract()
        #     items.append(item)

        # XPath 예제 (1)
        # for article in response.xpath('//*[@id="contentPane"]/table[2]/tr'):
        #     item = Sample1Item()
        #     item['title'] = article.xpath("td[5]/a/node()").extract()[0]
        #     items.append(item)

        # XPath 예제 (2)
        for article in response.xpath('//*[@id="contentPane"]/table[2]/tr/td[5]/a/node()'):
            item = Sample1Item()
            item['title'] = article.extract()
            items.append(item)

            print(item['title'])

        return items

# pipelines.py의 기본 내용을 다음과 같이 변경

from itemadapter import ItemAdapter
from scrapy.exporters import JsonItemExporter
import codecs

class Sample1Pipeline:
    def process_item(self, item, spider):
        return item

class JsonPipeline:
    def __init__(self):
        self.file = open('result.json', 'wb')
        self.exporter = JsonItemExporter(self.file, encoding='utf-8', ensure_ascii=False)
        self.exporter.start_exporting()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

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

# settings.py에 ITEM_PIPELINES 설정을 새롭게 추가

# ...[생략]...

ITEM_PIPELINES = {
    'sample1.pipelines.JsonPipeline': 300,
}

# ...[생략]...

이렇게 하고, scrapy를 다음과 같이 실행하면,

(scraptest) testusr@TESTPC:~/pyenv/scraptest/sample1$ scrapy crawl sysnet

오류 없이 실행되었으면 result.json 파일에 다음과 같이 5페이지에 해당하는 분량의 제목이 추출된 것을 볼 수 있습니다. ^^

[{"title": "기부/후원"},{"title": ".NET Framework: 1113. C# 10 - (13) 문자열 보간 성능 개선"},{"title": "개발 환경 구성: 603. GoLand - WSL 환경과 연동"},...[생략]...,{"title": ".NET Framework: 1080. xUnit 단위 테스트에 메서드/클래스 수준의 문맥 제공 - Fixture"},{"title": ".NET Framework: 1079. MSTestv2 단위 테스트에 메서드/클래스/어셈블리 수준의 문맥 제공"},{"title": ".NET Framework: 1078. C# 단위 테스트 - MSTestv2/NUnit의 Assert.Inconclusive 사용법(?)"}]





위에서, Spider의 parse 함수 내의 코드에서 결과물을 XPath 또는 CSS를 이용해 select하는 것이 어려울 때는 scrapy를 명령행에서 실행시켜 테스트하는 것이 더 편합니다.

일례로, 위의 경우 페이지 하나를 scrap 하는 다음의 명령을 실행하고,

$ scrapy shell 'https://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0'
...[생략]...
021-09-04 16:56:06 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7f063aad5100>
[s]   item       {}
[s]   request    <GET https://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0>
[s]   response   <200 https://www.sysnet.pe.kr/Default.aspx?mode=2&sub=0&pageno=0>
[s]   settings   <scrapy.settings.Settings object at 0x7f063aad2d90>
[s]   spider     <SysnetSpider 'sysnet' at 0x7f063a7a4100>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
>>>

진입한 shell 모드에서 다음과 같은 식으로 테스트해 볼 수 있습니다.

>>> response.css('#contentPane > table.postlist > tbody').getall()
[]
>>> response.css('#contentPane > table.postlist > tr').getall()
[...[생략]...]




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 10/1/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10899정성태2/17/201623387개발 환경 구성: 282. kernel32.dll, kernel32legacy.dll, api-ms-win-core-sysinfo-l1-2-0.dll [1]
10898정성태2/17/201621861.NET Framework: 547. PerformanceCounter의 InstanceName 지정 시 주의 사항파일 다운로드1
10897정성태2/17/201621252디버깅 기술: 76. windbg 분석 사례 - 닷넷 프로파일러의 GC 콜백 부하
10896정성태2/17/201622373오류 유형: 320. FATAL: 28000: no pg_hba.conf entry for host "fe80::1970:8120:695:a41e%12"
10895정성태2/17/201621150.NET Framework: 546. System.AppDomain으로부터 .NET Profiler의 AppDomainID 구하는 방법 [1]
10894정성태2/17/201621866오류 유형: 319. Visual Studio에서 찾기는 성공하지만 해당 소스 코드 정보가 보이지 않는 경우
10893정성태2/16/201620549.NET Framework: 545. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까? - 두 번째 이야기
10892정성태2/16/201621131오류 유형: 318. 탐색기에서 폴더 생성/삭제 시 몇 초 동안 멈추는 현상
10891정성태2/16/201624155VC++: 95. 내 CPU가 MPX/SGX를 지원할까요? [1]
10890정성태2/15/201624059.NET Framework: 544. C# 5의 Caller Info를 .NET 4.5 미만의 응용 프로그램에 적용하는 방법 [5]
10889정성태2/14/201620349.NET Framework: 543. C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기파일 다운로드1
10888정성태2/14/201618681.NET Framework: 542. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까?
10887정성태2/3/201619388VC++: 94. MPX(Memory Protection Extensions) 테스트파일 다운로드1
10886정성태2/3/201620617개발 환경 구성: 281. Intel MPX Runtime Driver 수동 설치
10885정성태2/2/201620315오류 유형: 317. Sybase.Data.AseClient.AseException: The command has timed out.
10884정성태1/11/201621515개발 환경 구성: 280. 닷넷에서 SAP Adaptive Server Enterprise 데이터베이스 사용파일 다운로드1
10882정성태1/6/201620834Windows: 113. 윈도우의 2179, 26143, 47001 TCP 포트 사용 [1]
10881정성태1/3/201622203오류 유형: 316. 윈도우 10 - 바탕/돋음 체가 사라져 한글이 깨지는 현상 [2]
10880정성태12/16/201519949오류 유형: 315. 닷넷 프로파일러의 오류 코드 정보
10879정성태12/16/201521926오류 유형: 314. Error : DEP0700 : Registration of the app failed. error 0x80070005
10878정성태12/9/201524914디버깅 기술: 75. UWP(유니버설 윈도우 플랫폼) 앱에서 global::System.Diagnostics.Debugger.Break 예외 발생 시 대응 방법
10877정성태12/9/201529331VC++: 93. std::thread 사용 시 R6010 오류 [2]
10876정성태11/26/201525428.NET Framework: 541. SignedXml을 이용한 ds:Signature만드는 방법 [3]파일 다운로드1
10875정성태11/26/201530382개발 환경 구성: 279. signtool.exe의 다중 서명 기능 [2]
10874정성태11/26/201526373개발 환경 구성: 278. 인증서와 인증서를 이용한 코드 사인의 해시 구분
10873정성태11/25/201525491.NET Framework: 540. C# - 부동 소수 계산 왜 이렇게 나오죠? (2) [3]파일 다운로드1
... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...