본문 바로가기

Dev/Python

Authorization Code 방식으로 티스토리 API를 이용하자 - 근성판

 

[Log] - 티스토리 API가 재대로 동작하지 않았던 이유를 알아내다.

 

티스토리 API가 재대로 동작하지 않았던 이유를 알아내다.

 9월 1일부터 주식 종목분석 자동 포스팅이 올라오지 않았다. [Log] - 티스토리 API에 다시 문제가 생겼다. 티스토리 API에 다시 문제가 생겼다.  9월 1일부터 개발했던 종목분석기의 기능인 티스토�

nemowork.com

 몇일 전까진 티스토리 API가 안되는 이유에 대해서 알아냈었고, 그렇다면 '이제 어떻게 해야하는가?'에 대한 고민을 하기 시작했다. 시간이 많다면 주식 정보를 간단하게나마 공유하고 내가 보길 원하는 정보를 더 보기 좋게 할수 있도록 작은 웹서비스나 앱이라도 만들어보겠지만 그럴시간이 없다. (난 둘째를 키워야 한다.) 티스토리 API 소스코드를 어떻게든 동작 가능하게 만드는게 가장 빠른 방법이었다.

 그런데 이전 포스팅에서도 기술했다시피 authorization code 방식은 중간에 웹페이지에서 사용자가 '허가하기'라는 버튼은 브라우저에서 매번 클릭해야 access_token을 발급받기 위한 코드가 발급된다. 그래서 다시 잔머리를 굴려 웹 ui 테스트에서 자주 사용되는 헤드리스 브라우저를 이용해 '허가하기'를 클릭하는 과정을 자동화해보기로 했다. 

 헤드리스 웹 브라우저는 selenium을 사용하였다. 사용을 하기 위해서는 2가지 선행해야할 작업이 필요한데, 하나는 selenium을 설치하는 것이고... 

$ pip install selenium
Collecting selenium
  Downloading https://files.pythonhosted.org/packages/80/d6/4294f0b4bce4de0abf13e17190289f9d0613b0a44e5dd6a7f5ca98459853/selenium-3.141.0-py2.py3-none-any.whl (904kB)
     |████████████████████████████████| 911kB 930kB/s
Requirement already satisfied: urllib3 in c:\programdata\anaconda3\envs\catapult\lib\site-packages (from selenium) (1.25.8)
Installing collected packages: selenium
Successfully installed selenium-3.141.0

 다른 하나는 selenium을 통해 작동시킬 브라우저의 드라이버를 설치하는 것이다. chrome, edge, firefox 등 대부분의 웹 브라우저를 지원하지만 크롬의 경우 크롬 드라이버가 특정 버전의 것으로 설치되었는데, PC에 설치된 크롬이 드라이버의 버전과 맞지 않으면 동작을 하지 않기 때문에 auto update를 비활성화 시켜줘야 하는 단점이 있다. firefox의 경우, selenium에서 가장 잘 동작하며, 드라이버 버전과 파이어폭스 버전의 호환성 문제에서 크롬에 비해 좀 더 자유로운 편이기에 난 Firefox를 사용해 보기로 하였다. Firefox의 브라우저 드라이버는 아래의 주소에서 받을 수 있다. 

Firefox Driver Download

두가지가 완료되면 아래의 코드를 이용해 티스토리 API의 access_token을 발급받을 수 있고, 그를 통해 글 등록이 기존처럼 가능하다.

import requests
import re
import os
import markdown
from selenium import webdriver
from config.config import config

class PostingService:
  access_token = None

  def getAccessToken(self):
    oauth_url = "https://www.tistory.com/oauth/authorize"  # 인증 서버 접속용
    callback_url = CONFIG_TISTORY_COLLBACK_URL
    api_code = None

    browser = webdriver.Firefox(executable_path=CONFIG_FIREFOX_PATH)
    browser.get(oauth_url + "?client_id=" + CONFIG_TISTORY_CLIENT_ID + "&redirect_uri=" + callback_url + "&response_type=code")
    username = browser.find_element_by_id("loginId")
    password = browser.find_element_by_id("loginPw")

    # 티스토리 login 화면으로 리다이렉트 되는데, 여기서 ID/Password를 입력하고 로그인 버튼을 클릭한다.
    username.send_keys(CONFIG_TISTORY_LOGIN_ID)
    password.send_keys(CONFIG_TISTORY_PASSWORD)
    browser.find_element_by_class_name("btn_login").click()

    # api 사용 허가하기
    try:
      browser.find_element_by_class_name("confirm").click()
    except:
      if "code" in browser.current_url:
        match = re.match('(.*?)code=(?P<code>.*?)&state=', browser.current_url)
        gd = match.groupdict()
        api_code = gd['code']
        response = requests.get("https://www.tistory.com/oauth/access_token?client_id=" + CONFIG_TISTORY_CLIENT_ID + "&client_secret=" +
                     CONFIG_TISTORY_CLIENT_SECRET + "&redirect_uri=" + callback_url + "&code=" + api_code + "&grant_type=authorization_code")
        if response.status_code == 200:
          self.access_token = response.text.split('=')[1]
          print("access token : ", self.access_token)
          return True
    return False

  def createPost(self, title, content, category_name, tag):
    access_token = self.access_token
    blog_name = CONFIG_TISTORY_BLOG_NAME
    title = title  # 제목 (필수)
    content = content  # 글내용(필수)
    visibility = "0"  # 발행상태 0비공개-기본, 1보호, 3발행
    category_id = self.getCategoryId(category_name)  # 카테고리 아이디 기본값 0
    slogan = ""  # 문자주소
    tag = tag  # 태그 ,로 구분
    acceptComment = "1"  # 댓글 허용 (0, 1 - 기본값)
    password = ""  # 보호글 비밀번호

    url = 'https://www.tistory.com/apis/post/write'
    data = { 'access_token':access_token,
             'output':'json',
             'blogName':blog_name,
             'title':title,
             'content':content,
             'visibility':visibility,
             'category':category_id,
             'slogan':slogan,
             'tag':tag,
             'acceptComment':acceptComment,
             'password':password
             }
    r = requests.post(url, data=data)

    print(r.text)
    return r.text

  def getCategoryId(self, category_name):
    access_token = self.access_token
    blog_name = CONFIG_TISTORY_BLOG_NAME

    url = f'https://www.tistory.com/apis/category/list?access_token={access_token}&output=json&blogName={blog_name}'
    r = requests.get(url)
    categories = r.json()['tistory']['item']['categories']
    ret = None
    for category in categories:
      if category['name'] == category_name:
        ret = category['id']
        break

    return ret

if __name__ == '__main__': 
  self.getAccessToken() 
  title = '타이틀'
  content = '내용~ 긴내용도 이젠 괜찮아~~ '
  category_name = '티스토리 카테고리 이름~~ '
  tag = '태그,콤마로,구분됨'
  self.createPost(title, content, category_name, tag)

 이 방식을 이용하게 되면 앞으로 문제없이 완벽하게 자동포스팅이 되는 것은 아니다. 3달에 한번쯤은 password를 변경하라며 브라우저에서 password 변경 화면이 나올텐데, 그에 대해서는 따로 예외처리를 해주지 않았기 때문에... 3달에 한번쯤은 오류가 나겠지??? 그래도 이거라도 어디냐 싶다. 카카오에서 티스토리 API도 좀 더 사용하기 편하게 개선해줬으면 좋겠다. 그나마라도 잘쓰던거 공지도 안하고 없애지나 말던가 -_-;;;

(그래도 카카오덕에 아마 authorization code 방식으로 티스토리 API 사용법 포스팅은 내가 최초일수도... )