sociallinks.dev

Blog / Entwickler-Guide

So bekommst du eine YouTube-Kanal-ID aus einem Handle (und umgekehrt)

Creator teilen ihren YouTube-Kanal als @handle, aber fast alles in der YouTube Data API läuft über die Kanal-ID. Wenn du nur Handles speicherst, macht eine Umbenennung deine Daten kaputt; wenn du nur IDs speicherst, kannst du Nutzern nicht den Namen zeigen, den sie kennen. Dieser Leitfaden zeigt dir, wie du ein Handle mit einem API-Aufruf in eine Kanal-ID auflöst, wie du von IDs zurück zu Handles kommst, 50 auf einmal, was du mit alten /user/- und /c/-URLs machst und wie du im Standard-Quota bleibst.

Handles, Kanal-IDs und alte Namen

Ein YouTube-Kanal ist über bis zu vier Arten von Kennungen erreichbar. Nur eine davon ist garantiert stabil.

KennungBeispielURL-FormHinweise
Kanal-IDUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...Die ID, mit der YouTube den Kanal identifiziert. Unterscheidet Groß- und Kleinschreibung. Nutze sie als Primärschlüssel.
Handle@GoogleDevelopersyoutube.com/@GoogleDevelopersEindeutig, unterscheidet nicht zwischen Groß- und Kleinschreibung, und der Inhaber kann es in YouTube Studio ändern.
Alter NutzernameGoogleDevelopersyoutube.com/user/GoogleDevelopersAus dem älteren Nutzernamen-System. Mit dem API-Filter forUsername auflösbar.
Alte benutzerdefinierte URLGoogleDevelopersyoutube.com/c/GoogleDevelopersÄltere personalisierte URLs, die laut YouTube weiter funktionieren. Kein API-Filter löst sie auf.

Speichere die Kanal-ID als Schlüssel und das Handle als Anzeigewert, den du aktualisierst. Im Rest des Leitfadens geht es darum, von dem, was der Nutzer dir gegeben hat, zu dieser ID zu kommen.

API-Key einrichten und das Quota verstehen

  1. Step 1Ein Google-Cloud-Projekt anlegen

    Leg in der Google Cloud Console ein Projekt an oder wähle ein bestehendes.

  2. Step 2YouTube Data API v3 aktivieren

    Öffne APIs und Dienste, such YouTube Data API v3 in der Bibliothek und aktiviere sie für das Projekt.

  3. Step 3Einen API-Key erstellen

    Erstelle unter Anmeldedaten einen API-Key. Öffentliche Kanaldaten wie channels.list nach Handle oder ID brauchen nur einen Key, kein OAuth. Beschränke den Key auf die YouTube Data API.

  4. Step 4Den Key aus dem Code heraushalten

    Leg ihn in eine Umgebungsvariable, zum Beispiel YOUTUBE_API_KEY, und lies ihn zur Laufzeit. Die Beispiele unten machen genau das.

  5. Step 5Dein Quota prüfen

    In der Console zeigt die Seite Kontingente der API deine tägliche Zuteilung und den Verbrauch.

Die Zahlen, auf die es ankommt, laut Googles Dokumentation:

Das Standard-Quota reicht also für etwa 10.000 Handle-Abfragen pro Tag und bis zu 50-mal so viele ID-Abfragen, weil ein Aufruf 50 Kanäle per ID für dieselbe eine Einheit holen kann.

Vom Handle zur Kanal-ID mit forHandle

channels.list akzeptiert genau einen Filter. Der Filter forHandle nimmt ein YouTube-Handle, und laut Referenz darf dem Wert ein @ vorangestellt sein, GoogleDevelopers und @GoogleDevelopers funktionieren also beide. Frag part=id an, wenn du nur die ID brauchst; die Antwort ist kleiner, die Kosten sind gleich.

cURL

curl -s "https://www.googleapis.com/youtube/v3/channels?part=id&forHandle=@GoogleDevelopers&key=$YOUTUBE_API_KEY"

Das Node-Beispiel unten packt die Anfrage in eine Hilfsfunktion, listChannels, die der Rest des Leitfadens wiederverwendet. Sie liefert eine leere Liste, wenn nichts passt, und wirft bei echten Fehlern wie einem falschen Key oder aufgebrauchtem Quota.

