Esta es una API de Python que te permite obtener la transcripción/subtítulos de un video de YouTube. También funciona con subtítulos generados automáticamente, soporta traducción de subtítulos y no requiere un navegador sin cabeza (headless), ¡a diferencia de otras soluciones basadas en selenium!
El mantenimiento de este proyecto es posible gracias a todos los contribuyentes y patrocinadores. Si deseas patrocinar este proyecto y que tu avatar o logo de empresa aparezca abajo, haz clic aquí. 💖
Se recomienda instalar este módulo usando pip:
pip install youtube-transcript-api
Puedes integrar este módulo en una aplicación existente o simplemente usarlo mediante una CLI.
La forma más sencilla de obtener una transcripción para un video dado es ejecutar:
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
ytt_api.fetch(video_id)
Nota: Por defecto, esto intentará acceder a la transcripción en inglés del video. Si tu video tiene un idioma diferente o estás interesado en obtener una transcripción en otro idioma, consulta la sección a continuación.
Nota: Proporciona el ID del video, NO la URL del video. Para un video con la URL
https://www.youtube.com/watch?v=12345
, el ID es12345
.
Esto devolverá un objeto FetchedTranscript
que se verá algo así:
FetchedTranscript(
snippets=[
FetchedTranscriptSnippet(
text="Hey there",
start=0.0,
duration=1.54,
),
FetchedTranscriptSnippet(
text="how are you",
start=1.54,
duration=4.16,
),
# ...
],
video_id="12345",
language="English",
language_code="en",
is_generated=False,
)
Este objeto implementa la mayoría de las interfaces de una List
:
ytt_api = YouTubeTranscriptApi()
fetched_transcript = ytt_api.fetch(video_id)
# is iterable
for snippet in fetched_transcript:
print(snippet.text)
# indexable
last_snippet = fetched_transcript[-1]
# provides a length
snippet_count = len(fetched_transcript)
Si prefieres manejar los datos brutos de la transcripción, puedes llamar a fetched_transcript.to_raw_data()
, que devolverá una lista de diccionarios:
[
{
'text': 'Hey there',
'start': 0.0,
'duration': 1.54
},
{
'text': 'how are you',
'start': 1.54
'duration': 4.16
},
# ...
]
Puedes añadir el parámetro languages
si deseas asegurarte de que las transcripciones se obtengan en tu idioma deseado (por defecto es inglés).
YouTubeTranscriptApi().fetch(video_id, languages=['de', 'en'])
Es una lista de códigos de idioma en orden de prioridad descendente. En este ejemplo, primero intentará obtener la transcripción en alemán ('de'
) y luego la transcripción en inglés ('en'
) si falla. Si deseas saber qué idiomas están disponibles primero, consulta list()
.
Si solo quieres un idioma, aún debes formatear el argumento languages
como una lista:
YouTubeTranscriptApi().fetch(video_id, languages=['de'])
También puedes añadir preserve_formatting=True
si deseas conservar elementos de formato HTML como <i>
(cursiva) y <b>
(negrita).
YouTubeTranscriptApi().fetch(video_ids, languages=['de', 'en'], preserve_formatting=True)
Si deseas listar todas las transcripciones disponibles para un video dado, puedes llamar a:
ytt_api = YouTubeTranscriptApi()
transcript_list = ytt_api.list(video_id)
Esto devolverá un objeto TranscriptList
que es iterable y proporciona métodos para filtrar la lista de transcripciones por idiomas y tipos específicos, como:
transcript = transcript_list.find_transcript(['de', 'en'])
Por defecto, este módulo siempre elige transcripciones creadas manualmente sobre las generadas automáticamente, si una transcripción en el idioma solicitado está disponible tanto manual como generada. El objeto TranscriptList
te permite omitir este comportamiento predeterminado buscando tipos de transcripción específicos:
# filter for manually created transcripts
transcript = transcript_list.find_manually_created_transcript(['de', 'en'])
# or automatically generated ones
transcript = transcript_list.find_generated_transcript(['de', 'en'])
Los métodos find_generated_transcript
, find_manually_created_transcript
, find_transcript
devuelven objetos Transcript
. Estos contienen metadatos sobre la transcripción:
print(
transcript.video_id,
transcript.language,
transcript.language_code,
# whether it has been manually created or generated by YouTube
transcript.is_generated,
# whether this transcript can be translated or not
transcript.is_translatable,
# a list of languages the transcript can be translated to
transcript.translation_languages,
)
y proporcionan el método que te permite obtener los datos reales de la transcripción:
transcript.fetch()
Esto devuelve un objeto FetchedTranscript
, al igual que YouTubeTranscriptApi().fetch()
.
YouTube tiene una función que permite traducir subtítulos automáticamente. Este módulo también permite acceder a esta función. Para ello, los objetos Transcript
proporcionan un método translate()
, que devuelve un nuevo objeto Transcript
traducido:
transcript = transcript_list.find_transcript(['en'])
translated_transcript = transcript.translate('de')
print(translated_transcript.fetch())
from youtube_transcript_api import YouTubeTranscriptApi
ytt_api = YouTubeTranscriptApi()
# retrieve the available transcripts
transcript_list = ytt_api.list('video_id')
# iterate over all available transcripts
for transcript in transcript_list:
# the Transcript object provides metadata properties
print(
transcript.video_id,
transcript.language,
transcript.language_code,
# whether it has been manually created or generated by YouTube
transcript.is_generated,
# whether this transcript can be translated or not
transcript.is_translatable,
# a list of languages the transcript can be translated to
transcript.translation_languages,
)
# fetch the actual transcript data
print(transcript.fetch())
# translating the transcript will return another transcript object
print(transcript.translate('en').fetch())
# you can also directly filter for the language you are looking for, using the transcript list
transcript = transcript_list.find_transcript(['de', 'en'])
# or just filter for manually created transcripts
transcript = transcript_list.find_manually_created_transcript(['de', 'en'])
# or automatically generated ones
transcript = transcript_list.find_generated_transcript(['de', 'en'])
RequestBlocked
o IpBlocked
)Desafortunadamente, YouTube ha comenzado a bloquear la mayoría de las IPs conocidas por pertenecer a proveedores de nube (como AWS, Google Cloud Platform, Azure, etc.), lo que significa que es probable que encuentres excepciones RequestBlocked
o IpBlocked
al implementar tu código en soluciones en la nube. Lo mismo puede ocurrir con la IP de tu solución autoalojada si realizas demasiadas solicitudes. Puedes evitar estos bloqueos usando proxies. Sin embargo, dado que YouTube puede bloquear proxies estáticos después de un uso prolongado, optar por proxies residenciales rotativos es la opción más confiable.
Hay diferentes proveedores que ofrecen proxies residenciales rotativos, pero después de probar diferentes opciones, he encontrado que Webshare es el más confiable y, por lo tanto, lo he integrado en este módulo para facilitar su configuración.
Una vez que hayas creado una cuenta en Webshare y comprado un paquete de proxies "Residenciales" que se ajuste a tu carga de trabajo (¡asegúrate de NO comprar "Proxy Server" o "Static Residential"!), abre la Configuración de Proxies de Webshare para obtener tu "Proxy Username" y "Proxy Password". Con esta información, puedes inicializar el YouTubeTranscriptApi
de la siguiente manera:
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import WebshareProxyConfig
ytt_api = YouTubeTranscriptApi(
proxy_config=WebshareProxyConfig(
proxy_username="<proxy-username>",
proxy_password="<proxy-password>",
)
)
# all requests done by ytt_api will now be proxied through Webshare
ytt_api.fetch(video_id)
Usar WebshareProxyConfig
utilizará por defecto proxies residenciales rotativos y no requiere configuración adicional.
Ten en cuenta que aquí se usan enlaces de referencia y cualquier compra realizada a través de estos enlaces apoyará este proyecto de código abierto, ¡lo cual es muy apreciado! 💖😊🙏💖
Sin embargo, por supuesto, eres libre de integrar tu propia solución de proxy usando la clase GenericProxyConfig
, si prefieres usar otro proveedor o implementar tu propia solución, como se explica en la siguiente sección.
Alternativamente a usar Webshare, puedes configurar cualquier proxy HTTP/HTTPS/SOCKS genérico usando la clase GenericProxyConfig
:
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.proxies import GenericProxyConfig
ytt_api = YouTubeTranscriptApi(
proxy_config=GenericProxyConfig(
http_url="http://user:[email protected]:port",
https_url="https://user:[email protected]:port",
)
)
# all requests done by ytt_api will now be proxied using the defined proxy URLs
ytt_api.fetch(video_id)
Ten en cuenta que usar un proxy no garantiza que no serás bloqueado, ya que YouTube siempre puede bloquear la IP de tu proxy. Por lo tanto, si deseas maximizar la confiabilidad, debes elegir una solución que rote a través de un grupo de direcciones de proxy.
Al inicializar un objeto YouTubeTranscriptApi
, se creará una requests.Session
que se usará para todas las solicitudes HTTP(S). Esto permite almacenar en caché cookies al recuperar múltiples solicitudes. Sin embargo, opcionalmente puedes pasar un objeto requests.Session
a su constructor, si deseas compartir manualmente cookies entre diferentes instancias de YouTubeTranscriptApi
, sobrescribir valores predeterminados, establecer encabezados personalizados, especificar certificados SSL, etc.
from requests import Session
http_client = Session()
# set custom header
http_client.headers.update({"Accept-Encoding": "gzip, deflate"})
# set path to CA_BUNDLE file
http_client.verify = "/path/to/certfile"
ytt_api = YouTubeTranscriptApi(http_client=http_client)
ytt_api.fetch(video_id)
# share same Session between two instances of YouTubeTranscriptApi
ytt_api_2 = YouTubeTranscriptApi(http_client=http_client)
# now shares cookies with ytt_api
ytt_api_2.fetch(video_id)
Algunos videos tienen restricción de edad, por lo que este módulo no podrá acceder a ellos sin algún tipo de autenticación. Desafortunadamente, algunos cambios recientes en la API de YouTube han roto la implementación actual de autenticación basada en cookies, por lo que esta función no está disponible actualmente.
Los formateadores están diseñados para ser una capa adicional de procesamiento de la transcripción que le pases. El objetivo es convertir un objeto FetchedTranscript
en una cadena consistente de un "formato" dado. Como texto básico (.txt
) o incluso formatos que tienen una especificación definida como JSON (.json
), WebVTT (.vtt
), SRT (.srt
), formato separado por comas (.csv
), etc.
El submódulo formatters
proporciona algunos formateadores básicos, que pueden usarse tal cual o extenderse según tus necesidades:
Aquí se muestra cómo importar desde el módulo formatters
.
# the base class to inherit from when creating your own formatter.
from youtube_transcript_api.formatters import Formatter
# some provided subclasses, each outputs a different string format.
from youtube_transcript_api.formatters import JSONFormatter
from youtube_transcript_api.formatters import TextFormatter
from youtube_transcript_api.formatters import WebVTTFormatter
from youtube_transcript_api.formatters import SRTFormatter
Supongamos que queremos obtener una transcripción y guardarla en un archivo JSON. Eso se vería así:
# your_custom_script.py
from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.formatters import JSONFormatter
ytt_api = YouTubeTranscriptApi()
transcript = ytt_api.fetch(video_id)
formatter = JSONFormatter()
# .format_transcript(transcript) turns the transcript into a JSON string.
json_formatted = formatter.format_transcript(transcript)
# Now we can write it out to a file.
with open('your_filename.json', 'w', encoding='utf-8') as json_file:
json_file.write(json_formatted)
# Now should have a new JSON file that you can easily read back into Python.
Pasando argumentos adicionales
Dado que JSONFormatter utiliza json.dumps()
, también puedes pasar argumentos adicionales a .format_transcript(transcript)
, como hacer que la salida del archivo sea más legible pasando el argumento indent=2
.
json_formatted = JSONFormatter().format_transcript(transcript, indent=2)
Puedes implementar tu propia clase de formateador. Solo hereda de la clase base Formatter
y asegúrate de implementar los métodos format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str
y format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str
, que finalmente deben devolver una cadena cuando se llamen en tu instancia de formateador.
class MyCustomFormatter(Formatter):
def format_transcript(self, transcript: FetchedTranscript, **kwargs) -> str:
# Do your custom work in here, but return a string.
return 'your processed output data as a string.'
def format_transcripts(self, transcripts: List[FetchedTranscript], **kwargs) -> str:
# Do your custom work in here to format a list of transcripts, but return a string.
return 'your processed output data as a string.'
Ejecuta el script CLI usando los IDs de video como parámetros y los resultados se imprimirán en la línea de comandos:
youtube_transcript_api <first_video_id> <second_video_id> ...
La CLI también te permite proporcionar una lista de idiomas preferidos:
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en
También puedes especificar si deseas excluir subtítulos generados automáticamente o creados manualmente:
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --exclude-generated
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --exclude-manually-created
Si prefieres escribirlo en un archivo o canalizarlo a otra aplicación, también puedes mostrar los resultados como JSON usando la siguiente línea:
youtube_transcript_api <first_video_id> <second_video_id> ... --languages de en --format json > transcripts.json
Traducir transcripciones usando la CLI también es posible:
youtube_transcript_api <first_video_id> <second_video_id> ... --languages en --translate de
Si no estás seguro de qué idiomas están disponibles para un video dado, puedes llamar a lo siguiente para listar todas las transcripciones disponibles:
youtube_transcript_api --list-transcripts <first_video_id>
Si el ID de un video comienza con un guión, tendrás que enmascarar el guión usando \
para evitar que la CLI lo confunda con un nombre de argumento. Por ejemplo, para obtener la transcripción del video con el ID -abc123
, ejecuta:
youtube_transcript_api "\-abc123"
Si encuentras errores RequestBlocked
o IpBlocked
porque YouTube bloquea tu IP, puedes evitarlo usando proxies residenciales como se explica en Evitar bloqueos de IP. Para usar proxies "Residenciales" de Webshare a través de la CLI, tendrás que crear una cuenta en Webshare y comprar un paquete de proxies "Residenciales" que se ajuste a tu carga de trabajo (¡asegúrate de NO comprar "Proxy Server" o "Static Residential"!). Luego puedes usar el "Proxy Username" y "Proxy Password" que puedes encontrar en tu Configuración de Proxies de Webshare, para ejecutar el siguiente comando:
youtube_transcript_api <first_video_id> <second_video_id> --webshare-proxy-username "username" --webshare-proxy-password "password"
Si prefieres usar otra solución de proxy, puedes configurar un proxy HTTP/HTTPS genérico usando el siguiente comando:
youtube_transcript_api <first_video_id> <second_video_id> --http-proxy http://user:pass@domain:port --https-proxy https://user:pass@domain:port
Para autenticarte usando cookies a través de la CLI como se explica en Autenticación con Cookies, ejecuta:
youtube_transcript_api <first_video_id> <second_video_id> --cookies /path/to/your/cookies.txt
Este código utiliza una parte no documentada de la API de YouTube, que es llamada por el cliente web de YouTube. Por lo tanto, no hay garantía de que no deje de funcionar mañana si cambian cómo funcionan las cosas. Sin embargo, haré todo lo posible para que las cosas vuelvan a funcionar lo antes posible si eso ocurre. ¡Así que si deja de funcionar, avísame!
Para configurar el proyecto localmente, ejecuta lo siguiente (requiere tener instalado poetry):
poetry install --with test,dev
Hay tareas de poe para ejecutar pruebas, cobertura, el linter y el formateador (necesitarás pasar todas ellas para que la compilación sea exitosa):
poe test
poe coverage
poe format
poe lint
Si solo quieres asegurarte de que tu código pasa todas las verificaciones necesarias para obtener una compilación exitosa, simplemente ejecuta:
poe precommit
Si este proyecto te hace feliz al reducir tu tiempo de desarrollo, puedes hacerme feliz invitándome a un café o convirtiéndote en un Patrocinador de este proyecto :)