Stage3 - 네이버 뉴스 수집하기

세번째 스테이지에서는 네이버 뉴스 기사를 수집하며 안티크롤링 회피방법과 다양한 선택자에 대해서 공부합니다.

네이버뉴스 기사 수집 시작하기

세번째 스테이지에서는 네이버 "뉴스" 검색을 통해 원하는 키워드를 검색하여 나오는 기사들(한 페이지에 10개의 기사)의 #1. 제목#2. 언론사를 수집해보려고 합니다.

소스코드를 가져오고, 파싱하기

네이버 검색창에 원하는 키워드를 검색한 후 나오는 페이지의 URL을 통해 아래처럼 데이터 수집을 준비합니다.

*URL은 사람마다 다를 수 있습니다!

week3_2.py
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://search.naver.com/search.naver?where=news&query=코알라")
html = BeautifulSoup(raw.text, "html.parser")

앞으로 오픈소스 패키지를 import한 후, 해당하는 페이지에서 소스코드를 받아(requests.get) html파싱(BeautifulSoup)하는 부분은 어떤 페이지를 수집하던지 공통적으로 적어줍니다.

웹페이지에서 크롤링을 막는다? - 안티 크롤링(Anti-Crawling)

바로 데이터 수집을 시작할 수도 있지만 종종 웹페이지에서 크롤링을 통한 데이터수집을 막아놓는 경우가 있습니다.

먼저, 크롤링 행위 자체는 불법이 아닙니다. 우리가 만드는 웹크롤링은 웹브라우저를 통해 확인할 수 있는 데이터를 가져오는 것이기 때문에 기업의 서버를 공격해서 데이터를 훔쳐오는 해킹과는 완전히 다른 행위입니다.

[판결]'웹사이트 무단 크롤링' 소송... 잡코리아, 사람인에 승소

하지만, 위의 기사내용과 같이 크롤링을 통해 수집한 데이터를 부정경쟁의 수단으로 사용하는 경우에는 분명 처벌받을 수 있습니다. 인터넷 서비스 제공업체들은 이와 같은 상황을 미연에 방지하기 위하여 다양한 방법을 사용해서 데이터수집을 방해하고있습니다. 이런 정책/기술을 안티 크롤링(Anti-Crawling)이라고 합니다.

간단한 안티 크롤링 회피방법

가장 기본적인 안티 크롤링의 원리를 알아보고 안티 크롤링의 회피방법을 알아보도록 하겠습니다.

일반적으로 우리가 일반 웹서비스에 접속해서 데이터를 요청할 때는 웹브라우저(Chrome, Safari, FireFox 등)를 통해서 데이터를 요청하고, 서버는 어디에서 데이터를 요청했는지 확인해서 요청한 데이터를 돌려주는 방식을 사용합니다.

하지만 코드로 만든 데이터수집 프로그램을 통해서 데이터를 요청하는 경우에는 서버가 데이터를 요청한 웹브라우저의 정보를 확인할 수 없기때문에 일부 웹서비스의 경우 요청한 데이터를 돌려주지 않습니다.

이 때 아래와 같은 코드를 수정하면 안티크롤링을 피해갈 수 있습니다.

week3_2.py
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://search.naver.com/search.naver?where=news&query=코알라",
headers={'User-Agent':'Mozilla/5.0'})
html = BeautifulSoup(raw.text, "html.parser")

기존 코드의 requests.get()함수 안에 headers={'User-Agent':'Mozilla/5.0'} 이라는헤더값이 추가된 형태입니다. headers를 통해 서버에 데이터를 요청하면서 웹브라우저에 대한 정보를 같이 전송하여 마치 웹브라우저를 통해 데이터를 요청하는 것처럼 보이게 합니다.

headers를 사용하면 위의 그림처럼 웹브라우저에서 데이터를 요청한 것처럼 서버를 속여 원하는 데이터를 얻을 수 있습니다.

*실제 User-Agent 값은 훨씬 길지만, 대부분의 경우 "Mozilla/5.0"까지만 적어도 회피할 수 있습니다.(아래는 chrome의 User-Agent 값)

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

뉴스기사 수집 과정 이해, 선택자 찾기

네이버뉴스 기사 수집 역시 네이버TV 영상 데이터 수집과 비슷한 과정으로 진행됩니다. 먼저 원하는 데이터를 가지고있는 #1. 컨테이너를 수집하고, #2. 기사별로 기사제목과 언론사 데이터를 수집하는 과정을 #3. 컨테이너의 개수만큼 반복합니다.

컨테이너 선택자 찾기

위와 같이 네이버뉴스 기사수집에서 컨테이너는 기사 정보를 모두 가지고 있는 영역이 됩니다. 이부분의 html코드를 검사창을 통해 살펴보면 아래와 같습니다.

<ul class="type01">
<li id="sp_nws1">...</li>
<li id="sp_nws2">...</li>
<li id="sp_nws5">...</li>
<li id="sp_nws6">...</li>
...(생략)

컨테이너는 li태그로 쌓여있는 부분이 되는데 어떤 선택자를 선택해야 원하는 컨테이너를 찾을 수 있을까요?

  1. li#sp_nws1로 선택하는 경우

    • 첫번째 기사부분의 컨테이너를 확인하면 "sp_nws1"이라는 id를 가지고 있기 때문에 li#sp_nws1이라는 선택자로 데이터를 선택할 수 있습니다.

    • 태그의 id는 유일한 이름이기때문에 첫번째 기사 데이터에 대해서는 정상적으로 선택이 가능하지만 우리가 찾으려고 컨테이너는 우리가 원하는 모든 기사를 모두 선택할 수 없습니다.

  2. ul.type01 > li로 선택하는 경우

    • 기사를 모두 선택할 수 없기 때문에 상위 태그에서부터 상하관계로 구분해주는 방법(자식, >)을 사용하면 ul.type01 > li와 같이 선택자를 만들 수 있습니다.

    • 이 경우 페이지 위에서 확인할 수 있는 모든 기사를 선택할 수 있습니다.

    • 가장 적합한 선택자입니다.

