이번 포스트에서는 Selenium으로 국내 온라인 커뮤니티중 하나인 DC Inside(디시인사이드)를 크롤링하는 방법에 대해 정리하고자 합니다.
일반적으로 웹에서 데이터를 가져올 때 해당 사이트에서 제공하는 공식 API가 있으면 가장 좋지만, 그렇지 않은 경우에 request와 BeautifulSoup을 이용해서 크롤링을 진행할 수 있습니다. 하지만 request를 사용하는 경우 sleep()을 사용했음에도 불구하고 접속 차단을 하는 사이트가 존재합니다. 또한 크롤링 타깃 사이트가 동적페이지인 경우, request를 이용하여 동적 페이지를 크롤링 못하는 건 아니지만(예시 : 네이버 데이터랩 크롤링) 개인적으로 다른 사이트에 적용 해 보았을 때 잘 안 되는 경우가 있었습니다. 이러한 경우에는 request를 사용하였을 때의 속도는 포기하더라도(?) 데이터를 가져오고 싶다면 Selenium을 고려해 볼 수 있습니다.
Selenium은 웹 브라우저를 컨트롤 할 수 있는 라이브러리입니다. 전체적인 프로세스는 selenium을 이용하여 웹 사이트를 컨트롤 한 뒤, BeautifulSoup을 이용하여 웹 사이트의 HTML source를 가져온 다음, 우리가 원하는 정보를 빼오는 방식으로 진행합니다.
크롤링의 목적은 DC인사이드의 '프로그래밍' 갤러리에서 특정 기간동안 발생한 게시글 목록의 제목과 해당 게시글의 내용 및 댓글정보를 모두 가져오는것으로 하겠습니다. (단, 그림 및 디시콘은 제외합니다.)
우선 selenium 및 기타 사용할 라이브러리들을 import 해줍니다.
#selenium 라이브러리
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
#추가 조작 라이브러리
import time
from bs4 import BeautifulSoup
from time import sleep
#기타 라이브러리
import pandas as pd
ser = Service(r"chromedriver.exe 경로 넣는 부분")
op = webdriver.ChromeOptions()
driver = webdriver.Chrome(service = ser,options = op)
다음으로 특정 날짜에 작성된 게시글을 찾아야 하므로 미리 날짜의 범위를 지정해 줍니다.
selenium으로 컨트롤을 할 때, 페이지를 앞에서부터 뒤로 넘어갈 예정이므로 날짜 범위중 현재와 가장 가깝게 작성된 게시글부터 발견이 되므로 start_date로 네이밍을 하였습니다. 반대로 end_date는 수집 기간의 마지막(오래된) 날짜를 의미합니다.
start_date = time.strptime("2023.1.1","%Y.%m.%d")
end_date = time.strptime("2022.1.1","%Y.%m.%d")
다음으로 수집한 정보를 저장해 둘 리스트를 설정합니다. 저 같은 경우는 서로 다른 DataFrame으로 설정하기 위해 아래와 같이 지정하였습니다.
#수집한 정보를 저장하는 리스트
c_gall_no_list = []
title_list = [] #제목
contents_list = [] #게시글 내용
contents_date_list = []
#수집한 정보를 저장하는 리스트
gall_no_list = [] #글 번호
reply_id = [] #답글 아이디
reply_content = [] #답글 내용
reply_date = [] #답글 등록 일자
이제는 selenium을 이용하여 직접 크롤링하는 부분입니다.
프로그래밍 갤러리가 아닌 다른 갤러리의 경우, "base_url = BASE + '/board/lists/?id=programming&page='+str(start_page)" 부분에서 가운데 주소 부분만 수정하면 바로 적용이 가능합니다.
#기본 URL
BASE = "http://gall.dcinside.com"
start_page = 1000
Flag = True
while Flag: #게시글의 페이지마다 loop를 수행(for i in range(100000000) 을 써도 무방)
#게시글 목록 페이지
base_url = BASE + '/board/lists/?id=programming&page='+str(start_page)
try:
driver.get(base_url)
sleep(3)
except : #예외 발생 시 다시 load
continue
#게시글 목록의 HTML source를 읽어옴
page_source = driver.page_source
soup = BeautifulSoup(page_source, "html.parser")
#모든 게시글의 정보를 찾음
article_list = soup.find('tbody').find_all('tr')
#수집하는 기간에 맞는 게시글이 목록에 있는지 체크
date = "20" + article_list[-1].find("td",{"class" : "gall_date"}).text
contents_date = time.strptime(date,"%Y.%m.%d")
if start_date < contents_date: #게시글의 마지막 날짜가 수집기간 날짜에 포함되는가?
start_page += 1
continue
elif contents_date < end_date: #수집기간보다 오래된 날짜면 수집을 완료함
print("수집을 종료합니다.")
Flag = False
continue
for article in article_list:
#게시글의 제목을 가져오는 부분
title = article.find('a').text
#게시글의 종류(ex-일반/설문/투표/공지/등등...)
head = article.find('td',{"class": "gall_subject"}).text
if head not in ['설문','AD','공지']: #사용자들이 쓴 글이 목적이므로 광고/설문/공지 제외
#게시글 번호 찾아오기
gall_id = article.find("td",{"class" : "gall_num"}).text
if gall_id in c_gall_no_list:
continue
#각 게시글의 주소를 찾기 -> 내용 + 댓글 수집 목적
tag = article.find('a',href = True)
content_url = BASE + tag['href']
#게시글 load
try:
driver.get(content_url)
sleep(3)
contents_soup = BeautifulSoup(driver.page_source,"html.parser")
#게시글에 아무런 내용이 없는 경우 -> 에러뜸 (빈 문자열로 처리하고 댓글만 가져와도 됩니다.)
contents = contents_soup.find('div', {"class": "write_div"}).text
except :
continue
#게시글의 작성 날짜
c_date = "20" + article.find("td",{"class" : "gall_date"}).text
#게시글 제목과 내용을 수집
c_gall_no_list.append(gall_id)
title_list.append(title)
contents_list.append(contents)
contents_date_list.append(c_date)
#댓글의 갯수를 파악
reply_no = contents_soup.find_all("li",{"class" : "ub-content"})
if len(reply_no) > 0 :
for r in reply_no:
try:
user_name = r.find("em").text #답글 아이디 추출
user_reply_date = r.find("span",{"class" : "date_time"}).text #답글 등록 날짜 추출
user_reply = r.find("p",{"class" : "usertxt ub-word"}).text #답글 내용 추출
#댓글의 내용을 저장
gall_no_list.append(gall_id)
reply_id.append(user_name)
reply_date.append(user_reply_date)
reply_content.append(user_reply)
except: #댓글에 디시콘만 올려놓은 경우
continue
else:
pass
#다음 게시글 목록 페이지로 넘어가기
start_page += 1
수집이 완료되면 원하는 형태로 DataFrame을 만든 다음, csv파일로 저장할 수 있습니다.
#수집한 데이터를 저장
contents_df = pd.DataFrame({"id" : c_gall_no_list,
"title" : title_list,
"contents" : contents_list,
"date" : contents_date_list
})
reply_df = pd.DataFrame({"id" : gall_no_list,
"reply_id" : reply_id,
"reply_content" : reply_content,
"reply_date" : reply_date})
contents_df.to_csv("contents.csv",encoding ='utf8',index = False)
reply_df.to_csv("reply.csv",encoding = 'utf8',index = False)
위 코드가 중간에 작동이 잘 안 되는 경우가 가끔(?) 있습니다. 갑자기 selenium이 멈춘다던지...
selenium이 request에 비해 속도가 느리다 보니 크롤링 속도를 빠르게 하기 위해 창 없이 크롤링하는 방법 및 기타 등등의 방법이 있습니다만, 저는 문제가 생길 시 바로 대처하기 위해서 그냥 창을 띄워두었습니다.(보기도 좋습니다)
위 코드를 작동시키면 다음과 같이 저장이 됩니다. 예시는 프로그래밍 갤러리가 아닌 '지하성과 용사 마이너 갤러리'로 해보았습니다.
보시는바와 같이 게시글 ID, 게시글 제목, 게시글 내용, 그리고 작성일자가 올바르게 수집되었음을 확인할 수 있었습니다.
또한 댓글 정보도 마찬가지로 게시글 ID, 댓글 작성자 ID, 댓글 내용, 그리고 댓글 작성일자가 올바르게 수집되었음을 확인할 수 있습니다.
각 게시글에 달린 댓글은 게시글 ID 속성으로 연결되어 있기 때문에 각 게시글에 달린 댓글의 내용을 확인 할 수 있습니다.
여담으로 더욱 빠르게 게시글과 댓글을 수집하기 위하여 Async를 이용하여 크롤링을 해보았지만, 자꾸 차단먹어서 포기했습니다..
혹시 방법을 아시는분이 계시다면 알려주시면 감사하겠습니다..
감사합니다.
'파이썬(Python) > 파이썬 관련' 카테고리의 다른 글
[Hugging Face🤗] Padding & Truncation 정리 (0) | 2023.06.28 |
---|---|
GPU 메모리 확보하기 (0) | 2023.06.13 |
Pytorch 버전 확인하기 (0) | 2021.11.05 |
[Python] 가상환경(Virtual Environment) 세팅하기 (0) | 2021.08.04 |