Tutorials

How to parse YouTube videos

Instruction on how to parse YouTube video with Python.

Requirements

Install the required Python libraries before running the notebooks:

# bash
pip install "yt-dlp[default]" openai-whisper pandas
# bash
pip install "yt-dlp[default]" openai-whisper pandas

Install ffmpeg:

#bash

sudo apt install ffmp
#bash

sudo apt install ffmp

- yt-dlp — downloads videos and audio from YouTube and other platforms; `[default]` includes the EJS scripts required for YouTube's JavaScript challenge solving

- openai-whisper — transcribes audio files using OpenAI's Whisper model; requires ffmpeg for audio processing

- pandas — handles metadata extraction and CSV export

- ffmpeg — processes and converts audio/video fil

yt-dlp YouTube Setup Guide

[yt-dlp](https://github.com/yt-dlp/yt-dlp) is a tool for downloading videos and audio from YouTube and hundreds of other platforms. It is a feature-rich fork of youtube-dl with active development and broader format support.

Step 1: Get cookies

YouTube restricts anonymous downloads and requires authentication to verify you are not a bot.

Cookies allow yt-dlp to use your existing YouTube session without logging in directly.

1. Install **Get cookies.txt LOCALLY** extension in Chrome

2. Allow the extension in incognito mode: `chrome://extensions/` → Details → Allow in incognito

3. Open an incognito window and log into YouTube

4. Navigate to `youtube.com/robots.txt` — do not close this tab

5. Click the extension icon → **Export As****Netscape**

6. Close the incognito window

7. Place `cookies.txt` next to your notebook

Step 2: Install Deno

yt-dlp requires a JavaScript runtime to solve YouTube's n-challenge — a mechanism YouTube uses to prevent automated downloads. Deno is recommended and enabled by default, but Node.js, Bun, and QuickJS are also supported. For more details, refer to the [official yt-dlp EJS guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS).

# bash
curl -fsSL https://deno.land/install.sh | sh
# bash
curl -fsSL https://deno.land/install.sh | sh

Import all the libraries

import json
import yt_dlp
import os
import whisper

import pandas as pd

from pathlib import Path

# add system binaries to PATH so yt-dlp can find ffmpeg and deno
os.environ['PATH'] += ':/usr/bin'

import json
import yt_dlp
import os
import whisper

import pandas as pd

from pathlib import Path

# add system binaries to PATH so yt-dlp can find ffmpeg and deno
os.environ['PATH'] += ':/usr/bin'

Download single video

Download audio file

# Replace with your YouTube video URL.
VIDEO_URL = 'https://www.youtube.com/watch?v=k5xjIiINCv8' 

# Get video metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
    info = ydl.extract_info(VIDEO_URL, download=False)

CHANNEL_DIR = Path('data') / info['uploader']  # Root folder for channel.
AUDIO_DIR = CHANNEL_DIR / 'audio'  # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True)  # Create folders if not exists.

ydl_opts = {
    'quiet': False,
    'no_warnings': False,
    'cookiefile': 'cookies.txt',  # Use cookies for authentication.
    'format': 'bestaudio/best',  # Download best available audio.
    'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'),  # Output: data/channel/audio/title [id].ext
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',  # Extract audio with ffmpeg.
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}

# Download audio and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    info = ydl.extract_info(VIDEO_URL)

# Replace with your YouTube video URL.
VIDEO_URL = 'https://www.youtube.com/watch?v=k5xjIiINCv8' 

# Get video metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
    info = ydl.extract_info(VIDEO_URL, download=False)

CHANNEL_DIR = Path('data') / info['uploader']  # Root folder for channel.
AUDIO_DIR = CHANNEL_DIR / 'audio'  # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True)  # Create folders if not exists.

ydl_opts = {
    'quiet': False,
    'no_warnings': False,
    'cookiefile': 'cookies.txt',  # Use cookies for authentication.
    'format': 'bestaudio/best',  # Download best available audio.
    'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'),  # Output: data/channel/audio/title [id].ext
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',  # Extract audio with ffmpeg.
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
}

# Download audio and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    info = ydl.extract_info(VIDEO_URL)

