[DEV] 기록

[크롤링] 경향신문 크롤링

꾸준함. 2021. 4. 12. 01:27

개요

친구가 자료수집을 위해 크롤링을 해달라고 요청해서 사전지식이 없는 상태에서 진행해봤습니다.

대부분의 내용은 yoonpunk.tistory.com/6 블로그에서 참고했으므로 도움이 되었다면 해당 블로그에 가서 좋아요를 눌러주시면 될 것 같습니다!

 

진행 과정

1. 우선, yoonpunk님이 작성하신 동아일보 신문기사 크롤링하기 코드를 확인해봅시다.

 

""" 동아일보 특정 키워드를 포함하는, 특정 날짜 이전 기사 내용 크롤러(정확도순 검색)
python [모듈 이름] [키워드] [가져올 페이지 숫자] [결과 파일명]
한 페이지에 기사 15개
"""
import sys
from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import quote
TARGET_URL_BEFORE_PAGE_NUM = "http://news.donga.com/search?p="
TARGET_URL_BEFORE_KEWORD = '&query='
TARGET_URL_REST = '&check_news=1&more=1&sorting=3&search_date=1&v1=&v2=&range=3'
# 기사 검색 페이지에서 기사 제목에 링크된 기사 본문 주소 받아오기
def get_link_from_news_title(page_num, URL, output_file):
for i in range(page_num):
current_page_num = 1 + i*15
position = URL.index('=')
URL_with_page_num = URL[: position+1] + str(current_page_num) \
+ URL[position+1 :]
source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
soup = BeautifulSoup(source_code_from_URL, 'lxml',
from_encoding='utf-8')
for title in soup.find_all('p', 'tit'):
title_link = title.select('a')
article_URL = title_link[0]['href']
get_text(article_URL, output_file)
# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
source_code_from_url = urllib.request.urlopen(URL)
soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
content_of_article = soup.select('div.article_txt')
for item in content_of_article:
string_item = str(item.find_all(text=True))
output_file.write(string_item)
# 메인함수
def main(argv):
if len(argv) != 4:
print("python [모듈이름] [키워드] [가져올 페이지 숫자] [결과 파일명]")
return
keyword = argv[1]
page_num = int(argv[2])
output_file_name = argv[3]
target_URL = TARGET_URL_BEFORE_PAGE_NUM + TARGET_URL_BEFORE_KEWORD \
+ quote(keyword) + TARGET_URL_REST
output_file = open(output_file_name, 'w')
get_link_from_news_title(page_num, target_URL, output_file)
output_file.close()
if __name__ == '__main__':
main(sys.argv)
view raw .py hosted with ❤ by GitHub

 

* 여기서 핵심은 상단에 정의된 URL 관련 상수와 get_link_from_news_title, get_text 메서드입니다.

 

2. 친구가 요청한 업무는 경향신문 크롤링이므로 경향신문 홈페이지에 들어간 후 아무 키워드를 작성해봅니다.

 

 

 

 

URL을 확인해보면 아래와 같습니다.

search.khan.co.kr/search.html?stb=khan&q=남북경제협력&pg=1&sort=2

 

URL을 하나하나 뜯어봅시다.

검색창에 해당하는 URL: search.khan.co.kr/search.html?stb=khan

검색어에 해당하는 URL: &q=[검색어]
페이지에 해당하는 URL: &pg=[원하는 페이지]

정렬 기준에 해당하는 URL: &sort=[원하는 정렬 타입]

* 최신순: 1, 정확도순: 2, 오래된순: 3

 

저 같은 경우에는 친구 논문에 필요한 자료이므로 정확도순이 좋을 것 같네요.

추가 적으로 동아일보와 달리 경향신문은 페이지가 순차적으로 1씩 증가하는 것을 확인할 수 있었습니다.

(ex. pg=1, pg=2, pg=3, ...)

 

이제 기존 코드 URL 상수를 아래와 같이 변경해봅시다.

 

TARGET_URL_BEFORE_PAGE_NUM = "http://search.khan.co.kr/search.html?stb=khan"
TARGET_URL_BEFORE_KEWORD = '&q='
TARGET_URL_PAGE = '&pg='
TARGET_URL_SORT = '&sort=2'
view raw .py hosted with ❤ by GitHub

 

3. 이제 검색한 페이지에서 각 뉴스 기사 링크를 찾아줘야 합니다.

 

개발자 도구 F12를 누르고 뉴스 링크들이 어떤 태그 내에 있는지 확인을 해줍니다.

 

 

 

 

* 확인해본 결과, phArtc 클래스를 가지는 dl 태그 내에 링크가 위치한 것을 확인할 수 있었습니다.

 

이제 기존 코드의 get_link_from_news_title 메서드를 아래와 같이 수정해봅시다.

 

# 기사 검색 페이지에서 기사 제목에 링크된 기사 본문 주소 받아오기
def get_link_from_news_title(page_num, URL, output_file):
for i in range(page_num):
current_page_num = i + 1
URL_with_page_num = URL + TARGET_URL_PAGE + str(current_page_num) + TARGET_URL_SORT
source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
soup = BeautifulSoup(source_code_from_URL, 'lxml',
from_encoding='utf-8')
for title in soup.find_all('dl', 'phArtc'):
title_link = title.select('a')
article_URL = title_link[0]['href']
get_text(article_URL, output_file)
view raw .py hosted with ❤ by GitHub

 

 

