Language/WebCrawling

Scrapy 사용해보기

아르비스 2020. 7. 9. 11:10

Scrapy 란?

최근 웹에는 수억개의 웹페이지가 있으며, 대부분의 페이지들은 수많은 정보를 가지고 있습니다. 최근 빅데이터가 대두되면서 이전에 작성되었던 페이지들의 정보를 모아 유의미한 정보를 도출하기 위한 여러가지 방법들이 논의되고 있고, 이를 Scraping(혹은 Crawling)이라고 합니다. Scrapy는 Scraping을 도와주기위한 파이썬 기반 라이브러리입니다. Scrapy를 이용하여 필요한 페이지로 접속하여 원하는 형태로 데이터를 가공하여 데이터를 저장할수 있도록 도와줍니다.

 

설치하기

터미널에 아래 명령어를 입력해 Scrapy를 설치합니다.

pip install scrapy

 

Scrapy Shell 사용해보기

Scrapy Shell을 사용함으로써, 프로젝트를 생성하지 않고 간단하게 Scrapy를 체험할 수 있습니다. 아래 명령어를 입력해서 Shell을 실행시킵니다.

scrapy shell

네이버 뉴스 페이지를 크롤링하려고 합니다. Scrapy 크롤러는 starting point를 필요로 합니다. 말 그대로, 크롤링을 시작할 위치를 정하는 겁니다. 아래 명령어를 통해 Starting Point를 설정합시다.

 

fetch('http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001')

 

혹은 경로를 지정해서 shell을 실행할 수 있음

scrapy shell http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001

실행하면 다음과 같다.

PS D:\> scrapy shell
2020-07-09 11:10:50 [scrapy.utils.log] INFO: Scrapy 2.0.0 started (bot: scrapybot)
2020-07-09 11:10:50 [scrapy.utils.log] INFO: Versions: lxml 4.5.0.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 19.10.0, Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d  10 Sep 2019), cryptography 2.8, Platform Windows-10-10.0.19041-SP0
2020-07-09 11:10:50 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.selectreactor.SelectReactor
2020-07-09 11:10:50 [scrapy.crawler] INFO: Overridden settings:
{'DUPEFILTER_CLASS': 'scrapy.dupefilters.BaseDupeFilter',
 'LOGSTATS_INTERVAL': 0}
