이전 포스팅을 꼭 참고해주시기 바랍니다.
[Develop/파이썬] 셀레니움 크롤링(Crawling) - Tistory 포럼 자동 댓글 등록 프로그램 : https://itstory1592.tistory.com/26
이번 포스팅에서는 지난 글에 이어 파이썬 셀레니움 라이브러리를 이용하여 개발했던
티스토리 포럼 게시글에 자동으로 댓글을 다는 프로그램의 문제점을 개선한 내용에 대해 다룰 것이다.
우선, 이 프로그램의 문제점을 요약해보면 이렇다.
1. 이미 댓글을 달아놓은 게시글에도 또 다시 댓글을 다는 문제점이 존재한다.
2. 이미 내 구독자임에도 맞구독을 하자는 댓글을 다는 상황이 발생한다.
위와 같은 이유로 기존 프로그램은 자칫하면 하나의 게시글에 중복된 댓글을 난무하는 문제의 프로그램이 될 뻔했다.
따라서 기존 코드에 새로운 로직을 추가할 필요가 있다고 생각했다.
첫 번째로는, 이미 내 구독자분이라면 맞구독을 요청할 필요가 없으니 해당 게시글은 넘기는 것이다.
두 번째로는, 이미 댓글을 달아놓은 게시글은 해당 게시글을 구분할 수 있는 데이터를 따로 기록해두고, 다음번에 댓글을 등록할 때, 동일한 게시글이면 그냥 넘겨버리는 것이다.
게시글을 구분하는 기준을 어떤 것으로 정해야할지 고민하던 중에 댓글로 좋은 조언을 하나 얻을 수 있었다.
바로 게시글의 '작성일시'를 기준으로 구분하는 것이다.
티스토리 포럼에는 웬만하면 동일한 분(minute) 단위로는 글이 올라오는 경우가 없기 때문에 작성 일시를 기준으로 삼아도 될 것이라고 판단하였다.
그럼 이제 수정한 코드를 살펴보도록 하자.
기존 코드와 동일한 부분이 있기 떄문에 일부 코드는 포스팅에서 제외하였으므로, 이전 포스팅을 함께 참고하면 좋을 것 같다.
def get_num_followers():
driver.get('https://www.tistory.com/feed')
time.sleep(5)
driver.implicitly_wait(3)
num_followers = int(driver.find_element_by_css_selector('#mFeature > div > div > a:nth-child(2) > span').text)
# 내 구독자 수
print('구독자 수 : {}'.format(num_followers))
return num_followers
우선, 내 구독자 수를 가져오는 코드를 추가하였다.
구독자 수를 알아야 하는 이유는 티스토리 '피드' 페이지에서 본인 구독자의 닉네임을 가져오기 위해서이다.
피드의 구독자 페이지는 Dialog 형식이기 때문에 저 많은 숫자가 query 형식으로 전달되어 열리는 게 아니다.
따라서 다음 페이지 버튼 (>) 을 눌러주면서 다음 페이지로 이동하여야 하는데,
전체 구독자 수를 구하는 이유는 바로 버튼을 몇 번 누를지 정해야하기 때문이다.
get_num_followers() 메소드를 사용한 아래 코드를 살펴보자.
def get_followers():
follower_list = list()
num_followers = get_num_followers()
# 피드 페이지에 있는 구독자 리스트 생성
pages = math.ceil(num_followers / 15)
# follower 페이지 연결
driver.get('https://www.tistory.com/feed/follower')
driver.implicitly_wait(1)
for i in range(pages):
followers = driver.find_elements_by_css_selector('#cMain > div > div > div.layer_body > div > ul > li > a > div.wrap_cont > div > div > em')
for follower in followers:
follower_list.append(follower.text)
#print(follower.text)
if i != pages-1:
next_button = driver.find_element_by_class_name('ico_next')
driver.execute_script("arguments[0].click();", next_button)
time.sleep(0.5)
return follower_list
num_followers 변수에 우리가 정의한 get_num_followers() 메소드로 전체 구독자 수를 불러오고,
한 페이지당 15명의 구독자가 보여지기 때문에 15로 나누어 올림(ceil)하면 전체 페이지 수를 얻을 수 있다.
그러고 나서 follower(구독자) 페이지로 이동하여 한 칸씩 넘기며 구독자의 이름을 follower_list 배열에 담도록 하였다.
여기서 한가지 문제가 있었는데, 다음 페이지 버튼이 Button 태그가 아닌 a태그로 되어있기 때문에 click() 메소드로는 정상적으로 실행되지 않는다는 것이었다.
구글링을 통해 찾아본 결과, 이런 경우에는 클릭 과정이 onclick 속성에 정의되어 있지 않고,
js를 통해 실행되도록 구성되어 있기 때문에 execute_script() 메소드를 사용하여 태그의 스크립트 구문을 실행해야 한다고 한다.
따라서, click() 메소드 대신에 execute_script() 메소드로 마지막 페이지가 아닐 때까지 다음 페이지로 넘어가며 구독자들의 닉네임을 크롤링하였다.
#파일 경로 지정
file_path = '/content/gdrive/MyDrive/ColabNotebooks/tistory_auto_comment'
그 후에는 내가 댓글을 작성한 게시글의 작성일시를 저장할 수 있는 'tistory_auto_comment' 파일을 하나 만들어주었다.
이 파일은 구글 드라이브에 만든 파일이며, 해당 파일을 가져오기 위해 경로를 담아 둘 변수를 하나 생성해 주었다.
(구글 드라이브에서 파일 읽는법을 알고 싶다면 아래 글을 참고하길 추천한다.)
[파이썬] 코랩(CoLab)에서 구글 드라이브 파일(csv, txt ...) 가져오기 : https://itstory1592.tistory.com/27
import pandas as pd
def load_file():
df = pd.read_csv(file_path)
data = str(df.to_csv(sep='\n', index=False))
datas = data.split('\n')
return datas
여기서도 한 가지 문제점이 있었다.
사실 파이썬에서는 파일 입출력을 위한 with open 구문이나 open() 메소드가 준비되어 있는데,
이상하게도 이 기능을 여기서 사용하면 'WebElement' is not callable 오류가 발생하였다.
물론 오류를 해결하기 위해 하루 종일 삽질해봤지만 끝내 해결하지 못하여 다른 방법을 생각해보았다.
내가 생각한 또 다른 방법은 판다스(pandas) 라이브러리의 read_csv() 메소드로 파일을 불러와서
인덱스 없이 한 줄씩 구분하여 읽도록 하고 String 형식으로 변형하는 것이다.
불러올 때 한 줄씩 읽었기 때문에 split() 메소드의 매개변수로 '\n'을 전달하여 한 줄씩 구분 지어 배열에 담아 반환하도록 하였다.
(새로운 파일을 만들어서 간단한 예제를 코드로 작성하여 실행해보면 with open구문과 open() 메소드 모두 정상적으로 작동한다...😑)
'실행 코드'
_# 포럼에서 자동으로 댓글달기
# 이미 구독자거나, 댓글을 등록한 게시글인 경우에는 PASS
from random import randrange
import numpy as np
followers = get_followers()
registered_dates = load_file()
filtering(registered_dates)
print('현재 저장되어 있는 작성일시 : {}개'.format(len(registered_dates)))
new_registered_dates = list() # 새로운 날짜를 담을 배열 (배열 사이즈가 부족하기 때문에)
num_comments = 0
comments = ['안녕하세요ㅎㅎ\n저는 IT/코딩 관련 블로그를 운영하고 있습니다~~!!\n서로 맞구독하고 소통해요 :)',
'안녕하세요!\n서로 맞구독하고 소통하고 지내면 좋을 것 같아요 ㅎㅎ :D\n자주 방문할게요~~',
'안녕하세요 :)\n맞구독하고 함께 성장해나가요!\n블로그 자주 방문하겠습니다~ㅎㅎ',
'반갑습니다!\nIT/코딩 블로그를 운영하는 초보 블로거입니다.\n맞구독하고 자주 소통해요~!!',
'안녕하세요 :D\n저는 코딩 블로그를 운영하고 있습니다!\n맞구독하면서 친하게 지내요~']
for page in range(startpage, lastpage+1):
driver.get('https://www.tistory.com/community/forum/?page=' + str(page))
driver.implicitly_wait(3)
#댓글창 펼치기
opens = driver.find_elements_by_css_selector('#root > div > div.content_list > ul > li > div > button')
for open in opens:
open.click()
dates = driver.find_elements_by_css_selector('#root > div > div.content_list > ul > li > div.box_desc.box_desc_type3 > div.wrap_cont > div.info_g > a.txt_date') # 게시글 작성 일자 리스트
driver.implicitly_wait(1)
#댓글 입력
for i in range(0,len(dates)):
try:
name = driver.find_element_by_css_selector('#root > div > div.content_list > ul > li:nth-child({}) > div > div > div > a.txt_id'.format(i+1)).text # 포럼 게시글 작성자
text_box = driver.find_element_by_css_selector('#root > div > div.content_list > ul > li:nth-child({}) > div.box_cmt > div > form > fieldset > textarea'.format(i+1)) # 댓글 텍스트 입력창
if name not in followers: # 내 구독자가 아니라면
if dates[i].text not in registered_dates: # 내가 등록한 댓글의 게시글이 아니라면 ('작성일시로 구분')
comment = comments[randrange(len(comments))]
print('added date log : {}'.format(dates[i].text))
print(comment)
text_box.send_keys(comment)
text_box.submit()
num_comments += 1
new_registered_dates.append(dates[i].text)
except NoSuchElementException as e:
pass
new_registered_dates_df = pd.DataFrame(new_registered_dates)
new_registered_dates_df.to_csv(file_path, mode='a', index=False, header=False)
new_registered_dates.clear()
print('='*30)
print('현재 등록한 페이지 : {}'.format(page))
print('현재까지 등록한 댓글 수 : {}'.format(num_comments))
print('='*30)
driver.quit()
이제 위에서 만든 사용자 정의 함수를 직접적으로 사용할 시간이 왔다.
코드를 차근차근 살펴보면,
get_followers()와 load_file() 메소드로 내 구독자 리스트와 댓글을 작성한 게시글의 작성 일시를 불러온다.
그리고 num_comments 변수를 선언하여 실제로 프로그램을 통해 몇개의 댓글을 달았는지 체크할 수 있도록 한다.
이번 코드가 지난번과 달라진 점은 댓글을 한가지가 아닌, 여러 개를 랜덤으로 선택하여 등록한다는 점이다.
나는 총 4개의 댓글을 리스트 변수에 담아 두어 사용하였다.
또한, 이 부분의 코드에 맨 처음에 언급한 2가지 조건이 추가되었다.
1. 포럼 게시글 작성의 닉네임을 살펴보고 아직 내 구독자가 아니라면
2. 게시글의 작성일시가 내가 불러온 tistory_auto_comment 파일에 없는 작성 일시라면
댓글을 등록하도록 하였다.
댓글을 등록할 때마다 num_comments의 숫자를 1씩 증가시키고, 어떤 댓글을 달았는지 출력 하도록 하였다.
댓글을 등록하면 당연히 해당 게시글의 작성 일시를 new_registered_dates 배열에 추가하는 로직이다.
전체 코드를 살펴보면 주석으로 filtering이라고 적어놓은 부분이 보일 것이다.
이 코드를 추가한 이유는 아래 이미지를 보면 이해할 수 있다.
판다스의 read_csv() 메소드로 데이터를 불러오면 DataFrame 형식으로 값을 받아오는데
이것 때문인지 0이란 숫자와 큰 따옴표 """ 가 파일에 함께 추가되어서 저장이 된다.
사실 저 문자들이 파일에 있어도 실제로 프로그램을 실행하는데 문제 되는 것은 없지만
작성 일시 데이터가 많아져서 불러올 문장들이 많아질 때를 대비하여 파일을 불러올 때만큼은 두 가지 문자를 제거하는 코드를 작성한 것이다.
마지막으로 한 페이지가 끝날때마다 new_registered_date 배열에 담긴 값을 파일에 덧붙여 저장하도록 하였다.
프로그램이 끝날때 추가하는 로직을 사용하면, 혹시라도 중간에 프로그램이 멈췄을 때 기록한 작성일시가 모두 날아가기 때문이다.
또한, 현재의 프로그램 진행 상황을 알 수 있도록 페이지와 등록한 댓글 수를 출력하도록 하였다.
자...!! 이제 코드를 실행시켜 지난번과 다른 부분을 실감해보자!
코드 테스트 용도로 이 프로그램을 사용하는 것이기 때문에 짧게 1 ~ 3 페이지까지만 댓글을 등록해보도록 하겠다.
코드를 실행해보면 처음에 현재 내 구독자수가 출력되고,
댓글을 등록한 게시글의 작성 일시가 추가됐다는 로그와 함께 등록한 댓글까지 출력해준다.
중간중간 결과를 확인해보면 프로그램이 등록한 댓글의 수와 페이지를 출력해준다.
포럼을 들어가 보면 실제로도 댓글이 잘 달려있는 걸 확인할 수 있다.
이제 tistory_auto_comment 파일을 열어서 작성 일시가 저장되어있는지도 확인해보자.
댓글을 작성한 게시글의 작성일시가 정상적으로 저장되었다.
다음에 다시 프로그램을 실행시키면 이 파일을 불러와서 중복 댓글을 방지해줄 것이다.
저번 글에서도 언급했지만 매크로적인 반복 행동은 티스토리 정지 사유가 될 수 있기 때문에
위 코드를 사용하는 건 자유지만, 개인적으로는 '프로그래밍을 통해 이런 것도 가능하구나!' 까지만 생각하는 것을 권장합니다.👍
👍클릭으로 구독하기👍
(이해가 다소 힘들거나, 틀린 부분이 있다면 댓글 부탁드리겠습니다! 😊)
💖도움이 되셨다면 '구독'과 '공감' 부탁드립니다!💖
'Programming > 파이썬' 카테고리의 다른 글
[파이썬] Python Sqlite3 모듈을 사용하여 Database를 생성하고 데이터를 관리해보자 (10) | 2021.06.14 |
---|---|
[파이썬] Python SMTP 모듈을 사용하여 Email 전송하기 / SMTPAuthenticationError : 534 오류 해결 방법 (27) | 2021.06.07 |
[파이썬] 코랩(CoLab)에서 구글 드라이브 파일(csv, txt ...) 가져오기 (23) | 2021.06.05 |
[Develop/파이썬] 셀레니움 크롤링(Crawling) - Tistory 포럼 자동 댓글 등록 프로그램 (37) | 2021.06.04 |
[파이썬/Error] 셀레니움 unknown error: ChromeDriver only supports characters in the BMP 해결방법 (94) | 2021.06.03 |