sociallinks.dev

Blogg / Utvecklarguide

Så får du fram ett YouTube-kanal-ID från ett handle (och tvärtom)

Kreatörer delar sin YouTube-kanal som @handle, men nästan allt i YouTube Data API utgår från kanal-ID:t. Om du bara sparar handles går dina poster sönder när någon byter namn. Om du bara sparar ID:n kan du inte visa det namn användarna känner igen. Den här guiden visar hur du slår upp ett handle till ett kanal-ID med ett enda API-anrop, hur du går från ID:n tillbaka till handles 50 åt gången, vad du gör med äldre /user/- och /c/-adresser och hur du håller dig inom standardkvoten.

Handles, kanal-ID:n och äldre namn

En YouTube-kanal kan nås via upp till fyra sorters identifierare. Bara en av dem är garanterat stabil.

IdentifierareExempelURL-formKommentar
Kanal-IDUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...Det ID som YouTube använder för att identifiera kanalen. Skiftlägeskänsligt. Använd det som primärnyckel.
Handle@GoogleDevelopersyoutube.com/@GoogleDevelopersUnikt, inte skiftlägeskänsligt, och ägaren kan ändra det i YouTube Studio.
Äldre användarnamnGoogleDevelopersyoutube.com/user/GoogleDevelopersFrån det äldre systemet med användarnamn. Går att slå upp med API:ets filter forUsername.
Äldre anpassad URLGoogleDevelopersyoutube.com/c/GoogleDevelopersÄldre personliga adresser som enligt YouTube fortsätter att fungera. Inget API-filter kan slå upp dem.

Spara kanal-ID:t som nyckel och behåll handlet som ett visningsvärde som du uppdaterar. Resten av guiden handlar om hur du kommer från det användaren gav dig till det ID:t.

Skapa en API-nyckel och förstå kvoten

  1. Step 1Skapa ett Google Cloud-projekt

    I Google Cloud-konsolen skapar du ett projekt eller väljer ett befintligt.

  2. Step 2Aktivera YouTube Data API v3

    Öppna API:er och tjänster, leta upp YouTube Data API v3 i biblioteket och aktivera det för projektet.

  3. Step 3Skapa en API-nyckel

    Under Inloggningsuppgifter skapar du en API-nyckel. Offentlig kanaldata, som channels.list via handle eller ID, kräver bara en nyckel, inte OAuth. Begränsa nyckeln till YouTube Data API.

  4. Step 4Håll nyckeln utanför koden

    Lägg den i en miljövariabel, till exempel YOUTUBE_API_KEY, och läs in den när programmet körs. Exemplen nedan gör precis så.

  5. Step 5Kontrollera din kvot

    I konsolen visar sidan Kvoter för API:et din dagliga tilldelning och förbrukning.

Siffrorna som spelar roll, enligt Googles dokumentation:

Standardkvoten räcker alltså till ungefär 10 000 handle-uppslag om dagen, och upp till 50 gånger så många ID-uppslag, eftersom ett anrop kan hämta 50 kanaler via ID för samma enda enhet.

Från handle till kanal-ID med forHandle

channels.list tar exakt ett filter. Filtret forHandle tar ett YouTube-handle, och enligt referensen kan värdet inledas med ett @, så både GoogleDevelopers och @GoogleDevelopers fungerar. Be om part=id när du bara behöver ID:t. Svaret blir mindre och kostnaden är densamma.

cURL

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

Node-exemplet nedan samlar förfrågan i en hjälpfunktion, listChannels, som resten av guiden återanvänder. Den returnerar en tom lista när inget matchar och kastar ett fel vid verkliga problem, som en felaktig nyckel eller slut på kvoten.

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

Skicka handles så som användaren skrev dem, efter att du avkodat eventuell procentkodning från URL:en. Handles kan innehålla bokstäver från många skriftsystem, inte bara ASCII, och både URLSearchParams och requests kodar parametern åt dig.

Äldre användarnamn och anpassade /c/-adresser

Gamla länkar i kreatörers bios och i kalkylark använder ofta någon av formerna från tiden före handles.

Från kanal-ID till handle, 50 åt gången

Åt andra hållet använder du filtret id, som tar en kommaseparerad lista med kanal-ID:n. Be om part=snippet och läs snippet.customUrl. channelsById ovan tar bort dubbletter, skickar ID:na i grupper om 50 och returnerar en map med ID som nyckel. ID:n som inte finns i mappen hittades inte, är raderade eller syns inte för API:et.

Inga träffar, fel och cache

Ett uppslag som inte matchar något är inget fel. För forHandle och forUsername saknar svaret helt enkelt poster, så kontrollera om arrayen items är tom eller saknas. De dokumenterade felen för channels.list är dessa:

