В одном из моих собственных проектов, возникла задача автоматической загрузки видео на канал на youtube
. Делается это достаточно просто, при помощи гугловского api
клиента для python
. Единственное затруднение вызвало полумагическое получение ключей доступа к api
.
Авторизация
Все коды доступа и ключи авторизации, использованные в статье, вымышленные.
Любое совпадение с реально существующими или когда-либо существовавшими ключами случайно.
Создание нового проекта
Для авторизации в сервисах google
с помощью протокола oauth2 необходимо зарегистрировать приложение и дать ему соответсвующие права. Для этого нужно перейти в консоль разработчика.
Нажимаем на кнопку Create Project
, выбираем имя и создаем новое приложение. После того как приложение будет создано, нужно добавить ему необходимые доступы к google API.
Для загрузки видео на youtube
нужно добавить YouTube Data API
. Для этого переходим во вкладку APIs & auth
→ APIs
. Также во вкладке APIs & auth
→ Credentials
нужно добавить доступы для oauth2
авторизации.
Указываем тип приложения Other
. Получаем доступы для авторизации: идентификатор и пароль.
- Client ID
230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com
- Client secret
qawsWCd3J6HTRvnqsjYUpgH9
Права доступа к аккаунту
Получив данные для авторизации, нужно перейти по следующей ссылке, заменив в ней параметр client_id
на тот, что Вы получили в предыдущем шаге.
https://accounts.google.com/o/oauth2/auth?
client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&
scope=https://www.googleapis.com/auth/youtube&
response_type=code&
access_type=offline
Далее выбираем к какому аккаунту гугл будет иметь доступ приложение, и соответсвенно к какому каналу на ютубе.
Соглашаемся с доступом к управлению каналом.
Получаем токен авторизации следующего вида 4/Rw6A9raJQ3PrPWL0Q9z49guYu89FZoz322RySVFtzNc
.
Обновляемый токен
После этого необходимо получить, так называемый, refresh_token
, для этого нужно отправить POST
запрос с токеном авторизации по адресу https://accounts.google.com/o/oauth2/token
. Сделать это легко, при помощи консольной утилиты curl.
data=""\
"code=4/Rw6A9raJQ3PrPWL0Q9z49guYu89FZoz322RySVFtzNc&"\
"client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&"\
"client_secret=qawsWCd3J6HTRvnqsjYUpgH9&"\
"redirect_uri=urn:ietf:wg:oauth:2.0:oob&"\
"grant_type=authorization_code"
curl --data $data "https://accounts.google.com/o/oauth2/token"
Токен авторизации сработает только один раз, при повторной попытке отправить его будет получено Code was already redeemed.
. В ответ на корректный запрос, гугл возвращает json
с временным токеном доступа и постоянным обновляемым токеном (собственно он нам и нужен).
{
"access_token" : "ya29.1wGYJU7NP7Ul69c13aE1Vuvbx0LfxrsgMiBjXdNY3sU3tuE9LmuJ3nOGHeb3e_824LH0",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/g1ixyts83iMrtR71oFqwGp3LSGbHz6ByxsBThrHRWCNIgOrJDtdun6zK6XiATCKT"
}
Токен доступа
Получив обновляемый токен, можем с его помощью каждый раз получать рабочий токен доступа, который предоставляется временем на 3600 секунд.
data=""\
"refresh_token=1/g1ixyts83iMrtR71oFqwGp3LSGbHz6ByxsBThrHRWCNIgOrJDtdun6zK6XiATCKT&"\
"client_id=230452130504-3uca1rp4ntlh06hdnsdbj50sqagaqfkt.apps.googleusercontent.com&"\
"client_secret=qawsWCd3J6HTRvnqsjYUpgH9&"\
"grant_type=refresh_token"
curl --data $data "https://accounts.google.com/o/oauth2/token"
В ответ гугл возвращает json
с временным токеном доступа.
{
"access_token" : "ya29.2AHpPjacO0prQkip0svapohuZtoK0wqdh7u0ohH49l0WWwrSyss7CWiwzMy5wX967tWsjQ",
"token_type" : "Bearer",
"expires_in" : 3600
}
Автоматическое получение токена доступа
Получать этот токен доступа нужно будет каждые раз, при подключении к api
. Для этого напишем простую функцию на python 3
с использованием стандартной библиотеки urllib.request
import json
import urllib
import urllib.request
def get_auth_code():
""" Get access token for connect to youtube api """
oauth_url = 'https://accounts.google.com/o/oauth2/token'
# create post data
data = dict(
refresh_token=settings.YOUTUBE_REFRESH_TOKEN,
client_id=settings.YOUTUBE_CLIENT_ID,
client_secret=settings.YOUTUBE_CLIENT_SECRET,
grant_type='refresh_token',
)
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
data = urllib.parse.urlencode(data).encode('utf-8')
# make request and take response
request = urllib.request.Request(oauth_url, data=data, headers=headers)
response = urllib.request.urlopen(request)
# get access_token from response
response = json.loads(response.read().decode('utf-8'))
return response['access_token']
Oauth2 авторизация
Вот теперь, мы наконец подошли к самой oauth2
авторизации в сервисах гугл. Для этого необходимо использовать следующие дополнительные библиотеки:
Далее, используя выше описанную функцию получения временного токена, создаем подключение к youtube api
.
Вообще в руководстве по работе с youtube api
рекомендуют использовать построение oauth2
подключения с использованием объекта flow_from_clientsecrets
, примерно так:
def get_authenticated_service(args):
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
scope=YOUTUBE_UPLOAD_SCOPE,
message=MISSING_CLIENT_SECRETS_MESSAGE)
storage = Storage("%s-oauth2.json" % sys.argv[0])
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)
return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
http=credentials.authorize(httplib2.Http()))
Но, как выяснилось на практике, такой подход, требует при каждой загрузки, давать разрешение на подключение к аккаунту youtube
вручную, это не очень удобно. Учитывая, что можно замечательным образом получать токен авторизации, из обновляемого токена, мы будем использовать для создания oauth2
подключения - объект AccessTokenCredentials
.
import httplib2
from oauth2client.client import AccessTokenCredentials
from apiclient.discovery import build
def get_authenticated_service():
""" Create youtube oauth2 connection """
# make credentials with refresh_token auth
credentials = AccessTokenCredentials(
access_token=get_auth_code(), user_agent='my-awesome-project/1.0'
)
# create connection to youtube api
return build(
'youtube', 'v3', http=credentials.authorize(httplib2.Http())
)
Теперь мы имеем созданное подключение, которое можно использовать для работы с api
.
Загрузка видео
Имея готовое подключение к api
загрузка видео происходит элементарно.
Определим функцию инициализации загрузки, которая принимает в качестве аргументов подключение к youtube api
и объект с информацией о видео.
from apiclient.http import MediaFileUpload
def initialize_upload(youtube, card):
""" Create youtube upload data """
# create video meta data
body = card.youtube_meta_data()
# Call the API's videos.insert method to create and upload the video
insert_request = youtube.videos().insert(
part=",".join(body.keys()), body=body,
media_body=MediaFileUpload(card.video.path, chunksize=-1, resumable=True))
# wait for file uploading
return resumable_upload(insert_request)
Метод youtube_meta_data
должен возвращать словарь описания видео согласно формату, например:
{
"snippet": {
"title": "Summer vacation in California",
"description": "Had fun surfing in Santa Cruz",
"tags": ["surfing", "Santa Cruz"],
"categoryId": "22"
},
"status": {
"privacyStatus": "private"
}
}
В моем случае данный метод имел следующий вид:
def youtube_meta_data(self):
""" Create metadata dict for youtube video upload """
return dict(
snippet=dict(
title=settings.YOUTUBE_TITLE.format(coord=self.position),
tags=settings.YOUTUBE_TAGS,
categoryId=settings.YOUTUBE_CATEGORY_ID,
description='{desc}\n{site_url}/{card_id}'.format(
desc=self.description, site_url=settings.SITE_URL, card_id=self.get_absolute_url()),
),
status=dict(
privacyStatus=settings.YOUTUBE_PRIVACY_STATUS,
),
recordingDetails=dict(
location=dict(
latitude=str(self.position.latitude),
longitude=str(self.position.longitude),
),
),
)
После инициализации загрузки, необходимо поддерживать соединение и дождаться ответа от ютуба с идентификатором видео. Для этого будем использовать следующую функцию.
import random
import http
import httplib2
# Explicitly tell the underlying HTTP transport library not to retry, since we are handling retry logic ourselves.
httplib2.RETRIES = 1
# Maximum number of times to retry before giving up.
MAX_RETRIES = 10
# Always retry when these exceptions are raised.
RETRIABLE_EXCEPTIONS = (
httplib2.HttpLib2Error, IOError, http.client.NotConnected,
http.client.IncompleteRead, http.client.ImproperConnectionState,
http.client.CannotSendRequest, http.client.CannotSendHeader,
http.client.ResponseNotReady, http.client.BadStatusLine)
# Always retry when an apiclient.errors.HttpError with one of these status codes is raised.
RETRIABLE_STATUS_CODES = (500, 502, 503, 504)
def resumable_upload(insert_request):
response = None
error = None
retry = 0
while response is None:
try:
status, response = insert_request.next_chunk()
if 'id' in response:
return response['id']
except HttpError as err:
if err.resp.status in RETRIABLE_STATUS_CODES:
error = True
else:
raise
except RETRIABLE_EXCEPTIONS:
error = True
if error:
retry += 1
if retry > MAX_RETRIES:
raise Exception('Maximum retry are fail')
sleep_seconds = random.random() * 2 ** retry
time.sleep(sleep_seconds)
Таким образом, загрузка видео запускается функцией initialize_upload
:
video_id = initialize_upload(get_authenticated_service(), card)
Полный код загрузки видео можно посмотреть в gist.
Санкции
Поскольку капиталистический запад, в лице корпорации зла, наложил на меня свои, безосновательные, санкции. Ограничив тем самым мое право доступа к свободной информации. Для работы с youtube api
мне необходимо использовать vpn
подключение.
VPN соединение
В качестве vpn
соединения я использую ssh
туннель и локальное socks5
прокси на 1080 порту. Включаю/отключаю ssh
тунель при помощи библиотеки subprocess
.
import subprocess
# init ssh connection
subprocess.Popen(['ssh', '-fN', '-D', '1080', 'forward@vpn_connection'])
# desctroy ssh connection
subprocess.Popen(['pkill', '-f', 'forward@vpn_connection'])
Не правильно
Что бы подключиться к локальному socks5
прокси, необходимо использовать библиотеку socksipy, как показано в примере работы с httplib2
:
import httplib2
import socks
h = httplib2.Http(proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_SOCKS5, 'localhost', 1080))
r, c = h.request('https://l2.io/ip')
Правильно
Но, вышеуказанный способ не работает. Библиотека socksipy
не поддерживает python 3
, поэтому необходимо делать по-другому. Использовать библиотеку socksipy-branch (gist зеркало). И оборачивать httplib2
с помощью метода wrapmodule
:
import httplib2
import socks
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, 'localhost', 1080)
socks.wrapmodule(httplib2)
h = httplib2.Http()
r, c = h.request('https://l2.io/ip')