Node.js

const API = "https://www.googleapis.com/youtube/v3/channels";
async function listChannels(params) {
  const key = process.env.YOUTUBE_API_KEY;
  if (!key) throw new Error("Set YOUTUBE_API_KEY");
  const url = new URL(API);
  url.search = new URLSearchParams({ ...params, key }).toString();
  const res = await fetch(url, { signal: AbortSignal.timeout(10_000) });
  const data = await res.json();
  if (!res.ok) {
    const reason = data.error?.errors?.[0]?.reason;
    if (reason === "channelNotFound") return [];
    throw new Error(`YouTube API ${res.status} ${reason ?? ""}: ${data.error?.message}`);
  }
  return data.items ?? []; // no match: items is empty or missing
}

/** "@GoogleDevelopers" or "GoogleDevelopers" -> "UC_x5XG1OV2P6uZZ5FSM9Ttw" or null */
export async function channelIdFromHandle(handle) {
  const items = await listChannels({ part: "id", forHandle: handle.trim() });
  return items[0]?.id ?? null;
}

/** Legacy username from a youtube.com/user/<name> URL -> channel ID or null */
export async function channelIdFromUsername(username) {
  const items = await listChannels({ part: "id", forUsername: username.trim() });
  return items[0]?.id ?? null;
}

/** Any number of channel IDs -> Map(id -> { title, customUrl }) */
export async function channelsById(ids) {
  const unique = [...new Set(ids)];
  const found = new Map();
  for (let i = 0; i < unique.length; i += 50) {
    const batch = unique.slice(i, i + 50);
    const items = await listChannels({ part: "snippet", id: batch.join(","), maxResults: "50" });
    for (const ch of items) {
      found.set(ch.id, { title: ch.snippet.title, customUrl: ch.snippet.customUrl ?? null });
    }
  }
  return found; // IDs missing from the map were not found
}

Python

import os

import requests

API = "https://www.googleapis.com/youtube/v3/channels"


def list_channels(**params):
    key = os.environ.get("YOUTUBE_API_KEY")
    if not key:
        raise RuntimeError("Set YOUTUBE_API_KEY")
    response = requests.get(API, params={**params, "key": key}, timeout=10)
    data = response.json()
    if not response.ok:
        errors = data.get("error", {}).get("errors") or [{}]
        reason = errors[0].get("reason")
        if reason == "channelNotFound":
            return []
        raise RuntimeError(f"YouTube API {response.status_code} {reason}: {data.get('error', {}).get('message')}")
    return data.get("items", [])  # no match: items is empty or missing


def channel_id_from_handle(handle):
    """'@GoogleDevelopers' or 'GoogleDevelopers' -> 'UC_x5XG1OV2P6uZZ5FSM9Ttw' or None"""
    items = list_channels(part="id", forHandle=handle.strip())
    return items[0]["id"] if items else None


def channel_id_from_username(username):
    """Legacy username from a youtube.com/user/<name> URL -> channel ID or None"""
    items = list_channels(part="id", forUsername=username.strip())
    return items[0]["id"] if items else None


def channels_by_id(ids):
    """Any number of channel IDs -> {id: {"title", "customUrl"}}; missing IDs were not found"""
    unique = list(dict.fromkeys(ids))
    found = {}
    for i in range(0, len(unique), 50):
        batch = unique[i:i + 50]
        for ch in list_channels(part="snippet", id=",".join(batch), maxResults=50):
            found[ch["id"]] = {
                "title": ch["snippet"]["title"],
                "customUrl": ch["snippet"].get("customUrl"),
            }
    return found

Übergib Handles so, wie der Nutzer sie geschrieben hat, nachdem du eine eventuelle Prozent-Kodierung aus der URL dekodiert hast. Handles können Buchstaben aus vielen Schriften enthalten, nicht nur ASCII, und sowohl URLSearchParams als auch requests kodieren den Parameter für dich.

Alte Nutzernamen und /c/-URLs

Alte Links in Creator-Bios und Tabellen nutzen oft eine der Formen aus der Zeit vor Handles.

