AI/정책 댓글 반응 NLP

[1] 네이버 뉴스 댓글 크롤러_ver1_1. 네이버 뉴스 링크 반환

eraser 2020. 4. 1. 01:28
반응형

 네이버 뉴스 홈에서 '주 52시간'을 검색한 뒤, 검색 결과 창에서 보이는 마지막 페이지(이 경우에는 10)까지 돌며 네이버 뉴스 링크를 받아오려고 한다. 작업을 통해 얻고자 하는 결과물네이버 포털에서 '주52시간'으로 검색되는 기사들 중, 네이버 뉴스 플랫폼에 등록되어 있는 기사들의 링크이다.

 


# 사용한 라이브러리

 

 


# 기본 변수 설정

 

 사전 작업에서 확인했듯, 검색을 진행할 기본 URL은 다음과 같다.

 

https://search.naver.com/search.naver?&where=news&[검색어]&sm=tab_pge&sort=0&photo=0&field=0
&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0

 

 'utf-8'로 검색어를 인코딩하여 검색어 위치에 넣어준다. 이후 프로젝트에서 다른 검색어를 사용할 수 있기 때문에, 검색어인코딩된 검색어를 변수로 만들고, 이를 URL에 넣어 URL 변수를 만들어 준다.

 

QUERY = "주52시간"
search_QUERY = urllib.parse.urlencode({'query':QUERY}, encoding='utf-8')
URL = f"https://search.naver.com/search.naver?&where=news&{search_QUERY}\
        &sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&\
        nso=so:r,p:all,a:all&mynews=0"

 


 

# 검색 결과 창의 마지막 페이지 알아 오기

 

 URL에 요청을 보내고, HTML을 파싱할 수 있는 BeautifulSoup 객체를 만들어준다.

 

import requests
from bs4 import BeautifulSoup

QUERY = "주52시간"
search_QUERY = urllib.parse.urlencode({'query':QUERY}, encoding='utf-8')
URL = f"https://search.naver.com/search.naver?&where=news&{search_QUERY}\
        &sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&\
        nso=so:r,p:all,a:all&mynews=0"
        
req = requests.get(URL)
soup = BeautifulSoup(req.text, 'lxml')

 

 요청을 보내 받아 온 HTML 페이지 소스를 문자열로 만들고, 이를 'lxml' 파서를 통해 파싱한다. 기본으로 제공되는 'html.parser'도 있지만, 여러 페이지를 돌면서 많은 정보를 파싱해야 할 것을 생각해 다른 파서를 사용했다.

 

 파싱한 BeautifulSoup 객체에서 페이지 번호를 알아오기 위해 개발자 도구를 통해 페이지를 검사한다. 'class'가 'paging'인 'div' 태그 아래 anchor 태그(a)가 있고, 그 태그 아래 text로 페이지 번호가 들어 있다.

 

 

F12 개발자 도구를 통해 페이지 번호를 검사한 결과

 

 

 파싱된 HTML에서 페이지 번호를 모두 반환하여 리스트로 만들었다. a 태그의 text만 추출하기 위해 'get_text()' 메서드를 사용했고, 혹시 공백이 있을 수도 있기 때문에 'strip=True' 옵션을 줬다.

 

 작업을 진행하는 과정에서 페이지 번호의 마지막에 다음 페이지가 있는 것을 알 수 있었다. '다음 페이지'는 필요가 없을 뿐더러, int형을 적용하여 정수형 자료로 만들 때 오류가 난다.  따라서 마지막에 있는 '다음 페이지'는 제외하고, 각 숫자들만을 리스트에 넣었다. 그리고 마지막 페이지의 번호를 저장하는 변수를 만든다.

 

pagination = soup.find('div', {'class' : 'paging'})
pages = pagination.find_all("a") # anchor 태그를 모두 찾는다.
page_list = []
for page in pages[1:-1]:
    page_list.append(int(page.get_text(strip=True)))
max_page = page_list[-1] # 마지막 페이지 담는 변수.

 


 

# module import
from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup

# 변수 설정
QUERY = "주52시간"
search_QUERY = urllib.parse.urlencode({'query':QUERY}, encoding='utf-8')
URL = f"https://search.naver.com/search.naver?&where=news&{search_QUERY}\
        &sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=\
        &docid=&nso=so:r,p:all,a:all&mynews=0"
        
# 마지막 페이지 호출하는 함수
def get_last_page():
    req = requests.get(URL)
    soup = BeautifulSoup(req.text, 'lxml')
    pagination = soup.find('div', {'class' : 'paging'})
    pages = pagination.find_all("a")
    page_list = []
    for page in pages[:-1]:
        page_list.append(int(page.get_text(strip = True)))
    max_page = page_list[-1]
    return max_page

 


 

# 검색 결과 페이지 끝까지 돌며 네이버 뉴스 링크 찾기

 

 사전 작업에서 검색 결과 페이지 번호에 따라 URL이 어떻게 달라지는지 확인했다. 'start=' 부분에 들어갈 공식을 쓰고,  range를 이용해 페이지 번호를 1씩 증가시키며 URL을 변화시킨다.

 

last_page = get_last_page() # 함수 호출하여 마지막 페이지를 얻는다

for page in range(last_page):
    print(f"{URL}&start={10*page+1}")

 

 출력 결과를 확인하면, start 부분만 start=1, start=11, ..., start=91로 변화하고 있기 때문에 URL 구조가 제대로 변화함을 알 수 있다.

 

 이제 해당 URL에 모두 get 요청을 보내고, 네이버 뉴스의 링크를 찾아야 한다. 보통 웹 HTML에서 하이퍼링크는 anchor 태그의 href 속성에 들어 있는 경우가 많다. 실제로 개발자 도구를 활용해 네이버 뉴스 링크를 검사해 보니, 아래와 같이 class가 '_sp_each_url'인 a 태그의 href 속성에 주소가 위치한 것을 알 수 있다.

 