# Extract relevant metadata fields.
fields = [
    'id',
    'title',
    'description',
    'uploader',
    'upload_date',
    'duration',
    'view_count',
    'like_count',
    'comment_count',
    'tags',
    'categories',
    'webpage_url'
]

meta = {k: info.get(k) for k in fields}
print(json.dumps(
    meta,
    ensure_ascii=False,
    indent=2
))
# Extract relevant metadata fields.
fields = [
    'id',
    'title',
    'description',
    'uploader',
    'upload_date',
    'duration',
    'view_count',
    'like_count',
    'comment_count',
    'tags',
    'categories',
    'webpage_url'
]

meta = {k: info.get(k) for k in fields}
print(json.dumps(
    meta,
    ensure_ascii=False,
    indent=2
))

Download playlist

PLAYLIST_URL = 'https://www.youtube.com/watch?v=ykjFMOcamd0&list=PLr0vtm6PM7z2VKhJ4U3BoDd6NxwTdsF5H' 
  # Replace with your YouTube playlist URL.

# Get playlist metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
    info = ydl.extract_info(PLAYLIST_URL, download=False)

CHANNEL_DIR = Path('data') / info['uploader']  # Root folder for channel.

# Define relevant metadata fields.
FIELDS = [
    'id',
    'title',
    'description',
    'uploader',
    'upload_date',
    'duration',
    'view_count',
    'like_count',
    'comment_count',
    'tags',
    'categories',
    'webpage_url'
]
PLAYLIST_URL = 'https://www.youtube.com/watch?v=ykjFMOcamd0&list=PLr0vtm6PM7z2VKhJ4U3BoDd6NxwTdsF5H' 
  # Replace with your YouTube playlist URL.

# Get playlist metadata without downloading to extract channel name.
with yt_dlp.YoutubeDL({'cookiefile': 'cookies.txt', 'quiet': True}) as ydl:
    info = ydl.extract_info(PLAYLIST_URL, download=False)

CHANNEL_DIR = Path('data') / info['uploader']  # Root folder for channel.

# Define relevant metadata fields.
FIELDS = [
    'id',
    'title',
    'description',
    'uploader',
    'upload_date',
    'duration',
    'view_count',
    'like_count',
    'comment_count',
    'tags',
    'categories',
    'webpage_url'
]

Download audio

AUDIO_DIR = CHANNEL_DIR / 'audio'  # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True)  # Create folders if not exists.

ydl_opts = {
    'quiet': False,
    'no_warnings': False,
    'cookiefile': 'cookies.txt',  # Use cookies for authentication.
    'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'),  # Output: data/channel/audio/title [id].ext.
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',  # Extract audio with ffmpeg.
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
    'ignoreerrors': True,  # Skip unavailable videos.
}

# Download playlist and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    playlist_info = ydl.extract_info(PLAYLIST_URL, download=True)

# Extract metadata for each video.
rows = []
for entry in playlist_info['entries']:
    if entry:  # Skip unavailable videos.
        row = {field: entry.get(field) for field in FIELDS}  # Extract selected fields.
        row['file_path'] = str(AUDIO_DIR / f"{entry.get('title', '')} [{entry.get('id', '')}].mp3")  # Path to downloaded file.
        rows.append(row)

# Save metadata to csv next to the files
metadata_df = pd.DataFrame(rows, columns=FIELDS + ['file_path'])
metadata_df.to_csv(AUDIO_DIR / 'metadata.csv', index=False)
AUDIO_DIR = CHANNEL_DIR / 'audio'  # Folder for audio files.
AUDIO_DIR.mkdir(parents=True, exist_ok=True)  # Create folders if not exists.

ydl_opts = {
    'quiet': False,
    'no_warnings': False,
    'cookiefile': 'cookies.txt',  # Use cookies for authentication.
    'outtmpl': str(AUDIO_DIR / '%(title)s [%(id)s].%(ext)s'),  # Output: data/channel/audio/title [id].ext.
    'postprocessors': [{
        'key': 'FFmpegExtractAudio',  # Extract audio with ffmpeg.
        'preferredcodec': 'mp3',
        'preferredquality': '192',
    }],
    'ignoreerrors': True,  # Skip unavailable videos.
}

# Download playlist and extract metadata.
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
    playlist_info = ydl.extract_info(PLAYLIST_URL, download=True)

