본문 바로가기
코딩 연습/코딩배우기

티스토리 글쓰기 - 파이썬 selenium 자동 등록 코드

by good4me 2024. 11. 21.

goodthings4me.tistory.com

2년 전에 티스토리 API를 활용하여 자동 등록하는 코드를 만들었는데, 이용을 안 하다가 최근에 다시 해볼까 해서 정보를 찾아보니 티스토리 등록 API가 없어졌다고 한다. 그래서 selenium을 만들어서 테스트한 후 그 코드를 올려봅니다.

 

티스토리 글쓰기 - 파이썬 selenium 자동 등록 코드

 

티스토리에 html 파일 자동 등록하기

티스토리에 여러개의 글을 html 파일 형태로 등록하기 위해서는 다음과 같은 절차로 진행합니다.

  • 카테고리를 하나 만든다. (카테고리가 없으면 생략해도 되지만, 아래 코드는 '테스트'라는 카테고리에 등록함)
  • 티스토리에 포스팅 등록할 html 파일을 먼저 만든다.
  • selnium을 사용하여 티스토리에 자동으로 html 모드 방식으로 등록하도록 코드를 작성한다. (아래 코드)

 

등록 페이지 먼저 확인하기

 

 

1. 티스토리 카테고리 만들기

카테고리

 

 

2. 포스팅 등록할 html 코드 작성하기

포스팅 등록할 html 파일

 

파일 이름이 긴 이유는

파일 이름 중 '$'를 구분자로 하여 파이썬 코드에서 split() 함수로 나눈 후,

앞 부분은 포스팅 제목으로 사용하고,

뒷 부분은 태그로 자동 등록하기 위함

 

[html 파일 코드]

<h2 style="text-align: center; font-weight: bold;">마인드시티 앱 다운로드</h2>
<p style="text-align: center;">
<a href="https://play.google.com/store/apps/details?id=com.kobotis.mindcity&amp;hl=ko" target="_blank" style="display: inline-block; background-color: green; color: white; font-weight: bold; padding: 10px 20px; border-radius: 15px; text-decoration: none; transition: background-color 0.3s;">Mind City 앱 다운로드(Android)</a>
</p>
<p style="text-align: center;">
<a href="https://apps.apple.com/kr/app/%EB%AF%B8%EC%85%98%EC%8B%9C%ED%8B%B0-missioncity/id1663107997" target="_self" style="display: inline-block; background-color: blue; color: white; font-weight: bold; padding: 10px 20px; border-radius: 15px; text-decoration: none; transition: background-color 0.3s;">Mind City 앱 다운로드(IOS)</a>
</p>

 

 

3. 포스팅 html 글을  selnium을 사용하여 티스토리에 자동 등록하는 파이썬 코드

import os, time, random
import pyperclip
import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
import keys_manager
import pyperclip

def init_driver():
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36')  # 사용자 에이전트 지정
    # chrome_options.add_argument('headless')
    chrome_options.add_experimental_option('detach', True)
    chrome_ver = chromedriver_autoinstaller.get_chrome_version()
    print(f'크롬 현재 버전: {chrome_ver}')
    chromedriver = f'./{chrome_ver.split(".")[0]}/chromedriver.exe'
    if not os.path.exists(chromedriver):
        os.makedirs(os.path.dirname(chromedriver), exist_ok=True)
        res = chromedriver_autoinstaller.install(True)  # 크롬 드라이버 다운로드
        if res:
            print(f'크롬 드라이버 설치 완료!({chrome_ver.split(".")[0]} 버전)')
        else:
            print(f'크롬 드라이버 설치 오류 발생!({chrome_ver.split(".")[0]} 버전)')
    driver = webdriver.Chrome(options=chrome_options)
    return driver


