기억보단 기록을
코린이의 키워드별 네이버 뉴스 기사수 카운터 제작 연대기(feat.크롤링) 본문
그렇다.
또 파이썬으로 응가를 해버렸다.
사실 파이썬을 독학한지 한달도 안됐기 때문에 많은 부분에서 똥이 흘러넘친다.
그럼에도 불구,파이썬을 조금이라도 만질 수 있다는 자신감으로 파이썬으로 또 한번 응가를 시전했다.
이번 응가의 이름은 이름하여 [네이버 뉴스 기사수 카운터]이다 .
이번 응가의 대표적인 특징은 입력한 날짜와 키워드에 따른 네이버 뉴스 기사량을 '최신순'으로 파악할 수 있따는 것!
# 사족 1
[[주의사항]]
이후 스크롤을 내릴 방랑객을 위해 굳이 사족을 달자면,
필자(?)는 컴퓨터 공학과 프로그램에 대해서 아무것도 모르는 문과 나부랭이에 불과하다.
얼마전(약 한달) 까지는 프로그래밍에 전혀 관심도 없었다.
이러한 내용을 종합해서 볼 때, 코딩을 참고하고자 스크롤을 내렸다가
얼기 설기 엮은 조악한 코딩에 분노의 감정을 느낄 수 있다.
코린이에게 훈수를 내려줄 랜선 선생님은 환영한다.
심한 욕도..수용한다.
# 사족 2
[[변명사항]]
조금이라도 욕을 덜 먹기 위해서 먼저 말하자면 크롤링이라는 단어를 사용하지 않은 이유를 위에서 설명하자면,
먼저 다른 곳에서 사용하는 멋진 코드가 아닐 뿐더러
얼마전에 단순히 웹에서 데이터를 뽑는 것은 크롤링이 아니라는 신랄한 비난글을 보았기에
크롤링이라고 말하지 않겠다.
다만 제목에 크롤링이라고 한 것은 이글을 보고 있는 당신을 낚기 위함이다.
제발 내 코딩에 훈수라는 자비를 베풀길..
보통 두 가지 의문이 생길 수 있다.
의문 1 . 왜 기사 수 카운팅이 필요한가? 네이버에서 제공하는 기능이 아닌가?
답변 : 최근 네이버가 대대적인 업데이트를 하면서 키워드에 따른 기사수를 제공하지 않게 되었다.
이로 인해 필자가 진행하고 있는 업무에 크나큰 노가다가 필요하게 되었고,
조금이나마 시간을 아껴보고자 카운터를 만들게 되었다.
의문 2. 굳이 네이버 뉴스를 최신순으로 카운팅 해야하는가?
답변 : 굳이 네이버 뉴스를 최신순으로 카운팅이 필요하겠는가라는 의문이 들 수 있겠지만,
네이버 뉴스의 경우 기본 값이 관련도 순으로 되어 있어 정확한 기사 수를 파악하기 힘들어,
보다 정확한 카운팅을 위해 최신순으로 정렬해 카운팅을하게 했다.
일단 다른 사람이 올려놓은 코드가 어디 없을까하고 검색해봤지만 검색능력의 부족인지 찾지 못했고,
연습이나 할겸 가벼운 마음으로 키워드와 날짜에 따라 네이버 뉴스를 최신순을 정렬해 기사수를 카운팅해주는 코드를 짜게 되었다.
그러나 순탄치 않았다.
첫번째 변명에서 말한 것처럼 나는 CSS, html, python, web, 컴퓨터 공학 등
그 어떤 것에도 익숙하지 않은 문돌이 출신 현대판 디지털 농노일뿐이기 때문이다.
먼저 카운터의 필요성은 인지했지만 만드는 과정이 너무 험난했다.
일단 내가 사용할 라이브러리는 아름다운 숩, BeautifulSoup과 사랑의 리퀘스트, requests 라는 점은 알고 있었고,
대충 그 전에 노마드 코더에서 파이썬 크롤링 수업을 무료로 들었기 때문에
어느 정도 만들 수 있을 것이라는 자신감이 있었다.
하지만 현실은 옹졸한 자신감을 빠르게 잿더미로 만들었다.
(노마드 코더의 파이썬 수업은 굉장히 유익하다. 내가 들었다고 하니 신뢰도가 떨어지는 것 같지만
크롤링에 관심이 있는 사람은 꼭 들어보는 것을 추천한다. 내가 뭐라고 이런 조언을?)
가장 먼저 pagination의 문제가 발생했다.
크롤링 또는 스크래핑의 기본은 페이지수를 파악하는 것이다.
하지만 네이버의 경우 마지막 페이지를 표시해주지 않았따.
노마드 코더에서 실습한 indeed와 stack over flow는 모든 페이지를 표시해주었기 떄문에 어렵지 않게
pagination 값을 구할 수 있었지만 네이버의 경우에는
위 이미지 처럼 전체 페이지를 표시해주지 않았다.
지금 보면 왜 고민했나 싶을 정도로 쉬운 문제이지만,
여기서 제작을 포기할뻔했다.
이 문제는 생각보다 쉽게 해결했다.
먼저 네이버 뉴스는 한 페이지당 10건의 기사를 보여주고, 키워드에 따라 4000건의 기사만 출력하고 더 이상의 결과는 보여주지 않는다.
이것을 역으로 이용해 400페이지 부터 기사가 있는지 없는지 파악하면 된다.
기사가 없을 경우에는 399, 398, 387... 이런식으로 내려가서 확인하면 된다.
물론 1페이지부터 기사를 확인할 수도 있겠지만,
다분히 개인적인 이유에서 맨 뒤에서부터 확인을 하게 만들었다.
내가 검색할 키워드는 기사가 많았다. 크흠
이윽고 수많은 문제들이 발생했다.
기사 수량 파악부터, 기사 존재 유무 파악까지, 단순한 구문 오류와 오타까지 환장의 용광로가 아닐 수가 없었다.
최대한 많은 검색과 레퍼런스를 보면서 문제를 '봉합'을 했지만, 아직도 이게 최선인지는 말할 것도 없고, 맞는건지조차 모르는 것들이 있다.
여기까지가 나의 변명이다.
이제 회초리를 맞을 시간.
랜선 코딩 선생님들은 훈수라는 회초리를 들길 바란다.
참고로 구글 코랩을 사용했으며,
기본적인 문법을 잘 알지도 못하는 상황이라 많은 지적을 달게 받을 작정이다.
from bs4 import BeautifulSoup
import requests
keyword = input("키워드 입력 >")
print("****날짜는 YYYY.MM.DD 형식으로 입력****")
sta_date = input("시작일 입력>")
end_date = input("종료일 입력>")
def news_counter():
pagenum = 400
for i in range(pagenum,0,-1):
pagination = 10*(i-1)+1
URL = f"https://search.naver.com/search.naver?&where=news&query={keyword}&sm=tab_pge&sort=1&photo=0&field=0&reporter_article=&pd=3&ds={sta_date}&de={end_date}&docid=&nso=so:dd,p:from20200903to20210111,a:all&mynews=0&start={pagination}&refresh_start=0"
result = requests.get(URL)
soup = BeautifulSoup(result.text, "html.parser")
results = soup.find("div",{"class":"group_news"})
if results is None:
print(pagenum, "페이지 기사 없음")
# print(URL)
pagenum -= 1
else:
li_tag = soup.find_all("li", {"class":"bx"})
for i in li_tag:
try:
numb = i.attrs['id']
numb = numb.lstrip('sp_nws')
print(numb)
# print(URL)
except KeyError:
return None
news_counter()
내가 만들었지만 보기 좋은 코드는 아닌것 같고, 분명히 더 쉽고 빠르게 할 수 있을 방법이 있을 것처럼 보인다.
하지만 그게 뭔지 모르는게 함정
어찌되었든 코드를 설명해보자면, = 변명을 해보자면
먼저 아름다운 숩 BeautifulSoup과 사랑의 리퀘스트 Requests 라이브러리 형님의 힘을 빌리고자 초청했다.
from bs4 import BeautifulSoup
import requests
그리고 검색할 기본 변수들을 직접 입력할 수 있게 input함수를 이용했다.
keyword = input("키워드 입력 >")
print("****날짜는 YYYY.MM.DD 형식으로 입력****")
sta_date = input("시작일 입력>")
end_date = input("종료일 입력>")
키워드, 시작일, 종료일을 직접 입력할 수 있게 만들었다.
여기서 날짜를 YYYY.MMMM.DDDD 형식으로 입력하게 하기 위해
사족을 출력하게 했다.
물론 개떡같이 입력해도 콩떡으로 바꿔줄 수도 있겠지만,
나는 지금 누굴 배려할 상황이 아니다.
나중에는 20201112,2021-01-12, 이런식으로 입력하더라도 필요한 값에 맞춰 바꾸는 방안도 생각(은) 하고 있다.
# 다시 생각해보니 변수가 너무 많다. 취소.
이렇게 입력해준 변수 값에 따라 데이터를 추출한다.
pagenum = 400
for i in range(pagenum,0,-1):
pagination = 10*(i-1)+1
URL = f"https://search.naver.com/search.naver?&where=news&query={keyword}&sm=tab_pge&sort=1&photo=0&field=0&reporter_article=&pd=3&ds={sta_date}&de={end_date}&docid=&nso=so:dd,p:from20200903to20210111,a:all&mynews=0&start={pagination}&refresh_start=0"
result = requests.get(URL)
soup = BeautifulSoup(result.text, "html.parser")
results = soup.find("div",{"class":"group_news"})
여기서부터 할 말이 많아지는데,
먼저 pagenum은 마지막 페이지 수다. 이건 실제로 실행할때 너무 길어지는 경우가 있어
150, 200 등으로 취향에 맞게 조절해서 쓰고는 한다.
나중에 효율화 방안을 설명할 때 한 번 더 언급할 기회가 있을 듯하다.
다음은 pagination이다.
네이버 뉴스 URL에서는 1페이지를 1, 2페이지를 11, 3페이지를 21 이런 식으로 넣어주어야한다. 그래서
pagination은 10*(pagenum-1)+1으로 정의했다.
다음은 URL.
네이버 URL은 이런식으로 진행된다.
https://search.naver.com/search.naver?&where=news&query=%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90&sm=tab_pge&sort=1&photo=0&field=0&reporter_article=&pd=3&ds=2021.01.10&de=2021.01.12&docid=&nso=so:dd,p:from20210110to20210112,a:all&mynews=0&start=11&refresh_start=0
# 계속해서 삼성전자를 사용하는 이유는 삼성전자주가 계속 올라 배가 아픈 나의 심정을 표현하기 위함이다.
여기서 주목할 만한 내용은 아래와 같다
https://search.naver.com/search.naver?&where=news&query=%EC%82%BC%EC%84%B1%EC%A0%84%EC%9E%90&sm=tab_pge&sort=1&photo=0&field=0&reporter_article=&pd=3&ds=2021.01.10&de=2021.01.12&docid=&nso=so:dd,p:from20210110to20210112,a:all&mynews=0&start=11&refresh_start=0
하나씩 설명해보자면 &query=는 검색어 즉 키워드를 의미하고, &sort=1은 최신순을 의미한다.(0은 관련도순, 2는 오랜된 순)
&ds와 &de은 시작일과 종료일, &start는 앞서 말한 페이지 수를 말한다.
여기서 입력한 변수에 맞게 출력하기 위해 f스트링을 이용했다.
이후 가져온 URL에 맞는 데이터를 호출 및 텍스트로 변환하는 과정(아름다운숩과 사랑의 리퀘스트)을 거쳤다.
result = requests.get(URL)
soup = BeautifulSoup(result.text, "html.parser")
그리고 가져온 html에서 과연 기사가 존재하는지 없는지 찾기 위해
div 태그의"class"="group_news"을 사용했다.
이는 기사가 없는 페이지에서는 없는 결과값으로 기사 존재여부를 판가름 하기 위한 좋은 태그가 되었다...!
물론 이게 효율적인 방법인지는 아직도 헷갈리지만, 실행만 되는 것이 일단 목표였으니 뭐..
results = soup.find("div",{"class":"group_news"})
if results is None:
print(pagenum, "페이지 기사 없음")
# print(URL)
pagenum -= 1
만약 가져온 페이지의 html에서 div 태그의"class"="group_news"가 없다면 해당 페이지에 기사가 없는 것이므로
if 구문을 사용해 결과 값이 없는 경우를 가정했다.
# == None 보다 is None이 더 빠르다는 글을 보고 is None을 사용했다.
그리고 결과 값이 없을 경우, 즉 결과 값이 None일 경우에는
pagenum과 페이지 기사 없음이라고 출력되게 했고 pagenum -=1값을 주어 내림차순이 되게 만들었다.
URL은 결과페이지가 없는경우 맞는 결과를 가지고 도출해낸 건지 사용했지만, 지금은 사용하지 않아 주석처리했다.
else:
li_tag = soup.find_all("li", {"class":"bx"})
for i in li_tag:
try:
numb = i.attrs['id']
numb = numb.lstrip('sp_nws')
print(numb)
# print(URL)
except KeyError:
return None
그리고 대망의 else.
결과값이 있을 경우이다. 즉 기사가 있다는 뜻인데, 이 경우에는 기사값을 파악해야해서 또 for 구문과 try, except구문을 사용했따.
페이지에 뉴스기사가 있는 경우 아래와같이 이런 태그가 보여진다.
해당 태그는 기사 하나에 해당하는 걸로 보여진다
(계속해서 말하지만 html을 잘모른다. 분명 의미가 있는 태그겠지)
여기서 <li class="bx" id=sp_nws11">...</li>를 선택하면 기사 아래와 같은 기사 하나가 선택된다.
(계속해서 말하지만 html을 잘모른다. 분명 더 깊은 의미가 있는 태그겠지)
그리고 sp_nws뒤에 있는 숫자는 변수에 따른 기사 수를 말한다!
즉 마지막 페이지에 있는 sp_nws값만 구하면 된다는 것!
그래서 기사가 있는 마지막 페이지에서 li class="bx"을 모두 찾고 거기서 id값을 구했다.
li_tag = soup.find_all("li", {"class":"bx"})
for i in li_tag:
try:
numb = i.attrs['id']
numb = numb.lstrip('sp_nws')
print(numb)
# print(URL)
except KeyError:
return None
여기서 try, execpt구문을 사용한건. 10개를 넘어가면 KeyError가 발생했기 때문.
try로 결과를 출력하고, KeyError는 execept와 return None으로 틀어막았다.
이 역시 쓰나미가 뒤덮은 강둑을 손가락으로 막은 셈이라고 생각되지만, 어떻게 바꿔야할지는 모르겠다.
어쨋든 찾은 sp_nws값에서 sp_nws를 지워내고 출력하면 끝!
물론 출력값이 이쁜 것이 아니고 보완할점이 많지만 출력은 아래와 같이 된다.
일단 키워드는 파이썬, 일자는 2021.01.11-2021.01.12로 잡았고,
400페이지부터 할 경우 너무 길어져, 5페이지부터 내림차순으로 수정해 결과값을 출력했다.
5페이지부터 내림차순으로
div 태그의"class"="group_news"가 있는지 파악한다.
3페이지까지는 div 태그의"class"="group_news"가 없었고,
2페이지에는 div 태그의"class"="group_news"가 존재해
else로 넘어갔다.
else:
li_tag = soup.find_all("li", {"class":"bx"})
for i in li_tag:
try:
numb = i.attrs['id']
numb = numb.lstrip('sp_nws')
print(numb)
# print(URL)
except KeyError:
return None
그리고 11, 12....16, 17은 id=sp_nws[]값을 찾아서 sp_nws를 제거하고 남은 값을 출력한 값이다.
물론 마지막 값만 뽑아 검색하신 조건에 따른 기사는 총 {}건 있습니다.로 출력하면 좋겠지만
어뜨케 하는지 모른다.
나중에 보완할 예정이다.
여기까지가 내 1차 [네이버 뉴스 기사수 카운터] 제작 연대기이다.
정말 고생했고, 비록 남루한 코드일지라도 결국 완성한 나에게 칭찬을 해주고 싶다.
물론 보완할 점은 수도 없이 많다.
일단 효율이 낮다.
앞서 실행해본 조건처럼 기사가 20건정도일 경우 400페이지부터 찾는 것은 너무나도 비효율적이다.
그래서 술게임에서 하던 up&down방식을 이용해 효율화를 진행할 생각이다.
먼저 400,300,200,100순으로 페이지에 기사 존재 유무를 파악하고,
거기서 up&down결과를 파악해 점점 범위를 좁혀갈 예정.
다만 이게 더 비효율적일 수도 있다는 생각이 들어 고민중이다.
두 번째는 출력값이 너무 이쁘지 않다는 것.
%페이지 기사 없음은 내가 출력이 잘 진행되고 있는 되고 있는지 궁금해서 넣은 것이라고 쳐도,
11, 12....16, 17은 너무 이쁘지 않다.
마지막 값만 뽑아 검색하신 조건에 따른 기사는 총 {}건 있습니다. 로 바꾸는 것을 생각하고 있다.
세 번째는 기사가 4,000건이 넘어갈 경우 대응방안이다.
4,000건이 넘어갈 경우는 일자를 더욱 좁혀 진행하는게 효율적이다.
예를 들어 키워드에 따른 1년 간 기사가 4,000건이 넘어 간다면
월, 보름, 5일로 나누어 진행하는 것이 4,000건을 넘는 키워드의 기사수를 파악할 수있는 방법이다.
이는 4,000건이 넘어갈 경우 6개월, 3개월, 1개월, 보름,...순으로 범위를 좁혀 기사를 카운팅할 수있는
코드를 짜봐야겠다는 보완점을 남긴다.
네 번째는 에러를 잡을 방안을 생각해야한다는것.
특히 시작일과 종료일에서 에러값이 잘 발생한다. 형식에서 조금일라도 벗어나게 될 경우 1990년부터 기사를 찾아내는 최악의 상황에 빠진다. 그래서 개떡같이 입력해도 콩떡같이 입력할 방안, 또는 개떡같이 입력하면, 뱉어낼 방안을 생각해야 할 것같다.
물론 내가 이 카운터를 출품하거나 누구한테 자랑할 것은 아니지만,
필요에 의해 만들었고, 실제로 유용하게 사용해서 나름 애착이 가는 친구이다.
요정도는 크롤링이라고 할 수 없고, 파이썬을 사용했다고 한다면
길가다가 뺨을 후려맞을 정도로 조악하지만 말이다.
다만 지나가는 랜선 선생님께서 랜선 회초리를 마음껏 휘둘러 주길 바라는 마음이다.
어찌되었든 나의 첫번째 파이썬으로 응가하기, 네이버 뉴스 기사 카운터는 연대기는 여기까지이다.
이후에 조금 더 공부하면서 코드를 수정하고 보완하면 추가글을 올리겠다.
응가임에도 글을 작성하는 이유.
기억보다는 기록을.