Von der Kanal-ID zum Handle, 50 auf einmal

Für die Gegenrichtung nutzt du den Filter id, der eine kommagetrennte Liste von Kanal-IDs nimmt. Frag part=snippet an und lies snippet.customUrl. channelsById von oben entfernt doppelte IDs, schickt sie in Paketen zu 50 und liefert eine Map mit der ID als Schlüssel. IDs, die nicht in der Map auftauchen, wurden nicht gefunden, gelöscht oder sind für die API nicht sichtbar.

Nicht gefunden, Fehler und Caching

Eine Abfrage ohne Treffer ist kein Fehler. Bei forHandle und forUsername hat die Antwort einfach keine Einträge, prüfe also auf ein leeres oder fehlendes items-Array. Die dokumentierten Fehler für channels.list sind diese:

FehlergrundHTTPWann er auftrittWas du tust
Keine items in einer 200-Antwort200Handle oder Nutzername existiert nichtnull zurückgeben; den Fehltreffer kurz cachen
channelNotFound404Ein Kanal im Parameter id wird nicht gefundenAls nicht gefunden behandeln, wie die Hilfsfunktionen oben
invalidCriteria400Mehr als ein Filter in der AnfrageGenau einen von forHandle, forUsername oder id schicken
channelForbidden403Der Kanal unterstützt die Anfrage nicht, oder sie ist nicht richtig autorisiertProtokollieren und den Kanal überspringen
quotaExceeded403Dein Projekt hat sein Tagesquota verbrauchtStoppen und nach Mitternacht pazifischer Zeit erneut versuchen oder mehr Quota beantragen
badRequest (API key not valid)400Fehlender oder falscher KeyKonfiguration korrigieren; nicht erneut versuchen

Cache Ergebnisse, um Quota und Latenz zu sparen, aber halte dich an die Regeln von YouTube zum Speichern von Daten. Die YouTube API Services Developer Policies (Abschnitt III.E.4) erlauben, nicht autorisierte API-Daten höchstens 30 Kalendertage zu speichern; danach musst du sie löschen oder aktualisieren. Ein Cache mit einer TTL deutlich unter 30 Tagen, mit dem kleingeschriebenen Handle als Schlüssel, bringt dir den Großteil des Nutzens:

Node.js-Cache

import { channelIdFromHandle } from "./youtube.mjs";

const TTL_MS = 7 * 24 * 60 * 60 * 1000; // refresh well inside YouTube's 30-day limit
const cache = new Map(); // lowercase handle -> { id, at }

export async function cachedChannelId(handle) {
  const key = handle.trim().replace(/^@/, "").toLowerCase();
  const hit = cache.get(key);
  if (hit && Date.now() - hit.at < TTL_MS) return hit.id;
  const id = await channelIdFromHandle(handle);
  cache.set(key, { id, at: Date.now() }); // caches misses (null) too
  return id;
}

In Produktion legst du dieselbe Logik in Redis oder eine Datenbanktabelle mit einer Spalte expires_at und gibst Fehltreffern eine kürzere TTL als Treffern, damit ein neu angelegtes Handle schnell erkannt wird.

Der Weg ohne API-Key: die Kanalseite lesen

Öffentliche Kanalseiten enthalten die Kanal-ID in ihrem HTML. Eine Anfrage an https://www.youtube.com/@GoogleDevelopers liefert derzeit eine Seite, deren <link rel="canonical"> auf https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw zeigt. Dasselbe gilt für /c/- und /user/-Seiten. Damit ist das der einzige Weg, einen /c/-Namen ohne Raten aufzulösen.

Node.js

