텍스트 수집 자동화는 사실 '크롤러' 라고 이름 붙이기 애매하지만,
이미 크롤러 라는 단어가 일상적으로 쓰이고 있으므로 편의상 크롤러라고 칭하겠습니다.
이전에 올린 것은 코드를 반복 실행해야 해서 함수로 만들어 편리성을 높였습니다.
(↓ 이전 포스팅)
https://chocolemon.tistory.com/105
[텍스트 마이닝-수집] 네이버 블로그 스크래핑
import pandas as pd import time import urllib.request from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException, TimeoutException from selenium import webdriver from selenium.webdriver.common.by import By from tqdm.note
chocolemon.tistory.com
이 크롤러를 만들 때 도움받은 티스토리입니다.
https://wonhwa.tistory.com/category/NLP
'NLP' 카테고리의 글 목록
Everyday Happy day 같이 성장해 나가는 블로그가 되어요 ;-)
wonhwa.tistory.com
셀레니움으로 자동화하여 수집합니다.
필요한 패키지를 불러옵니다.
import pandas as pd
import numpy as np
import time
from tqdm.notebook import tqdm
import random
import urllib.request
from selenium.common.exceptions import NoSuchElementException, UnexpectedAlertPresentException, TimeoutException
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
# 크롤링 차단 방지용 user-agent (임시)
import user_agent
from user_agent import generate_user_agent
웹드라이버와 user-agent를 설정합니다.
# 웹드라이버 설정
options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# print는 설정이 잘 됐는지 확인해보려는 것이므로 굳이 실행하지 않아도 됩니다.
# print(generate_user_agent(device_type='desktop'))
# print(generate_user_agent(os='win', device_type='desktop'))
# print(generate_user_agent(os=('mac', 'linux'), device_type='desktop'))
# 눈속임용이긴 하지만 user-agent를 설정해줍니다.
random_user = generate_user_agent(os='win', device_type='desktop')
# print(random_user)
chrome_options = Options()
chrome_options.add_argument(random_user)
이제 크롬 창을 실행시켜줍니다.
driver = webdriver.Chrome(executable_path ='chromedriver.exe',options = chrome_options)
driver.maximize_window() # 창 크기 설정
driver.get("https://www.naver.com/") # 접속하고자 하는 주소 설정
time.sleep(2)
왜 이 단계에서마저 time 설정을 하는지 의문일 수도 있습니다.
수집이 막히지 않도록 처음부터 조심하는 것이 좋습니다.
하지만 뭐 ... 코드를 사용하는 분들의 선택에 따라 실행하지 않아도 됩니다.
스크롤할 때 쓰는 코드 함수를 먼저 만들어줍니다.
크롤링 함수에 스크롤 함수가 들어가므로 반드시 스크롤 함수가 먼저 생성되어야 합니다.
네이버 검색창에서 블로그를 검색하면 한 페이지에 최대 1,050개까지만 나타납니다. 이때 이 1,050개의 글이 한 번에 다 표시되는 것이 아니고 스크롤을 끝까지 내려야만 모두 표시되므로 맨 끝까지 스크롤을 내려주어야 합니다.
맨처음 스크롤할 때 global 변수로 글 링크, 제목, 날짜 리스트를 선언하므로 스크롤 함수를 두 개 만듭니다.
리스트를 global로 만드는 이유는 함수 사용이 끝난 후 함수 밖에서도 사용하기 위함입니다.
아래는 첫 번째 스크롤 함수입니다.
def scroll_first():
#### 처음 제목, 링크, 날짜 리스트에 담기
# 스크롤이 계속 내려가는 페이지일 경우
prev_height = driver.execute_script("return document. body.scrollHeight")
while True:
# 첫 번째 스크롤
driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")
# 시간 대기
time.sleep(2)
# 현재 높이 저장
current_height = driver.execute_script("return document. body.scrollHeight")
# 현재 높이와 더이상 스크롤이 되지 않는 상태의 높이가 같으면 while 종료
if(current_height == prev_height):
break
else:
prev_height = driver.execute_script("return document.body.scrollHeight")
# 검색 결과 블로그글 url과 제목 가져오기
class_articles = ".api_txt_lines.total_tit"
url_link = driver.find_elements(By.CSS_SELECTOR, class_articles)
# 글 작성 일자 가져오기
class_datetime = ".sub_time.sub_txt"
date_time = driver.find_elements(By.CSS_SELECTOR, class_datetime)
global url_list
global title_list
global date_list
url_list = []
title_list = []
date_list = []
for article in url_link:
url = article.get_attribute('href')
url_list.append(url)
for article in url_link:
title = article.text
title_list.append(title)
for date in date_time:
datetime = date.text
date_list.append(datetime)
이제 두 번째부터 사용하는 스크롤 함수입니다.
def scroll_next():
prev_height = driver.execute_script("return document. body.scrollHeight")
while True:
driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")
time.sleep(2)
current_height = driver.execute_script("return document. body.scrollHeight")
if(current_height == prev_height):
break
else:
prev_height = driver.execute_script("return document.body.scrollHeight")
class_articles = ".api_txt_lines.total_tit"
url_link = driver.find_elements(By.CSS_SELECTOR, class_articles)
class_datetime = ".sub_time.sub_txt"
date_time = driver.find_elements(By.CSS_SELECTOR, class_datetime)
for article in url_link:
url = article.get_attribute('href')
url_list.append(url)
for article in url_link:
title = article.text
title_list.append(title)
for date in date_time:
datetime = date.text
date_list.append(datetime)
마지막으로 크롤링할 때 쓰는 함수를 만들어줍니다.
월별로 수집하도록 코드는 작성한 이유는 검색량이 많은 데이터를 수집할 경우 시간이 오래 걸리기 때문입니다.
추후 월별 데이터를 연도별로 합칠 것입니다.
# year=연도(연도에 지정된 숫자! 2018년은 16), month=월(1~12), days=해당 월의 일수
def basic_crawling(year, month, days):
# 함수 입력값인 days 값이 맨 처음에 오고, 1이 맨 뒤에 오도록 거꾸로 일자 리스트를 만듭니다.
day_list = list(range(days,0,-1))
# 네이버 검색창에서 블로그로 들어가기 위해 선택하는 과정입니다.
driver.find_element(By.LINK_TEXT, "VIEW").click()
time.sleep(1)
driver.find_element(By.LINK_TEXT, "블로그").click()
time.sleep(1)
# 정렬과 기간을 설정하기 위해 옵션을 클릭합니다.
driver.find_element(By.LINK_TEXT, "옵션").click()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "#snb > div.api_group_option_sort._search_option_detail_wrap > ul > li.bx.lineup > div > div > a:nth-child(2)").click()
time.sleep(1)
# 옵션 > 기간 > 직접입력 클릭
driver.find_element(By.CSS_SELECTOR, "#snb > div.api_group_option_sort._search_option_detail_wrap > ul > li.bx.term > div > div.option > a.txt.txt_option._calendar_select_trigger").click()
time.sleep(1)
# 기간 직접입력 시작과 끝의 앞부분 동일 CSS 코드 변수로 담기 (코드 단순화용)
GS_head_css = "#snb > div.api_group_option_sort._search_option_detail_wrap > ul > li.bx.term > div > div.api_select_option.type_calendar._calendar_select_layer > "
# 기간 직접입력 년월일 앞 CSS 코드 변수로 담기 (코드 단순화용)
GSYMD_head_css = "#snb > div.api_group_option_sort._search_option_detail_wrap > ul > li.bx.term > div > div.api_select_option.type_calendar._calendar_select_layer > div.select_wrap._root > "
for day in day_list:
if day == days:
print('{}일 작성글 검색 후 수집중 . . .'.format(day))
# ★☆★☆ 맨 처음 기간설정 할 때 ☆★☆★
#### 기간 설정시작 클릭
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.set_calendar > span:nth-child(1) > a").click()
time.sleep(1)
# 연도는 year
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(1) > div > div > div > ul > li:nth-child({})".format(year)).click()
time.sleep(1)
# 월은 month
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(2) > div > div > div > ul > li:nth-child({})".format(month)).click()
time.sleep(1)
# 일자는 days. 마지막 일자 부터 검색해서 저장할 것이기 때문!
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(3) > div > div > div > ul > li:nth-child({})".format(days)).click()
time.sleep(1)
#### 기간 설정끝 클릭
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.set_calendar > span:nth-child(3) > a").click()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(1) > div > div > div > ul > li:nth-child({})".format(year)).click()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(2) > div > div > div > ul > li:nth-child({})".format(month)).click()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(3) > div > div > div > ul > li:nth-child({})".format(days)).click()
time.sleep(1)
#### 기간 설정란 적용 버튼 클릭
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.btn_area > button").click()
time.sleep(1)
# 첫 번째 스크롤&전역변수 선언&제목/링크/날짜 저장
scroll_first()
else:
# 두 번째부터는 스크롤 맨 위로 올려서 일자만 바꾸기!
print('{}일 작성글 검색 후 수집중 . . .'.format(day))
driver.execute_script('window.scrollTo(0,0)')
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "#snb > div.api_group_option_sort._search_option_detail_wrap > ul > li.bx.term > div > div.option > a.txt.txt_option._calendar_select_trigger").click()
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.set_calendar > span:nth-child(1) > a").click()
time.sleep(0.5)
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(3) > div > div > div > ul > li:nth-child({})".format(day)).click()
time.sleep(0.5)
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.set_calendar > span:nth-child(3) > a").click()
time.sleep(0.5)
driver.find_element(By.CSS_SELECTOR, GSYMD_head_css + "div:nth-child(3) > div > div > div > ul > li:nth-child({})".format(day)).click()
driver.find_element(By.CSS_SELECTOR, GS_head_css + "div.btn_area > button").click()
time.sleep(1)
# 두 번째 스크롤&제목/링크/날짜 저장
scroll_next()
global crawl_df
crawl_df = pd.DataFrame({'url': url_list, 'title':title_list, 'date':date_list})
간혹 중복 수집되는 경우도 있으므로 중복을 제거해줍니다.
crawl_DF = crawl_df.drop_duplicates()
crawl_DF
네이버에서 검색했더라도 티스토리나 다른 사이트의 블로그도 검색됩니다.
네이버만 남겨주는 작업을 실행합니다.
link_list = crawl_DF['url'].to_list()
print(len(link_list))
find_naver = [i for i in range(len(link_list)) if 'naver' in link_list[i]]
print(len(find_naver))
NB_DF = crawl_DF.iloc[find_naver]
NB_DF
다른 월도 수집한다면 NB-DF를 다른 이름의 데이터프레임으로 임시 저장합니다.
df_월 = NB_DF
# 잘 저장됐나 확인해봅니다.
df_월
1년치 데이터를 다 수집했다면 이제 월별 데이터프레임을 하나로 합쳐줍니다.
# 1월부터 저장해도 되고 12월부터 저장해도 됩니다. 저는 최신순 정렬을 선호해서 역순으로 작성했습니다.
df_연도 = pd.concat([df_12, df_11, df_10, df_9, df_8, df_7, df_6, df_5, df_4, df_3, df_2, df_1], axis=0)
# 잘 저장됐나 확인해봅니다.
df_연도
이제 1년치 데이터를 원하는 형식으로 저장하면 끝입니다.
# 텍스트 파일로 저장할 경우
df_연도.to_csv("저장경로.txt", index=False, encoding='UTF-8')
# csv 파일로 저장할 경우. 이때 구분자는 탭(sep='\t')도 가능합니다.
df_연도.to_csv("저장경로.csv", index=False, encoding='UTF-8')
# 엑셀 파일로 저장할 경우.
df_연도.to_excel("저장경로.xlsx")
다른 기술 블로그들이 그렇듯,
코드를 블로그에 공유하는 것은 기록과 '공유'를 위해서입니다.
만약 제 글의 도움을 받아 본인의 글을 작성하신다면 출처를 남겨주시길 바랍니다.
문의는 댓글로 남겨주시면 확인 후 답변드리겠습니다.
'데이터 분석 > Python' 카테고리의 다른 글
[텍스트 마이닝-전처리] 불용어 제거 및 한글만 남기기: 파이썬 정규표현식 (0) | 2023.09.17 |
---|---|
[텍스트 마이닝-수집] 네이버 블로그 크롤러(2) - 본문 수집 (0) | 2023.09.17 |
[텍스트 마이닝-수집] 구글 학술 검색 인용 스크래핑 (0) | 2023.09.12 |
[텍스트 마이닝-오류] 크롬드라이버 오류: 셀레니움 크롤링 실행시 scoped_dir 폴더 및 파일 생성 (0) | 2023.05.25 |
[텍스트 마이닝-정제] Mecab-Ko 사전 품사 태그 (0) | 2023.03.17 |
댓글