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.
| Identifierare | Exempel | URL-form | Kommentar |
|---|---|---|---|
| Kanal-ID | UC_x5XG1OV2P6uZZ5FSM9Ttw | youtube.com/channel/UC... | Det ID som YouTube använder för att identifiera kanalen. Skiftlägeskänsligt. Använd det som primärnyckel. |
| Handle | @GoogleDevelopers | youtube.com/@GoogleDevelopers | Unikt, inte skiftlägeskänsligt, och ägaren kan ändra det i YouTube Studio. |
| Äldre användarnamn | GoogleDevelopers | youtube.com/user/GoogleDevelopers | Från det äldre systemet med användarnamn. Går att slå upp med API:ets filter forUsername. |
| Äldre anpassad URL | GoogleDevelopers | youtube.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
- Step 1Skapa ett Google Cloud-projekt
I Google Cloud-konsolen skapar du ett projekt eller väljer ett befintligt.
- 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.
- Step 3Skapa en API-nyckel
Under Inloggningsuppgifter skapar du en API-nyckel. Offentlig kanaldata, som
channels.listvia handle eller ID, kräver bara en nyckel, inte OAuth. Begränsa nyckeln till YouTube Data API. - 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å. - 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:
- Ett anrop till
channels.listkostar 1 enhet, oavsett vilket filter du använder. - Projekt får en standardtilldelning på 10 000 enheter per dag för alla endpoints utom
search.listochvideos.insert, som numera har egna gränser på 100 anrop per dag vardera. - Varje förfrågan kostar minst 1 enhet, även ogiltiga förfrågningar.
- Den dagliga kvoten nollställs vid midnatt Pacific Time (amerikansk västkusttid).
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 -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.
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
}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 foundSkicka 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.
- `youtube.com/user/<name>`: skicka namnet till
forUsernamemedchannelIdFromUsernameovan. Det kostar 1 enhet, precis som alla andra anrop tillchannels.list. - `youtube.com/c/<name>`: det finns inget filter i
channels.listför anpassade adresser. Många kanaler valde ett handle som matchar deras gamla anpassade namn, så att provaforHandlemed samma sträng kan fungera, men en träff bevisar inte att det är samma kanal. Bekräfta det på annat sätt innan du litar på det, till exempel genom att kontrollera titeln eller läsa sidans kanoniska länk (beskrivs nedan). - `search.list` returnerar rankade resultat för en sökfråga, inte ett exakt uppslag, och standardgränsen är 100 anrop per dag. Undvik det när du ska slå upp identifierare.
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.
- Batchstorlek. Referensen beskriver
idsom en kommaseparerad lista och sätter taket förmaxResultstill 50. Den anger ingen separat gräns för antalet ID:n, så koden skickar 50 per anrop och sättermaxResults=50så att standardvärdet 5 aldrig kan kapa en batch. - Ordning. Räkna inte med att resultaten kommer tillbaka i den ordning du skickade dem. Indexera svaret på
id. - `customUrl` är valfritt. Referensen beskriver det bara som kanalens anpassade URL, och en revisionsnotering från 2016 säger att alla kanaler inte har en. Behandla fältet som nullbart, ta bort ett inledande
@om det finns, och jämför formatet mot några kanaler du känner till innan du bygger vidare på det. - Titlar är inte handles.
snippet.titleär visningsnamnet. Det är inte unikt och kan innehålla mellanslag och emojier.
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:
| Felorsak | HTTP | När det händer | Vad du gör |
|---|---|---|---|
Inga items i ett 200-svar | 200 | Handlet eller användarnamnet finns inte | Returnera null och cacha missen en kort tid |
channelNotFound | 404 | En kanal i parametern id kan inte hittas | Behandla det som ingen träff, som hjälpfunktionerna ovan gör |
invalidCriteria | 400 | Mer än ett filter i förfrågan | Skicka exakt ett av forHandle, forUsername eller id |
channelForbidden | 403 | Kanalen stöder inte förfrågan eller så är den inte korrekt auktoriserad | Logga det och hoppa över kanalen |
quotaExceeded | 403 | Projektet har förbrukat sin dagliga kvot | Stoppa och försök igen efter midnatt Pacific Time, eller ansök om mer kvot |
badRequest (API key not valid) | 400 | Nyckeln saknas eller är fel | Rä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:
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.
// 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;
}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 NoneAnvänd det sparsamt och bara där API:et inte kan hjälpa. Det är inofficiellt och bräckligt:
- Markupen är inget dokumenterat gränssnitt och kan ändras när som helst.
- YouTube kan visa samtyckessidor, hastighetsbegränsningar eller en CAPTCHA för automatiserade förfrågningar, särskilt från IP-adresser hos molnleverantörer. I våra tester hamnade vi på en CAPTCHA-sida från Google när vi följde en
youtu.be-länk från en molnserver. - Automatiserad åtkomst till webbplatsen kan strida mot YouTubes användarvillkor. Läs dem och använd hellre det officiella API:et.
Metoderna jämförda
| Metod | Indata | Kostnad | Fördelar | Nackdelar |
|---|---|---|---|---|
channels.list med forHandle | Handle, med eller utan @ | 1 enhet per handle | Officiellt, exakt matchning, fungerar för alla handles | Ett handle per anrop |
channels.list med forUsername | Äldre /user/-namn | 1 enhet per namn | Officiellt, exakt matchning | Bara för äldre användarnamn |
channels.list med id | Upp till 50 kanal-ID:n | 1 enhet per 50 ID:n | Officiellt, i batchar, returnerar titel och customUrl | customUrl kan saknas |
search.list | Fritext | Egen gräns på 100 anrop per dag som standard | Hittar kanaler via namn | Rankade gissningar, inte exakt, liten tilldelning |
| Kanalsidans kanoniska länk | Valfri kanaladress, även /c/ | Ingen API-kvot | Fungerar utan nyckel, löser upp /c/-namn | Inofficiellt, 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.