[Tistory API] 셀레니움, 정규표현식을 사용하여 티스토리 Access Token 발급하기(python)
사이드 프로젝트 : 공모주 알리미의 카카오톡 어플리케이션 사이트 도메인을 티스토리로 구성하기 위해, 티스토리 API를 활용하게 되었다.
아침부터 많은 구글링을 하며 삽질해서, python 코드로 자동화 하도록 만들었다.
글에 오류가 났던 원인들 및 해결하지 못했지만 대체 방안들을 제시해두려고 한다.
오류가 될만한 원인들은 주의1 주의2 와 같이 주황색 배경색으로 적어두고, 맨 아래에도 적어두려고 한다.
오늘안에 공모주 알리미 배포를 하려고 했지만 직접 코드들을 수정하느라(적절한 reference들을 한번에 못 찾아서 그런 거겠지만)
시간이 꽤 오래걸려서 미뤄질 것 같다. 그래도 오늘 API Key, Token 발급에 있어서 눈을 꽤 뜬것 같아서 기분이 좋다.
아마 티스토리 Acceess Token을 발급하려는 분들이 이 글을 읽는다면, 꽤나 시행착오를 줄여서 쉽게 발급할 수 있을 것 같다.
(오늘 티스토리 API Key들은 금방 발급하고 자동으로 글을 작성해서 공모주 알리미 서비스를 내일 서비스 시작할 수 있을 것 같았지만, 큰 착각이였다.. 한번에 될리가 없지😅😅😅😅)
내용이 너무 길어서, 접어두었다. 접은 글을 펴서 보면 될 것 같다.
0. 참고 블로그
[티스토리 API] 를 이용해서 글 작성하기 -1 (Authentication code, Access Token 발급 )
안녕하세요, 에이도입니다 ! 저는 요즘 API를 이용해보는게 재미있어서 여러 가지 API를 이용해가며 간단한 프로그램들을 만들어 보고 있습니다! 이번에는 티스토리 API를 이용해서 글 작성을 해
lheon.tistory.com
- 처음부터 따라했을 때, 오류만 안난다면 제일 좋겠지만 오류도 나고 자동화가 안되어 있지만 코드를 전혀 모르는 사람들이 순수 주소 접속으로 할 수 있는 방법을 적어둠
Notion to Tistory 23편 - Tistory API로 Access Token 발급
Notion 2 Tistory Tistory API로 글 업로드 (2) 목차 개요 API 동작 순서 1. 클라이언트 등록하기 2. 인증 요청 및 Authentication code 발급 2-1. 로그인 및 리다이렉션 2-2. Authentication Code 찾기 3. Acces..
boltlessengineer.tistory.com
- c#이지만, 맨 위 블로그만 보면서 웹 뜯어볼 생각을 하지 않고 하던 나에게 도움을 준 블로그이다.
[python] 티스토리 API 이용을 위한 access token을 파이썬으로 받아오기
* 2020.12.07 수정 검색에 잘 안 걸리는 글인데도 어떻게 찾아와 주시는 분들이 계셔서 추가합니다. 티스토리 API 가 변경됨에 따라서, 아래 작성한 방식으로 진행하시면 막히는 부분이 생기실 수 있
mydevdiary.tistory.com
- 더 나아가서, 위 블로그는 로그인 하는 방법을 post 방법으로 하셨지만 나는 셀레니움을 사용하게 되었다.
1. 앱 등록하기

블로그 관리 하단에 오픈 API를 클릭하면, 아래와 같은 앱 등록을 위해 리디렉션된다.
아래 그림은 등록 후 <앱 관리>의 설정창을 누른 상태이다.
Secret Key는 클라이언트 정보 비밀번호이며, 티스토리 측에서 제공한 readme에서 말하길,
이 정보는 티스토리와 Client만이 공유해야하며 절대 외부에 공개되면 안된다고 한다.(사실 아이디도 공개되도 좋을 게 없다)