4. 마지막으로 뉴스 기사 내 제목, 기자님 성함, 그리고 기사 원문이 어디 태그 내에 위치하는지를 확인해야 합니다.

 

마찬가지로 개발자 도구 F12를 눌러 확인을 해봅시다.

 

 

 

 

* 우선, 제목과 기자님 성함 및 이메일은 subject 클래스를 가지는 div 태그 내에 위치한 것을 확인할 수 있었습니다.

 

이어서, 기사 원문이 어디 태그 내에 위치하는지를 확인해봅시다.

 

 

 

 

* 확인해본 결과, art_body 클래스를 가지는 div 태그context_text 클래스를 가지는 p 태그들을 모두 모으면 기사 원문을 발췌할 수 있는 것을 확인할 수 있습니다.

 

따라서, get_text 메서드를 아래와 같이 수정해줍니다.

 

# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
req = Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
source_code_from_url = urlopen(req).read()
soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
# 제목
content_of_article = soup.select('div.subject')
for item in content_of_article:
string_item = str(item.find_all(text=True))
output_file.write(string_item)
# 본문
content_of_article = soup.select('div.art_body')
for item in content_of_article:
string_item = str(item.find_all("p", {"class": "content_text"}))
output_file.write(string_item)
view raw .py hosted with ❤ by GitHub

 

* 주의: 기존 코드대로 urllib.request.urlopen(URL)를 통해 링크를 열면 HTTP 403 에러가 발생합니다.

 

따라서, stackoverflow.com/questions/16627227/http-error-403-in-python-3-web-scraping 링크 답변처럼 HTTP 요청에 헤더를 추가해줘야 합니다.

해당 부분 코드는 아래와 같습니다.

 

 

import urllib.request
from urllib.parse import quote
from urllib.request import Request, urlopen
# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
req = Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
source_code_from_url = urlopen(req).read()
view raw .py hosted with ❤ by GitHub

 

전체 코드

 

""" 경향신문 특정 키워드를 포함하는, 특정 날짜 이전 기사 내용 크롤러(정확도순 검색)
python [모듈 이름] [키워드] [가져올 페이지 숫자] [결과 파일명]
한 페이지에 기사 10개
"""
import sys
from bs4 import BeautifulSoup
import urllib.request
from urllib.parse import quote
from urllib.request import Request, urlopen
TARGET_URL_BEFORE_PAGE_NUM = "http://search.khan.co.kr/search.html?stb=khan"
TARGET_URL_BEFORE_KEWORD = '&q='
TARGET_URL_PAGE = '&pg='
TARGET_URL_SORT = '&sort=2'
# 기사 검색 페이지에서 기사 제목에 링크된 기사 본문 주소 받아오기
def get_link_from_news_title(page_num, URL, output_file):
for i in range(page_num):
print(i)
current_page_num = i + 1
URL_with_page_num = URL + TARGET_URL_PAGE + str(current_page_num) + TARGET_URL_SORT
source_code_from_URL = urllib.request.urlopen(URL_with_page_num)
soup = BeautifulSoup(source_code_from_URL, 'lxml',
from_encoding='utf-8')
for title in soup.find_all('dl', 'phArtc'):
title_link = title.select('a')
article_URL = title_link[0]['href']
get_text(article_URL, output_file)
# 기사 본문 내용 긁어오기 (위 함수 내부에서 기사 본문 주소 받아 사용되는 함수)
def get_text(URL, output_file):
req = Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
source_code_from_url = urlopen(req).read()
soup = BeautifulSoup(source_code_from_url, 'lxml', from_encoding='utf-8')
# 제목
content_of_article = soup.select('div.subject')
for item in content_of_article:
string_item = str(item.find_all(text=True))
output_file.write(string_item)
# 본문
content_of_article = soup.select('div.art_body')
for item in content_of_article:
string_item = str(item.find_all("p", {"class": "content_text"}))
output_file.write(string_item)
# 메인함수
def main(argv):
if len(argv) != 4:
print("python [모듈이름] [키워드] [가져올 페이지 숫자] [결과 파일명]")
return
keyword = argv[1]
page_num = int(argv[2])
output_file_name = argv[3]
target_URL = TARGET_URL_BEFORE_PAGE_NUM + TARGET_URL_BEFORE_KEWORD \
+ quote(keyword)
output_file = open(output_file_name, 'w', encoding='UTF-8')
get_link_from_news_title(page_num, target_URL, output_file)
output_file.close()
if __name__ == '__main__':
main(sys.argv)
view raw .py hosted with ❤ by GitHub

 

실행 방법

 

저 같은 경우 간단한 코드이므로 주피터 노트북에서 실행했습니다.

쥬피터 노트북에서 실행하신다면 아래와 같이 실행해주시면 됩니다.

 

 

 

sys.argv 매개변수에 대해 설명하자면

모듈: python3

검색어: 남북경제협력

가져올 페이지 숫자: 43 (총 425건 검색이므로 43)

결과 파일명: final_test.txt

 

결과물

 

 

 

 

태그들이 모두 존재하기 때문에 썩 마음에 드는 결과물은 아니지만, 친구가 이 정도면 괜찮다고 해서 여기서 끝냈습니다.

정규 문법을 사용한다면 불필요한 태그들과 특수문자들도 충분히 지울 수 있을 것이라고 봅니다!

 

출처

yoonpunk.tistory.com/6

stackoverflow.com/questions/16627227/http-error-403-in-python-3-web-scraping

반응형