반응형

안녕하세요.

크롤러를 만들다 보면,

내가 가져오고 싶은 부분이 어디에 위치해 있는지 알아야 할 때가 있습니다.

그 때 개발자도구를 사용하면 해당 사이트 html의 어느 부분에 있는지 금방 파악이 가능합니다.

지금부터 그 방법을 같이 알아보도록 하겠습니다.

 

여기에서 사용할 예시 사이트는 네이버 뉴스의 어느 한 기사 입니다.

- 예시 사이트: https://n.news.naver.com/article/666/0000019702

위의 사이트를 클릭해서 들어가 주세요.

위와 같은 기사가 보일 것입니다.

1. F12키를 눌러 개발자 도구 열기

우리가 만약 기사 제목을 수집하고 싶다고 가정해 봅시다.

제목을 가져오려면 html에 어디에 제목이 위치해 있는지 알아야 할 것입니다.

이 때 개발자 도구를 열어줍니다.

개발자 도구는 F12키를 누르면 확인할 수 있습니다.

위와 같이 우측에 창이 생길 것입니다.

하지만 소스 코드 전체를 한눈에 보기엔 어려움이 있습니다.

제목 또한 어디에 있는지 알기 어렵습니다. 

이럴 때는 요소를 클릭하면 바로 알 수 있습니다.

 

2. 요소 선택하기

위의 개발자 도구 창을 보면 좌측 상단에 커서 아이콘이 보입니다.

이 아이콘은 페이지에서 요소를 선택해 검사할 수 있는 기능을 가지고 있습니다.

위의 사진에서 빨간색 박스로 표시된 아이콘을 클릭 후 왼쪽에 기사에 내가 확인하고 싶은 기사에 커서를 가져가면

아래 사진과 같이 파란색으로 표시가 됩니다.

그후 오른쪽 창을 보면 아까는 보이지 않았던 제목 부분이 보입니다.

여기서 원래대로라면 body 밑에 div class name이 end_container인것 밑에 div ct_wrap 밑에 ct_scroll_wrapper 밑에 newsct... 밑에 이런 식으로 쭉쭉 타고 내려가서 h2에 span 에 제목이 위치해 있는 것을 확인할 수 있겠습니다만 귀찮고 복잡합니다.

이렇게 하나하나 눈으로 봐도 되지만 이 대신에  copy selector를 사용하면

더 간단하게 Beautiful Soup의 html selector를 사용해서 바로 가져올 수 있습니다.

3. selector 복사하기

copy selector는 간단합니다.

아까 찾은 개발자 도구에서 제목부분 우클릭을 하고 Copy(복사) 클릭, Copy selector 클릭하면 복사가 됩니다.

이렇게 하고 Ctrl+V를 해서 붙여넣기를 하면 아래와 같이 복사됩니다.

#title_area > span

위의 뜻은 id가 title_area 아래의 span 태그에 제목이 위치해 있다는 것을 뜻합니다.

이것을 만약 Beautiful Soup의 select로 가져오려면 아래처럼 작성하면 제목을 가져오는 것을 확인할 수 있습니다.

#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests

#테스트 기사
test_url = "https://n.news.naver.com/article/666/0000019702"

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

news = requests.get(test_url,headers=headers)
news_html = BeautifulSoup(news.text,"html.parser")

title = news_html.select("#title_area > span")
title
 

결과: [<span>폭염으로 닭 폐사에 크기까지 줄어든 계란…“왕란이 사라졌다”</span>]

 

이런 식으로 다른 부분들도 개발자 도구를 이용하면 간단하게 가져올 수 있습니다.

반응형
반응형

안녕하세요. 정말 오랜만의 포스팅입니다.

그동안 올렸던 네이버 뉴스 크롤러 최신 버전을 공유드리고자 합니다.

그 전 버전인 아래 포스팅에서는

https://wonhwa.tistory.com/46#comment18426434

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

 

Selenium을 사용하여 크롤러를 만들었지만 날짜 내용이 빠지고 Selenium을 가동 시간이 길어 다소 불편한 점도 있었습니다.

그래서 이번에는 Selenium을 사용하지 않고 바로 주소를 파싱하여 네이버 뉴스 크롤링 하는 방법을 공유드리고자 합니다.

크롤링 제작의 자세한 내용은 그 전 포스팅에 나와있으니

자세한 설명이 필요하신 분들은 제 블로그 Crawling 카테고리에서 확인해 주시면 됩니다:)

그럼 시작하도록 하겠습니다.

1. 라이브러리 불러오기
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import datetime
from tqdm import tqdm
import sys

 

2. 크롤링 시 필요한 함수 만들기
# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)

# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)

def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls    

# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

#html생성해서 기사크롤링하는 함수 만들기(url): 링크를 반환
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")

    url_naver = html.select("div.group_news > ul.list_news > li div.news_area > div.news_info > div.info_group > a.info")
    url = news_attrs_crawler(url_naver,'href')
    return url

 

3. 크롤링할 네이버 뉴스 URL 추출하기
#####뉴스크롤링 시작#####

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   


# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
news_dates = []
for i in url:
    url = articles_crawler(url)
    news_url.append(url)


#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist

    
#제목, 링크, 내용 담을 리스트 생성
news_url_1 = []

#1차원 리스트로 만들기(내용 제외)
makeList(news_url_1,news_url)

#NAVER 뉴스만 남기기
final_urls = []
for i in tqdm(range(len(news_url_1))):
    if "news.naver.com" in news_url_1[i]:
        final_urls.append(news_url_1[i])
    else:
        pass

 

4.뉴스 본문 및 날짜 크롤링하기
# 뉴스 내용 크롤링

for i in tqdm(final_urls):
    #각 기사 html get하기
    news = requests.get(i,headers=headers)
    news_html = BeautifulSoup(news.text,"html.parser")
    # 뉴스 제목 가져오기
    title = news_html.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    if title == None:
        title = news_html.select_one("#content > div.end_ct > div > h2")
    
    # 뉴스 본문 가져오기
    content = news_html.select("article#dic_area")
    if content == []:
        content = news_html.select("#articeBody")
        
    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=str(title))
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    news_titles.append(title)
    news_contents.append(content)

    try:
        html_date = news_html.select_one("div#ct> div.media_end_head.go_trans > div.media_end_head_info.nv_notrans > div.media_end_head_info_datestamp > div > span")
        news_date = html_date.attrs['data-date-time']
    except AttributeError:
        news_date = news_html.select_one("#content > div.end_ct > div > div.article_info > span > em")
		news_date = re.sub(pattern=pattern1,repl='',string=str(news_date))
    # 날짜 가져오기
    news_dates.append(news_date)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(final_urls)
print("\n[뉴스 내용]")
print(news_contents)

print('news_title: ',len(news_titles))
print('news_url: ',len(final_urls))
print('news_contents: ',len(news_contents))
print('news_dates: ',len(news_dates))

 

5. 데이터 프레임 만들고 CSV파일로 저장하기
###데이터 프레임으로 만들기###
import pandas as pd

#데이터 프레임 만들기
news_df = pd.DataFrame({'date':news_dates,'title':news_titles,'link':final_urls,'content':news_contents})
news_df

#중복 행 지우기
news_df = news_df.drop_duplicates(keep='first',ignore_index=True)
print("중복 제거 후 행 개수: ",len(news_df))

#데이터 프레임 저장
now = datetime.datetime.now() 
news_df.to_csv('{}_{}.csv'.format(search,now.strftime('%Y%m%d_%H시%M분%S초')),encoding='utf-8-sig',index=False)

 

이렇게 업그레이드 된 크롤러가 완성이 되었습니다 :)

예시 실행화면

전체 코드
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import datetime
from tqdm import tqdm
import sys

# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)

# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)

def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls    

# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

#html생성해서 기사크롤링하는 함수 만들기(url): 링크를 반환
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")

    url_naver = html.select("div.group_news > ul.list_news > li div.news_area > div.news_info > div.info_group > a.info")
    url = news_attrs_crawler(url_naver,'href')
    return url


#####뉴스크롤링 시작#####

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   


# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
news_dates = []
for i in url:
    url = articles_crawler(url)
    news_url.append(url)


#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist

    
#제목, 링크, 내용 담을 리스트 생성
news_url_1 = []

#1차원 리스트로 만들기(내용 제외)
makeList(news_url_1,news_url)

#NAVER 뉴스만 남기기
final_urls = []
for i in tqdm(range(len(news_url_1))):
    if "news.naver.com" in news_url_1[i]:
        final_urls.append(news_url_1[i])
    else:
        pass

# 뉴스 내용 크롤링

for i in tqdm(final_urls):
    #각 기사 html get하기
    news = requests.get(i,headers=headers)
    news_html = BeautifulSoup(news.text,"html.parser")

    # 뉴스 제목 가져오기
    title = news_html.select_one("#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    if title == None:
        title = news_html.select_one("#content > div.end_ct > div > h2")
    
    # 뉴스 본문 가져오기
    content = news_html.select("article#dic_area")
    if content == []:
        content = news_html.select("#articeBody")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=str(title))
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    news_titles.append(title)
    news_contents.append(content)

    try:
        html_date = news_html.select_one("div#ct> div.media_end_head.go_trans > div.media_end_head_info.nv_notrans > div.media_end_head_info_datestamp > div > span")
        news_date = html_date.attrs['data-date-time']
    except AttributeError:
        news_date = news_html.select_one("#content > div.end_ct > div > div.article_info > span > em")
        news_date = re.sub(pattern=pattern1,repl='',string=str(news_date))
    # 날짜 가져오기
    news_dates.append(news_date)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(final_urls)
print("\n[뉴스 내용]")
print(news_contents)

print('news_title: ',len(news_titles))
print('news_url: ',len(final_urls))
print('news_contents: ',len(news_contents))
print('news_dates: ',len(news_dates))

