В одном из моих собственных проектов, возникла задача автоматической загрузки видео на канал на 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')