FelorsakHTTPNär det händerVad du gör
Inga items i ett 200-svar200Handlet eller användarnamnet finns inteReturnera null och cacha missen en kort tid
channelNotFound404En kanal i parametern id kan inte hittasBehandla det som ingen träff, som hjälpfunktionerna ovan gör
invalidCriteria400Mer än ett filter i förfråganSkicka exakt ett av forHandle, forUsername eller id
channelForbidden403Kanalen stöder inte förfrågan eller så är den inte korrekt auktoriseradLogga det och hoppa över kanalen
quotaExceeded403Projektet har förbrukat sin dagliga kvotStoppa och försök igen efter midnatt Pacific Time, eller ansök om mer kvot
badRequest (API key not valid)400Nyckeln saknas eller är felRätta konfigurationen, försök inte igen

Cacha resultaten för att spara kvot och svarstid, men följ YouTubes regler för lagring av data. Enligt YouTube API Services Developer Policies (avsnitt III.E.4) får du lagra icke-auktoriserad API-data i högst 30 kalenderdagar, därefter måste du radera eller uppdatera den. En cache med en TTL (livslängd) som ligger en bra bit under 30 dagar, med handlet i gemener som nyckel, ger det mesta av nyttan:

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;
}

I produktion lägger du samma logik i Redis eller i en databastabell med en kolumn expires_at, och ger missar en kortare TTL än träffar så att ett nyskapat handle plockas upp snabbt.

Reservlösningen utan API-nyckel: läs kanalsidan

Offentliga kanalsidor innehåller kanal-ID:t i sin HTML. En förfrågan till https://www.youtube.com/@GoogleDevelopers returnerar i dag en sida vars <link rel="canonical"> pekar på https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, och samma sak gäller /c/- och /user/-sidor. Det gör detta till det enda sättet att slå upp ett /c/-namn utan att gissa.

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

Använd det sparsamt och bara där API:et inte kan hjälpa. Det är inofficiellt och bräckligt:

Metoderna jämförda

MetodIndataKostnadFördelarNackdelar
channels.list med forHandleHandle, med eller utan @1 enhet per handleOfficiellt, exakt matchning, fungerar för alla handlesEtt handle per anrop
channels.list med forUsernameÄldre /user/-namn1 enhet per namnOfficiellt, exakt matchningBara för äldre användarnamn
channels.list med idUpp till 50 kanal-ID:n1 enhet per 50 ID:nOfficiellt, i batchar, returnerar titel och customUrlcustomUrl kan saknas
search.listFritextEgen gräns på 100 anrop per dag som standardHittar kanaler via namnRankade gissningar, inte exakt, liten tilldelning
Kanalsidans kanoniska länkValfri kanaladress, även /c/Ingen API-kvotFungerar utan nyckel, löser upp /c/-namnInofficiellt, bräckligt, kan blockeras, kan strida mot villkoren

För de flesta produkter är rätt upplägg enkelt: slå upp handles med forHandle, spara kanal-ID:t, uppdatera handles i batchar om 50 med filtret id, och ha reservlösningen med kanalsidan bakom en flagga för den ovanliga /c/-länken. Om du också behöver kreatörens konton på andra plattformar är sociallinks.dev ett API som tar en profiladress från Instagram, TikTok, YouTube eller X och returnerar kreatörens konton på andra plattformar, med en konfidenspoäng och bevisen för varje matchning.

Vanliga frågor

Behöver jag @ när jag skickar ett handle till forHandle?

Nej. Referensen för channels.list säger att värdet i forHandle kan inledas med ett @, och ger både GoogleDevelopers och @GoogleDevelopers som exempel.

Hur mycket kvot använder ett handle-uppslag?

En enhet. Varje anrop till channels.list kostar 1 enhet, och standardtilldelningen är 10 000 enheter per dag, som nollställs vid midnatt Pacific Time. Ogiltiga förfrågningar kostar också minst 1 enhet.

Kan jag slå upp flera handles i ett anrop?

Nej. forHandle tar ett enda handle. Bara filtret id accepterar en kommaseparerad lista, så batcha åt andra hållet (från ID:n till handles) och cacha uppslagen åt det första hållet.

Kan ett kanal-ID ändras?

Kanal-ID:t är den identifierare som YouTube använder för att unikt identifiera kanalen. Att byta handle eller titel ändrar det inte. Därför är det rätt primärnyckel. Handles, titlar och anpassade adresser kan alla ändras.

Varför är snippet.customUrl tomt?

Alla kanaler har inte en anpassad URL, vilket YouTubes egen revisionshistorik påpekar. Behandla fältet som valfritt och visa adressen /channel/UC... som reserv.

Hur länge får jag cacha resultaten?

YouTubes Developer Policies begränsar lagrad API-data till 30 kalenderdagar innan du måste radera eller uppdatera den. En TTL på en vecka eller kortare för träffar, och kortare för missar, håller dig med god marginal inom den gränsen.

Hitta en kreatörs konton