###데이터 프레임으로 만들기###
import pandas as pd

#데이터 프레임 만들기
news_df = pd.DataFrame({'date':news_dates,'title':news_titles,'link':final_urls,'content':news_contents})

#중복 행 지우기
news_df = news_df.drop_duplicates(keep='first',ignore_index=True)
print("중복 제거 후 행 개수: ",len(news_df))

#데이터 프레임 저장
now = datetime.datetime.now() 
news_df.to_csv('{}_{}.csv'.format(search,now.strftime('%Y%m%d_%H시%M분%S초')),encoding='utf-8-sig',index=False)

 

참고자료

https://wonhwa.tistory.com/8?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 크롤링하기(1)

오늘은 전에 알려드린 오픈 API 사용과 더불어 파이썬으로 크롤러 만드는 방법을 소개하도록 하겠습니다. 네이버 오픈 API의 경우 사용하는 방법을 알면 간편하게 뉴스, 블로그, 카페 등등을 크롤

wonhwa.tistory.com

https://wonhwa.tistory.com/11?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 크롤링하기(2)

오늘은 저번에 올린 네이버 뉴스 크롤링(1)에서 한 단계 업그레이드된 뉴스 크롤러를 공유하려 합니다 :) 1편에서는 뉴스 1페이지만 크롤링을 할 수 있었는데요 2편에서는 여러 페이지를 크롤링

wonhwa.tistory.com

https://wonhwa.tistory.com/46?category=996518 

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

 

마무리

크롤링 하실때 더 빠르고 편하게 하시길 바랍니다:)

기타 의견이 있으시거나 안되는 부분이 있으시다면 댓글 남겨주세요!

++08/10 날짜 크롤링 에러 오류 및 title 길이 수정

++08/11 오류 수정 및 df 중복 행 제거 추가

++23/08/26 뉴스 내용 가져오는 selector 수정

반응형
반응형

안녕하세요!
크롤링 포스팅을 오랜만에 진행하네요~
이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다.
지난 크롤러에서 아쉬웠던점은
언론마다 홈페이지 html 구조가 달라 내용을 제대로 가져오기 어려웠는데요
이번에는 이러한 문제점을 극복하고자
검색 결과로 나오는 기사 중 '네이버 뉴스(news.naver.com)'에 해당 기사가 존재하는 url만 가져와서
내용도 깔끔하게 크롤링할 수 있도록 만들어 보았습니다.

그럼 시작하겠습니다!

1. 필요한 라이브러리들 불러오기
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager

#웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

2. 크롤링시 필요한 함수 만들기
# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)


# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search,start_pg,end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ",url)
        return url
    else:
        urls= []
        for i in range(start_pg,end_pg+1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ",urls)
        return urls

3. 검색어를 받아서 네이버 검색 결과 페이지 url생성하기
##########뉴스크롤링 시작###################

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")

#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   

# naver url 생성
search_urls = makeUrl(search,page,page2)

여기까지는 그전 크롤러와 같습니다.
이제 본격적으로 네이버 뉴스의 기사들만 가져와 보겠습니다.

4. Selenium 사용하여 네이버 기사 url 추출하기

그 전에는 네이버 검색 결과 페이지 html에 필요한 url, 기사제목 등이 다 나와있어 바로 가져올 수 있었는데요,
네이버 뉴스만 가져오기 위해서는 다른 방법으로 접근해야 합니다.

예를 들어, '코로나' 관련 하여 검색을 진행했을 때, 하이라이트가 있는 '네이버 뉴스'를 클릭해야
아래처럼 네이버 뉴스 페이지의 해당 기사로 접속할 수 있습니다.

그래서 해당 url을 가져오기 위해
개발자 도구를 사용해 (F12)
검색 결과 페이지에 있는 네이버 뉴스 링크를 바로 가져와 파싱하려고

위의 사진 url인
('/p/crd/rd?m=1&px=405&py=267&sx=405&sy=167&p=hmkWFlprvTVssTYb7QKssssstb0-460233&q=%EC%BD%94%EB%A1%9C%EB%82%98&ie=utf8&rev=1&ssc=tab.news.all&f=news&w=news&s=rswJojqRF8DwzkNAwDOPrg%3D%3D&time=1645669880103&abt=%5B%7B%22eid%22%3A%2210%22%2C%22vid%22%3A%2218%22%7D%5D&a=nws*h.nav&r=1&i=88000127_000000000000000011024241&u=https%3A%2F%2Fnews.naver.com%2Fmain%2Fread.naver%3Fmode%3DLSD%26mid%3Dsec%26sid1%3D102%26oid%3D003%26aid%3D0011024241')
위 링크를 가져와 바로 주소창에 넣어보았으나....

제대로 열 수 없었습니다.
그 이유는

onclick, 즉 클릭을 하여야 링크가 올바른 링크로 바뀌어서 불러오게 되는 구조이기 때문입니다.
그래서 셀레니움을 사용하여 클릭 동작을 주고 해당 기사의 올바른 url 을 가져오는 방법을 선택하였습니다.
그래서 구글 웹드라이버도 크롬 버전에 맞게 설치하여 준 후,
웹드라이버로 해당 요소들을 가져와 클릭동작을 넣고 창 전환 후 현재 url을 가져왔습니다.
그리고 네이버 기사들만 naver_url 리스트에 추가해 주었습니다.

## selenium으로 navernews만 뽑아오기##
# 버전에 상관 없이 os에 설치된 크롬 브라우저 사용
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)


# selenium으로 검색 페이지 불러오기 #

naver_urls=[]

for i in search_urls:
    driver.get(i)
    time.sleep(1) #대기시간 변경 가능

    # 네이버 기사 눌러서 제목 및 본문 가져오기#
    # 네이버 기사가 있는 기사 css selector 모아오기
    a = driver.find_elements(By.CSS_SELECTOR,'a.info')

    # 위에서 생성한 css selector list 하나씩 클릭하여 본문 url얻기
    for i in a:
        i.click()

        # 현재탭에 접근
        driver.switch_to.window(driver.window_handles[1])
        time.sleep(3) #대기시간 변경 가능

        # 네이버 뉴스 url만 가져오기

        url = driver.current_url
        print(url)

        if "news.naver.com" in url:
            naver_urls.append(url)
  
        else:
            pass
        
        # 현재 탭 닫기
        driver.close()

        # 다시처음 탭으로 돌아가기(매우 중요!!!)
        driver.switch_to_window(driver.window_handles[0])

print(naver_urls)

selenium을 사용할 때는 동작 후 로딩 시간을 고려해 time.sleep()을 넣어 주는 것이 좋습니다.
(때문에 이 부분에서 크롤링 할 내용이 많으면 많을수록 시간이 걸립니다.)
여차저차 해서 드디어 우리에게 필요한 네이버 뉴스 기사들을 얻을 수 있었습니다!

5. 기사 제목 및 본문 크롤링하기

그 다음에는 네이버뉴스의 html구조만 파악하여 제목과 본문을 가져오면 됩니다.
네이버뉴스의 경우, 기사 제목은

div#ct > div.media_end_head.go_trans > div.media_end_head_title > h2

여기에 들어가 있고,
기사 내용은

div#dic_area

이곳에 들어가 있습니다.
위의 경로를 확인하려면 웹페이지에서 F12를 누른 후, 해당 요소를 클릭 후 우클릭 > Copy > Copy selector로 보다 편하게 확인할 수 있습니다.
그리고 내용을 가져올때, html태그도 다 가져오기 때문에
html 태그를 정규식 패턴을 이용하여 제거 후 각각 저장해 주도록 하겠습니다.

###naver 기사 본문 및 제목 가져오기###

# ConnectionError방지
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102" }

titles = []
contents=[]
for i in naver_urls:
    original_html = requests.get(i,headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과확인시
    #print(html)
    
    #뉴스 제목 가져오기
    title = html.select("div.content > div.article_header > div.article_info > h3")
    # list합치기
    title = ''.join(str(title))
    # html태그제거
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1,repl='',string=title)
    titles.append(title)

    #뉴스 본문 가져오기

    content = html.select("div.content > div#articleBody > div#articleBodyContents")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))
    
    #html태그제거 및 텍스트 다듬기
    content = re.sub(pattern=pattern1,repl='',string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2,'')

    contents.append(content)

print(titles)
print(contents)

확실히 전과 비교하여 깔끔하게 출력이 됩니다.
나머지 필요한 전처리들은 분석시 진행하면 됩니다.

6. DataFrame으로 만들기

그 후 결과물을 데이터프레임으로 정리하여 보기 좋게 해 줍니다.

#데이터프레임으로 정리(titles,url,contents)
import pandas as pd

news_df = pd.DataFrame({'title':titles,'link':naver_urls,'content':contents})

news_df.to_csv('NaverNews_%s.csv'%search,index=False,encoding='utf-8-sig')

보통 데이터 프레임을 csv 파일로 저장할 때,
encoding 설정을 따로 해 주지 않으면 기본으로 utf-8로 저장이 되는데
이 파일을 그냥 열면 한글이 다 깨져 보입니다.
그래서 이를 방지하기 위해 encoding='utf-8-sig'로 설정을 해 줍니다.
그러면

이렇게 csv파일을 열어도 한글이 깨지지 않습니다 ㅎㅎ
전체 내용을 보고 싶으시다면 아래 파일을 확인해 주세요:)
저는 코로나 검색어로 2페이지만 크롤링 하였습니다.

NaverNews_코로나.csv
0.05MB
전체 코드
# 크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
import re
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager


# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)


# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
# 입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num + 1
    else:
        return num + 9 * (num - 1)


# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search, start_pg, end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(
            start_page)
        print("생성url: ", url)
        return url
    else:
        urls = []
        for i in range(start_pg, end_pg + 1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ", urls)
        return urls


##########뉴스크롤링 시작###################

# 검색어 입력
search = input("검색할 키워드를 입력해주세요:")

# 검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):"))  # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ", page, "페이지")
# 검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):"))  # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ", page2, "페이지")

# naver url 생성
search_urls = makeUrl(search, page, page2)

## selenium으로 navernews만 뽑아오기##

# 버전에 상관 없이 os에 설치된 크롬 브라우저 사용
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)

# selenium으로 검색 페이지 불러오기 #

naver_urls = []

for i in search_urls:
    driver.get(i)
    time.sleep(1)  # 대기시간 변경 가능

    # 네이버 기사 눌러서 제목 및 본문 가져오기#
    # 네이버 기사가 있는 기사 css selector 모아오기
    a = driver.find_elements(By.CSS_SELECTOR, 'a.info')

    # 위에서 생성한 css selector list 하나씩 클릭하여 본문 url얻기
    for i in a:
        i.click()

        # 현재탭에 접근
        driver.switch_to.window(driver.window_handles[1])
        time.sleep(3)  # 대기시간 변경 가능

        # 네이버 뉴스 url만 가져오기

        url = driver.current_url
        print(url)

        if "news.naver.com" in url:
            naver_urls.append(url)

        else:
            pass
        # 현재 탭 닫기
        driver.close()
        # 다시처음 탭으로 돌아가기(매우 중요!!!)
        driver.switch_to.window(driver.window_handles[0])

print(naver_urls)

###naver 기사 본문 및 제목 가져오기###

# ConnectionError방지
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102"}

titles = []
contents = []
for i in naver_urls:
    original_html = requests.get(i, headers=headers)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과확인시
    # print(html)

    # 뉴스 제목 가져오기
    title = html.select("div#ct > div.media_end_head.go_trans > div.media_end_head_title > h2")
    # list합치기
    title = ''.join(str(title))
    # html태그제거
    pattern1 = '<[^>]*>'
    title = re.sub(pattern=pattern1, repl='', string=title)
    titles.append(title)

    # 뉴스 본문 가져오기

    content = html.select("div#dic_area")

    # 기사 텍스트만 가져오기
    # list합치기
    content = ''.join(str(content))

    # html태그제거 및 텍스트 다듬기
    content = re.sub(pattern=pattern1, repl='', string=content)
    pattern2 = """[\n\n\n\n\n// flash 오류를 우회하기 위한 함수 추가\nfunction _flash_removeCallback() {}"""
    content = content.replace(pattern2, '')

    contents.append(content)

print(titles)
print(contents)

# 데이터프레임으로 정리(titles,url,contents)
import pandas as pd

news_df = pd.DataFrame({'title': titles, 'link': naver_urls, 'content': contents})

news_df.to_csv('NaverNews_%s.csv' % search, index=False, encoding='utf-8-sig')

코드 파일
naver_news_0517.py
0.00MB
마무리

이번 포스팅도 금방 작성할 거라 생각했으나...
항상 예상을 벗어나는 법이죠 ㅠㅠ 네.. 오래걸렸어요...
그래도 저번보다 훨씬 좋은 크롤러로 업그레드한 것 같아 좋네요ㅎㅎ
앞으로도 지속적으로 리뷰 및 업그레이드 하겠습니다.
좋은 아이디어나 질문이 있다면 댓글 남겨주세요:)
------------------------------------------------------------------------------------
+04/05 셀레니움 오류 수정하였습니다.
+04/15 driver 오류 수정 완료.
+05/03 bs4 select 수정 완료.
+05/17 driver 및 창닫기 추가.
+ 07/14 최신버전을 확인하고 싶다면...?
https://wonhwa.tistory.com/m/52

[python] 네이버 뉴스 크롤러(날짜,링크,제목, 본문)

안녕하세요. 정말 오랜만의 포스팅입니다. 그동안 올렸던 네이버 뉴스 크롤러 최신 버전을 공유드리고자 합니다. 그 전 버전인 아래 포스팅에서는 https://wonhwa.tistory.com/46#comment18426434 [python] 원하

wonhwa.tistory.com

반응형
반응형

안녕하세요~! 오늘은 공공데이터 openAPI의 xml을 Pandas DataFrame으로 변환하여 보도록 하겠습니다.
json에서 DataFrame으로의 변환은 여기를 클릭해서 확인해 주세요 :)

step1. 데이터 활용신청하기

공공데이터 포털

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

위 사이트에 접속 후 로그인하여 주세요.
여기서 '코로나'를 검색해 주세요.
그 후 스크롤 다운을 하셔서 아래의 내용을 찾으신 후 API 활용신청을 해 주세요.

https://www.data.go.kr/data/15043378/openapi.do

 

공공데이터활용지원센터_보건복지부 코로나19 시·도발생 현황

코로나19감염증으로 인한 시.도별 신규확진자,신규사망자,격리중인환자수,격리해제환자수등에 대한 현황자료 (이 제공자료는 관련 발생 상황에 대한 정보를 신속 투명하게 공개하기 위한 것으

www.data.go.kr

또는 위의 링크를 클릭하셔도 됩니다 :)
활용신청을 했다면 마이페이지에 들어가 주세요 :)

오픈 API > 개발계정> 공공데이터 활용지원센터_보건복지부 코로나19 시.도 발생 현황을 클릭해 주세요.

그 후 상세설명 클릭하여 요청 값 및 출력 값(컬럼)을 확인해 주세요.
각각의 컬럼 값에 어떤 내용이 있는지 확인하기 위해서 꼭 필요하기 때문에 잘 읽어주세요.

스크롤 다운을 하면 아래의 샘플코드> Python을 클릭하여 코드를 참고하여 xml을 불러오면 됩니다.

 

step2. API를 사용하여 xml 불러오기

이제부터 데이터를 요청하는 코드를 작성해 보도록 하겠습니다.

# 모듈 import
import requests
import pprint

#인증키 입력
encoding = '발급받은 인코딩 인증키를 복사하여 붙여넣기 해 주세요.'
decoding = '발급받은 디코딩 인증키를 복사하여 붙여넣기 해 주세요.'

#url 입력
url = 'http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson'
params ={'serviceKey' : decoding , 
'pageNo' : '1', 
'numOfRows' : '10', 
'startCreateDt' : '2020', 
'endCreateDt' : '20211103' }

response = requests.get(url, params=params)

# xml 내용
content = response.text

# 깔끔한 출력 위한 코드
pp = pprint.PrettyPrinter(indent=4)
#print(pp.pprint(content))

위의 내용까지는 전 게시글의 내용과 비슷합니다.
서비스키에 들어갈 수 있는 인증키로는 인코딩과 디코딩이 있는데 인코딩으로 실행하였을 때 오류가 나서 디코딩 인증키를 넣어 요청하였습니다.

step3. xml을 DataFrame으로 변환하기

xml 문서는 <item>안에 각 값이 태그 형식<>으로 들어 있습니다.
이 점을 이용해 뷰티플수프를 이용하여 파싱해보도록 하겠습니다.

### xml을 DataFrame으로 변환하기 ###
from os import name
import xml.etree.ElementTree as et
import pandas as pd
import bs4
from lxml import html
from urllib.parse import urlencode, quote_plus, unquote

## 각 컬럼 값 ## (포털 문서에서 꼭 확인하세요)
"""
SEQ : 게시글번호(국내 시도별 발생현황 고유값)
CREATE_DT: 	등록일시분초
DEATH_CNT: 	사망자 수
GUBUN: 	시도명(한글)
GUBUN_CN: 	시도명(중국어)
gubunEn: 시도명(영어)
INC_DEC: 전일대비 증감 수
ISOL_CLEAR_CNT: 격리 해제 수
QUR_RATE: 10만명당 발생률
STD_DAY: 기준일시
UPDATE_DT: 수정일시분초
DEF_CNT: 확진자 수
ISOL_ING_CNT: 격리중 환자수
OVER_FLOW_CNT: 해외유입 수
LOCAL_OCC_CNT: 지역발생 수

""" 

#bs4 사용하여 item 태그 분리

xml_obj = bs4.BeautifulSoup(content,'lxml-xml')
rows = xml_obj.findAll('item')
print(rows)

출력:

위의 내용만으로 한 눈에 어떤 정보가 있는지 보기 힘들기 때문에 데이터 프레임으로 만들기 전 이에 필요한
각각의 행값, 열 이름값, 데이터값을 추출하는 코드를 작성해 보겠습니다.

# 각 행의 컬럼, 이름, 값을 가지는 리스트 만들기
row_list = [] # 행값
name_list = [] # 열이름값
value_list = [] #데이터값

# xml 안의 데이터 수집
for i in range(0, len(rows)):
    columns = rows[i].find_all()
    #첫째 행 데이터 수집
    for j in range(0,len(columns)):
        if i ==0:
            # 컬럼 이름 값 저장
            name_list.append(columns[j].name)
        # 컬럼의 각 데이터 값 저장
        value_list.append(columns[j].text)
    # 각 행의 value값 전체 저장
    row_list.append(value_list)
    # 데이터 리스트 값 초기화
    value_list=[]

여기서 name_list는 열 이름들을 가지고 있고, row_list는 한 행의 값을 가지고 있습니다.
이제 위 변수들에 저장된 내용을 가지고 DataFrame으로 만들어 보겠습니다.

#xml값 DataFrame으로 만들기
corona_df = pd.DataFrame(row_list, columns=name_list)
print(corona_df.head(19))

가끔 데이터 프레임을 만들 때  Assertion Error가 나는 경우가 있는데 이때는 columns를 사용하지 마시고 df를 만들면 됩니다.

#xml값 DataFrame으로 만들기
#Assertion Error가 난 경우
corona_df = pd.DataFrame(row_list)
# 이후에 컬럼을 설정해 주세요.

출력:

깔끔하게 데이터프레임으로 만들어짐을 확인할 수 있습니다 :)
추가로 만들어진DataFrame을 csv파일로 저장하고 싶다면 아래의 코드를 추가하시면 됩니다.

#DataFrame CSV 파일로 저장
corona_df.to_csv('corona_kr.csv', encoding='utf-8-sig')

 

전체 코드
# 모듈 import
import requests
import pprint


#인증키 입력
encoding = '발급받은 인코딩 인증키를 복사하여 붙여넣기 해 주세요.'
decoding = '발급받은 디코딩 인증키를 복사하여 붙여넣기 해 주세요.'

#url 입력
url = 'http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson'
params ={'serviceKey' : decoding , 'pageNo' : '1', 'numOfRows' : '10', 'startCreateDt' : '2020', 'endCreateDt' : '20211103' }

response = requests.get(url, params=params)

# xml 내용
content = response.text

# 깔끔한 출력 위한 코드
pp = pprint.PrettyPrinter(indent=4)
#print(pp.pprint(content))

### xml을 DataFrame으로 변환하기 ###
from os import name
import xml.etree.ElementTree as et
import pandas as pd
import bs4
from lxml import html
from urllib.parse import urlencode, quote_plus, unquote

## 각 컬럼 값 ## (포털 문서에서 꼭 확인하세요)
"""
SEQ : 게시글번호(국내 시도별 발생현황 고유값)
CREATE_DT: 	등록일시분초
DEATH_CNT: 	사망자 수
GUBUN: 	시도명(한글)
GUBUN_CN: 	시도명(중국어)
gubunEn: 시도명(영어)
INC_DEC: 전일대비 증감 수
ISOL_CLEAR_CNT: 격리 해제 수
QUR_RATE: 10만명당 발생률
STD_DAY: 기준일시
UPDATE_DT: 수정일시분초
DEF_CNT: 확진자 수
ISOL_ING_CNT: 격리중 환자수
OVER_FLOW_CNT: 해외유입 수
LOCAL_OCC_CNT: 지역발생 수

""" 

#bs4 사용하여 item 태그 분리

xml_obj = bs4.BeautifulSoup(content,'lxml-xml')
rows = xml_obj.findAll('item')
print(rows)
"""
# 컬럼 값 조회용
columns = rows[0].find_all()
print(columns)
"""

# 각 행의 컬럼, 이름, 값을 가지는 리스트 만들기
row_list = [] # 행값
name_list = [] # 열이름값
value_list = [] #데이터값

# xml 안의 데이터 수집
for i in range(0, len(rows)):
    columns = rows[i].find_all()
    #첫째 행 데이터 수집
    for j in range(0,len(columns)):
        if i ==0:
            # 컬럼 이름 값 저장
            name_list.append(columns[j].name)
        # 컬럼의 각 데이터 값 저장
        value_list.append(columns[j].text)
    # 각 행의 value값 전체 저장
    row_list.append(value_list)
    # 데이터 리스트 값 초기화
    value_list=[]

#xml값 DataFrame으로 만들기
corona_df = pd.DataFrame(row_list, columns=name_list)
###assertion error의 경우###
###corona_df = pd.DataFrame(row_list)
print(corona_df.head(19)) 

#DataFrame CSV 파일로 저장
corona_df.to_csv('corona_kr.csv', encoding='utf-8-sig')

 

코드 파일

corona_DataFrame.py
0.00MB

참고 사이트

Python (파이썬) 공공데이터 수집 (Open API - XML)

 

Python (파이썬) 공공데이터 수집 (Open API - XML)

공공데이터포털의 특징은 자료를 활용을 요약하자면 1. 회원 가입 후 '사용자 인증키'를 생성해야한다. 2. 이후 원하는 데이터를 '활용 신청'을 해서 승인이 떨어지고 활용 권한을 획득해야한다

greendreamtrre.tistory.com

 

마무리

저번에는 json -> DataFrame 하는 방법을 공유하였고
이번에는 xml -> DataFrame 하는 방법을 공유하였습니다.
다음 시간에는 위에서 만든 corona_df를 가지고 bar_chart_race로 시각하여 보도록 하겠습니다 ^ㅇ^
이해 가지 않는 내용이나 질문이 있으면 댓글로 남겨 주세요 :)

반응형
반응형

오늘은 저번에 올린 네이버 뉴스 크롤링(1)에서 한 단계 업그레이드된 뉴스 크롤러를 공유하려 합니다 :)

1편에서는 뉴스 1페이지만 크롤링을 할 수 있었는데요

2편에서는 여러 페이지를 크롤링 할 수 있는 코드를 구현하여 보았습니다!

반복적인 작업이 있는 부분을 함수로 만들고

그 함수의 객체를 만들어 구현하여 기사 제목, 본문을 가지고 올 수 있는 크롤러를 만들었습니다.

오늘의 포스팅은 코드 위주이니 코드가 왜 이렇게 나왔는지 자세한 설명이 필요하시다면 (1) 편을 확인해 주세요 :-)

 

step1. 크롤링 시 필요한 라이브러리 불러오기
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests

 

step 2. 반복적인 작업 함수화하기

반복적인 작업들을 def를 이용해 함수를 만들어 보겠습니다.

(1) 입력받은 페이지 url 형식에 맞추어 숫자를 바꿔주는 함수
# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)
(2) Naver news url 생성하는 함수
# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search,start_pg,end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ",url)
        return url
    else:
        urls= []
        for i in range(start_pg,end_pg+1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ",urls)
        return urls
(3) html에서 원하는 속성 값 추출해주는 함수
# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content
(4) 뉴스 기사 내용 크롤링하는 함수
#뉴스기사 내용 크롤링하는 함수 만들기(각 뉴스의 url)
def news_contents_crawler(news_url):
    contents=[]
    for i in news_url:
        #각 기사 html get하기
        news = requests.get(i)
        news_html = BeautifulSoup(news.text,"html.parser")
            #기사 내용 가져오기 (p태그의 내용 모두 가져오기) 
        contents.append(news_html.find_all('p'))
    return contents
(5) 뉴스기사 크롤러(main) 함수
#html생성해서 기사크롤링하는 함수 만들기(제목,url): 3개의 값을 반환함(제목, 링크, 내용)
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과
    articles = html.select("div.group_news > ul.list_news > li div.news_area > a")
    title = news_attrs_crawler(articles,'title')
    url = news_attrs_crawler(articles,'href')
    content = news_contents_crawler(url)
    return title, url, content #3개의 값을 반환
step3. 함수를 이용하여 뉴스 크롤링 하기
#뉴스크롤링 시작

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")

#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   

# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
for i in url:
    title, url,content = articles_crawler(url)
    news_titles.append(title)
    news_url.append(url)
    news_contents.append(content)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(news_url)
print("\n[뉴스 내용]")
print(news_contents)

[결과]

중략...

중략...

중략.....

이렇게 각각 뉴스 제목, 링크, 내용이 각각 출력됨을 확인 가능합니다 ㅎㅎ

크롤링할 페이지가 많을수록 시간이 오래 걸린 다는 점 참고해주세요 :)

step4. 데이터 프레임으로 만들기

이제 이 데이터들을 데이터 프레임으로 만들어 보도록 하겠습니다!

데이터 프레임으로 만들기 전에 내용을 보니 리스트가 [[]] 이런 식으로 중첩으로 되어서 저장되어 있기 때문에

한 개의 기사의 한 개의 제목, 링크, 내용을 할당하기 위해 for문을 사용하여 1차원리스트로 변경해 주겠습니다.

makeList라는 함수를 만들어 간편하게 변경해 주도록 하겠습니다.

###데이터 프레임으로 만들기###
import pandas as pd

#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist
    
#제목, 링크, 내용 담을 리스트 생성
news_titles_1, news_url_1, news_contents_1 = [],[],[]

#1차원 리스트로 만들기(내용 제외)
makeList(news_titles_1,news_titles)
makeList(news_url_1,news_url)
makeList(news_contents_1,news_contents)


#데이터 프레임 만들기
news_df = pd.DataFrame({'title':news_titles_1,'link':news_url_1,'content':news_contents_1})
news_df

출력:

이런 식으로 데이터 프레임이 만들어졌습니다.

중간에 내용이 빈 칸은 사이트가 iframe으로 되어 있거나 할 것 같네요.

내용도 다 채우고 싶다면 한 사이트의 기사들만 추출하는 것이 깔끔합니다.

때문에 나는 더 깔끔하게 기사 내용을 추출하고 싶다! 하시는 분들은

1편의 내용을 참고하여 본인이 크롤링 하고 싶은 사이트만 전문으로 크롤링하는 코드를 만들어도 좋을 것 같습니다 .

전체 코드
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests

# 페이지 url 형식에 맞게 바꾸어 주는 함수 만들기
  #입력된 수를 1, 11, 21, 31 ...만들어 주는 함수
def makePgNum(num):
    if num == 1:
        return num
    elif num == 0:
        return num+1
    else:
        return num+9*(num-1)

# 크롤링할 url 생성하는 함수 만들기(검색어, 크롤링 시작 페이지, 크롤링 종료 페이지)
def makeUrl(search,start_pg,end_pg):
    if start_pg == end_pg:
        start_page = makePgNum(start_pg)
        url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(start_page)
        print("생성url: ",url)
        return url
    else:
        urls= []
        for i in range(start_pg,end_pg+1):
            page = makePgNum(i)
            url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page)
            urls.append(url)
        print("생성url: ",urls)
        return urls

# html에서 원하는 속성 추출하는 함수 만들기 (기사, 추출하려는 속성값)
def news_attrs_crawler(articles,attrs):
    attrs_content=[]
    for i in articles:
        attrs_content.append(i.attrs[attrs])
    return attrs_content

