sociallinks.dev

ব্লগ / ডেভেলপার গাইড

হ্যান্ডেল থেকে YouTube চ্যানেল ID কীভাবে পাবেন (আর উল্টো দিকেও)

ক্রিয়েটররা তাঁদের YouTube চ্যানেল শেয়ার করেন @handle হিসেবে, কিন্তু YouTube Data API-র প্রায় সবকিছুর চাবি হলো চ্যানেল ID। শুধু হ্যান্ডেল সংরক্ষণ করলে নাম বদলালেই আপনার রেকর্ড ভেঙে যায়; শুধু ID সংরক্ষণ করলে ইউজারদের চেনা নামটি দেখাতে পারবেন না। এই গাইডে দেখানো হয়েছে কীভাবে একটি API কলে হ্যান্ডেল থেকে চ্যানেল ID বের করবেন, কীভাবে একবারে ৫০টি করে ID থেকে হ্যান্ডেলে ফিরবেন, পুরোনো /user/ আর /c/ URL নিয়ে কী করবেন, আর কীভাবে ডিফল্ট কোটার মধ্যে থাকবেন।

হ্যান্ডেল, চ্যানেল ID আর পুরোনো নাম

একটি YouTube চ্যানেলে চার ধরনের পর্যন্ত আইডেন্টিফায়ার দিয়ে পৌঁছানো যায়। এর মধ্যে শুধু একটিই স্থির থাকার নিশ্চয়তা আছে।

আইডেন্টিফায়ারউদাহরণURL-এর রূপনোট
চ্যানেল IDUC_x5XG1OV2P6uZZ5FSM9Ttwyoutube.com/channel/UC...চ্যানেল চেনার জন্য YouTube যে ID ব্যবহার করে। বড়-ছোট হাতের অক্ষরে পার্থক্য করে। এটিকে প্রাইমারি কি হিসেবে ব্যবহার করুন।
হ্যান্ডেল@GoogleDevelopersyoutube.com/@GoogleDevelopersঅনন্য, বড়-ছোট হাতের অক্ষরে পার্থক্য করে না, আর মালিক YouTube Studio থেকে এটি বদলাতে পারেন।
পুরোনো ইউজারনেমGoogleDevelopersyoutube.com/user/GoogleDevelopersপুরোনো ইউজারনেম ব্যবস্থা থেকে আসা। API-র forUsername ফিল্টার দিয়ে বের করা যায়।
পুরোনো কাস্টম URLGoogleDevelopersyoutube.com/c/GoogleDevelopersপুরোনো ব্যক্তিগত URL, যেগুলো YouTube-এর মতে এখনো কাজ করে। কোনো API ফিল্টার এগুলো বের করতে পারে না।

চ্যানেল ID-কে কি হিসেবে সংরক্ষণ করুন আর হ্যান্ডেলকে দেখানোর একটি মান হিসেবে রাখুন, যা আপনি রিফ্রেশ করবেন। এই গাইডের বাকি অংশ হলো ইউজার যা-ই দিন, তা থেকে সেই ID-তে কীভাবে পৌঁছাবেন।

একটি API কি সেট আপ করুন আর কোটা বুঝে নিন

  1. Step 1একটি Google Cloud প্রজেক্ট বানান

    Google Cloud console-এ একটি প্রজেক্ট বানান বা আগের একটি বেছে নিন।

  2. Step 2YouTube Data API v3 চালু করুন

    APIs and services খুলুন, লাইব্রেরিতে YouTube Data API v3 খুঁজে প্রজেক্টের জন্য চালু করুন।

  3. Step 3একটি API কি বানান

    Credentials-এ গিয়ে একটি API কি বানান। হ্যান্ডেল বা ID দিয়ে channels.list-এর মতো পাবলিক চ্যানেল ডেটার জন্য শুধু একটি কি লাগে, OAuth লাগে না। কি-টিকে YouTube Data API-তে সীমাবদ্ধ করুন।

  4. Step 4কি-টি কোডের বাইরে রাখুন

    এটি একটি এনভায়রনমেন্ট ভেরিয়েবলে রাখুন, যেমন YOUTUBE_API_KEY, আর রানটাইমে পড়ুন। নিচের উদাহরণগুলো ঠিক তাই করে।

  5. Step 5আপনার কোটা দেখুন

    কনসোলে API-র Quotas পেজ আপনার দৈনিক বরাদ্দ আর ব্যবহার দেখায়।