def tistory_login(_driver):
    _driver.get('https://www.tistory.com/auth/login')
    time.sleep(3)
    login_button = _driver.find_element(By.CLASS_NAME, 'txt_login')
    login_button.click()
    time.sleep(1)

    # 아이디 입력
    email_input = _driver.find_element(By.NAME, 'loginId')
    email_input.click()
    time.sleep(0.5)
    email_input.send_keys(keys_manager.mindcity[0])
    time.sleep(1)

    # 비밀번호 입력
    password_input = _driver.find_element(By.NAME, 'password')
    password_input.click()
    time.sleep(0.5)
    password_input.send_keys(keys_manager.mindcity[1])
    time.sleep(1)

    # 로그인 버튼 클릭
    login_button = _driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
    login_button.click()
    time.sleep(60)  # 2단계 인증 또는 캡차 있을 시 60


def tistory_write():
    _driver = init_driver()  # chrome driver init
    tistory_login(_driver)  # 티스토리 로그인

    ## 예약 발행 시, 분
    hour_minute = [
        (10, random.randint(1, 15)),
        (10, random.randint(41, 59)),
        (11, random.randint(1, 15)),
        (11, random.randint(41, 59)),
        ]
        
    ## 포스팅 티스토리 URL, 카테고리 지정 필수!!
    tistory_blog_name = f'''https://mind-city.tistory.com'''
    tistory_category_name = '테스트'

    # html 파일이 있는 폴더 경로
    folder_path = "./data/tistory/"

    file_list = []
    for filename in os.listdir(folder_path):
        file_list.append(filename)
    print(file_list)

    hm_idx = 0
    for n, file_name in enumerate(file_list, 1):
        posting_title = file_name.split('$')[0]
        tags = file_name.split('$')[1].replace('.html', '')
        print(f'\n{posting_title}\n{tags}\n\n')
        time.sleep(3)

        # tistory 포스팅 시작
        _driver.get(f'{tistory_blog_name}/manage/post')
        time.sleep(3)
        # _driver.maximize_window()  # 창 최대화

        # 이전에 쓰고 있는 글이 있어도 새로운 글을 작성하도록 alert popup 처리
        try:
            obj = _driver.switch_to.alert
            print(f'\nalert dismiss')
            obj.dismiss()
            time.sleep(2)
        except:
            pass
            
        ### html 모드로 작성 ###
        # html 모드로 변환
        _driver.find_element(By.ID, 'editor-mode-layer-btn-open').click()
        time.sleep(2)
        _driver.find_element(By.ID, 'editor-mode-html').click()
        time.sleep(2)
        _driver.switch_to.alert.accept()  # html 모드 변환시 alert 처리
        time.sleep(2)
        
        # 카테고리 선택 - 위에서 카테고리로 지정한 것 찾음
        _driver.find_element(By.ID, 'category-btn').click()
        time.sleep(2)
        _driver.find_element(By.XPATH, f"//span[normalize-space()='{tistory_category_name}']").click()
        time.sleep(2)
        
        # 제목 입력
        _driver.find_element(By.ID, 'post-title-inp').click()
        pyperclip.copy(posting_title)
        ActionChains(_driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
        time.sleep(1)
        
        # 글쓰기 -------------------------------------------------------------------------
        post_body_content = ''
        if file_name.endswith(".html"):
            input_path = os.path.join(folder_path, file_name)
                
            # 파일을 여러 인코딩 방식으로 시도하여 읽기
            encodings = ['utf-8', 'cp949']
            for encoding in encodings:
                try:
                    with open(input_path, 'r', encoding=encoding) as file:
                        post_body_content = file.read()
                    break
                except UnicodeDecodeError:
                    print(f"Failed to read {input_path} with encoding {encoding}")
                    continue
            else:
                print(f"Failed to read {input_path} with all tried encodings.")
                continue

        _driver.find_element(By.CLASS_NAME, 'CodeMirror-lines').click()
        pyperclip.copy(post_body_content)
        ActionChains(_driver).key_down(Keys.CONTROL).send_keys('v').key_up(Keys.CONTROL).perform()
        time.sleep(1)

        # 태그 입력하기
        tag_input = _driver.find_element(By.ID, "tagText")
        tag_input.click()
        time.sleep(1)
        tag_input.send_keys(tags)
        tag_input.send_keys(Keys.RETURN)
        time.sleep(1)

        # 완료 버튼 누르기
        _driver.find_element(By.ID, 'publish-layer-btn').click()
        time.sleep(1)

        # 공개 버튼
        _driver.find_element(By.ID, 'open20').click()
        time.sleep(2)

        # 예약 버튼 - 위 예약 버튼, 형제 태그인 예약 버튼 찾기에서 에러가 발생하여 아래 코드로 수정하여 사용함
        current_reserve = _driver.find_elements(By.CLASS_NAME, "btn_date")
        current_reserve[1].click()
        time.sleep(1)

        new_hour = hour_minute[hm_idx][0]
        new_minute = hour_minute[hm_idx][1]

        # 예약 시간 입력: 두 번째 box_date의 input 태그 찾기
        hour_input = _driver.find_element(By.XPATH, "(//div[@class='box_date']/input[@type='number'])[1]")  # 시간
        hour_input.click()
        time.sleep(1)

        # 새 시간 입력
        hour_input.clear()  # 기존 값 지우기
        hour_input.send_keys(str(new_hour))
        time.sleep(1)

        # 예약 시간 입력: 세 번째 box_date의 input 태그 찾기
        minute_input = _driver.find_element(By.XPATH, "(//div[@class='box_date']/input[@type='number'])[2]")  # 분
        minute_input.click()
        time.sleep(1)

        # 새 시간 입력
        minute_input.clear()  # 기존 값 지우기
        minute_input.send_keys(str(new_minute))
        time.sleep(1)

        # '공개 발행' 버튼 클릭
        publish_button = _driver.find_element(By.ID, "publish-btn")
        publish_button.click()
        time.sleep(2)

        hm_idx += 1

    _driver.quit()
    print("\nEND!!!")

tistory_write()

 

  • init_driver() 함수는 selenium 웹드라이버 구동 함수이고,
  • tistory_login() 함수는 selenium 구동 웹 페이지에서 tistory.com에 접속한 후 아이디와 비밀번호를 자동으로 입력함
  • 이때, keys_manager 모듈을 통해 해당 티스토리의 계정(id, pw)을 가진 리스트 변수에서 id, pw를 불러오도록 함
  • 로그인 버튼 클릭 마지막 부분에 있는 time.sleep(60)은 혹시 있을 티스토리 2단계 인증 또는 캡챠를 대비하기 위해 60초로 설정되어 있고, 이 2가지가 뜨지 않는다면 2~3초 설정해도 됨

 

 

  • tistory_write() 함수에서 hour_minute 부분은 예약 설정을 위한 것으로, (10, random.randint(1, 15)) 의미는 10시 기준으로 1분에서 15분 사이의 숫자 1개를 사용하도록 함
  • 첫번째 for 문은 지정 폴더에서 여러 개의 html 파일을 읽어서 file_list 리스트에 넣고,
  • 두번째 for 문은 실제 파일을 하나씩 불어와서 포스팅 제목과 태그를 설정하고, 포스팅을 자동으로 시작하는 코드임
  • 잠시 기다리면 html 모드로 해당 html 파일을 자동으로 등록함

 

4. 자동 등록 관련 내용 보기

자동 등록 중인 페이지 캡처 이미지

자동 등록 중인 페이지 캡처 이미지

 

 

 

 

자동 등록 예약 페이지

자동 등록 예약 페이지

 

 

수정 버튼 클릭 시 페이지

수정 버튼 클릭 시 페이지

 

 

등록된 포스팅 리스트

등록된 포스팅 리스트

 

 

 

등록된 포스팅 내용

등록된 포스팅 내용

 

 

댓글