F12 개발자 도구를 통해 기사 링크를 검사한 결과

 

생각보다 쉽다고 생각하고, class가 '_sp_each_url'인 a 태그의 href 속성을 모두 찾았다. 그러나, 그 결과로 아래와 같이 모든 뉴스 기사 링크가 다 검출되는 것을 확인할 수 있었다.

 

https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=015&aid=0004309472 https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=103&oid=421&aid=0004530483 https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=020&aid=0003275384 https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=011&aid=0003712414 https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=102&oid=020&aid=0003276109 http://www.naeil.com/news_view/?id_art=344056
https://www.asiatime.co.kr/news/newsview.php?ncode=1065576989314338 http://www.etoday.co.kr/news/section/newsview.php?idxno=1875133
https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=015&aid=0004308435 https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=025&aid=0002985291 http://www.kyeongin.com/main/view.php?key=20200315010003962

 

유심히 보니, 네이버 뉴스 플랫폼에 등록된 기사는 URL이 'https://news.naver.com/main/read.nhn?'으로 시작하는 것을 알 수 있었다.

 

https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=102&oid=020&aid=0003276109 http://www.naeil.com/news_view/?id_art=344056 
https://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=101&oid=025&aid=0002985291 http://www.kyeongin.com/main/view.php?key=20200315010003962

 

 BeautifulSoup의 find_all에서는 태그 탐색 필터로 정규표현식을 사용할 수 있다. 따라서 re 모듈을 이용해 URL의 시작 부분을 정규표현식으로 지정하고, 일치하는 링크들만 모두 가져와 리스트에 저장했다.

 

import re

last_page = get_last_page()
LINK_PAT = "https:\/\/news\.naver\.com\/main\/read\.nhn\?"

links = []
for page in range(last_num):
    req = requests.get(f"{URL}&start={10*page+1}")
    print(req.status_code) # 접속 상태 확인
    soup = BeautifulSoup(req.text, 'lxml')
    results = soup.find_all('a', {'href' : re.compile(LINK_PAT)}) # 패턴에 일치하는 링크 찾기
    for result in results:
    	links.append(result['href']) # 'href' 속성을 통해 링크에 접근해 리스트에 저장
        
print(f"총 {len(links)}개의 뉴스를 찾았습니다.") # 확인용

 

 

 이후 프로젝트 진행 과정에서 다른 플랫폼에서 뉴스를 가져와야 할 경우가 발생할 때를 대비해, 링크의 패턴을 저장하는 변수를 만들었다.

 


 

import re

LINK_PAT = "https:\/\/news\.naver\.com\/main\/read\.nhn\?" # 추후 변경 가능

# 검색 결과 내 패턴에 일치하는 링크들을 모두 찾는 함수

def get_news_links(page_num, link_pattern):
    links = []
    for page in range(page_num):
        print(f"Scrapping page : {page+1}") # 확인용
        req = requests.get(f"{URL}&start={10*page+1}"); print(req.status_code) # try except로 확인 코드 넣기 나중에
        soup = BeautifulSoup(req.text, 'lxml')
        results = soup.find_all('a', {'href' : re.compile(link_pattern)})
        for result in results:
            links.append(result['href'])
    print(f"총 {len(links)}개의 뉴스 링크를 찾았습니다.") # 확인용
    return links

 


 

 이 단계에서의 작업을 모두 정리하여 함수로 만들면 다음과 같다.

 

더보기

 'main()' 함수는 마지막에 모든 작업을 실행할 함수이다. print() 문을 활용하여 콘솔에 결과를 확인한다.

 일단, 현재 단계까지는 10페이지까지 이동하며 네이버 뉴스 플랫폼에 등록된 기사 링크를 무리 없이 가져오는 것을 확인할 수 있다.

 

# module import
from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup
import re

# 변수 설정
QUERY = "주52시간"
search_QUERY = urllib.parse.urlencode({'query':QUERY}, encoding='utf-8')
URL = f"https://search.naver.com/search.naver?&where=news&{search_QUERY}\
        &sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=\
        &docid=&nso=so:r,p:all,a:all&mynews=0"
LINK_PAT = "https:\/\/news\.naver\.com\/main\/read\.nhn\?"

# 마지막 페이지 호출
def get_last_page():
    req = requests.get(URL)
    soup = BeautifulSoup(req.text, 'lxml')
    pagination = soup.find('div', {'class' : 'paging'})
    pages = pagination.find_all("a")
    page_list = []
    for page in pages[-1]:
        page_list.append(int(page.get_text(strip = True)))
    max_page = page_list[-1]
    return max_page
    
# 뉴스 링크 찾기
def get_news_links(page_num, link_pattern):
    links = []
    for page in range(page_num):
        print(f"Scrapping page : {page+1}") # 확인용
        req = requests.get(f"{URL}&start={10*page+1}"); print(req.status_code) # try except로 확인 코드 넣기 나중에
        soup = BeautifulSoup(req.text, 'lxml')
        results = soup.find_all('a', {'href' : re.compile(link_pattern)})
        for result in results:
            links.append(result['href'])
    print(f"총 {len(links)}개의 뉴스 링크를 찾았습니다.") # 확인용
    return links
    
# 모든 작업 진행
def main():
	last_page = get_last_page()
    print(last_page) # 확인용
    news_links = get_news_links(last_page, LINK_PAT)
    print(news_links) # 확인용
    return

# 함수 실행
main()

 

 

 

 

반응형