본문 바로가기
Back-end/Python

[크롤링] 무작정 시작하기 (3) - Spider

by 허도치 2019. 11. 19.

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (1) - 패키지 선택

2019/11/19 - [Back-end/Python] - [크롤링] 무작정 시작하기 (2) - 프로젝트 준비

 

  이전 포스트에서 전반적인 프로젝트 준비를 진행하였다. 이번 포스트에서는 Spider를 구현하여 네이버 뉴스의 목록을 수집하는 것을 진행할 것이다. 크롤링은 접속한 페이지에서 접근 가능한 데이터는 무엇이든지 수집할 수 있다. 따라서, 저작권에 위배되는 행위를 하게될 수도 있다. 이를 예방하는 차원에서 웹사이트에는 크롤러가 접근할 수 있는 페이지를 정의한 [robots.txt]를 제공하고있다. 이는 단순히 안내문 정도로 생각하면 쉽다.

 

시작하기에 앞서 [ robots.txt ]를 간략하게 확인해보자.

 

1. robots.txt

    1-1. [ https://news.naver.com/robots.txt ] 에 접속.

   1-2. 한줄 요약

           - User-agent: 로봇인지 사용자인지 판단.

           - Allow: 접근 가능한 페이지 

           - Disallow: 접근 불가능한 페이지, / 는 전체 접근 불가.

    1-3. 네이버뉴스는 로봇의 접근을 모두 차단하고 있는 것이므로 이곳에서 수집하는 데이터는 보관하지 않는 것을 권장.

    1-4. 불이익이 따를 경우 전적으로 개발자 자신에게 책임이 있음. 필자도 예제로 사용하는 것 외에 따로 다루지않음.

    1-5. Scrapy는 robots.txt를 읽고 disallow된 페이지는 탐색하지 않도록 설정할 수 있음.

           - crawler/settings.py 에서 [ ROBOTSTXT_OBEY = True ]로 설정

 

 

2. 수집 대상 선정 및 패턴 분석

    2-1. 뉴스홈, 속보, 정치, IT/과학 등 다양한 카테고리 중 Spider가 수집할 대상은 [ IT/과학 ]로 선정하였음.

 

   2-2. URL 패턴 분석

           - 첫 진입  : ?mode=LS2D&sid2=283&sid1=105&mid=shm&date=20191119

           - 2p 진입 : ?mode=LS2D&sid2=283&mid=shm&sid1=105&date=20191119&page=2

           - 다른일자: ?mode=LS2D&sid2=283&sid1=105&mid=shm&date=20191118

           - 페이지가 변경되면 [ page ]가 추가되며, 다른 페이지를 선택하면 선택한 페이지의 값으로 변경됨.

           - 일자가 변경되면 [ date ]가 선택한 일자의 값으로 변경됨.

           - 위와 같은 방식으로 URL의 패턴을 분석하는 연습을 하는 것이 필요함.

           - 이외에도 mode, mid, sid1, sid2 등 카테고리에 관련된 값들도 있지만 이 포스트에서는 다루지 않음.

 

   2-3. 글 목록 패턴 분석

           - 아래 그림과 같이 글 목록에서 확인할 수 있는 정보는 [ 제목, 요약, 작성자, 작성시간, 이미지 ]이며 이중에서 유의미한 데이터는 [ 제목, 요약, 작성자 ]임.

           - 이미지는 저작권 문제도 걸려있으므로 함부로 수집해서는 안되며, 작성시간은 '1시간전'으로 노출되므로 무의미한 데이터임.

           - 학습을 목적으로하는 수집이므로 간단하게 [ 제목, 작성자 ]를 수집하기로 함.

   2-4. 수집 대상 XPATH 구하기.

           - Chrome 브라우저에서 오른쪽 클릭하고 [ 검사 ] 탭 선택하면 DevTools이 실행됨.

           - DevTool에서 왼쪽 상단에 마우스(?) 버튼을 클릭하고 뉴스 기사의 제목을 선택하면 해당 태그의 위치를 보여줌.

           - 태그로 위치가 이동되면 오른쪽 클릭하여 [ Copy > Copy XPath ] 를 선택하면 클립보드에 XPATH가 복사됨.

           - 위의 방법으로 가장 최신글의 XPath를 수집하면 아래와 같음.

              제목 XPath: //*[@id="main_content"]/div[1]/ul[1]/li[1]/a

              작성자 XPath: //*[@id="main_content"]/div[1]/ul[1]/li[1]/span[1]

 

            - 네이버 뉴스는 [ ul > li ] 로 글 목록이 반복되는 구조이므로 아래와 같이 패턴을 변경하여 전체 목록의 text만 수집.

              제목 XPath: //*[@id="main_content"]/div[1]/ul/li/a/text()

              작성자 XPath: //*[@id="main_content"]/div[1]/ul/li/span[1]/text()

 

 

   2-5. 패턴 분석 결과

           - 수집URL패턴: https://news.naver.com/main/list.nhn?mode=LSD&mid=shm&sid1=105&sid2=731&listType=title&date=[ YYYYMMDD ]&page=[ 페이지번호 ]

           - 제목 XPath: //*[@id="main_content"]/div[1]/ul/li/a/text()

           - 작성자 XPath: //*[@id="main_content"]/div[1]/ul/li/span[1]/text()

 

 

 

준비끝, Spider 개발 시작!

 

3. Spider 확인

   3-1. crawler/spiders/navernews.py

   3-2. 소스 확인

   3-3. 라인별 한줄 요약

          - 4 ln: scrapy.Spider를 상속받음. 부모의 메소드 중 start_requests를 재정의하여 사용할 계획.

          - 5 ln: Spider의 이름으로써 실행할 때 사용할 명칭.

          - 6 ln: allowed_domains - 접근 가능한 도메인 목록. 크롤러가 접근 도메인이 목록에 없으면 접근이 제한됨. 생략가능.

          - 7 ln: start_urls - 데이터를 수집할 url의 목록으로 start_requests에서 사용됨.

          - 9 ln: def parse(self, response) - start_requests에서 보낸 요청에 대한 응답을 처리하는 함수.

 

 

4. Spider 작성

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
# -*- coding: utf-8 -*-
import scrapy
from datetime import datetime as dt
 
class NavernewsSpider(scrapy.Spider):
    name = 'navernews'
    allowed_domains = ['news.naver.com']
 
    def __init__(self, *args, **kargs):
        today = dt.now().strftime('%Y%m%d')
        pages = [ 123 ]
 
        self.start_urls = []
        for page in pages:
            self.start_urls.append(
                f'https://news.naver.com/main/list.nhn?mode=LSD&mid=shm&sid1=105&sid2=731&listType=title&date={ today }&page={ page }'
            )
 
    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request( url=url, callback=self.parse, method='GET', encoding='utf-8')
 
    def parse(self, response):
        contents = response.xpath('//*[@id="main_content"]/div[1]/ul/li')
        
        for content in contents:
            title = content.xpath('a/text()').extract_first()
            author = content.xpath('span[1]/text()').extract_first()
 
            item = {
                'title': title.strip() if title else title
                , 'author': author.strip() if author else author
            }
            print( item )
 
            yield item
 
cs

   4-1. 라인별 한줄요약

         - 9 ln: Spider가 생성될 때 실행되는 함수로 초기값을 지정할 때 사용.

         - 10 ln: 현재 년월일 구하기.

         - 11 ln: page를 따로 크롤링하지 않기 때문에 강제적으로 지정.

         - 16 ln: 위에서 지정한 페이지 수 만큼 start_url을 설정함.

         - 19 ln: Spider가 실행되면 start_requests 함수가 호출되며, HttpResponse를 반환.

         - 21 ln: yield는 현재 함수를 제너레이터(Generator)로 만드는 키워드이며, 단순하게 실행결과를 여러번 반환 한다고 생각하자.

                     ( 제너레이터는 다른 포스트에서 다루도록 하겠음. )

         - 21 ln: scrapy.Request( ... callback=self.parse ... ) callback은 결과값을 반환할 함수를 설정, request의 결과를 pasre로 받음.

         - 23 ln: 위에서 실행한 Request의 결과를 처리하는 함수로 Item 또는 Dict 을 반환.

         - 24 ln: xpath를 이용해서 뉴스의 목록만 수집.

         - 27~28 ln: 목록에서 뉴스를 하나씩 뽑아내면서 제목과 작성자 데이터를 수집. xpath의 결과값은 list이며 extract_first로 첫번째 값만 뽑아냄.

         - 30~33 ln: 추출한 데이터를 Dict에 저장, 추후 Item에 Schema를 정의해서 변경.

 

 

5. Spider 실행

    5-1. 명령어 입력

1
$ scrapy crawl navernews
cs

 

    5-2. 실행 결과 확인

         - downloader/response_count: 요청에 대한 응답 수.

         - downloader/response_status_count/200: 정상 응답 수.

         - item_scraped_count: 수집한 데이터의 수.

 

 

 

여기까지 간단하게 네이버뉴스를 크롤링하는 Spider를 만들어보았다. 소스코드에서도 알 수 있듯이 Spider를 만드는 것은 어렵지않다. python을 접한지 얼마 안된 사람들에게는 yield와 callback 같은  낯선 문법이 있을 뿐이다. 완벽한 크롤러를 만들기 위해서는 수집하고자하는 사이트의 URL패턴과 정확한 데이터를 긁어오기위한 패턴을 분석하는데 시간을 많이 투자해야한다.

 

다음 포스트에서는 Selenium을 붙여보도록 하겠다.

 

댓글