sociallinks.dev

Blog / Guida per sviluppatori

Come ottenere l’ID di un canale YouTube da un handle (e viceversa)

I creator condividono il proprio canale YouTube come @handle, ma quasi tutto nella YouTube Data API usa come chiave l’ID del canale. Se salvi solo gli handle, un cambio di nome rompe i tuoi record; se salvi solo gli ID, non puoi mostrare agli utenti il nome che riconoscono. Questa guida mostra come convertire un handle in ID canale con una sola chiamata API, come tornare dagli ID agli handle 50 alla volta, cosa fare con i vecchi URL /user/ e /c/, e come restare entro la quota predefinita.

Handle, ID canale e nomi legacy

Un canale YouTube può essere raggiunto tramite fino a quattro tipi di identificatore. Solo uno di questi è garantito come stabile.

IdentificatoreEsempioForma dell’URLNote
ID canaleUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...L’ID che YouTube usa per identificare il canale. Distingue maiuscole e minuscole. Usalo come chiave primaria.
Handle@GoogleDevelopersyoutube.com/@GoogleDevelopersUnico, non distingue maiuscole e minuscole, e il titolare può cambiarlo in YouTube Studio.
Username legacyGoogleDevelopersyoutube.com/user/GoogleDevelopersDal vecchio sistema di username. Si può convertire con il filtro forUsername dell’API.
URL personalizzato legacyGoogleDevelopersyoutube.com/c/GoogleDevelopersVecchi URL personalizzati che, secondo YouTube, continuano a funzionare. Nessun filtro dell’API li converte.

Salva l’ID del canale come chiave e tieni l’handle come valore da mostrare, da aggiornare periodicamente. Il resto di questa guida spiega come arrivare a quell’ID partendo da qualsiasi cosa ti abbia dato l’utente.

Configura una chiave API e capisci la quota

  1. Step 1Crea un progetto Google Cloud

    Nella console Google Cloud, crea un progetto o scegline uno esistente.

  2. Step 2Attiva la YouTube Data API v3

    Apri API e servizi, trova YouTube Data API v3 nella libreria e attivala per il progetto.

  3. Step 3Crea una chiave API

    In Credenziali, crea una chiave API. Per i dati pubblici dei canali, come channels.list per handle o ID, basta una chiave, non serve OAuth. Limita la chiave alla YouTube Data API.

  4. Step 4Tieni la chiave fuori dal codice

    Mettila in una variabile d’ambiente, per esempio YOUTUBE_API_KEY, e leggila in fase di esecuzione. Gli esempi qui sotto fanno esattamente questo.

  5. Step 5Controlla la quota

    Nella console, la pagina Quote dell’API mostra l’allocazione giornaliera e l’utilizzo.

I numeri che contano, secondo la documentazione di Google:

Quindi la quota predefinita copre circa 10.000 ricerche per handle al giorno, e fino a 50 volte tante ricerche per ID, perché una chiamata può recuperare 50 canali per ID con la stessa singola unità.

Da handle a ID canale con forHandle

channels.list accetta esattamente un filtro. Il filtro forHandle accetta un handle YouTube, e la documentazione dice che il valore può essere preceduto dal simbolo @, quindi funzionano sia GoogleDevelopers sia @GoogleDevelopers. Chiedi part=id quando ti serve solo l’ID: la risposta è più piccola e il costo è lo stesso.

cURL

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

L’esempio Node qui sotto racchiude la richiesta in un’unica funzione di supporto, listChannels, che il resto della guida riutilizza. Restituisce una lista vuota quando non c’è corrispondenza e lancia un’eccezione sugli errori veri, come una chiave errata o una quota esaurita.

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

Passa gli handle come li ha scritti l’utente, dopo aver decodificato l’eventuale percent-encoding dell’URL. Gli handle possono usare lettere di molti alfabeti, non solo ASCII, e sia URLSearchParams sia requests codificano il parametro al posto tuo.

Username legacy e URL personalizzati /c/

I vecchi link nelle bio dei creator e nei fogli di calcolo usano spesso una delle forme precedenti agli handle.

Da ID canale a handle, 50 alla volta

Per la direzione opposta si usa il filtro id, che accetta una lista di ID canale separati da virgole. Richiedi part=snippet e leggi snippet.customUrl. channelsById visto sopra elimina i duplicati, invia gli ID in blocchi da 50 e restituisce una mappa con chiave l’ID. Gli ID che non compaiono nella mappa non sono stati trovati, sono stati eliminati o non sono visibili all’API.

Nessun risultato, errori e cache

Una ricerca senza corrispondenze non è un errore. Per forHandle e forUsername la risposta semplicemente non ha elementi, quindi controlla se l’array items è vuoto o assente. Gli errori documentati per channels.list sono questi:

Motivo dell’erroreHTTPQuando succedeCosa fare
Nessun items in una risposta 200200L’handle o lo username non esisteRestituisci null; metti in cache il mancato risultato per poco tempo
channelNotFound404Un canale nel parametro id non si trovaTrattalo come non trovato, come fanno le funzioni sopra
invalidCriteria400Più di un filtro nella richiestaInvia esattamente uno tra forHandle, forUsername o id
channelForbidden403Il canale non supporta la richiesta o la richiesta non è autorizzata correttamenteRegistralo nei log e salta il canale
quotaExceeded403Il progetto ha esaurito la quota giornalieraFermati e riprova dopo mezzanotte, ora del Pacifico, oppure richiedi più quota
badRequest (API key not valid)400Chiave mancante o errataCorreggi la configurazione; non riprovare

Metti in cache i risultati per risparmiare quota e latenza, ma rispetta le regole di YouTube sulla conservazione dei dati. Le YouTube API Services Developer Policies (sezione III.E.4) dicono che puoi conservare i dati API non autorizzati per non più di 30 giorni di calendario, dopo i quali devi eliminarli o aggiornarli. Una cache con un TTL ben sotto i 30 giorni, con chiave l’handle in minuscolo, offre gran parte del vantaggio:

Cache in Node.js

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 produzione, metti la stessa logica in Redis o in una tabella di database con una colonna expires_at, e dai ai mancati risultati un TTL più breve rispetto ai risultati trovati, così un handle appena creato viene rilevato in fretta.

L’alternativa senza chiave API: leggere la pagina del canale

Le pagine pubbliche dei canali includono l’ID del canale nel loro HTML. Una richiesta a https://www.youtube.com/@GoogleDevelopers oggi restituisce una pagina il cui <link rel="canonical"> punta a https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw, e lo stesso vale per le pagine /c/ e /user/: per questo è l’unico modo per convertire un nome /c/ senza tirare a indovinare.

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

Usalo con parsimonia e solo dove l’API non può aiutarti. È non ufficiale e fragile:

Confronto tra gli approcci

ApproccioInputCostoProContro
channels.list con forHandleHandle, con o senza @1 unità per handleUfficiale, corrispondenza esatta, funziona con qualsiasi handleUn handle per chiamata
channels.list con forUsernameNome legacy /user/1 unità per nomeUfficiale, corrispondenza esattaSolo per username legacy
channels.list con idFino a 50 ID canale1 unità ogni 50 IDUfficiale, a batch, restituisce titolo e customUrlcustomUrl può mancare
search.listTesto liberoLimite separato di 100 chiamate al giorno per impostazione predefinitaTrova canali per nomeIpotesi ordinate, non esatte; poche chiamate disponibili
Link canonico della pagina del canaleQualsiasi URL di canale, anche /c/Nessuna quota APIFunziona senza chiave; converte i nomi /c/Non ufficiale, fragile, può essere bloccato, può violare i termini

Per la maggior parte dei prodotti la configurazione giusta è semplice: converti gli handle con forHandle, salva l’ID del canale, aggiorna gli handle a blocchi di 50 con il filtro id e tieni l’alternativa della pagina dietro un flag per i rari link /c/. Se ti servono anche gli account del creator sulle altre piattaforme, sociallinks.dev è un’API che prende un URL di profilo Instagram, TikTok, YouTube o X e restituisce gli account del creator sulle altre piattaforme, con un punteggio di affidabilità e le prove di ogni collegamento.

Domande frequenti

Serve la @ quando passo un handle a forHandle?

No. La documentazione di channels.list dice che il valore di forHandle può essere preceduto dal simbolo @, e riporta come esempi sia GoogleDevelopers sia @GoogleDevelopers.

Quanta quota consuma una ricerca per handle?

Un’unità. Ogni chiamata channels.list costa 1 unità, e l’allocazione predefinita è di 10.000 unità al giorno, azzerate a mezzanotte, ora del Pacifico. Anche le richieste non valide costano almeno 1 unità.

Posso cercare più handle in una sola chiamata?

No. forHandle accetta un solo handle. Solo il filtro id accetta una lista separata da virgole, quindi usa i batch nella direzione inversa (da ID a handle) e la cache nella direzione diretta.

L’ID di un canale cambia mai?

L’ID del canale è l’identificatore che YouTube usa per identificare il canale in modo univoco. Cambiare l’handle o il titolo non lo modifica. Per questo è la chiave primaria giusta. Handle, titoli e URL personalizzati possono invece cambiare.

Perché snippet.customUrl torna vuoto?

Non tutti i canali hanno un URL personalizzato, come indica la cronologia delle revisioni di YouTube. Considera il campo facoltativo e, in alternativa, mostra l’URL /channel/UC....

Per quanto tempo posso tenere in cache i risultati?

Le Developer Policies di YouTube limitano i dati API conservati a 30 giorni di calendario, dopo i quali devi eliminarli o aggiornarli. Un TTL di una settimana o meno per i risultati trovati, e più breve per i mancati risultati, ti tiene ben dentro questo limite.

Trova gli account di un creator