যে সংখ্যাগুলো জরুরি, Google-এর ডকুমেন্টেশন থেকে:

অর্থাৎ ডিফল্ট কোটায় দিনে প্রায় ১০,০০০টি হ্যান্ডেল খোঁজা যায়, আর ID খোঁজা তার ৫০ গুণ পর্যন্ত, কারণ একটি কলে একই ১ ইউনিটে ID দিয়ে ৫০টি চ্যানেল আনা যায়।

forHandle দিয়ে হ্যান্ডেল থেকে চ্যানেল ID

channels.list ঠিক একটি ফিল্টার নেয়। forHandle ফিল্টার একটি YouTube হ্যান্ডেল নেয়, আর রেফারেন্স বলে মানটির শুরুতে একটি @ চিহ্ন থাকতে পারে, তাই GoogleDevelopers আর @GoogleDevelopers দুটোই কাজ করে। শুধু ID দরকার হলে part=id চান; রেসপন্স ছোট হয় আর খরচ একই থাকে।

cURL

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

নিচের Node উদাহরণটি রিকোয়েস্টটিকে একটি হেল্পার listChannels-এ মুড়ে দেয়, যা গাইডের বাকি অংশে আবার ব্যবহার হয়েছে। কিছু না মিললে এটি একটি ফাঁকা তালিকা ফেরত দেয়, আর ভুল কি বা কোটা শেষ হওয়ার মতো আসল এররে থ্রো করে।

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

হ্যান্ডেলগুলো ইউজার যেভাবে লিখেছেন সেভাবেই পাঠান, URL থেকে পার্সেন্ট-এনকোডিং ডিকোড করার পর। হ্যান্ডেলে শুধু ASCII নয়, অনেক লিপির অক্ষর থাকতে পারে, আর URLSearchParams ও requests দুটোই প্যারামিটারটি আপনার হয়ে এনকোড করে।

পুরোনো ইউজারনেম আর /c/ কাস্টম URL

ক্রিয়েটরের বায়ো আর স্প্রেডশিটের পুরোনো লিংকগুলো প্রায়ই হ্যান্ডেলের আগের কোনো একটি রূপ ব্যবহার করে।

চ্যানেল ID থেকে হ্যান্ডেল, একবারে ৫০টি

উল্টো দিকে যেতে id ফিল্টার ব্যবহার হয়, যা কমা দিয়ে আলাদা করা চ্যানেল ID-র তালিকা নেয়। part=snippet চান আর snippet.customUrl পড়ুন। ওপরের channelsById ID-গুলোর ডুপ্লিকেট সরায়, ৫০টি করে ভাগে পাঠায় আর ID দিয়ে চাবি করা একটি ম্যাপ ফেরত দেয়। যে ID ম্যাপে নেই সেগুলো পাওয়া যায়নি, মুছে ফেলা হয়েছে বা API-র কাছে দৃশ্যমান নয়।

না পাওয়া, এরর আর ক্যাশিং

কিছুই না মেলা কোনো এরর নয়। forHandle আর forUsername-এর ক্ষেত্রে রেসপন্সে কোনো আইটেম থাকে না, তাই ফাঁকা বা অনুপস্থিত items অ্যারে আছে কি না দেখুন। channels.list-এর ডকুমেন্টেড এররগুলো এই:

এররের কারণHTTPকখন ঘটেকী করবেন
200 রেসপন্সে কোনো items নেই200হ্যান্ডেল বা ইউজারনেমের অস্তিত্ব নেইnull ফেরত দিন; না-পাওয়াটিকে অল্প সময়ের জন্য ক্যাশ করুন
channelNotFound404id প্যারামিটারের কোনো চ্যানেল পাওয়া যায়নিনা পাওয়া হিসেবে ধরুন, ওপরের হেল্পারগুলো যেমন করে
invalidCriteria400রিকোয়েস্টে একাধিক ফিল্টারforHandle, forUsername বা id-এর ঠিক একটি পাঠান
channelForbidden403চ্যানেলটি রিকোয়েস্ট সমর্থন করে না বা ঠিকভাবে অনুমোদিত নয়লগ করুন আর চ্যানেলটি বাদ দিন
quotaExceeded403আপনার প্রজেক্ট দৈনিক কোটা শেষ করেছেথামুন আর Pacific Time-এর মধ্যরাতের পর আবার চেষ্টা করুন, বা বেশি কোটা চান
badRequest (API কি বৈধ নয়)400কি নেই বা ভুলকনফিগারেশন ঠিক করুন; আবার চেষ্টা করবেন না

কোটা আর সময় বাঁচাতে ফলাফল ক্যাশ করুন, কিন্তু ডেটা সংরক্ষণ নিয়ে YouTube-এর নিয়ম মানুন। YouTube API Services Developer Policies (সেকশন III.E.4) অনুযায়ী অননুমোদিত API ডেটা ৩০ ক্যালেন্ডার দিনের বেশি রাখা যাবে না, এরপর মুছে ফেলতে বা রিফ্রেশ করতে হবে। ছোট হাতের হ্যান্ডেল দিয়ে চাবি করা, ৩০ দিনের অনেক কম TTL-এর একটি ক্যাশেই বেশিরভাগ সুবিধা পাওয়া যায়:

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

প্রোডাকশনে একই লজিক Redis-এ বা expires_at কলামওয়ালা একটি ডেটাবেস টেবিলে রাখুন, আর না-পাওয়াগুলোকে পাওয়াগুলোর চেয়ে ছোট TTL দিন, যাতে নতুন তৈরি হ্যান্ডেল দ্রুত ধরা পড়ে।

API কি ছাড়া বিকল্প: চ্যানেলের পেজ পড়া

পাবলিক চ্যানেল পেজের HTML-এ চ্যানেল ID থাকে। https://www.youtube.com/@GoogleDevelopers-এ রিকোয়েস্ট করলে এখন এমন একটি পেজ আসে যার <link rel="canonical"> নির্দেশ করে https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw-এ, আর /c/ ও /user/ পেজেও একই কথা খাটে, তাই অনুমান ছাড়া একটি /c/ নাম বের করার এটিই একমাত্র উপায়।

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

এটি কম ব্যবহার করুন, আর শুধু যেখানে API সাহায্য করতে পারে না। এটি অনানুষ্ঠানিক আর ভঙ্গুর:

পদ্ধতিগুলোর তুলনা

পদ্ধতিইনপুটখরচসুবিধাঅসুবিধা
forHandle সহ channels.listহ্যান্ডেল, @ সহ বা ছাড়াপ্রতি হ্যান্ডেলে ১ ইউনিটঅফিশিয়াল, হুবহু মিল, যেকোনো হ্যান্ডেলে কাজ করেপ্রতি কলে একটি হ্যান্ডেল
forUsername সহ channels.listপুরোনো /user/ নামপ্রতি নামে ১ ইউনিটঅফিশিয়াল, হুবহু মিলশুধু পুরোনো ইউজারনেমের জন্য
id সহ channels.list৫০টি পর্যন্ত চ্যানেল IDপ্রতি ৫০টি ID-তে ১ ইউনিটঅফিশিয়াল, ব্যাচে, টাইটেল আর customUrl ফেরত দেয়customUrl না থাকতে পারে
search.listমুক্ত লেখাডিফল্ট হিসেবে দিনে ১০০টি কলের আলাদা সীমানাম দিয়ে চ্যানেল খোঁজের‍্যাঙ্ক করা অনুমান, হুবহু নয়; বরাদ্দ কম
চ্যানেল পেজের canonical লিংকযেকোনো চ্যানেল URL, /c/ সহকোনো API কোটা নেইকি ছাড়াই কাজ করে; /c/ নাম বের করেঅনানুষ্ঠানিক, ভঙ্গুর, ব্লক হতে পারে, শর্তের সঙ্গে সাংঘর্ষিক হতে পারে

