Youtube Workflow

BA Inhaltsanalyse: Inhalte öffentlicher Kommunikation KF E
Wintersemester 2025/2026
Dozent
Raum

Felix Dietrich

02 507

Wichtig

Um diesen Workflow ohne Probleme reproduzieren zu können, führen Sie ihn am besten auf dem RStudio-Server durch. Machen Sie regelmäßig lokale Backups, indem Sie einzelne Dateien oder den gesamten Projektordner herunterladen. Hierzu markieren Sie im Files-Tab rechts unten die gewünschten Dateien oder den Ordner (Häkchen links neben dem Namen setzen) und dann auf “More” -> “Export…” klicken.

RStudio Projekt anlegen

Wir legen ein neues RStudio-Projekt an, z.B. youtube-workflow. Dazu klicken wir rechts oben in RStudio auf das Symbol für Projekte und wählen “New Project…”. Anschließend wählen wir “New Directory” und dann “New Project”. Wir geben dem Projekt den Namen youtube-workflow und klicken auf “Create Project”.

Virtuelle Python-Umgebung erstellen

In der Kommandozeile (Achtung: Nicht in der R-Konsole) führen wir den folgenden Befehl aus, der die virtuelle Python-Umgebung anlegt. Um das Terminal zu öffnen, wechseln wir links oben im RStudio-Fenster auf den Tab “Terminal”. Dort sollte nun ein Terminal geöffnet sein. Falls nicht, klicken wir auf das “Terminal” und wählen “New Terminal”. Wenn das Terminal geöffnet ist, sollte in der Eingabemaske Ihr Nutzername und der Pfad des Projektordners angezeigt werden (z.B: meinname@rstudio:~/youtube-workflow$). Nun können wir den folgenden Befehl ausführen:

Terminal
python3 -m venv python-env

Im Files-Tab rechts unten sollte nun ein neuer Ordner namens python-env erscheinen. Wenn dies geklappt hat, aktivieren wir die virtuelle Umgebung mit dem folgenden Befehl:

Terminal
source python-env/bin/activate

Nun sollte der Name der virtuellen Umgebung in Klammern vor dem Prompt im Terminal erscheinen, z.B. (python-env) meinname@rstudio:~/youtube-workflow$. Wenn das Terminal neu gestartet wird, wird auch die virtuelle Umgebung neu gestartet. Dies erkennen wir daran, dass der Name der Umgebung nicht mehr vor dem Prompt erscheint. In diesem Fall müssen wir die virtuelle Umgebung erneut mit dem obigen Befehl aktivieren.

Pakete installieren

Anschließend installieren wir die benötigten Python-Pakete. Die Installation muss nur einmal durchgeführt werden, die Pakete stehen anschließend in der virtuellen Umgebung zur Verfügung.

Wenn Sie Videos herunterladen möchten, installieren Sie yt-dlp mit dem folgenden Befehl:

Terminal
pip install yt-dlp

Wenn Sie Videos auch transkribieren möchten, installieren Sie außerdem whisper-ctranslate2 mit dem folgenden Befehl:

Terminal
pip install whisper-ctranslate2

Ausgewählte YouTube-Videos herunterladen

Um ausgewählte Videos herunterzuladen, speichern wir die Video-URLs in einer Textdatei, wobei jede URL in einer neuen Zeile stehen sollte. Zu Testzwecken können wir eine Datei erstellen über “File”, “dann”New File” und “Text File”. Wir fügen einige YouTube-Video-URLs in die Datei ein und speichern sie als video-urls.txt im Projektordner.

Anschließend können wir die Videos mit dem folgenden Befehl herunterladen (im Terminal, nicht in der R-Konsole). Stellen Sie sicher, dass die virtuelle Umgebung aktiviert ist, bevor Sie den Befehl ausführen. Der Parameter "video-urls.txt" sollte durch den Namen der Datei ersetzt werden, die Ihre Video-URLs enthält.

Terminal
yt-dlp -w --write-info-json --write-thumbnail --no-write-playlist-metafiles \
         -c -o "%(id)s.%(ext)s" -P "videos" -a "video-urls.txt" \
         -f "bestvideo[ext=mp4][height<=480]+bestaudio[ext=m4a]/best[ext=mp4][height<=480]" --merge-output-format mp4 \
         --download-archive "__downloaded.txt" \
         --min-sleep-interval 30 --max-sleep-interval 60 \
         --write-comments