#뉴스기사 내용 크롤링하는 함수 만들기(각 뉴스의 url)
def news_contents_crawler(news_url):
    contents=[]
    for i in news_url:
        #각 기사 html get하기
        news = requests.get(i)
        news_html = BeautifulSoup(news.text,"html.parser")
            #기사 내용 가져오기 (p태그의 내용 모두 가져오기) 
        contents.append(news_html.find_all('p'))
    return contents

#html생성해서 기사크롤링하는 함수 만들기(제목,url): 3개의 값을 반환함(제목, 링크, 내용)
def articles_crawler(url):
    #html 불러오기
    original_html = requests.get(i)
    html = BeautifulSoup(original_html.text, "html.parser")
    # 검색결과
    articles = html.select("div.group_news > ul.list_news > li div.news_area > a")
    title = news_attrs_crawler(articles,'title')
    url = news_attrs_crawler(articles,'href')
    content = news_contents_crawler(url)
    return title, url, content #3개의 값을 반환

#####뉴스크롤링 시작#####

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")

#검색 시작할 페이지 입력
page = int(input("\n크롤링할 시작 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 시작 페이지: ",page,"페이지")   
#검색 종료할 페이지 입력
page2 = int(input("\n크롤링할 종료 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("\n크롤링할 종료 페이지: ",page2,"페이지")   

# naver url 생성
url = makeUrl(search,page,page2)

#뉴스 크롤러 실행
news_titles = []
news_url =[]
news_contents =[]
for i in url:
    title, url,content = articles_crawler(url)
    news_titles.append(title)
    news_url.append(url)
    news_contents.append(content)

print("검색된 기사 갯수: 총 ",(page2+1-page)*10,'개')
print("\n[뉴스 제목]")
print(news_titles)
print("\n[뉴스 링크]")
print(news_url)
print("\n[뉴스 내용]")
print(news_contents)

###데이터 프레임으로 만들기###
import pandas as pd

#제목, 링크, 내용 1차원 리스트로 꺼내는 함수 생성
def makeList(newlist, content):
    for i in content:
        for j in i:
            newlist.append(j)
    return newlist
    
#제목, 링크, 내용 담을 리스트 생성
news_titles_1, news_url_1, news_contents_1 = [],[],[]

#1차원 리스트로 만들기(내용 제외)
makeList(news_titles_1,news_titles)
makeList(news_url_1,news_url)
makeList(news_contents_1,news_contents)


#데이터 프레임 만들기
news_df = pd.DataFrame({'title':news_titles_1,'link':news_url_1,'content':news_contents_1})
news_df

 

코드 파일

naver_news_crawler.ver1.0.ipynb
0.98MB

마무리

1편에서 좀 더 업그레이드된 크롤러를 만들어 보았는데요

궁금한 사항이나 크롤러 업그레이드에 대한 좋은 의견이 있으시다면

댓글 남겨주세요 ^o^

제 코드가 도움이 되길 바랍니다.ㅎㅎ

 

+ 최신 버전을 확인하고 싶다면??

https://wonhwa.tistory.com/46?category=996518

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

위의 게시물을 방문해 주세요:)

반응형
반응형

오늘은 NAVER API를 이용하여 네이버 블로그 크롤링을 해 보겠습니다 ;)

NAVER OPEN API 신청하는 방법은 여기를 클릭해주세요ㅎㅎ

 

step1. 네이버 OPEN API를 이용하여 원하는 검색어로 관련 게시물 불러오기

신청한 네이버 OPEN API 아이디와 시크릿 코드를 이용하여 블로그 게시물을 불러와줍니다.

저는 검색어를 '상암 맛집' 으로, 출력 개수는 예시로 10개 받아와 보도록 하겠습니다.

import urllib.request
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager


# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

# 정보입력
client_id = "*********************" # 발급받은 id 입력
client_secret = "*************" # 발급받은 secret 입력 
quote = input("검색어를 입력해주세요.: ") #검색어 입력받기
encText = urllib.parse.quote(quote)
display_num = input("검색 출력결과 갯수를 적어주세요.(최대100, 숫자만 입력): ") #출력할 갯수 입력받기
url = "https://openapi.naver.com/v1/search/blog?query=" + encText +"&display="+display_num# json 결과
# url = "https://openapi.naver.com/v1/search/blog.xml?query=" + encText # xml 결과
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
rescode = response.getcode()

if(rescode==200):
    response_body = response.read()
    #print(response_body.decode('utf-8'))
else:
    print("Error Code:" + rescode)

body = response_body.decode('utf-8')
body

[결과]

위를 보면 블로그 게시물에 대한 정보들이 나와 있습니다.

여기서 title (제목) 과 link(블로그 게시물 링크)를 추출하도록 하겠습니다.

여기서는 블로그의 대략적인 내용만 나오지 블로그 내용 전체가 나오지 않으므로

블로그 링크를 추출하여 각각의 블로그로 들어가 내용을 크롤링 하도록 해야 합니다.

step2. 게시글 제목 및 링크만 추출하기

위의 결괏값을 보면 "(큰따옴표)가 글 앞뒤로 붙어있는데 replace를 활용하여 지워주겠습니다.

# 불필요한 ""(큰따옴표)지워주기
body = body.replace('"','')

그다음 "가 없어진 내용을 가지고 제목, 링크를 추출해 보도록 하겠습니다.

그 전에 네이버 블로그 링크만 받아오기 위해 body를 split으로 나누어 글 하나 당 리스트 요소 1개가 되도록 나누도록 하겠습니다. 그 후 list comprehension을 사용하여 naver가 들어간 글만 리스트에 남아있도록 해 줍니다.

#body를 나누기
list1 = body.split('\n\t\t{\n\t\t\t')
#naver블로그 글만 가져오기
list1 = [i for i in list1 if 'naver' in i]
list1

[결과]

이렇게 naver블로그 글만 리스트에 담겼습니다.

제목은 title:, link: 앞에 위치하므로  re를 사용하여 제목만 추출하도록 하겠습니다.

각 링크는 link: 뒤, description 앞에 위치하여 있습니다.

for문을 이용하여 한번에 제목, 링크를 추출해 보도록 하겠습니다.

#블로그 제목, 링크 뽑기
import re
titles = []
links = []
for i in list1:
   	title = re.findall('"title":"(.*?)",\n\t\t\t"link"',i)
    link = re.findall('"link":"(.*?)",\n\t\t\t"description"',i)
    titles.append(title)
    links.append(link)
 
titles = [r for i in titles for r in i]
links = [r for i in links for r in i]

print('<<제목 모음>>')
print(titles)
print('총 제목 수: ',len(titles),'개')#제목갯수확인
print('\n<<링크 모음>>')
print(links)
print('총 링크 수: ',len(links),'개')#링크갯수확인

[결과]

위의 링크를 확인해 보면 보통의 url과는 다르게 '\\'(역 슬래쉬)가 많이 들어가 있음을 확인할 수 있습니다.

보통의 url링크를 가져오기 위해 링크 사이의 '\\'를 지워 다듬어진 링크를 가져오도록 하겠습니다.

# 링크를 다듬기 (필요없는 부분 제거 및 수정)
blog_links = []
for i in links:
    a = i.replace('\\','')
    b = a.replace('?Redirect=Log&logNo=','/')
    # 다른 사이트 url이 남아있을 것을 대비해 한번 더 네이버만 남을 수 있게 걸러준다.
    if 'naver.blog.com' in b:
    	blog_links.append(b)

print(blog_links)
print('생성된 링크 갯수:',len(blog_links),'개')

[결과]

그러면 위와 같이 링크가 예쁘게 출력되었습니다.

step3. 게시글 본문 가져오기

다음으로 블로그 링크를 활용하여 각 페이지에 접근해 본문을 가져오도록 하겠습니다.

NAVER 블로그는 각 게시물에 들어가 보면 아래와 같이 iframe 안에 게시물 본문 글이 위치하여 있습니다.

<body>밑에 iframe이 위치되어있음을 확인가능합니다.

때문에 Selenium을 이용하여 각 게시물의 iframe 접근 후 내용을 추출하는 방식으로 크롤링을 하여야 합니다.

Selenium을 이용할 때는 전용 drvier 가 있어야 합니다.

여기서는 크롬 드라이버를 이용하겠습니다.

여기를 클릭하여 현재 본인이 사용하고 있는 구글 버전에 맞는 드라이버를 다운받아주세요.

#본문 크롤링
import time
from selenium import webdriver

# 크롬 드라이버 설치
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)

그다음으로 아까 다듬어진 링크에 접속하여 iframe 접근 후 본문의 내용을 크롤링하겠습니다.

본문은 iframe 안의 <div> class='se-main-container' 여기에 아래의 사진과 같이 들어있음을 확인할 수 있습니다.

파란색 부분이 있는 class 이름이 se-main-container임을 확인할 수 있습니다.

그래서 find_element_by_css_selector를 사용해 본문 글을 가져와 contents 변수에 담아 줍니다.

그리고 가끔 예전 블로그 글을 가져오게 될 때가 있는데 그 때는 현재 블로그 html 구조와 달라

No Such Element Error가 날 수 있습니다. 이때 구 블로그 글도 잘 가져올 수 있도록

예외처리를 하여 내용 부분을 크롤링 해 줍니다.

#블로그 링크 하나씩 불러서 크롤링
contents = []
for i in blog_links:
    #블로그 링크 하나씩 불러오기
    driver.get(i)
    time.sleep(1)
    #블로그 안 본문이 있는 iframe에 접근하기
    driver.switch_to.frame("mainFrame")
    #본문 내용 크롤링하기
    #본문 내용 크롤링하기
    try:
        a = driver.find_element(By.CSS_SELECTOR,'div.se-main-container').text
        contents.append(a)
    # NoSuchElement 오류시 예외처리(구버전 블로그에 적용)
    except NoSuchElementException:
        a = driver.find_element(By.CSS_SELECTOR,'div#content-area').text
        contents.append(a)
    #print(본문: \n', a)