বেশিরভাগ প্রোডাক্টের জন্য সঠিক সেটআপ সহজ: forHandle দিয়ে হ্যান্ডেল বের করুন, চ্যানেল ID সংরক্ষণ করুন, id ফিল্টার দিয়ে ৫০টির ব্যাচে হ্যান্ডেল রিফ্রেশ করুন, আর বিরল /c/ লিংকের জন্য পেজ-বিকল্পটি একটি ফ্ল্যাগের পেছনে রাখুন। অন্য প্ল্যাটফর্মে ক্রিয়েটরের অ্যাকাউন্টও দরকার হলে, sociallinks.dev একটি API যা একটি Instagram, TikTok, YouTube বা X প্রোফাইল URL নেয় আর অন্য প্ল্যাটফর্মে ক্রিয়েটরের অ্যাকাউন্টগুলো ফেরত দেয়, প্রতিটি মিলের জন্য কনফিডেন্স স্কোর আর প্রমাণসহ।

প্রায়শই জিজ্ঞাসিত প্রশ্ন

forHandle-এ হ্যান্ডেল পাঠানোর সময় কি @ লাগবে?

না। channels.list রেফারেন্স বলে forHandle-এর মানের শুরুতে একটি @ চিহ্ন থাকতে পারে, আর উদাহরণ হিসেবে GoogleDevelopers ও @GoogleDevelopers দুটোই দেয়।

একটি হ্যান্ডেল খুঁজতে কত কোটা লাগে?

এক ইউনিট। প্রতিটি channels.list কলে ১ ইউনিট খরচ হয়, আর ডিফল্ট বরাদ্দ দিনে ১০,০০০ ইউনিট, যা Pacific Time-এর মধ্যরাতে রিসেট হয়। অবৈধ রিকোয়েস্টেও অন্তত ১ ইউনিট খরচ হয়।

একটি কলে কি কয়েকটি হ্যান্ডেল খোঁজা যায়?

না। forHandle একটিমাত্র হ্যান্ডেল নেয়। শুধু id ফিল্টার কমা দিয়ে আলাদা করা তালিকা নেয়, তাই উল্টো দিকটি (ID থেকে হ্যান্ডেল) ব্যাচে করুন আর সামনের দিকটি ক্যাশ করুন।

চ্যানেল ID কি কখনো বদলায়?

চ্যানেল ID হলো সেই আইডেন্টিফায়ার যা দিয়ে YouTube চ্যানেলটিকে অনন্যভাবে চেনে। হ্যান্ডেল বা টাইটেল বদলালে এটি বদলায় না। এ কারণেই এটি সঠিক প্রাইমারি কি। হ্যান্ডেল, টাইটেল আর কাস্টম URL সবই বদলাতে পারে।

snippet.customUrl কেন ফাঁকা আসে?

সব চ্যানেলের কাস্টম URL থাকে না, YouTube-এর নিজের রিভিশন ইতিহাসেই এটি বলা আছে। ফিল্ডটিকে ঐচ্ছিক ধরুন আর বিকল্প হিসেবে /channel/UC... URL দেখান।

ফলাফল কতদিন ক্যাশ করা যায়?

YouTube-এর Developer Policies সংরক্ষিত API ডেটা মুছে ফেলা বা রিফ্রেশের আগে ৩০ ক্যালেন্ডার দিনে সীমাবদ্ধ রাখে। পাওয়া ফলাফলের জন্য এক সপ্তাহ বা কম TTL, আর না-পাওয়ার জন্য আরও কম, আপনাকে সেই সীমার অনেক ভেতরে রাখে।

কোনো ক্রিয়েটরের অ্যাকাউন্ট খুঁজুন