2020-07-09 11:10:50 [scrapy.extensions.telnet] INFO: Telnet Password: e41c091ec934461c
2020-07-09 11:10:50 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole']
2020-07-09 11:10:51 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-07-09 11:10:51 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-07-09 11:10:51 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-07-09 11:10:51 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-07-09 11:10:53 [asyncio] DEBUG: Using proactor: IocpProactor
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x061C3190>
[s]   item       {}
[s]   settings   <scrapy.settings.Settings object at 0x061C3118>
[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
2020-07-09 11:10:54 [asyncio] DEBUG: Using proactor: IocpProactor
In [1]:

In [1]: fetch('http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001')
2020-07-09 11:11:10 [scrapy.core.engine] INFO: Spider opened
2020-07-09 11:11:10 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET https://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001> from <GET http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001>
2020-07-09 11:11:10 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001> (referer: None)

In [2]:

 

크롤러를 통해서 다운받은 파일을 확인할 수 있다.

view(response)

이 명령어는 크롤러가 다운로드한 페이지를 기본 브라우저를 통해 실행합니다

 

file:///C:/Users/juseok.yun/AppData/Local/Temp/tmpbbvtuh26.html

경로를 보면 알겠지만, 로컬에 요청페이지가 로컬에 저장된다. 소스코드는 아래와 같이 확인 가능하다.

print(response.text)

 

다운로드 된, 소스가 출력된다.

 

웹사이트의 원하는 Data를 가져오기 위해서, 소스보기를 눌러서 어떤 값을 찾아와야 하는지를 찾는다.

개발자 도구[F12]를 사용하면 더 간편하게 할 수 있다.

[Elements]를 클릭하면, html소스를 볼수 있다. 

 

 [우클릭 ] > copy > [ xpath copy]를 선택하여 해당 html코드를 xpath형태로 복사해오게 된다.

 

xpath인자의 마지막에   /text()를 붙히고 xpath함수를 호출하면, 텍스트만 뽑아내서 selector에 담긴
  그 뒤에 extract()함수까지 적용하면 Selector에  담긴 텍스트를 추출해서 표시한다.

response.xpath('//*[@id="question-summary-49154165"]/div[2]/h3/a/text()').extract()

 

크롤링 해보기..

제목

그러면 제목을 먼저 크롤링 해보겠습니다. 개발자 도구에 들어가서 제목 부분의 XPath를 복사합니다. 두번째 뉴스의 XPath와 맨 아랫줄에있는 뉴스의 XPath도 비교해봅니다.

    1. 첫번째 뉴스 : //*[@id=”main_content”]/div[2]/ul[1]/li[1]/dl/dt[2]/a
    2. 두번째 뉴스 : //*[@id=”main_content”]/div[2]/ul[1]/li[2]/dl/dt[2]/a
    3.   ....
    4. 마지막 뉴스 : //*[@id=”main_content”]/div[2]/ul[2]/li[10]/dl/dt[2]/a

위와 같은 구조로 된 경우,  li[1]~li[10]으로 구성되어 있었다.

두번째로, ui[1]과 ui[2] 로 나뉜것이다.

 

결국 모든 포스트를 list에 저장하기 위해서는 태그[숫자]의 숫자 부분을 삭제해주면 됩니다.

//*[@id="main_content"]/div[2]/ul/li/dl/dt[2]/a/

XPath를 얻었으니 이제 크롤링을 해봅니다.

response.xpath('//*[@id="main_content"]/div[2]/ul/li/dl/dt[2]/a/text()').extract()

 

뉴스 사이트의 CSS Selector를 통해 크롤링 해보자.

response.css('.writing::text').extract()

 

Spider 작성하기

기본적인 scrapy 프로젝트의 구조는 이렇습니다. 참고하고 프로젝트를 만들어봅시다.

 

 

Hello Scrapy World!

아래 명령어를 통해 scrapy project를 생성해줍니다.

scrapy startproject naverscraper

그리고 프로젝트 안에 있는 spiders폴더에 들어갑니다.

D:\db\Scrapy> scrapy startproject naverscraper
New Scrapy project 'naverscraper', using template directory 'd:\utils\python\python38\lib\site-packages\scrapy\templates\project', created in:
    D:\db\Scrapy\naverscraper

You can start your first spider with:
    cd naverscraper
    scrapy genspider example example.com
D:\db\Scrapy>
PS D:\DB\Scrapy> cd .\naverscraper\naverscraper\spiders\
PS D:\DB\Scrapy\naverscraper\naverscraper\spiders> ls


    디렉터리: D:\DB\Scrapy\naverscraper\naverscraper\spiders


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----      2020-03-18   오전 9:17                __pycache__
-a----      2020-03-18   오전 9:17            161 __init__.py


PS D:\DB\Scrapy\naverscraper\naverscraper\spiders>

 

기본적인 scrapy 프로젝트 구조

( $> scrapy startproject {scrapy project명}  실행시킨 경우) 

.{scrapy project}/
       scrapy.cfg                   # deploy configuration file
       {scrapy project}/          # project's Python module, you'll import your code form here
              spiders/              # a directory where you'll later put your spiders
                   __init__.py              
 
              __init__.py            
              items.py              # project items definition file
              middlewares.py     # project middlewares file
              pipelines.py          # project pipelines file
              settings.py           # project settings file

         1. scrapy.cfg - 프로젝트 설정 파일, 프로젝트명, settings파일 경로 등을 설정할 수 있음.

         2. spiders - 데이터를 수집하고 가공하는 일을 하는 Worker를 Spider라고 하며, 여러 Spider들이 모여있는 폴더.

         3. items.py - Spider가 작업을 완료한 후 반환하는 결과값의 Schema를 정의하는 파일.

         4. middlewares.py - Spider가 request를 던질때 Proecces를 controll하는 파일, Selenium을 여기에 적용.

         5. pipelines.py - Spider가 response를 받았을 때 처리하는 Process를 controll하는 파일.

         6. settings.py - Spider 실행에 필요한 전반적인 옵션을 설정하는 파일.

Spider 생성하기

(프로젝트 안에 있는 spiders폴더에서 실행)

genspider 명령어를 통해서 newsbot을 생성합니다.

scrapy genspider newsbot 'news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001'

 

(실행 예시)

PS D:\DB\Scrapy\naverscraper\naverscraper\spiders> scrapy genspider newsbot 'news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001'
Created spider 'newsbot' using template 'basic' in module:
  naverscraper.spiders.newsbot
PS D:\DB\Scrapy\naverscraper\naverscraper\spiders>

 

생성된 newsbot.py 에 소스를 입력합니다.

(초기 생성코드)

# -*- coding: utf-8 -*-
import scrapy


class NewsbotSpider(scrapy.Spider):
    name = 'newsbot'
    allowed_domains = ['news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001']
    start_urls = ['http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001/']

    def parse(self, response):
        pass

 

[ 수정코드 ]

import scrapy

class NewsbotSpider(scrapy.Spider):
	name = 'newsbot'
	start_urls = ['http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001']

	def parse(self, response):
		titles = response.xpath('//*[@id="main_content"]/div[2]/ul/li/dl/dt[2]/a/text()').extract()
		authors = response.css('.writing::text').extract()
		previews = response.css('.lede::text').extract()

		for item in zip(titles, authors, previews):
			scraped_info = {
				'title' : item[0].strip(),
				'author' : item[1].strip(),
				'preview' : item[2].strip(),
			}
			yield scraped_info

8~10: 크롤링 한 데이터를 list로 저장
12: zip 함수는 아래 사진 참고
13: scraped_info 에 zip으로 slice 한 데이터들을 저장
14~16: strip 함수를 이용해 문자열에 필요없는 공백을 제거

 

Spider 실행하고 결과 확인하기

newsbot을 실행시켜봅니다.

scrapy crawl newsbot

 

(실행예시)

PS D:\DB\Scrapy\naverscraper\naverscraper\spiders> scrapy crawl newsbot
2020-07-10 10:56:07 [scrapy.utils.log] INFO: Scrapy 2.0.0 started (bot: naverscraper)
2020-07-10 10:56:07 [scrapy.utils.log] INFO: Versions: lxml 4.5.0.0, libxml2 2.9.5, cssselect 1.1.0, parsel 1.5.2, w3lib 1.21.0, Twisted 19.10.0, Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1d  10 Sep 2019), cryptography 2.8, Platform Windows-10-10.0.19041-SP0
2020-07-10 10:56:07 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.selectreactor.SelectReactor
2020-07-10 10:56:07 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'naverscraper',
 'NEWSPIDER_MODULE': 'naverscraper.spiders',
 'ROBOTSTXT_OBEY': True,
 'SPIDER_MODULES': ['naverscraper.spiders']}