# Extract metadata for each video.
rows = []
for entry in playlist_info['entries']:
    if entry:  # Skip unavailable videos.
        row = {field: entry.get(field) for field in FIELDS}  # Extract selected fields.
        row['file_path'] = str(AUDIO_DIR / f"{entry.get('title', '')} [{entry.get('id', '')}].mp3")  # Path to downloaded file.
        rows.append(row)

# Save metadata to csv next to the files
metadata_df = pd.DataFrame(rows, columns=FIELDS + ['file_path'])
metadata_df.to_csv(AUDIO_DIR / 'metadata.csv', index=False)

Transcribe downloaded files

audio_files = [
    {'path': p, 'id': p.stem.split('[')[-1].rstrip(']'), 'title': p.stem.rsplit('[', 1)[0].strip()}
    for p in sorted(AUDIO_DIR.iterdir())  # Iterate over audio folder.
    if p.suffix == '.mp4'  # Filter mp4 files only

audio_files = [
    {'path': p, 'id': p.stem.split('[')[-1].rstrip(']'), 'title': p.stem.rsplit('[', 1)[0].strip()}
    for p in sorted(AUDIO_DIR.iterdir())  # Iterate over audio folder.
    if p.suffix == '.mp4'  # Filter mp4 files only

WHISPER_MODEL = 'medium'  # Whisper model size.
LANGUAGE = 'ru'  # Transcription language.
TRANSCRIPTS_DIR = CHANNEL_DIR / 'transcripts'  # Folder for transcripts next to audio and video.
TRANSCRIPTS_DIR.mkdir(parents=True, exist_ok=True)  # Create folder if not exists.

model = whisper.load_model(WHISPER_MODEL)  # Load whisper model.

for item in audio_files:
    result = model.transcribe(str(item['path']), language=LANGUAGE, verbose=False, fp16=False)  # Transcribe audio.

    out = TRANSCRIPTS_DIR / f"{item['path'].stem}_transcript.txt"  # Same name as audio file + _transcript.
    with open(out, 'w', encoding='utf-8') as f:
        f.write(f"# {item['title']}\n\n")  # Write title.
        for seg in result['segments']:
            s, e = int(seg['start']), int(seg['end'])
            ts = lambda x: f'{x // 60:02d}:{x % 60:02d}'  # Format timestamp as mm:ss.
            f.write(f"[{ts(s)} -> {ts(e)}] {seg['text'].strip()}\n")  # Write segment with timestamps.
WHISPER_MODEL = 'medium'  # Whisper model size.
LANGUAGE = 'ru'  # Transcription language.
TRANSCRIPTS_DIR = CHANNEL_DIR / 'transcripts'  # Folder for transcripts next to audio and video.
TRANSCRIPTS_DIR.mkdir(parents=True, exist_ok=True)  # Create folder if not exists.

model = whisper.load_model(WHISPER_MODEL)  # Load whisper model.

for item in audio_files:
    result = model.transcribe(str(item['path']), language=LANGUAGE, verbose=False, fp16=False)  # Transcribe audio.

    out = TRANSCRIPTS_DIR / f"{item['path'].stem}_transcript.txt"  # Same name as audio file + _transcript.
    with open(out, 'w', encoding='utf-8') as f:
        f.write(f"# {item['title']}\n\n")  # Write title.
        for seg in result['segments']:
            s, e = int(seg['start']), int(seg['end'])
            ts = lambda x: f'{x // 60:02d}:{x % 60:02d}'  # Format timestamp as mm:ss.
            f.write(f"[{ts(s)} -> {ts(e)}] {seg['text'].strip()}\n")  # Write segment with timestamps.
CONTACT US
 INSTITUTE FOR EUROPEAN, RUSSIAN AND EURASIAN STUDIES 1957 E St NW Washington, DC 20052

1957 E St., NW, Suite 412,
Washington, DC 20052

russiaprogram@gwu.edu
+1 (202) 9946340

CONTACT US
 INSTITUTE FOR EUROPEAN, RUSSIAN AND EURASIAN STUDIES 1957 E St NW Washington, DC 20052

1957 E St., NW, Suite 412,
Washington, DC 20052

russiaprogram@gwu.edu
+1 (202) 9946340