Dieser Befehl lädt die Videos in den Ordner videos herunter. Die Videos werden in einer Auflösung von maximal 480p heruntergeladen, um Speicherplatz zu sparen. Außerdem werden Metadaten und Kommentare der Videos gespeichert. Bereits heruntergeladene Videos werden in der Datei “__downloaded.txt” dokumentiert und bei erneutem Ausführen übersprungen, um doppelte Downloads zu vermeiden. Wenn alle Videos heruntergeladen sind, sichern Sie den Ordner videos nach Möglichkeit auch lokal, indem Sie ihn herunterladen (im Files-Tab rechts unten den Ordner markieren und auf “More” -> “Export…” klicken). Achtung: Je nach Anzahl und Länge der Videos kann der Download einige Zeit in Anspruch nehmen und viel Speicherplatz benötigen.

Wenn Sie nur Videos aus einem bestimmten Zeitraum herunterladen möchten, können Sie den Befehl wie folgt anpassen, indem Sie die gewünschten Start- und Enddaten im Format YYYYMMDD angeben:

Terminal
yt-dlp -w --write-info-json --write-thumbnail --no-write-playlist-metafiles \
         -c -o "%(id)s.%(ext)s" -P "videos" -a "video-urls.txt" \
         -f "bestvideo[ext=mp4][height<=480]+bestaudio[ext=m4a]/best[ext=mp4][height<=480]" --merge-output-format mp4 \
         --download-archive "__downloaded.txt" \
         --min-sleep-interval 30 --max-sleep-interval 60 \
         --write-comments \
         --dateafter 20220101 --datebefore 20221231

Alle Videos oder Shorts eines Kanals herunterladen

Alternativ können wir auch alle Videos oder Shorts eines Kanals herunterladen:

Terminal
yt-dlp -w --write-info-json --write-thumbnail --no-write-playlist-metafiles \
         -c -o "%(id)s.%(ext)s" \
         -f "bestvideo[ext=mp4][height<=480]+bestaudio[ext=m4a]/best[ext=mp4][height<=480]" \
         --merge-output-format mp4 \
         --download-archive "__downloaded.txt" \
         --min-sleep-interval 30 --max-sleep-interval 60 \
         --write-comments \
         -P "videos" \
         https://www.youtube.com/@johnmayer/shorts

Als letzten Parameter geben wir die URL des Kanals oder der Playlist an, die heruntergeladen werden soll. Wenn wir Shorts herunterladen möchten, fügen wir /shorts an die Kanal-URL an. Wenn wir nur die Videos eines Kanals herunterladen möchten, fügen wir statt /shorts /videos an die Kanal-URL an. Auch hier können wir, wie oben beschrieben, die Datumsfilter --dateafter und --datebefore verwenden, um nur Videos oder Shorts aus einem bestimmten Zeitraum herunterzuladen.

Nur Metadaten (ohne Videos) herunterladen

Wenn Sie zunächst nur die Metadaten (z.B. die Titel der Videos) aller Videos eines Kanals herunterladen möchten, um eine Vorselektion der Inhalte zu treffen, bevor Sie ausgewählte Videos herunterladen, können Sie den folgenden Befehl verwenden:

Terminal
yt-dlp -w --write-info-json --skip-download \
         -c -o "%(id)s.%(ext)s" \
         --min-sleep-interval 30 --max-sleep-interval 60 \
         -P "metadata" \
         https://www.youtube.com/@johnmayer/shorts

Diese können wir anschließend in R einlesen und analysieren, um eine Vorauswahl der Videos zu treffen, die wir herunterladen möchten.

metadata <- 
  map(
    list.files("metadata", pattern = "*.info.json", full.names = TRUE),
    ~ fromJSON(.x) |> 
      (\(x) tibble(
        id = x$id,
        title = x$title,
        description = x$description,
        view_count = x$view_count,
      ))()
  ) |> 
  list_rbind() |> 
  filter(!is.na(view_count)) |> 
  select(-view_count)

TikTok Videos herunterladen

Um TikTok Videos herunterzuladen, können wir ebenfalls yt-dlp verwenden. Das Vorgehen ist dabei sehr ähnlich, wobei wir bei TikTok leider nicht direkt die Kommentare laden können (dazu nutzen Sie bitte die Browser-Erweiterung Zeeschuimer). Der folgende Befehl lädt alle Videos eines TikTok-Kanals herunter (alterativ können Sie wie oben beschrieben auch ausgewählte Video-URLs in einer Textdatei speichern und diese mit dem Parameter -a angeben):

Terminal
yt-dlp -w --write-info-json --write-thumbnail --no-write-playlist-metafiles \
         -c -o "%(id)s.%(ext)s" \
         --download-archive "__downloaded.txt" \
         --min-sleep-interval 30 --max-sleep-interval 60 \
         -P "videos" \
         https://www.tiktok.com/@tagesschau/

Kommentare in R einlesen

Nachdem die Videos und Metadaten heruntergeladen wurden, können wir die Kommentare in R einlesen und analysieren. Dazu verwenden wir das folgende R-Skript:

library(tidyverse)
library(jsonlite)