2020-07-10 10:56:07 [scrapy.extensions.telnet] INFO: Telnet Password: 7637d0062438979b
2020-07-10 10:56:07 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
 'scrapy.extensions.telnet.TelnetConsole',
 'scrapy.extensions.logstats.LogStats']
2020-07-10 10:56:08 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
 'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
 'scrapy.downloadermiddlewares.retry.RetryMiddleware',
 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
 'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-07-10 10:56:08 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
 'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
 'scrapy.spidermiddlewares.referer.RefererMiddleware',
 'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
 'scrapy.spidermiddlewares.depth.DepthMiddleware']
2020-07-10 10:56:08 [scrapy.middleware] INFO: Enabled item pipelines:
[]
2020-07-10 10:56:08 [scrapy.core.engine] INFO: Spider opened
2020-07-10 10:56:08 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-07-10 10:56:08 [scrapy.extensions.telnet] INFO: Telnet console listening on 127.0.0.1:6023
2020-07-10 10:56:08 [scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET https://news.naver.com/robots.txt> from <GET http://news.naver.com/robots.txt>
2020-07-10 10:56:08 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://news.naver.com/robots.txt> (referer: None)
2020-07-10 10:56:08 [scrapy.downloadermiddlewares.robotstxt] DEBUG: Forbidden by robots.txt: <GET http://news.naver.com/main/list.nhn?mode=LSD&mid=sec&sid1=001/>
2020-07-10 10:56:08 [scrapy.core.engine] INFO: Closing spider (finished)
2020-07-10 10:56:08 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 1,
 'downloader/exception_type_count/scrapy.exceptions.IgnoreRequest': 1,
 'downloader/request_bytes': 456,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 459,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 1,
 'downloader/response_status_count/302': 1,
 'elapsed_time_seconds': 0.412998,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2020, 7, 10, 1, 56, 8, 772541),
 'log_count/DEBUG': 3,
 'log_count/INFO': 10,
 'response_received_count': 1,
 'robotstxt/forbidden': 1,
 'robotstxt/request_count': 1,
 'robotstxt/response_count': 1,
 'robotstxt/response_status_count/200': 1,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2020, 7, 10, 1, 56, 8, 359543)}
2020-07-10 10:56:08 [scrapy.core.engine] INFO: Spider closed (finished)
PS D:\DB\Scrapy\naverscraper\naverscraper\spiders>

 

CSV로 내보내기

프로젝트 폴더에있는 settings.py에 입력합니다:

....
FEED_FORMAT = "csv"
FEED_URI = "naver_news.csv"

 

 

그리고 다시 Spider을 실행합니다.

scrapy crawl newsbot

그럼 같은 디렉토리에 뉴스 내용들이 크롤링되어 csv에 저장됩니다.