driver.quit() #창닫기
print("<<본문 크롤링이 완료되었습니다.>>")

[결과]

그러면 이렇게 모든 게시물의 본문 내용을 가져올 수 있습니다 ;-ㅇ

step4. 크롤링 내용들 DataFrame으로 만들기

마지막으로 아까 추출한 제목, 블로그 링크, 내용을 DataFrame으로 만들어 깔끔하게 정리해 보도록 하겠습니다.

#제목, 블로그링크, 본문내용 Dataframe으로 만들기
import pandas as pd
df = pd.DataFrame({'제목':titles, '링크':blog_links,'내용':contents})

#df 저장
df.to_csv('{}_블로그.csv'.format(quote),encoding='utf-8-sig',index=False)

[결과]

깔끔하게 df로 만들어졌습니다. ㅎㅎ

전체 코드
import urllib.request
from selenium.common.exceptions import NoSuchElementException
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager


# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

#정보입력
client_id = "********************" # 발급받은 id 입력
client_secret = "**********" # 발급받은 secret 입력 
quote = input("검색어를 입력해주세요.: ") #검색어 입력받기
encText = urllib.parse.quote(quote)
display_num = input("검색 출력결과 갯수를 적어주세요.(최대100, 숫자만 입력): ") #출력할 갯수 입력받기
url = "https://openapi.naver.com/v1/search/blog?query=" + encText +"&display="+display_num# json 결과
# url = "https://openapi.naver.com/v1/search/blog.xml?query=" + encText # xml 결과
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id",client_id)
request.add_header("X-Naver-Client-Secret",client_secret)
response = urllib.request.urlopen(request)
rescode = response.getcode()

if(rescode==200):
    response_body = response.read()
    #print(response_body.decode('utf-8'))
else:
    print("Error Code:" + rescode)

body = response_body.decode('utf-8')
print(body)

#body를 나누기
list1 = body.split('\n\t\t{\n\t\t\t')
#naver블로그 글만 가져오기
list1 = [i for i in list1 if 'naver' in i]
print(list1)

#블로그 제목, 링크 뽑기
import re
titles = []
links = []
for i in list1:
    title = re.findall('"title":"(.*?)",\n\t\t\t"link"',i)
    link = re.findall('"link":"(.*?)",\n\t\t\t"description"',i)
    titles.append(title)
    links.append(link)
 
titles = [r for i in titles for r in i]
links = [r for i in links for r in i]

print('<<제목 모음>>')
print(titles)
print('총 제목 수: ',len(titles),'개')#제목갯수확인
print('\n<<링크 모음>>')
print(links)
print('총 링크 수: ',len(links),'개')#링크갯수확인

# 링크를 다듬기 (필요없는 부분 제거 및 수정)
blog_links = []
for i in links:
    a = i.replace('\\','')
    b = a.replace('?Redirect=Log&logNo=','/')
    # 다른 사이트 url이 남아있을 것을 대비해 한번 더 네이버만 남을 수 있게 걸러준다.
    if 'naver.blog.com' in b:
    	blog_links.append(b)

print(blog_links)
print('생성된 링크 갯수:',len(blog_links),'개')

#본문 크롤링
import time
from selenium import webdriver

# 크롬 드라이버 설치
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.implicitly_wait(3)

#블로그 링크 하나씩 불러서 크롤링
contents = []
for i in blog_links:
    #블로그 링크 하나씩 불러오기
    driver.get(i)
    time.sleep(1)
    #블로그 안 본문이 있는 iframe에 접근하기
    driver.switch_to.frame("mainFrame")
    #본문 내용 크롤링하기
    try:
        a = driver.find_element(By.CSS_SELECTOR,'div.se-main-container').text
        contents.append(a)
    # NoSuchElement 오류시 예외처리(구버전 블로그에 적용)
    except NoSuchElementException:
        a = driver.find_element(By.CSS_SELECTOR,'div#content-area').text
        contents.append(a)
    #print(본문: \n', a)




driver.quit() #창닫기
print("<<본문 크롤링이 완료되었습니다.>>")

#제목 및 본문 txt에 저장
total_contents = titles + contents

text = open("blog_text.txt",'w',encoding='utf-8') 
for i in total_contents:
    text.write(i)
text.close()

#제목, 블로그링크, 본문내용 Dataframe으로 만들기
import pandas as pd

df = pd.DataFrame({'제목':titles, '링크':blog_links,'내용':contents})
print(df)

#df 저장
df.to_csv('{}_블로그.csv'.format(quote),encoding='utf-8-sig',index=False)

 

마무리

오늘은 네이버 API를 이용하여 블로그 제목 및 내용을 가져와봤는데요

이번 포스팅을 작성하는 데 꽤 시간이 걸렸는데 이렇게 깔끔하게 할 수 있어 정말 기쁘네요.ㅎㅎ

다음에는 크롤링 내용을 가지고 워드 클라우드를 만드는 실습을 해볼까 합니다.

포스팅에 대한 좋은 의견이 있다면 주저 말고 댓글로 추천해주세요!

--------------

+ 22.05.13 NoSuchElementException import 추가

+ 22.05.25 제목, 링크 추출 코드 수정

+ 22.06.03 네이버 블로그 글만 추출하는 코드 추가

+ 22.07.29 제목,링크 추출 코드 수정

+24.02.16 코드수정

반응형
반응형

[Python] 공공데이터 포털의 OPEN API 사용 방법의 2부 지금 시작하도록 하겠습니다.

저번에는 공공데이터 포털의 open API 신청 방법 및 파이썬 코드 확인방법까지 알아보았습니다.

오늘은 파이썬으로 어떻게 하면 open API를 사용해 데이터를 불러올 수 있는지 알아보도록 합시다.

 

step1. 공공데이터 포털 접속 API 확인
공공데이터 포털 사이트: https://www.data.go.kr

위의 사이트에 접속 후 로그인을 해 줍니다.

마이페이지 > 오픈API > 개발계정

접속 후 1부에서 신청한 전국CCTV 표준데이터를 클릭해줍니다.

여기서 확인을 클릭해 주고

미리보기를 클릭해주면,

위와 같이 미리보기 창이 뜹니다. 거기서 빨간색 박스 표시를 한 url을 복사해줍니다.

그러면 아래와 같이 url이 나타납니다.

http://api.data.go.kr/openapi/tn_pubr_public_cctv_api?serviceKey=인증키&pageNo=1&numOfRows=100&type=xml

여기서 본인의 인증키를 입력하고 페이지시작(pageNo), 한번에 출력되는 행 갯수(numOfRows), json또는xml타입(type)을 원하는 대로 입력하면 됩니다.

이 url을 이용하여 파이썬으로 데이터를 불러와 보겠습니다.

저는 numOfRows = 10 , type =json 으로 설정하여 url을 사용하겠습니다.

http://api.data.go.kr/openapi/tn_pubr_public_cctv_api?serviceKey=인증키&pageNo=1&numOfRows=10&type=json

 

step2. 라이브러리 import 및 url입력
# 라이브러리 import
import requests
import pprint
import json

# url 입력
url = 'http://api.data.go.kr/openapi/tn_pubr_public_cctv_api?serviceKey=개인인증키입력&pageNo=1&numOfRows=10&type=json'

# url 불러오기
response = requests.get(url)

#데이터 값 출력해보기
contents = response.text

url을 불러오고 데이터 값을 출력해 보면

이렇게 출력이 됩니다. 한 눈에 보기 어려우니 예쁘게 출력해주는 pprint 라이브러리를 사용하여 출력해 봅시다.

# 데이터 결과값 예쁘게 출력해주는 코드
pp = pprint.PrettyPrinter(indent=4)
print(pp.pprint(contents))

그럼 위와 같이 예쁘게 출력이 됩니다.

step3. 문자열을 json으로 변경하기

여기서 위의 문자열을 json으로 변경해 줍시다.

#문자열을 json으로 변경
json_ob = json.loads(contents)
print(json_ob)
print(type(json_ob)) #json타입 확인

이제 json으로변경이 됐고 dict 타입임을 확인할 수 있습니다.

step4. 필요한 내용만 남기기

위를 보면 데이터의 내용에서 response 키 값의 body, body 속에 items 가 있고 우리가 원하는 정보가 거기 모두 들어있음을 확인할 수 있습니다. 이를 확인 후 아래와 같이 items내용만 꺼내보도록 합시다.

# 필요한 내용만 꺼내기
body = json_ob['response']['body']['items']
print(body)

그럼 이렇게 items의 내용만 출력이 됩니다.

step5. Dataframe으로 변환하기

이제 마지막 단계입니다. 위에서 작성한 body 변수에 저장된 내용을 pandas를 활용하여 Dataframe으로 만들어 줍니다.

# pandas import
import pandas as pd

# Dataframe으로 만들기
dataframe = pd.json_normalize(body)

print(dataframe)

그럼 이렇게 깔끔하게 DataFrame으로 저장된 것을 확인할 수 있습니다.

이것을 토대로 분석을 진행하시면 됩니다^o^

전체코드
#라이브러리 import
import requests
import pprint
import json

# url 입력

url = 'http://api.data.go.kr/openapi/tn_pubr_public_cctv_api?serviceKey=개인인증키입력&pageNo=1&numOfRows=10&type=json'

response = requests.get(url)

contents = response.text

# 데이터 결과값 예쁘게 출력해주는 코드
pp = pprint.PrettyPrinter(indent=4)
print(pp.pprint(contents))

## json을 DataFrame으로 변환하기 ##

#문자열을 json으로 변경
json_ob = json.loads(contents)
print(type(json_ob)) #json타입 확인

# 필요한 내용만 꺼내기
body = json_ob['response']['body']['items']
print(body)

# pandas import
import pandas as pd