comments <-
  map(
    list.files("videos", pattern = "*.info.json", full.names = TRUE),
    ~ fromJSON(.x)$comments |>
      as_tibble() |>
      mutate(
        id = fromJSON(.x)$id,
        title = fromJSON(.x)$title,
        description = fromJSON(.x)$description
      )
  ) |> 
  list_rbind() |>
  mutate(datetime = as_datetime(timestamp))

Zur Sicherheit speichern wir die Kommentare als CSV-Datei:

write_csv(comments, "comments.csv")

Sichern Sie diese Datei nach Möglichkeit auch lokal, indem Sie sie herunterladen (im Files-Tab rechts unten die Datei markieren und auf “More” -> “Export…” klicken).

Videos transkribieren

Um die heruntergeladenen Videos zu transkribieren, verwenden wir den folgenden Befehl im Terminal (nicht in der R-Konsole). Stellen Sie sicher, dass die virtuelle Umgebung aktiviert ist, bevor Sie den Befehl ausführen.

Terminal
whisper-ctranslate2 --model small videos/*.mp4 --output_format tsv --output_dir "transcripts"

Transkripte in R einlesen und Segmente erstellen

Nachdem die Videos transkribiert wurden, können wir die Transkripte in R einlesen und in 60-Sekunden-Segmente unterteilen. Die Segmentlänge können wir beliebig anpassen. Dazu verwenden wir das folgende R-Skript:

transcripts <-
  map(
    list.files("transcripts", pattern = "*.tsv", full.names = TRUE),
    ~ read_tsv(.x, col_types = "iic")) |> 
  set_names(list.files("transcripts", pattern = "*.tsv", full.names = FALSE) |> str_remove(".tsv")) |>
  list_rbind(names_to = "id") |> 
  group_by(id) |> 
  mutate(interval = floor(start / 60000)) |> # hier kann die Segmentlänge angepasst werden (60000 ms = 60 Sekunden)
  group_by(id, interval) |>
  reframe(
    start_time = min(start) + 3000, # Sicherheitsabstand von 3 Sekunden
    end_time = max(end) - 3000,
    text = str_c(text, collapse = " ")
  ) |> 
  ungroup() |> 
  distinct()

Wenn wir keine Segmente erstellen wollen, sondern jeweils das gesammte Videotranskript auf einmal codieren möchten, können wir folgende vereinfachte Version verwenden:

transcripts <-
  map(
    list.files("transcripts", pattern = "*.tsv", full.names = TRUE),
    ~ read_tsv(.x, col_types = "iic")
  ) |>
  set_names(
    list.files("transcripts", pattern = "*.tsv", full.names = FALSE) |>
      str_remove(".tsv")
  ) |>
  list_rbind(names_to = "id") |>
  group_by(id) |>
  reframe(
    start_time = min(start),
    end_time = max(end),
    text = str_c(text, collapse = " ")
  ) |>
  ungroup() |>
  distinct()

Zur Sicherheit speichern wir die Transkripte als CSV-Datei:

write_csv(transcripts, "transcripts.csv")

Sichern Sie diese Datei nach Möglichkeit auch lokal, indem Sie sie herunterladen (im Files-Tab rechts unten die Datei markieren und auf “More” -> “Export…” klicken).

Klassifizieren

Um die Kommentare oder Transkript-Segmente zu klassifizieren, kombinieren wir sie mit dem interpolate()-Befehl aus dem ellmer-Paket (siehe auch hier).

library(ellmer)

coding_task <- "Geht es in diesem Kommentar um xyz? Antworte mit TRUE oder FALSE."
# für Kommentare
tasks <- interpolate("{{coding_task}} KOMMENTAR: {{comments$text}}")
# für Transkripte
tasks <- interpolate("{{coding_task}} TRANSKRIPT: {{transcripts$text}}")

Je nach größe des Datensatzes kann die Klassifikation einige Zeit in Anspruch nehmen. Daher bietet es sich an, die Klassifikation in einzelne Batches aufzuteilen und nacheinander zu verarbeiten. So können Sie außerdem die Batches untereinander aufteilen und mehrere API-Keys gleichzeitig verwenden.

batch_size <- 100

# Ordner erstellen, falls nicht vorhanden
if (!dir.exists("batches")) dir.create("batches", recursive = TRUE)

# In Batches aufteilen und speichern
comments |> # oder transcripts
  mutate(.batch_id = rep(1:ceiling(n() / batch_size), 
                        each = batch_size, 
                        length.out = n())) |>
  group_split(.batch_id) |>
  map(~ select(.x, -.batch_id)) |>
  iwalk(~ write_csv(.x, file.path("batches", sprintf("%s_%03d.csv", "batch", .y))))

# Anschließend können die einzelnen Batch-Dateien einzeln eingelesen und dann wie oben klassifiziert werden:
batch_01 <- read_csv("batches/batch_001.csv")
# usw...