서비스 url 및 callback url을 등록할 때 https 대신 http로 해서 Access Token 발급 시 오류가 났던 것 같다.
정확한 이유는 모르겠지만, 아래 github issues를 보고 기존 앱을 삭제하고 https로 다시 했더니 오류가 해결되었다.
https://github.com/tistory/document-tistory-apis/issues/7
API 사용이 안 됩니다 · Issue #7 · tistory/document-tistory-apis
Readme 내용을 그대로 따라 authorization code까지 발급받는 데 성공하였고 https://www.tistory.com/oauth/authorize? 에 파라미터 추가해서 요청 날렸는데 다음과 같은 에러가 뜹니다. error=invalid_request error_descrip
github.com
주의1 : 서비스 URL 및 Callback 경로를 https://로 바꿔주고 자신의 티스토리 블로그 주소로 세팅해주자.
(앱 등록시 default는 http:// 이다)

오픈 API 가이드를 누르면, 티스토리에서 제공하는 깃헙블로그로 이동하며 아래와 같이 API readme 파일이 나온다.
소개 · GitBook
No results matching ""
tistory.github.io
인증가이드 → Authorization Code 방식을 누르면 API를 많이 다뤄본 사람이라면 구글링 없이 해결할 수 있다.
(나는 구글 시트, 카카오톡 API만 사용해봐서 약간의 감만 있던 상태이지만, 아직 잘 못한다!)
2-1. Authentication code 발급 (자동화 X)

{client-id} : 앱 등록시에 발급받은 App ID 값
{redirect-uri} : 앱 등록시에 설정한 Callback 경로
response_type : '인증 요청의 결과인 response를 code로 받겠다'라는 parameter 설정
state : 필수는 아니지만, 어떻게 설정해야될 지 몰라서 그냥 그대로 넣었다.
위 사항들을 적용하여, 아래와 같이 입력하였다.

https://www.tistory.com/oauth/authorize? client_id=95XXXXXXXXXXXXX &redirect_uri=https://hzoo.tistory.com/ &response_type=code &state=someValue
주의2 : 홈페이지에서 제공한 코드에서 parameter 값만 바꿔서 그대로 복사하자. 한 줄로 풀어서 썼더니 오류가 났었다.
주의3 : redirect_uri에서 앱 등록시 적어준 것 처럼, 맨 마지막 /를 붙여주자.
안 붙이면, error=invalid_request&error_description=Maybe_redirect_uri_was_mismatched와 같은 오류가 난다.

[사진5] 에서 허가하기를 누르면, 요청 결과로 위에서 말한 것과 같이 인증 요청의 결과인 response를 code로 받았다.

사진6의 리디렉션 URI를 확인해보면, 아래와 같다.
{redirect-uri}? code={authorization-code} &state={state-param}
≫ https://hzoo.tistory.com/?code=3a396b61b8736d2eXXXXXXXXXXXX&state={state-param}
여기서 Authorization code는 = 표시 뒤에 있는 주황색 부분(3a396~~~)이며,
1시간 이내에만 사용가능하며 재사용 할 수 없다고 명시되어있다.
하지만 이 방법은, 서버에서 사용하도록 자동화 한 '코드'가 아닌 직접 요청을 보내는 방법이기 때문에
2-2. 셀레니움, 정규표현식을 사용한 Authentication code 발급 (자동화 O) 에서 다루도록 하겠다.
2-2. 셀레니움, 정규표현식을 사용한 Authentication code 발급 (자동화 O)
def get_authorization_code():
with open('../../config.yaml') as f:
TISTORY_INFO = yaml.load(f, Loader=yaml.FullLoader)
CLIENT_ID = TISTORY_INFO['TISTORY_API_ID']
KAKAO_ID = TISTORY_INFO['KAKAO_ID']
KAKAO_PW = TISTORY_INFO['KAKAO_PW']
callback_url = 'https://hzoo.tistory.com/'
oauth_url = 'https://www.tistory.com/oauth/authorize?client_id=' + CLIENT_ID + '&redirect_uri=' + callback_url + '&response_type=code'
driver = webdriver.Chrome('../../chromedriver.exe')
driver.implicitly_wait(3)
driver.get(oauth_url)
driver.find_element_by_class_name('btn_login.link_kakao_id').click()
driver.find_element_by_name('email').send_keys(KAKAO_ID)
driver.find_element_by_name('password').send_keys(KAKAO_PW)
driver.find_element_by_class_name('btn_g.btn_confirm.submit').click()
driver.find_element_by_class_name('confirm').click()
authorization_code = driver.current_url
authorization_code = re.compile("\S+\?code=").sub('', authorization_code)
authorization_code = re.compile("\&\S+").sub('', authorization_code)
driver.close()
return authorization_code
config.yaml 파일에 Tistory API 정보 및 Tistory 로그인(카카오톡 아이디 연동)을 위한 개인 정보를 담아두었다.
yaml 파일을 불러오면,
TISTORY_INFO = {'TISTORY_API_ID': 티스토리 App ID,
'KAKAO_ID' : 카카오톡 ID,
'KAKAO_PW' : 카카오톡 PW} 와 같이, 딕셔너리 형태로 값이 리턴되어 있다.
아래 블로그를 보면, PyYAML 라이브러리를 이용하여 yaml 파일 읽기, 쓰기법이 잘 나와있으니 읽어 보길 바란다.
[Python] PyYAML로 YAML 파일 읽고 쓰기 (Parse and Serialize YAML in Python)
이전 포스팅에서 Python으로 JSON 데이터 읽고 쓰기, XML 데이터 읽고 쓰기에 대해서 소개한 적이 있습니다. 이번 포스팅에서는 Python의 PyYAML 라이브러리를 이용하여 YAML 파일을 파싱하여 파이썬 객
rfriend.tistory.com
9번 라인에서 oauth_url은 2-1에서 우리가 인증요청 할 때 사용하던 주소이다.
여기서, 달라진 점이라면 이 주소를 크롬드라이버를 사용하여 셀레니움으로 자동화하는 과정이다.
아래 블로그를 확인하면 어떻게 다운받고, 어떻게 사용할 수 있는지 확인할 수 있다.
나만의 웹 크롤러 만들기(3): Selenium으로 무적 크롤러 만들기 - Beomi's Tech blog
좀 더 보기 편한 깃북 버전의 나만의 웹 크롤러 만들기가 나왔습니다! Updated @ 2019.10.10. Typo/Layout fix, 네이버 로그인 Captcha관련 수정 추가 이전게시글: 나만의 웹 크롤러 만들기(2): Login with Session Se
beomi.github.io
10번 라인에서 크롬이 켜지고,
13번 라인이 실행되면 아래와 같이 주소에 접속하게 된다.

사진 8~10과 같이 사이트를 뜯어보자.


사이트를 뜯어보니,
사진 8에서 카카오계정 로그인 버튼은 'btn_login.link_kakao_id' 이며,
사진 9에서 id, pw, 로그인 버튼은 'email', 'password', 'btn_g.btn_confirm.submit' 이며,
사진 10에서 허가하기 버튼은 'confirm' 이므로
19번 라인까지 코드를 driver.find_element_by_name/class_name 으로 element를 가져와서 제어하였다.
19번 라인이 끝나면 아래 사진과 같이 주소가 리디렉션 되면서, authorization_code가 리턴된다.

2-1 처럼, 요청 결과로 위에서 말한 것과 같이 인증 요청의 결과인 response를 code로 받았다.
{redirect-uri}? code={authorization-code} &state={state-param} 이 중, 주황색 부분만 추출하기 위해 정규표현식을 사용하였다.
re.compile("\S+\?code=").sub('', authorization_code) 로 주황색 앞부분을 제외(빨간부분과 ?code=)하고,
re.compile("\&\S+").sub('', authorization_code)로 주황색 이후부분을 제외(&state=과 연두색)시켰다.
이후 테스트 중인 웹 드라이버를 종료시켜준 후, authorization_code를 리턴하였다.
3-1. Aceess Token 발급하기 (자동화 X)

https://www.tistory.com/oauth/access_token? client_id={client-id} &client_secret={client-secret} &redirect_uri={redirect-uri} &code={code} &grant_type=authorization_code
본문에서 그대로 복사해와서, client-id, client-secret, redirect-uri, code 치환해서 주소를 입력해주면 된다.

주의4 : 여기서도 마찬가지로, parameter 간의 띄어쓰기는 그대로 유지하여야 한다. 안그러면 위 사진처럼 오류가 난다.
여기서 code는 2번 과정에서 얻은 Authentication Code이다.
주소를 입력한 결과는 아래 사진과 같다.

개발자 모드를 키고, Network를 열어둔 다음 HTTP 200 응답(녹색)이 나타나면 access_token=XXXXXXXX와 같이 나온다.
여러번 시도해본 결과, access_token 값이 변하지 않는 점 + 유효기간이 별도로 정해져있지 않은 점을 보아,
한번 발급 후 계속 사용할 수 있는 것 같다.
따라서 Access Token도 App pw와 같이 잘 보관해서 사용해야 한다.
리디렉션된 창이 오류창 같아서 처음엔 된건 줄도 모르고 엄청 헤맸다.
이걸 보시는 분은 이런 시행착오를 안겪으셨으면 좋겠다.
3-2. Aceess Token 발급하기 (자동화 O)

곰곰히 자동화 할 방법을 찾아보다가, 카카오톡 API에서 GET, POST와 같은 방법으로 request를 날려서 response를 받았었다.
공식 Readme에도 GET이라고 적혀 있기 때문에, 한번 시도해봤는데 몇번의 시행착오 끝에 access token을 얻을 수 있었다.
def get_access_token():
try:
with open('../../tistory_token.json', 'r') as f:
json_data = json.load(f)
access_token = json_data['access_token']
return access_token
except:
auth_code = get_authorization_code()
with open('../../config.yaml') as f:
TISTORY_INFO = yaml.load(f, Loader=yaml.FullLoader)
CLIENT_ID = TISTORY_INFO['TISTORY_API_ID']
CLIENT_PW = TISTORY_INFO['TISTORY_API_SECRET_KEY']
url = 'https://www.tistory.com/oauth/access_token'
data = {
'client_id': CLIENT_ID,
'client_secret': CLIENT_PW,
'redirect_uri': 'https://hzoo.tistory.com/',
'code': auth_code,
'grant_type': 'authorization_code'
}
response = requests.get(url, params=data)
access_token = response.text.split('=')[1]
access_token_dic = {}
access_token_dic['access_token'] = access_token
with open('../../tistory_token.json', 'w') as json_file:
json.dump(access_token_dic, json_file)
return access_token
Access Token이 변하지 않는 점을 이용하여, PC에 토큰을 가지고 있으면(한번이라도 발급 후 저장해두었으면) 해당 토큰을 리턴한다.
아니라면, 2-2에서 작성한 get_authorization_code() 메소드를 이용하여 authorization 코드를 받아오고,
get 방식으로 request를 parameter와 함께 날려보면, 아래와 사진과 같은 결과를 얻을 수 있다.

json 파일로 저장하기 위해, '='를 기준으로 나눠서 값을 access_token_dic에 넣어준 후
tistory_token.json 파일로 저장하였다.
지금 이 일이 급한게 아닌데, 오늘 이렇게 자동화 하는 과정을 하면서 앞으로 있을 API Key 발급 자동화 과정에 있어서
자신감을 좀 얻게된 것 같다😃👊
4. 전체 코드
import requests
import json
import re
import yaml
from selenium import webdriver
def get_authorization_code():
with open('../../config.yaml') as f:
TISTORY_INFO = yaml.load(f, Loader=yaml.FullLoader)
CLIENT_ID = TISTORY_INFO['TISTORY_API_ID']
KAKAO_ID = TISTORY_INFO['KAKAO_ID']
KAKAO_PW = TISTORY_INFO['KAKAO_PW']
callback_url = 'https://hzoo.tistory.com/'
oauth_url = 'https://www.tistory.com/oauth/authorize?client_id=' + CLIENT_ID + '&redirect_uri=' + callback_url + '&response_type=code'
driver = webdriver.Chrome('../../chromedriver.exe')
driver.implicitly_wait(3)
driver.get(oauth_url)
driver.find_element_by_class_name('btn_login.link_kakao_id').click()
driver.find_element_by_name('email').send_keys(KAKAO_ID)
driver.find_element_by_name('password').send_keys(KAKAO_PW)
driver.find_element_by_class_name('btn_g.btn_confirm.submit').click()
driver.find_element_by_class_name('confirm').click()
authorization_code = driver.current_url
authorization_code = re.compile("\S+\?code=").sub('', authorization_code)
authorization_code = re.compile("\&\S+").sub('', authorization_code)
driver.close()
return authorization_code
def get_access_token():
try:
with open('../../tistory_token.json', 'r') as f:
json_data = json.load(f)
access_token = json_data['access_token']
return access_token
except:
auth_code = get_authorization_code()
with open('../../config.yaml') as f:
TISTORY_INFO = yaml.load(f, Loader=yaml.FullLoader)
CLIENT_ID = TISTORY_INFO['TISTORY_API_ID']
CLIENT_PW = TISTORY_INFO['TISTORY_API_SECRET_KEY']
url = 'https://www.tistory.com/oauth/access_token'
data = {
'client_id': CLIENT_ID,
'client_secret': CLIENT_PW,
'redirect_uri': 'https://hzoo.tistory.com/',
'code': auth_code,
'grant_type': 'authorization_code'
}
response = requests.get(url, params=data)
access_token = response.text.split('=')[1]
access_token_dic = {}
access_token_dic['access_token'] = access_token
with open('../../tistory_token.json', 'w') as json_file:
json.dump(access_token_dic, json_file)
return access_token
access_token = get_access_token()
5. 주의 사항 Remind
▶ 1) 주의1 : 앱 생성시 서비스 URL 및 Callback 경로를 https://로 바꿔주고 자신의 티스토리 블로그 주소로 세팅해주자.
▶ 2) 주의2 : auth_code 발급 시 제공한 코드에서 parameter 값만 바꿔서 그대로 복사하자. 한 줄로 풀어서 쓰기 X.
▶ 3) 주의3 : redirect_uri에서 앱 등록시 적어준 것 처럼, 맨 마지막 /를 붙여주자.
▶ 4) 주의4 : access_token 발급 시에도 parameter 간의 띄어쓰기는 그대로 유지하여야 한다. 안그러면 오류가 난다.
※회고
- 급하게 작성한 코드이기 때문에 방어코드를 신경써서 작성하지 못했다. 그래도 꾸준히 리팩토링 할 생각이다!
발급한 Access Token으로 빨리 글 작성하러 가봐야지 😎 ㅎㅎㅎㅎㅎ