네이버뉴스 기사 컨테이너의 선택자: ul.type01 > li

기사제목 선택자 찾기

<a class="_sp_each_url _sp_each_title" ...(생략)>
오스트레일리아 폭염, 목마른 코알
</a>

네이버뉴스 기사의 제목은 위와 같이 a태그 안에 저장되어 있습니다. 기사제목이 저장되어있는 a태그의 class를 살펴보면 "_sp_each_url _sp_each_title"입니다. 그런데 크롬을 통해 표시되는 선택자는 a._sp_each_url _sp_each_title이 아니라 a._sp_each_url._sp_each_title로 표현되어 있습니다.

사실 하나의 태그는 여러개의 클래스를 가질 수 있고, 클래스의 구분을 공백으로 하기 때문인데요. 기사제목을 포함하는 a태그는 "_sp_each_url _sp_each_title"라는 클래스를 가지고 있는 것이 아니라 "_sp_each_url""_sp_each_title"라는 두개의 클래스를 동시에 가지고 있는 것입니다.

기사 제목을 포함하고있는 a태그는 2가지 클래스를 가지고 있으므로 다음과 같이 3가지 방법으로 표현할 수 있습니다.

  1. a._sp_each_url._sp_each_title ; 2가지 클래스를 모두 가지고있는 a태그

    • 제목 데이터 중 일부는 2가지 클래스를 모두 가지고있고, 일부는 1가지 클래스만 가지고 있는 경우가 있기 때문에 10개 기사의 제목이 모두 선택되지 않을 수 있습니다.

    • 모든 데이터를 가리키고 있지 못하므로 좋은 선택자가 아닙니다.

  2. a._sp_each_url;"_sp_each_url" 클래스를 가지고있는 a태그

    • 제목 외에도 "_sp_each_url" 클래스를 가지고있는 경우가 있기 때문에 기사 개수 이상의 데이터가 선택됩니다.

    • 원하는 데이터보다 더 많은 데이터를 가리키고 있으므로 좋은 선택자가 아닙니다.

  3. a._sp_each_title;"_sp_each_title" 클래스를 가지고 있는 a태그

    • 기사 개수만큼의 제목데이터만 선택하고 있습니다.

    • 원하는 데이터를 정확히 가리키고 있으므로 적절한 선택자입니다.

네이버뉴스 기사 제목의 선택자: a._sp_each_title

언론사 선택자 찾기

클래스도 하나이고, 원하는 데이터를 모두 가리킨다.

네이버뉴스 기사의 언론사 부분은 위와 같이 명확하게 하나의 class("_sp_each_source")를 가지고있고, 해당하는 선택자(span._sp_each_source)를 통해서 모든 데이터를 가리키고 있기 때문에 쉽게 선택자를 결정할 수 있다.

네이버뉴스 기사 언론사의 선택자: a._sp_each_source

파이썬으로 뉴스기사 수집하기

앞에서 살펴본 데이터수집 과정에 맡게 선택자를 활용해서 코딩을 통한 데이터수집을 시작합니다.

#1. 컨테이너 수집

week3_2.py
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://search.naver.com/search.naver?where=news&query=코알라",
headers={'User-Agent':'Mozilla/5.0'})
html = BeautifulSoup(raw.text, "html.parser")
articles = html.select("ul.type01 > li")

앞에서 찾은 컨테이너를 가리키는 선택자(ul.type01 > li)를 통해서 페이지 위의 모든 기사를 articles변수에 저장합니다.

#2. 기사별 데이터 수집

week3_2.py
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://search.naver.com/search.naver?where=news&query=코알라",
headers={'User-Agent':'Mozilla/5.0'})
html = BeautifulSoup(raw.text, "html.parser")
articles = html.select("ul.type01 > li")
# 첫번째 기사에 대한 제목/언론사를 수집해서 출력합니다.
title = articles[0].select_one("a._sp_each_title").text
source = articles[0].select_one("span._sp_each_source").text
print(title, source)

실행결과 오스트레일리아 폭염, 목마른 코알라 국제뉴스

articles변수에 저장된 기사에 대한 데이터는 모든 기사가 리스트 형식으로 저장되어 있으므로 첫번째 기사(articles[0])에 대한 제목, 언론사를 출력해봅니다.

*.text를 사용한 변환은 체이닝 방법을 이용해서 select_one() 바로 뒤에서도 사용할 수 있습니다.

#3. 수집반복(컨테이너)

week3_2.py
import requests
from bs4 import BeautifulSoup
raw = requests.get("https://search.naver.com/search.naver?where=news&query=코알라",
headers={'User-Agent':'Mozilla/5.0'})
html = BeautifulSoup(raw.text, "html.parser")
articles = html.select("ul.type01 > li")
# 리스트를 사용한 반복문으로 모든 기사에 대해서 제목/언론사 출
for ar in articles:
title = ar.select_one("a._sp_each_title").text
source = ar.select_one("span._sp_each_source").text
print(title, source)

리스트를 사용한 반복문 for ar in articles:를 활용해서 페이지 위의 모든 기사에 대해서 제목/언론사를 출력합니다. 임시변수는 articles의 약자 ar을 사용합니다.