# Dataframe으로 만들기
dataframe = pd.json_normalize(body)

print(dataframe)

 

마무리

1부에 이어 2부에서는 파이썬 코드로 어떻게 데이터를 불러오는지 알아보았습니다.

다른 자료들도 위의 방법을 적용하여 즐겁게 분석 하시길 바랍니다. ;-)

 

+10/14 json->dataframe으로 변환하는 코드 수정

반응형
반응형

오늘은 전에 알려드린 오픈 API 사용과 더불어 파이썬으로 크롤러 만드는 방법을 소개하도록 하겠습니다.

네이버 오픈 API의 경우 사용하는 방법을 알면 간편하게 뉴스, 블로그, 카페 등등을 크롤링하여 정보를 수집할 수 있습니다. 하지만 개인당 일일 오픈 API 사용량이 제한되어 있어 빅데이터를 분석 시 그보다 더 큰 데이터를 수집해야 할 때가 있어 오픈 API만으로 필요한 정보를 모두 수집하기는 어렵습니다. 그래서 이번에는 Python으로 네이버 뉴스 크롤링하는 방법을 알아보려고 합니다.

이렇게 네이버 오픈 API는 할당된 사용량이 정해져 있음을 확인할 수 있습니다.

 

step1. 네이버 뉴스 홈페이지 접속 및 HTML 구조 파악

먼저, 네이버 창에 "코로나"를 검색하여 나오는 뉴스 탭을 확인하도록 합시다. 

<위 사진의 URL>
https://search.naver.com/search.naver?where=news&sm=tab_jum&query=코로나

뉴스 크롤링을 위에 보이는 페이지만 하는 것이 아니기 때문에 스크롤 다운을 하여 페이지 1을 클릭하여 줍니다.

<페이지 1을 클릭한 뒤의 URL>
https://search.naver.com/search.naver?where=news&sm=tab_pge&query=코로나&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=32&mynews=0&office_type=0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&start=1

페이지 1을 클릭하고 URL을 확인하여 보니 아까 검색어만 입력했을 때와는 달리 엄청 길고 복잡한 URL이 나왔습니다.

다음으로 페이지 2,3을 각각 클릭하여 위의 URL과 어떤 다른 점이 있는지 확인하여 보겠습니다.

<페이지 1을 클릭한 뒤의 URL>
https://search.naver.com/search.naver?where=news&sm=tab_pge&
query=코로나
&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=32&mynews=0&office_type=0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&
start=1

<페이지 2를 클릭한 뒤의 URL>
https://search.naver.com/search.naver?where=news&sm=tab_pge&query=코로나&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=64&mynews=0&office_type=0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&start=11

<페이지 3을 클릭한 뒤의 URL>
https://search.naver.com/search.naver?where=news&sm=tab_pge&query=코로나&sort=0&photo=0&field=0&pd=0&ds=&de=&cluster_rank=87&mynews=0&office_type=0&office_section_code=0&news_office_checked=&nso=so:r,p:all,a:all&start=21

위와 같이 URL을 모아 보면 공통부분이 있습니다.

검색어를 입력하는 &query=코로나 부분과,

페이지를 입력하는 &start=1(1페이지),11(2페이지),21(3페이지)... 이 부분이 공통적인 요소입니다. 

위의 내용을 토대로 중간 부분을 생략하고(생략하여도 검색에 문제는 없습니다.) 공통의 URL을 도출해 내면 아래와 같습니다.

https://search.naver.com/search.naver?where=news&sm=tab_pge&query="검색어"&start="페이지"

이제 파이썬으로 뉴스를 크롤링하는 코드를 만들어 보도록 합시다.

step 2. 파이썬에서 사용할 library(라이브러리들)  import
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests
step3. 검색어 입력받기

검색할 키워드와 페이지를 입력받는 코드를 input을 사용하여 작성해 줍니다.

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색할 페이지 입력
page = int(input("크롤링할 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("크롤링할 페이지: ",page,"페이지")

결괏값:

검색할 키워드를 입력해주세요: (검색 키워드 입력)

크롤링할 페이지를 입력해주세요. ex)1(숫자만입력): 1(숫자 입력) 

크롤링할 페이지: 1 페이지

step4. 입력받은 내용에 해당하는 URL 생성하기

페이지 입력을 받을 때  naver에서는 1페이지가 1, 2페이지가 11, 3페이지가 21... 이런 식으로 표현되기 때문에 그것에 맞도록 바꿔주는 코드를 추가하여 url을 생성합니다.

#start수를 1, 11, 21, 31 ...만들어 주는 함수
page_num = 0

if page == 1:
    page_num =1
elif page == 0:
    page_num =1
else:
    page_num = page+9*(page-1)
    
#url 생성
url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page_num)
print("생성url: ",url)

결괏값:

생성url: https://search.naver.com/search.naver?where=news&sm=tab_pge&query=검색키워드&start=페이지숫자 

step5. html 불러와서 BeautifulSoup로 해당 페이지 파싱 하기
# ConnectionError방지
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/100.0.48496.75" }

#html불러오기
original_html = requests.get(url, headers=headers)
html = BeautifulSoup(original_html.text, "html.parser")
step6. 원하는 값을 추출하기 위한 코드 작성

예시로 코로나를 검색하여 나오는 페이지에서 필요한 내용(제목, 각 기사 url 등)을 찾아보도록 합시다.

'코로나' 검색후 나오는 1페이지 화면

위의 화면에서 F12를 눌러주면 다음과 같이 오른쪽에 개발자 도구가 나오게 됩니다.

여기서 우리가 얻고 싶은 정보는 뉴스가 나와있는 부분이죠. 이 부분에 커서를 대면 빨간 박스와 같이 어디에 위치하여 있는지 표시가 됩니다. 

각 기사의 제목과 url은 group_news라는 클래스 이름의 div 안 ul태그의 list_news라는 이름의 class 안 li태그 안의 div의 news_area라는 이름의 class 안의 a태그에 위치하여 있습니다.

이것을 코드로 나타내면 아래와 같이 작성할 수 있습니다.

div.group_news > ul.list_news > li div.news_area > a

.(점) 뒤에는 클래스 이름을, >는 ~안에 있다는 뜻입니다.

만약 html 개발자 도구로 확인할 때 class = 클래스 이름 이 아니라 id = 아이디 이름 이 있으면. 대신 #을 앞에 붙이면 됩니다.

# 검색결과
articles = html.select("div.group_news > ul.list_news > li div.news_area > a")
print(articles)
# 검색된 기사의 갯수
print(len(articles),"개의 기사가 검색됌.")

결괏값: 

[<a class="news_tit" href="http://www.edaily.co.kr/news/newspath.asp?newsid=01610486629212920" onclick="return goOtherCR(this, 'a=nws*h.tit&amp;r=1&amp;i=880000E7_000000000000000005059762&amp;g=018.0005059762&amp;u='+urlencode(this.href));" target="_blank" title="[속보]코로나19 신규 확진자 1940명…'연휴' 끝, 증가세 계속">[속보]<mark>코로나</mark>19 신규 확진자 1940명…'연휴' 끝, 증가세 계속</a>, <a class="news_tit" href="https://biz.chosun.com/it-science/bio-science/2021/10/14/S4RMV6PZZ5DXRCHU6GYD2354ZE/?utm_source=naver&utm_medium=original&utm_campaign=biz" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=3&amp;i=88127058_000000000000000000766474&amp;g=366.0000766474&amp;u='+urlencode(this.href));" target="_blank" title="[단독] 적십자, 제약사에 혈액 헐값 공급… 코로나19로 혈액수급 비상인데">[단독] 적십자, 제약사에 혈액 헐값 공급… <mark>코로나</mark>19로 혈액수급 비상인데</a>, <a class="news_tit" href="https://news.kbs.co.kr/news/view.do?ncd=5300642&ref=A" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=4&amp;i=88000114_000000000000000011137195&amp;g=056.0011137195&amp;u='+urlencode(this.href));" target="_blank" title="[속보] 코로나19 신규 확진 1,940명…100일째 네 자릿수">[속보] <mark>코로나</mark>19 신규 확진 1,940명…100일째 네 자릿수</a>, <a class="news_tit" href="https://health.chosun.com/site/data/html_dir/2021/10/14/2021101400675.html" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=5&amp;i=88156f6f_000000000000000000045032&amp;g=346.0000045032&amp;u='+urlencode(this.href));" target="_blank" title="[속보] 코로나 신규 확진 1940명… 다시 2000명대 육박">[속보] <mark>코로나</mark> 신규 확진 1940명… 다시 2000명대 육박</a>, <a class="news_tit" href="https://www.khan.co.kr/national/health-welfare/article/202110140939001" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=6&amp;i=8800006B_000000000000000003103628&amp;g=032.0003103628&amp;u='+urlencode(this.href));" target="_blank" title="[속보]코로나19 신규 확진 1940명···10명 중 8명 수도권">[속보]<mark>코로나</mark>19 신규 확진 1940명···10명 중 8명 수도권</a>, <a class="news_tit" href="http://www.fnnews.com/news/202110140647114518" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=7&amp;i=880000FF_000000000000000004722854&amp;g=014.0004722854&amp;u='+urlencode(this.href));" target="_blank" title="'얀센+모더나 부스터샷' 코로나 백신 효과 가장 좋다">'얀센+모더나 부스터샷' <mark>코로나</mark> 백신 효과 가장 좋다</a>, <a class="news_tit" href="https://www.ytn.co.kr/_ln/0103_202110140934000806" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=8&amp;i=880000AF_000000000000000001652079&amp;g=052.0001652079&amp;u='+urlencode(this.href));" target="_blank" title="[속보] 코로나19 신규 환자 1,940명...100일째 네 자릿수">[속보] <mark>코로나</mark>19 신규 환자 1,940명...100일째 네 자릿수</a>, <a class="news_tit" href="https://www.ytn.co.kr/_ln/0102_202110131304564350" onclick="return goOtherCR(this, 'a=nws*h.tit&amp;r=9&amp;i=880000AF_000000000000000001651712&amp;g=052.0001651712&amp;u='+urlencode(this.href));" target="_blank" title="분사형 제품, 살균력 낮고 '코로나 예방' 과장 광고">분사형 제품, 살균력 낮고 '<mark>코로나</mark> 예방' 과장 광고</a>, <a class="news_tit" href="http://www.newsis.com/view/?id=NISX20211014_0001612990&cID=10301&pID=10300" onclick="return goOtherCR(this, 'a=nws*j.tit&amp;r=11&amp;i=88000127_000000000000000010768538&amp;g=003.0010768538&amp;u='+urlencode(this.href));" target="_blank" title="軍 병원 코로나19 의료진에 KF94 마스크 3000장 기부">軍 병원 <mark>코로나</mark>19 의료진에 KF94 마스크 3000장 기부</a>, <a class="news_tit" href="http://news.heraldcorp.com/view.php?ud=20211013000716" onclick="return goOtherCR(this, 'a=nws*h.tit&amp;r=12&amp;i=8800010E_000000000000000001898691&amp;g=016.0001898691&amp;u='+urlencode(this.href));" target="_blank" title="‘위드코로나’ 준비 첫발 내딛다">‘위드<mark>코로나</mark>’ 준비 첫발 내딛다</a>] 