// Unofficial fallback: reads the public channel page. Fragile, prefer the API.
export async function channelIdFromPage(handle) {
  const url = `https://www.youtube.com/@${encodeURIComponent(handle.replace(/^@/, ""))}`;
  const res = await fetch(url, {
    headers: { "Accept-Language": "en-US" },
    signal: AbortSignal.timeout(10_000),
  });
  if (!res.ok) return null;
  const html = await res.text();
  const match = html.match(/<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/(UC[A-Za-z0-9_-]{22})"/);
  return match?.[1] ?? null;
}

Python

import re
from urllib.parse import quote

import requests

CANONICAL = re.compile(r'<link rel="canonical" href="https://www\.youtube\.com/channel/(UC[A-Za-z0-9_-]{22})"')


def channel_id_from_page(handle):
    """Unofficial fallback: reads the public channel page. Fragile, prefer the API."""
    url = "https://www.youtube.com/@" + quote(handle.lstrip("@"), safe="._-")
    response = requests.get(url, headers={"Accept-Language": "en-US"}, timeout=10)
    if not response.ok:
        return None
    match = CANONICAL.search(response.text)
    return match.group(1) if match else None

Nutze das sparsam und nur dort, wo die API nicht hilft. Es ist inoffiziell und fragil:

Die Ansätze im Vergleich

AnsatzEingabeKostenVorteileNachteile
channels.list mit forHandleHandle, mit oder ohne @1 Einheit pro HandleOffiziell, exakter Treffer, funktioniert für jedes HandleEin Handle pro Aufruf
channels.list mit forUsernameAlter /user/-Name1 Einheit pro NameOffiziell, exakter TrefferNur für alte Nutzernamen
channels.list mit idBis zu 50 Kanal-IDs1 Einheit pro 50 IDsOffiziell, gebündelt, liefert Titel und customUrlcustomUrl kann fehlen
search.listFreitextEigenes Limit, standardmäßig 100 Aufrufe pro TagFindet Kanäle nach NamenGerankte Vermutungen, nicht exakt; kleines Kontingent
Canonical-Link der KanalseiteJede Kanal-URL, auch /c/Kein API-QuotaFunktioniert ohne Key; löst /c/-Namen aufInoffiziell, fragil, kann blockiert werden, kann gegen die Bedingungen verstoßen

Für die meisten Produkte ist das richtige Setup einfach: Handles mit forHandle auflösen, die Kanal-ID speichern, Handles in Paketen zu 50 mit dem Filter id aktualisieren und den Seiten-Fallback hinter einem Flag für den seltenen /c/-Link halten. Wenn du auch die Konten des Creators auf anderen Plattformen brauchst: sociallinks.dev ist eine API, die eine Profil-URL von Instagram, TikTok, YouTube oder X nimmt und die Konten des Creators auf anderen Plattformen liefert, mit einem Confidence-Score und dem Beleg für jeden Treffer.

FAQ

Brauche ich das @, wenn ich ein Handle an forHandle übergebe?

Nein. Laut Referenz zu channels.list darf dem Wert von forHandle ein @ vorangestellt sein, und sie nennt sowohl GoogleDevelopers als auch @GoogleDevelopers als Beispiele.

Wie viel Quota verbraucht eine Handle-Abfrage?

Eine Einheit. Jeder channels.list-Aufruf kostet 1 Einheit, und die Standardzuteilung liegt bei 10.000 Einheiten pro Tag, zurückgesetzt um Mitternacht pazifischer Zeit. Auch ungültige Anfragen kosten mindestens 1 Einheit.

Kann ich mehrere Handles in einem Aufruf abfragen?

Nein. forHandle nimmt ein einzelnes Handle. Nur der Filter id akzeptiert eine kommagetrennte Liste. Bündle also die Gegenrichtung (IDs zu Handles) und cache die Hinrichtung.

Ändert sich eine Kanal-ID jemals?

Die Kanal-ID ist die Kennung, mit der YouTube den Kanal eindeutig identifiziert. Eine Änderung von Handle oder Titel ändert sie nicht. Deshalb ist sie der richtige Primärschlüssel. Handles, Titel und benutzerdefinierte URLs können sich alle ändern.

Warum ist snippet.customUrl leer?

Nicht jeder Kanal hat eine benutzerdefinierte URL, wie YouTubes eigene Revisionshistorie anmerkt. Behandle das Feld als optional und zeig ersatzweise die /channel/UC...-URL.

Wie lange darf ich die Ergebnisse cachen?

Die Developer Policies von YouTube begrenzen gespeicherte API-Daten auf 30 Kalendertage, bevor du sie löschen oder aktualisieren musst. Eine TTL von einer Woche oder weniger für Treffer und kürzer für Fehltreffer hält dich deutlich darunter.

Konten eines Creators finden