การรวม YouTube API: การอัปโหลดวิดีโอด้วย Django
เผยแพร่แล้ว: 2022-03-11ไม่นานมานี้ ฉันกำลังทำงานให้กับลูกค้า โดยรวบรวมรีวิววิดีโอไว้ในเว็บไซต์ของตน เช่นเดียวกับนักพัฒนาที่มีแรงจูงใจในการแก้ปัญหาใหม่ สิ่งแรกที่ฉันทำคือ Google และฉันพบคำตอบที่ไม่มีประโยชน์หรือผิดพลาดมากมายเกี่ยวกับวิธีการบรรลุสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง หรือแพ็คเกจ Python ที่ล้าสมัยและไม่ได้รับการดูแล ในที่สุดฉันก็กัดกระสุนและทีม และสร้างทุกอย่างตั้งแต่เริ่มต้น: เราสร้างมุมมอง เรียนรู้เกี่ยวกับ API ของ Google สร้างไคลเอนต์ API และในที่สุดก็ประสบความสำเร็จในการอัปโหลดวิดีโอจาก Django โดยทางโปรแกรม
ในโพสต์นี้ ฉันจะพยายามแนะนำคุณทีละขั้นตอนในการโพสต์วิดีโอ YouTube จากแอป Django ของคุณ การดำเนินการนี้ต้องใช้ข้อมูลประจำตัวของ Google API เล็กน้อย อันดับแรกด้วยอินเทอร์เฟซเว็บ ตามด้วยโค้ด ส่วนของ YouTube นั้นตรงไปตรงมามาก เราต้องเข้าใจวิธีการทำงานของ Google เพราะบางครั้งอาจยุ่งยากและข้อมูลก็กระจายไปในหลายๆ ที่
ข้อกำหนดเบื้องต้น
ฉันขอแนะนำให้ทำความคุ้นเคยกับสิ่งต่อไปนี้ก่อนที่เราจะเริ่มทำงาน:
- API ข้อมูล YouTube: Python Quickstart
- API ข้อมูล YouTube: ข้อมูลอ้างอิง API
- YouTube Data API: ตัวอย่างโค้ด
- ไลบรารีไคลเอ็นต์ Google Python API
- ไลบรารีไคลเอ็นต์ Google Python API: เอกสารอ้างอิง
- ไลบรารีไคลเอ็นต์ Google Python API: ตัวอย่างโค้ด
- YouTube API: ตัวอย่างโค้ด Python
โค้ดที่น่าสนใจที่ควรทราบคือ Python Snippet จาก Google YouTube API Docs:
# Sample python code for videos.insert def videos_insert(client, properties, media_file, **kwargs): resource = build_resource(properties) # See full sample for function kwargs = remove_empty_kwargs(**kwargs) # See full sample for function request = client.videos().insert( body=resource, media_body=MediaFileUpload(media_file, chunksize=-1, resumable=True), **kwargs ) # See full sample for function return resumable_upload(request, 'video', 'insert') media_file = 'sample_video.flv' if not os.path.exists(media_file): exit('Please specify a valid file location.') videos_insert(client, {'snippet.categoryId': '22', 'snippet.defaultLanguage': '', 'snippet.description': 'Description of uploaded video.', 'snippet.tags[]': '', 'snippet.title': 'Test video upload', 'status.embeddable': '', 'status.license': '', 'status.privacyStatus': 'private', 'status.publicStatsViewable': ''}, media_file, part='snippet,status')
เริ่มต้น
หลังจากที่คุณได้อ่านข้อกำหนดเบื้องต้นแล้ว ก็ถึงเวลาเริ่มต้น มาดูกันว่าเราต้องการอะไร
เข็มขัดเครื่องมือ
โดยพื้นฐานแล้ว มาสร้างสภาพแวดล้อมเสมือนจริงกันเถอะ โดยส่วนตัวแล้วฉันชอบ pyenv การตั้งค่าทั้งสองนั้นอยู่นอกขอบเขตของโพสต์นี้ ดังนั้นฉันจะโพสต์คำสั่ง pyenv ด้านล่าง และหากคุณตั้งค่าตามความชอบของคุณ virtualenv
ลังเลที่จะเปลี่ยนคำสั่งตามนั้น
ฉันจะใช้ Python 3.7 และ Django 2.1 ในโพสต์นี้
➜ ~/projects $ mkdir django-youtube ➜ ~/projects $ cd django-youtube ➜ ~/projects/django-youtube $ pyenv virtualenv 3.7.0 djangoyt ➜ ~/projects/django-youtube $ vim .python-version
มาใส่สิ่งนี้ในเนื้อหา (ถ้าคุณใช้ pyenv ดังนั้นมันจะเปิดใช้งานโดยอัตโนมัติเมื่อคุณเข้าสู่โฟลเดอร์):
djangoyt
การติดตั้งการพึ่งพา:
➜ ~/projects/django-youtube $ pip install google-api-python-client google-auth\ google-auth-oauthlib google-auth-httplib2 oauth2client Django unipath jsonpickle
ตอนนี้ได้เวลาเริ่มโครงการ django ของเราแล้ว:
➜ ~/projects/django-youtube $ django-admin startproject django_youtube .
หยุดชั่วคราวสำหรับ Google Config
มากำหนดค่าข้อมูลประจำตัวของโครงการกันตอนนี้เพื่อให้เราสามารถใช้ Google API ได้
ขั้นตอนที่ 1 ไปที่ URL ต่อไปนี้:
https://console.developers.google.com/apis/library/youtube.googleapis.com
ขั้นตอนที่ 2 สร้างโครงการใหม่
ขั้นตอนที่ 3 คลิก “เปิดใช้งาน API และบริการ”
ขั้นตอนที่ 4 ค้นหา YouTube Data API v3 แล้วคลิก "เปิดใช้งาน"
ขั้นตอนที่ 5. คุณควรได้รับข้อความเกี่ยวกับข้อมูลประจำตัว
ขั้นตอนที่ 6 คลิกที่ปุ่มสีน้ำเงิน "สร้างข้อมูลรับรอง" ทางด้านขวาและคุณควรได้รับหน้าจอต่อไปนี้:
ขั้นตอนที่ 7 เลือกเว็บเซิร์ฟเวอร์ ข้อมูลผู้ใช้:
ขั้นตอนที่ 8 เพิ่มต้นทาง JS ที่ได้รับอนุญาตและเปลี่ยนเส้นทาง URI ไปต่อจนจบ:
ตกลง เราเสร็จสิ้นการตั้งค่าข้อมูลประจำตัวของเราแล้ว คุณสามารถดาวน์โหลดข้อมูลประจำตัวในรูปแบบ JSON หรือคัดลอก รหัสไคลเอ็นต์และรหัส ลับไคลเอ็นต์
กลับไปที่จังโก้
มาเริ่มแอพ Django แรกของเรากันเถอะ ฉันมักจะตั้งชื่อมันว่า "แกน":
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py startapp core
ตอนนี้ มาเพิ่มสิ่งต่อไปนี้ในไฟล์ root urls.py เพื่อกำหนดเส้นทางคำขอหน้าแรกไปยังแอปหลักของเรา:
# <root>/urls.py from django.urls import path, include path('', include(('core.urls', 'core'), namespace='core')),
ในแอพหลัก เรามามีไฟล์ urls.py อีกไฟล์หนึ่ง โดยมีการกำหนดค่าบางอย่างด้วย:
# core/urls.py from django.conf import settings from django.conf.urls.static import static from django.urls import path from .views import HomePageView urlpatterns = [ path('', HomePageView.as_view(), name='home') ] if settings.DEBUG: urlpatterns += static( settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
ดูว่ามีเส้นทางว่างที่ชี้ไปที่ HomePageView
ได้เวลาเพิ่มโค้ดแล้ว
ลองทำ TemplateView
ง่ายๆ เพื่อดูว่ามันทำงานอย่างไร
# core/views.py from django.shortcuts import render from django.views.generic import TemplateView class HomePageView(TemplateView): template_name = 'core/home.html'
และแน่นอนว่าเราต้องการเทมเพลตพื้นฐาน:
# core/templates/core/home.html <!DOCTYPE html> <html> <body> <h1>My First Heading</h1> <p>My first paragraph.</p> </body> </html>
เราจำเป็นต้องปรับแต่งการตั้งค่าบางอย่าง:
# settings.py from unipath import Path # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).parent INSTALLED_APPS 'core', STATIC_ROOT = BASE_DIR.parent.child('staticfiles') STATIC_URL = '/static/' MEDIA_ROOT = BASE_DIR.parent.child('uploads') MEDIA_URL = '/media/'
มาสร้าง YoutubeForm
แล้วเพิ่มเป็น form_class
ให้กับมุมมอง:
# core/views.py from django import forms from django.views.generic.edit import FormView class YouTubeForm(forms.Form): pass class HomePageView(FormView): template_name = 'core/home.html' form_class = YouTubeForm
ลองเรียกใช้แอปพลิเคชันของคุณตอนนี้ และหน้าจะมีลักษณะดังนี้:
หยุดชั่วคราวเพื่อทำการอนุญาต
ก่อนอื่น คุณต้องสร้างแบบจำลองเพื่อเก็บข้อมูลรับรองของคุณ คุณสามารถทำได้ผ่านไฟล์ ระบบแคช หรือโซลูชันการจัดเก็บข้อมูลอื่นๆ แต่ฐานข้อมูลดูสมเหตุสมผลและปรับขนาดได้ และคุณยังสามารถจัดเก็บข้อมูลรับรองต่อผู้ใช้ได้หากต้องการ
ก่อนดำเนินการต่อ ต้องทำการปรับเปลี่ยน—มีทางแยกของ oauth2client ที่รองรับ Django 2.1 ที่เราต้องใช้ เร็วๆ นี้ เราจะได้รับการสนับสนุนอย่างเป็นทางการ แต่ในระหว่างนี้ คุณสามารถตรวจสอบการเปลี่ยนแปลงของส้อมได้ พวกมันง่ายมาก
pip install -e git://github.com/Schweigi/[email protected]#egg=oauth2client Because of compatibility with Django 2.1
ไปที่ settings.py
ของคุณแล้ววาง Client ID และ Client Secret ที่คุณได้รับจาก Google ในขั้นตอนก่อนหน้า
# settings.py GOOGLE_OAUTH2_CLIENT_ GOOGLE_OAUTH2_CLIENT_SECRET = '<your client secret>'
ข้อควรระวัง: ไม่ แนะนำให้เก็บความลับในรหัสของคุณ ฉันทำสิ่งนี้เพียงเพื่อเป็นการสาธิต ฉันขอแนะนำให้ใช้ตัวแปรสภาพแวดล้อมในแอปที่ใช้งานจริง และไม่เข้ารหัสลับในไฟล์แอปพลิเคชัน หรือหากคุณดาวน์โหลด JSON จาก Google คุณยังสามารถระบุเส้นทางของ JSON แทนการตั้งค่าด้านบน:
GOOGLE_OAUTH2_CLIENT_SECRETS_JSON = '/path/to/client_id.json'
แพ็คเกจ oauth2client มีฟังก์ชันมากมายอยู่แล้ว ด้วย CredentialsField
ที่เราสามารถใช้ได้ เป็นไปได้ที่จะเพิ่มฟิลด์อื่นๆ เช่น คีย์นอกและวันที่สร้าง/แก้ไข เพื่อให้เรามีประสิทธิภาพมากขึ้น แต่ให้พูดง่ายๆ ก่อน
รูปแบบที่เรียบง่ายในการจัดเก็บข้อมูลประจำตัว:
# core/models.py from django.db import models from oauth2client.contrib.django_util.models import CredentialsField class CredentialsModel(models.Model): credential = CredentialsField()
เวลาในการสร้างการโยกย้ายและการโยกย้าย:

(djangoyt) ➜ ~/projects/django-youtube $ ./manage.py makemigrations core (djangoyt) ➜ ~/projects/django-youtube $ ./manage.py migrate
ตอนนี้เรามาเปลี่ยนมุมมอง API ของเราเพื่อให้สามารถอนุญาตแอปพลิเคชันของเรา:
ในไฟล์ core/urls.py
ของเรา มาเพิ่มอีกรายการสำหรับมุมมองการให้สิทธิ์ครั้งแรก:
# core/urls.py from .views import AuthorizeView, HomePageView urlpatterns = [ # [...] path('authorize/', AuthorizeView.as_view(), name='authorize'), ]
ดังนั้น ส่วนแรกของ AuthorizeView จะเป็น:
# core/views.py from django.conf import settings from django.shortcuts import render, redirect from django.views.generic.base import View from oauth2client.client import flow_from_clientsecrets, OAuth2WebServerFlow from oauth2client.contrib import xsrfutil from oauth2client.contrib.django_util.storage import DjangoORMStorage from .models import CredentialsModel # [...] class AuthorizeView(View): def get(self, request, *args, **kwargs): storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') credential = storage.get() flow = OAuth2WebServerFlow( client_id=settings.GOOGLE_OAUTH2_CLIENT_ID, client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/') # or if you downloaded the client_secrets file '''flow = flow_from_clientsecrets( settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/')'''
แล้วส่วนที่สอง:
if credential is None or credential.invalid == True: flow.params['state'] = xsrfutil.generate_token( settings.SECRET_KEY, request.user) authorize_url = flow.step1_get_authorize_url() return redirect(authorize_url) return redirect('/')
ดังนั้นหากไม่มีข้อมูลประจำตัวหรือข้อมูลประจำตัวไม่ถูกต้อง ให้สร้างแล้วเปลี่ยนเส้นทางไปยัง URL ที่อนุญาต มิฉะนั้น เพียงไปที่หน้าแรกเพื่อให้เราสามารถอัปโหลดวิดีโอได้!
เข้าสู่มุมมองตอนนี้และดูว่าเกิดอะไรขึ้น:
ให้สร้างผู้ใช้ก่อนไปที่หน้านั้น
(djangoyt) ➜ ~/projects/django-youtube $ python manage.py createsuperuser Username (leave blank to use 'ivan'): ivan Email address: ivan***@mail.com Password: Password (again): This password is too short. It must contain at least 8 characters. Bypass password validation and create user anyway? [y/N]: y Superuser created successfully.
มาเข้าสู่ระบบด้วย /admin
กัน หลังจากนั้น ให้เข้าไปที่ /authorize/
ดูของเราอีกครั้ง
แล้ว,
ตกลง มันพยายามเปลี่ยนเส้นทางไปยัง URL การโทรกลับที่เรากำหนดค่าไว้กับ Google นานมาแล้ว ตอนนี้เราจำเป็นต้องใช้มุมมองการโทรกลับ
มาเพิ่มอีกรายการหนึ่งใน core/urls.py ของเรา:
# core/urls.py from .views import AuthorizeView, HomePageView, Oauth2CallbackView urlpatterns = [ # [...] path('oauth2callback/', Oauth2CallbackView.as_view(), name='oauth2callback') ]
และอีกมุมมองหนึ่ง:
# core/views.py # the following variable stays as global for now flow = OAuth2WebServerFlow( client_id=settings.GOOGLE_OAUTH2_CLIENT_ID, client_secret=settings.GOOGLE_OAUTH2_CLIENT_SECRET, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/') # or if you downloaded the client_secrets file '''flow = flow_from_clientsecrets( settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, scope='https://www.googleapis.com/auth/youtube', redirect_uri='http://localhost:8888/oauth2callback/')''' # [...] class Oauth2CallbackView(View): def get(self, request, *args, **kwargs): if not xsrfutil.validate_token( settings.SECRET_KEY, request.GET.get('state').encode(), request.user): return HttpResponseBadRequest() credential = flow.step2_exchange(request.GET) storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') storage.put(credential) return redirect('/')
หมายเหตุ: โฟลว์ถูกย้ายไปภายนอก AuthorizeView กลายเป็นสากล ตามหลักการแล้ว คุณควรสร้างมันขึ้นมาภายใต้ AuthorizeView และบันทึกในแคช จากนั้นดึงมันกลับมาในการโทรกลับ แต่นั่นก็อยู่นอกขอบเขตของโพสต์นี้
วิธีการรับของ AuthorizeView คือตอนนี้:
def get(self, request, *args, **kwargs): storage = DjangoORMStorage( CredentialsModel, 'id', request.user.id, 'credential') credential = storage.get() if credential is None or credential.invalid == True: flow.params['state'] = xsrfutil.generate_token( settings.SECRET_KEY, request.user) authorize_url = flow.step1_get_authorize_url() return redirect(authorize_url) return redirect('/')
คุณสามารถดูการใช้งานที่คล้ายกันได้ที่นี่ แพ็คเกจ oauth2client
นั้นให้มุมมอง แต่ฉันต้องการใช้มุมมอง Oauth ที่กำหนดเองเป็นพิเศษ
- https://github.com/google/google-api-python-client/blob/master/samples/django_sample/plus/views.py
- https://github.com/google/oauth2client/blob/master/oauth2client/contrib/django_util/views.py
ตอนนี้ หากคุณลองใช้ /authorize/
URL อีกครั้ง โฟลว์ OAuth ควรใช้งานได้ ถึงเวลาดูว่างานนี้คุ้มค่าหรือไม่และอัปโหลดวิดีโอของเรา! HomePageView
จะตรวจสอบข้อมูลประจำตัวก่อน และหากทุกอย่างดี เราก็พร้อมสำหรับการอัปโหลดวิดีโอของเรา
มาดูกันว่าโค้ดใหม่สำหรับ HomePageView จะเป็นอย่างไร:
import tempfile from django.http import HttpResponse, HttpResponseBadRequest from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload class HomePageView(FormView): template_name = 'core/home.html' form_class = YouTubeForm def form_valid(self, form): fname = form.cleaned_data['video'].temporary_file_path() storage = DjangoORMStorage( CredentialsModel, 'id', self.request.user.id, 'credential') credentials = storage.get() client = build('youtube', 'v3', credentials=credentials) body = { 'snippet': { 'title': 'My Django Youtube Video', 'description': 'My Django Youtube Video Description', 'tags': 'django,howto,video,api', 'categoryId': '27' }, 'status': { 'privacyStatus': 'unlisted' } } with tempfile.NamedTemporaryFile('wb', suffix='yt-django') as tmpfile: with open(fname, 'rb') as fileobj: tmpfile.write(fileobj.read()) insert_request = client.videos().insert( part=','.join(body.keys()), body=body, media_body=MediaFileUpload( tmpfile.name, chunksize=-1, resumable=True) ) insert_request.execute() return HttpResponse('It worked!')
และเทมเพลตใหม่:
{# core/templates/core/home.html #} <!DOCTYPE html> <html> <body> <h1>Upload your video</h1> <p>Here is the form:</p> <form action="." method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <input type="submit" value="Submit"> </form> </body> </html>
อย่าลืมเพิ่มช่องวิดีโอใน YouTubeForm:
class YouTubeForm(forms.Form): video = forms.FileField()
ไปเลย!
จากนั้นตรวจสอบที่หน้า Studio บัญชี YouTube ของคุณ (ต้องมีช่อง):
โว้ว!
ปิดหมายเหตุ
โค้ดต้องมีการปรับปรุงบ้าง แต่เป็นจุดเริ่มต้นที่ดี ฉันหวังว่าจะช่วยแก้ปัญหาการผสานรวม YouTube API ของ Google ส่วนใหญ่ สิ่งสำคัญอีกสองสามข้อที่ควรทราบมีดังนี้
- สำหรับการอนุญาต สิ่งสำคัญคือต้องเข้าสู่ระบบและการอนุญาตพิเศษสำหรับผู้ใช้ที่จะอนุญาตให้แอปพลิเคชันของคุณอัปโหลดวิดีโอ
- ต้องย้ายตัวแปรโฟลว์ออกจากการเป็นโกลบอล ไม่ปลอดภัยในสภาพแวดล้อมการผลิต ควรใช้แคชตาม ID ผู้ใช้หรือเซสชันที่เข้าถึงข้อมูลพร็อพเพอร์ตี้แรกเป็นต้น
- Google ให้โทเค็นการรีเฟรชเมื่อคุณให้สิทธิ์ครั้งแรกเท่านั้น ดังนั้นหลังจากเวลาผ่านไป ส่วนใหญ่หนึ่งชั่วโมง โทเค็นของคุณจะหมดอายุ และหากคุณไม่ได้โต้ตอบกับ API ของพวกเขา คุณจะเริ่มได้รับการตอบกลับ
invalid_grant
การให้สิทธิ์ผู้ใช้รายเดิมที่อนุญาตลูกค้าแล้วจะไม่รับประกันโทเค็นการรีเฟรชของคุณ คุณต้องเพิกถอนแอปพลิเคชันในหน้าบัญชี Google ของคุณ จากนั้นดำเนินการตามขั้นตอนการอนุญาตอีกครั้ง ในบางกรณี คุณอาจต้องเรียกใช้งานเพื่อรีเฟรชโทเค็นต่อไป - เราจำเป็นต้องเข้าสู่ระบบในมุมมองของเรา เนื่องจากเรากำลังใช้ข้อมูลรับรองผู้ใช้ที่เกี่ยวข้องโดยตรงกับคำขอ
การอัปโหลดใช้เวลานาน และการดำเนินการในกระบวนการสมัครหลักอาจทำให้ทั้งแอปพลิเคชันถูกบล็อกในขณะที่การอัปโหลดเกิดขึ้น วิธีที่ถูกต้องคือการย้ายไปยังกระบวนการของตนเองและจัดการการอัปโหลดแบบอะซิงโครนัส
สับสน? อย่าเป็นเช่นนั้น อ่านเพิ่มเติมใน Orchestrating a Background Job Workflow ใน Celery for Python