트위터가 2021년 11월 16일자로 v2 API로의 migration을 선언했다. 찾아보니까 개발 시작일은 2020년쯤인 것 같은데 1년 동안 많은 일이 있었던 모양이다.
릴리즈 공지에 따르면 v2 API에는 바뀐 트위터 환경에 맞추어 추가된 스페이스/유료 팔로워/답글 비추천 관련 기능이나 리스트 엔드포인트가 포함돼 있다고 한다. 확실히 많은 일이 있었던 것 같다.
그렇지만 이 포스트의 목적은 변경된 API를 상세히 뜯어보는 게 아니다. API가 개편되면서 기본적인 초기 설정 단계에서부터 몇 시간씩 막히게 되는 경우가 생겼는데, 이걸로 며칠 정도를 날렸으니 이 글을 읽는 여러분은 시간과 정신건강을 절약하기를 바라는 마음에서 작성한 글이다.
트위터 API를 이용하는 (나 같은) 아마추어 개발자들은 보통 트위터의 GET/POST url를 그대로 사용하기보다는 API를 다시 다듬어서 만든 tweepy, Twitterlib 등의 라이브러리를 사용한다. 확실히 이쪽이 더 깔끔하고 직관적이다. 본인도 2021년 8월즈음 트위터 자동봇을 구현할 때는 tweepy 라이브러리 덕을 톡톡히 보았다.
그리고 2022년 4월 현재, 다시 봇을 구현할 일이 생겨서 기존에 사용하던 tweepy 기반 코드를 꺼냈다. 겸사겸사 구글 앱스 스크립트(GAS)의 트리거 기능을 이용한 서버 없는 상시 가동 봇을 구현해 볼 생각이었다.
우선은 Twitterlib을 불러온 뒤, authorization이 잘 되는지 확인하기 위해 국룰인 Hello world!
를 트윗으로 보내려고 했다. 사용한 코드는 이곳을 참고했으며 다음과 같다.
※ 이 포스트에서는 일단 OAuth를 이용해 user access token을 받아오는 과정 등은 생략하도록 하겠다 (개발자 계정 신청에 관한 내용은 하단에 나온다).
function sendTweet(status) {
status = "Hello world!";
var twitterKeys = {
TWITTER_CONSUMER_KEY: 'CONSUMER_KEY',
TWITTER_CONSUMER_SECRET: 'CONSUMER_SECRET',
TWITTER_ACCESS_TOKEN: 'ACCESS_TOKEN',
TWITTER_ACCESS_SECRET: 'ACCESS_TOKEN_SECRET',
};
var props = PropertiesService.getScriptProperties();
props.setProperties(twitterKeys);
var service = new Twitterlib.OAuth(props);
if (service.hasAccess()) {
var response = service.sendTweet(status);
if (response) {
Logger.log('Tweet ID ' + response.id_str);
} else {
// Tweet could not be sent
// Go to View -> Logs to see the error message
}
}
}
보다시피 consumer key(=API key)와 access token을 이용해 OAuth 객체를 생성하고 트윗을 전송하는 간단한 코드다. 이건 뭐… 터지는 게 이상한 것 아닌가? 나는 이 코드가 멀쩡히 돌아갈 것이라 믿어 의심치 않으며 실행 버튼을 눌렀다.
터졌다.
다시 봐도 어처구니가 없다. 파이썬 IDE 처음 켜서 print("Hello world!")
했는데 터진 꼴이다. 게다가 에러랍시고 출력된 건 꼴랑 "Exception"
이 전부였다. 일시적 서버 오류인 줄 알고 액세스 키도 다시 발급받고 몇 시간 기다렸다가 다시 돌려 보고 아무튼 별 짓을 다 해 봤는데 그래도 안 됐다.
시간과 노력을 대가로 일시적인 오류는 아니라는 결론에 도달한 나는 "twitterlib post tweet error"라는 검색어로 구글링을 했다. 과연 동일한 코드로 동일한 에러를 경험한 스택 오버플로우 글이 눈에 들어왔다. 괜히 삽질하지 말고 재깍재깍 검색의 힘을 빌리자.
글에는 다음과 같은 댓글이 달려 있었다.
Does your app have Essential Access, or Elevated Access? You will need elevated access to use the V1.1 API which is probably what the Google Script is using. – Andy Piper
그러니까 GAS는 v1.1 API를 쓰는데 그걸 사용하려면 Elevated access가 필요하다는 거다.
Essential access? Elevated access? 그게 다 뭐람. 나 때는 그런 거 없었다. 하지만 헬로 월드 하나 출력하려면 엘리베이티드 어쩌고가 필요하다는데 뭐 어쩌겠는가 (다시 일주일 가량의 삽질 후에 나는 이게 아니라는 것을 알게 된다. 결론부터 말하자면 essential access만 있어도 된다).
찾아보니 Twitter Developers 대시보드에서 다음과 같이 세 단계로 구분된 액세스 권한을 확인할 수 있었다.
트위터는 2021년 11월 v2 API로의 마이그레이션 선언과 함께 새로운 액세스 단계(access level) 정책을 도입했다.
본 포스트에서는 상위 단계에 해당하는 액세스는 다루지 않고, (아마 대부분의 아마추어 개발자들이 이용할) Essential과 Elevated 액세스 단계에 관해 간단하게 해설하겠다. 영어가 낯설지 않다면 이곳에 표로 잘 정리되어 있으니 저쪽을 확인하자.
기존에 트위터 개발자 계정 신청을 진행하면 모든 신청자가 일괄적으로 몇 가지 설문을 영문으로 작성한 뒤 승인을 받아야 했다. 하지만 현재 트위터 개발자 홈페이지에서 Sign up으로 등록을 하면 클릭 몇 번에 곧바로 "축하드립니다! 당신은 지금부터 트위터 개발자입니다!"를 해 준다 (사용 목적 등은 밝혀야 하지만 서술형 질문을 들이대지는 않는다).
이렇게 모든 유저가 별도의 검토/승인 과정 없이 획득할 수 있는 액세스 권한 단계가 Essential access다. 해당 단계에서는 트위터의 표준/프리미엄 v1.1 API를 제외한 v2 API를 사용할 수 있으며, 생성 가능한 애플리케이션과 읽어들일 수 있는 트윗의 개수가 다소 제한적이다.
Essential access로는 충분하지 않을 경우 추가적인 신청을 통해 획득할 수 있는 상위 단계가 바로 Elevated access다. 해당 단계에서는 v1.1 API를 포함한 모든 API를 사용할 수 있고, Essential 단계에 비해 몇 배 정도 많은 양의 데이터를 수집할 수 있다.
Elevated access 획득을 신청할 경우, 기존(2021년 11월 이전)에 개발자 계정 신청 시 작성하던 설문과 거의 동일한 내용의 설문에 영어로 답해야 한다. 질문은 총 5개로, 첫 번째 질문은 필수 작성이며 나머지 네 개는 사용 계획에 따라 작성하지 않을 수도 있다.
How will you use the Twitter API or Twitter Data?
트위터 API 또는 트위터 데이터를 어떻게 사용할 것입니까?
In English, please describe how you plan to use Twitter data and/or APIs. The more detailed the response, the easier it is to review and approve.
당신이 트위터 데이터 및 API를 어떻게 사용할 예정인지를 영어로 설명해 주십시오. 답변이 상세할수록 검토와 승인이 용이해집니다. *공백 포함 영문 200자 이상
Are you planning to analyze Twitter data?
트위터 데이터를 분석할 예정입니까?
Please describe how you will analyze Twitter data including any analysis of Tweets or Twitter users.
당신이 트위터 데이터를 어떤 방식으로 분석할지, 트윗 또는 트위터 유저에 관한 모든 내용을 포함하여 설명해 주십시오. *공백 포함 영문 100자 이상
↑ 참고로 이 항목은 단순히 트윗을 읽어들여서 파싱하는 등의 아주 간단한 가공이라도 전부 포괄하는 내용이라고 한다.
Will your App use Tweet, Retweet, Like, Follow, or Direct Message functionality?
당신의 애플리케이션은 트윗, 리트윗, 마음에 들어요, 팔로우, 또는 쪽지 기능을 사용할 예정입니까?
Please describe your planned use of these features.
이 기능들의 사용 계획을 설명해 주십시오. *공백 포함 영문 100자 이상
Do you plan to display Tweets or aggregate data about Twitter content outside Twitter?
트위터 외부에서 트윗을 표시하거나 트위터에 관한 데이터를 집계할 예정입니까?
Please describe how and where Tweets and/or data about Twitter content will be displayed outside of Twitter.
어디에서 어떻게 트윗 및 트위터에 관한 데이터가 외부에 표시될지 설명해 주십시오. *공백 포함 영문 100자 이상
Will your product, service, or analysis make Twitter content or derived information available to a government entity?
당신의 제품, 서비스, 혹은 분석을 통해 트위터 콘텐츠 또는 그로부터 유래된 정보를 정부 단체에 제공할 것입니까?
Please list all government entities you intend to provide Twitter content or derived information to under this use case.
당신이 트위터 콘텐츠 또는 그로부터 유래된 정보를 제공하려 하는 정부 단체를 모두 나열해 주십시오. *최소 분량 제한 없음
이곳에 작성한 내용만으로 충분하지 않으면 트위터 본사로부터 더 구체화해 달라는 내용의 메일이 온다.
그런데 문제가 있다. 이전에는 그냥 '아 봇 만들어요~ 아 멘션 읽어서 자동으로 답장해요~' 수준으로 설렁설렁 답장하기만 해도 승인해 줬었는데, 액세스 단계를 구분하면서 승인받기가 굉장히 까다로워졌다. 거진 사흘 동안 트위터 고객센터와 장문의 실랑이를 했는데 결국 실패했다.
tweepy와 Twitterlib 모두 v1.1 API를 기반으로 작성되어 있으며, 트위터가 마이그레이션을 선언한 지 반 년이 되어 가는 지금까지도 v2에 맞춰 수정되지 않았다. 확인해 보지는 않았지만 아마 그 외 다수의 라이브러리가 비슷한 상황이 아닐까 한다 (라이브러리 만든 사람들은 다 상위 단계 갖고 있을 테니까...).
그리고 v1.1 API는 Elevated access 단계에서만 사용할 수 있다.
그렇다. API 개편과 함께 편리한 라이브러리들을 싸그리 압수당한 것이다.
v2 API는 생각보다 잘 되어 있다. 이 포스트에서는 v2 API를 이용해 헬로 월드! 를 출력하는 과정까지만 다루고, 다음 편에서 API를 구체적으로 뜯어볼 예정이다.
다행히도 아주 맨땅은 아니다. 우리에게는 낯선 v2 API의 활용법을 알려줄 공식 샘플 코드가 있다. 이 중에서 트윗 작성 및 삭제와 관련된 코드는 Manage-Tweets
폴더에 있다. Python/JavaScript/Ruby 등 세 가지 언어로 된 파일들을 확인할 수 있는데, 언어만 다를 뿐 실행하는 작업은 전부 동일하다.
create_tweet.py
의 내용을 살펴보자.
from requests_oauthlib import OAuth1Session
import os
import json
# In your terminal please set your environment variables by running the following lines of code.
# export 'CONSUMER_KEY'='<your_consumer_key>'
# export 'CONSUMER_SECRET'='<your_consumer_secret>'
consumer_key = os.environ.get("CONSUMER_KEY")
consumer_secret = os.environ.get("CONSUMER_SECRET")
# Be sure to add replace the text of the with the text you wish to Tweet. You can also add parameters to post polls, quote Tweets, Tweet with reply settings, and Tweet to Super Followers in addition to other features.
payload = {"text": "Hello world!"}
# Get request token
request_token_url = "https://api.twitter.com/oauth/request_token?oauth_callback=oob&x_auth_access_type=write"
oauth = OAuth1Session(consumer_key, client_secret=consumer_secret)
try:
fetch_response = oauth.fetch_request_token(request_token_url)
except ValueError:
print(
"There may have been an issue with the consumer_key or consumer_secret you entered."
)
resource_owner_key = fetch_response.get("oauth_token")
resource_owner_secret = fetch_response.get("oauth_token_secret")
print("Got OAuth token: %s" % resource_owner_key)
# Get authorization
base_authorization_url = "https://api.twitter.com/oauth/authorize"
authorization_url = oauth.authorization_url(base_authorization_url)
print("Please go here and authorize: %s" % authorization_url)
verifier = input("Paste the PIN here: ")
# Get the access token
access_token_url = "https://api.twitter.com/oauth/access_token"
oauth = OAuth1Session(
consumer_key,
client_secret=consumer_secret,
resource_owner_key=resource_owner_key,
resource_owner_secret=resource_owner_secret,
verifier=verifier,
)
oauth_tokens = oauth.fetch_access_token(access_token_url)
access_token = oauth_tokens["oauth_token"]
access_token_secret = oauth_tokens["oauth_token_secret"]
# Make the request
oauth = OAuth1Session(
consumer_key,
client_secret=consumer_secret,
resource_owner_key=access_token,
resource_owner_secret=access_token_secret,
)
# Making the request
response = oauth.post(
"https://api.twitter.com/2/tweets",
json=payload,
)
if response.status_code != 201:
raise Exception(
"Request returned an error: {} {}".format(response.status_code, response.text)
)
print("Response code: {}".format(response.status_code))
# Saving the response as JSON
json_response = response.json()
print(json.dumps(json_response, indent=4, sort_keys=True))
PIN-based Oauth1을 이용해 브라우저로 로그인된 계정의 액세스 토큰을 가져오고, 이를 이용해 생성한 Oauth1 세션으로 지정된 내용의 트윗을 전송하는 방식이다.
돌려보면 (당연하지만) 잘 나온다. 가린 부분에는 원래 Twitter Web App, Twitter for Android 등 트윗이 작성된 경로가 나오는데 API를 이용해 작성한 트윗의 경우에는 해당하는 애플리케이션의 이름이 출력된다.
공식 샘플 코드에는 상기한 것을 포함하여 v2 API의 핵심적인 기능이 모두 소개되어 있다. 하지만...
1의 경우 구글 앱스 스크립트의 트리거 기능을 이용하면 해결할 수 있다. 제공된 코드의 GAS 버전 번역은 다른 글에서 다루도록 하겠다.
2는 authorization 과정을 수정함으로써 해결할 수 있다. 다음 글에서는 팔로우 중인 비공개 계정의 트윗을 읽는 것을 목표로 Oauth에 대해 자세히 다뤄 보도록 하겠다.
안녕하세요 제가 자동봇을 만들고 실행중인데 되다가 저번주부터 오류가 계속 떠서요.. 혹시 도움을 좀 부탁드려도 될까요 사례 해보겠습니다ㅜㅜ