10 개의 기사가 검색됌.

 

이렇게 제목 및 기사 내용을 알 수 있는 url이 추출이 되었습니다. 하지만 아직 지저분해 보입니다.

여기서 제목 및 기사 url만 추출하는 코드를 작성해 보겠습니다.

제목은 title이라는 속성 값을 가진 곳에 작성이 되어 있습니다.

이 부분만 추출하는 코드는 아래와 같습니다.

#뉴스기사 제목 가져오기
news_title = []
for i in articles:
    news_title.append(i.attrs['title'])
news_title

결괏값:

["[속보]코로나19 신규 확진자 1940명…'연휴' 끝, 증가세 계속", '[단독] 적십자, 제약사에 혈액 헐값 공급… 코로나19로 혈액수급 비상인데', '[속보] 코로나19 신규 확진 1,940명…100일째 네 자릿수', '[속보] 코로나 신규 확진 1940명… 다시 2000명대 육박', '[속보]코로나19 신규 확진 1940명···10명 중 8명 수도권', "'얀센+모더나 부스터샷' 코로나 백신 효과 가장 좋다", '[속보] 코로나19 신규 환자 1,940명...100일째 네 자릿수', "분사형 제품, 살균력 낮고 '코로나 예방' 과장 광고", '軍 병원 코로나19 의료진에 KF94 마스크 3000장 기부', '‘위드코로나’ 준비 첫발 내딛다']

이제 뉴스 기사 url을 가져와 봅시다.

url은 href라는 속성 값에 위치에 있습니다.

#뉴스기사 URL 가져오기
news_url = []
for i in articles:
    news_url.append(i.attrs['href'])
news_url

결괏값:

['http://www.edaily.co.kr/news/newspath.asp?newsid=01610486629212920', 'https://biz.chosun.com/it-science/bio-science/2021/10/14/S4RMV6PZZ5DXRCHU6GYD2354ZE/?utm_source=naver&utm_medium=original&utm_campaign=biz', 'https://news.kbs.co.kr/news/view.do?ncd=5300642&ref=A', 'https://health.chosun.com/site/data/html_dir/2021/10/14/2021101400675.html', 'https://www.khan.co.kr/national/health-welfare/article/202110140939001', 'http://www.fnnews.com/news/202110140647114518', 'https://www.ytn.co.kr/_ln/0103_202110140934000806', 'https://www.ytn.co.kr/_ln/0102_202110131304564350', 'http://www.newsis.com/view/?id=NISX20211014_0001612990&cID=10301&pID=10300', 'http://news.heraldcorp.com/view.php?ud=20211013000716']

step7. 각 기사의 내용을 크롤링하기

각 기사의 내용을 크롤링하려면 step6에서와 같이 각각의 기사 링크를 타고 들어가 f12를 누르고 개발자 도구를 통해 html의 구조 및 필요한 내용이 어디에 위치해 있는지를 확인하여야 합니다.

하지만 각각의 언론사는 다 다른 html 구조를 가지고 있기 때문에 정확하게 크롤링하려면 번거롭고 시간이 많이 듭니다.

보통 기사는 p 태그에 본문 내용이 있기 때문에 p태그에 있는 모든 내용을 가져오는 코드를 작성해 보도록 하겠습니다.

#뉴스기사 내용 크롤링하기
contents = []
for i in news_url:
    #각 기사 html get하기
    news = requests.get(i)
    news_html = BeautifulSoup(news.text,"html.parser")
    #기사 내용 가져오기 (p태그의 내용 모두 가져오기) 
    contents.append(news_html.find_all('p'))
contents

결괏값:

p class="tit">이시간 <span>핫 뉴스</span></p>, <p class="tit">오늘의 헤드라인</p>, <p class="tit"><a href="/view?id=NISX20211014_0001613089">"확진 1940명…100일째 네자리 연휴 끝난 후 3일 연속 증가세</a></p>, <p class="subTit"></p>, <p class="txt"><a href="/view?id=NISX20211014_0001613089">코로나19 신규 확진자 수가 1940명으로 집계돼 다시 2000명대에 육박했다. 신규 확진자 수는 최근 3일 연속 증가세인데, 정부는 단계적 일상회복 전 마지막 사회적 거리두기 단계 조정안을 오는 15일 발표할 예정이다. 질병관리청 중앙방역대책본부(방대본)에 따르면 14일 0시 기준 누적 확진자는 전날보다 1940명 증가한 33만7679명이다.</a></p>, <p class="tit"><a href="/view?id=NISX20211014_0001612865">"김만배, 구속심사 출석…'700억대' 뇌물 등 혐의</a></p>, <p class="tit"><a href="/view?id=NISX20211014_0001612930">"李 34%·尹 33.7%…이낙연 지지층, 尹으로 이탈</a></p>, <p class="tit"><a href="/view?id=NISX20211013_0001612719">"[단독]음저협, 저작권료 41억 받고도 미분배</a></p>, <p class="tit"><a href="/view?id=NISX20211014_0001613023">"日언론 "기시다, 이르면 오늘 文대통령과 통화"</a></p>, <p class="tit"><a href="/view?id=NISX20211014_0001613075">"尹 "당 없어지는 게 낫다"에 劉·洪 "못된 버르장머리"</a></p>, <p class="tit">많이 본 기사</p>...(중략)

기사의 모든 본문을 가지고 있지만 본문 뿐만이 아니라 p태그의 모든 내용이 크롤링 되기 때문에 크롤링 후 전처리가 필수적으로 진행이 되어야 합니다. 추후 전처리에 관련해서도 포스팅 할 예정입니다.

전체 코드
#크롤링시 필요한 라이브러리 불러오기
from bs4 import BeautifulSoup
import requests

#검색어 입력
search = input("검색할 키워드를 입력해주세요:")
#검색할 페이지 입력
page = int(input("크롤링할 페이지를 입력해주세요. ex)1(숫자만입력):")) # ex)1 =1페이지,2=2페이지...
print("크롤링할 페이지: ",page,"페이지")   
#start수를 1, 11, 21, 31 ...만들어 주는 함수
page_num = 0

if page == 1:
    page_num =1
elif page == 0:
    page_num =1
else:
    page_num = page+9*(page-1)
    
#url 생성
url = "https://search.naver.com/search.naver?where=news&sm=tab_pge&query=" + search + "&start=" + str(page_num)
print("생성url: ",url)

# ConnectionError방지
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/100.0.48496.75" }

#html불러오기
original_html = requests.get(url, headers=headers)
html = BeautifulSoup(original_html.text, "html.parser")

# 검색결과
articles = html.select("div.group_news > ul.list_news > li div.news_area > a")
print(articles)
# 검색된 기사의 갯수
print(len(articles),"개의 기사가 검색됌.")

#뉴스기사 제목 가져오기
news_title = []
for i in articles:
    news_title.append(i.attrs['title'])
news_title

#뉴스기사 URL 가져오기
news_url = []
for i in articles:
    news_url.append(i.attrs['href'])
news_url

#뉴스기사 내용 크롤링하기
contents = []
for i in news_url:
    #각 기사 html get하기
    news = requests.get(i,headers=headers)
    news_html = BeautifulSoup(news.text,"html.parser")
    #기사 내용 가져오기 (p태그의 내용 모두 가져오기) 
    contents.append(news_html.find_all('p'))
contents
코드 파일

naver_news_crawler.ver0.9.ipynb
0.09MB

마무리

오늘의 포스팅은 네이버 뉴스를 파이썬으로 크롤링하는 코드를 만들어 보았습니다. 이번에는 1페이지씩 크롤링하는 코드를 작성하였는데요, 다음 포스팅에는 한 번에 여러 페이지를 크롤링하는 코드를 만들어 보도록 하겠습니다. :-)

 

+ 최신 버전을 확인하고 싶다면??

https://wonhwa.tistory.com/46?category=996518

 

[python] 원하는 검색어로 네이버 뉴스 기사 제목 및 내용만 크롤링하기

안녕하세요! 크롤링 포스팅을 오랜만에 진행하네요~ 이번에는 네이버 뉴스 검색 결과중 네이버 뉴스에 기사가 있는 링크들만 가져와 크롤링을 진행해 보도록 하겠습니다. 지난 크롤러에서 아쉬

wonhwa.tistory.com

위의 게시물을 방문해 주세요:)

+2022.4.6 connection error 수정

반응형

+ Recent posts