ブログ / 開発者ガイド
SNSのプロフィールURLを解析して正規化する方法(Instagram、TikTok、YouTube、X)
クリエイターはあらゆる形でプロフィールのリンクを貼ります。wwwの有無、モバイルアプリからのトラッキングパラメータ付き、短縮リンク、古いtwitter.comのURL、プロフィールではなく投稿へのリンク。プロダクトがこうした文字列を受け取ったまま保存すると、同じクリエイターが3回登場し、結合は気づかないうちに漏れていきます。このガイドでは、Instagram、TikTok、YouTube、XのどんなプロフィールURLでも、プラットフォーム、ハンドルまたはチャンネルID、1つの正規URLに変換する方法を、テスト済みのJavaScriptとPythonのコードで紹介します。
プロフィールURLの正規化が重要な理由
プロフィールURLは識別子であり、識別子は1通りに書かれていて初めて機能します。https://www.instagram.com/natgeo/、instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA==、https://m.instagram.com/natgeoはすべて同じアカウントを指しますが、文字列としては3つの異なる値です。
- 重複排除。 クリエイターが2回登録したり、2人のチームメンバーが同じインフルエンサーをキャンペーンに追加したりしたとき、レコードは2つではなく1つにしたいものです。
- 結合。 CRMと別のツールのエクスポートを突き合わせられるのは、両方が同じキーを使っている場合だけです。
- 不正な入力を早めに弾く。 リール、検索ページ、ログイン画面へのリンクはプロフィールではありません。入力の時点で見つけるほうが、後で片付けるより安く済みます。
- APIの呼び出し。 公式APIが求めるのはハンドルかIDであり、トラッキングパラメータの付いたURLではありません。
目指すのは小さく厳格な関数です。ユーザーが実際に貼り付けるものを受け付け、本物のプロフィールURLには正規化した結果を返し、それ以外にはすべてnullを返します。
実際に目にするURLの形
以下の形は2026年10月に実際のサイトで確認したものです。プラットフォームは予告なくルーティングを変えるので、これはスナップショットとして扱い、テストケースを最新に保ちましょう。
- プロフィールは
https://www.instagram.com/<handle>/にあります。instagram.comとm.instagram.comのホストはwww.instagram.comにリダイレクトされます。 - アプリから共有されたリンクには、
?igsh=パラメータが付いていることがよくあります。クエリ文字列とフラグメントは丸ごと削除します。 - 投稿は
/p/<code>/、リールは/reel/<code>/、ストーリーズは/stories/<handle>/<id>/です。パスにハンドルが含まれていても、どれもプロフィールURLではありません。 /explore/、/accounts/、/direct/などのアプリのルートは、パスの最初のセグメントをハンドルと共有しているので、予約語のリストが必要です。下のコードのリストは網羅的ではありません。
TikTok
- プロフィールは
https://www.tiktok.com/@<handle>です。tiktok.comのホストはwwwにリダイレクトされます。 - 動画は
/@<handle>/video/<id>です。ハンドルは投稿者のものですが、URLは動画のリンクなので、下のパーサーは受け付けません。 vm.tiktok.com、vt.tiktok.com、www.tiktok.com/t/<code>/の短縮リンクにはハンドルがまったく含まれていません。リンク先を知るにはHTTPのリダイレクトをたどり、最終的なURLを解析する必要があります。@のないパス(たとえばtiktok.com/khaby.lame)はプロフィールURLではありません。
YouTube
- ハンドルのURL:
https://www.youtube.com/@<handle>。YouTubeのヘルプセンターによれば、ハンドルは自動的にチャンネルのURLになります。 - チャンネルIDのURL:
https://www.youtube.com/channel/UC...。これが安定した形です。チャンネルIDは実際にはUCで始まる24文字です。Googleはこの形式をドキュメント化していないので、下の確認はルールではなく妥当性の目安です。 - 旧形式:
/user/<username>と/c/<customname>。どちらも現在も機能し、ヘルプセンターによれば以前のカスタムURLは引き続き使えます。どちらも、検索しない限りチャンネルIDは分かりません。 youtube.com/<name>だけの形も一部のチャンネルではまだ機能しますが、サイトのルートと名前空間を共有しているので、下のパーサーは受け付けません。/@handle/videosや/channel/UC.../featuredのようなタブはチャンネルに属するので、受け付けても安全です。m.youtube.comはwww.youtube.comにリダイレクトされます。動画のリンク(/watch?v=、/shorts/、youtu.be/<id>)はチャンネルではありません。
X(旧Twitter)
- プロフィールは
https://x.com/<handle>です。twitter.com、www.twitter.com、www.x.com、mobile.twitter.com、mobile.x.comはすべてx.comかtwitter.comのプロフィールにリダイレクトされるので、1つのプラットフォームとして扱います。 - 投稿は
/<handle>/status/<id>です。TikTokの動画と同じく、投稿者の名前は含まれていますが、プロフィールURLではありません。 /home、/explore、/search、/settings、/notifications、/messagesなどのアプリのルートと、/i/以下のものはすべてハンドルではありません。- 共有されたリンクには
?s=や?t=のパラメータが付いていることがよくあります。削除しましょう。
プラットフォームごとのハンドルの文字のルール
ハンドルを検証すると、入力ミスや、プロフィールではないパスの大半を見つけられます。プラットフォームがルールを公開している場合はそれを使い、公開していない場合は本物のアカウントを決して弾かないよう、確認をゆるめにしておきます。
| プラットフォーム | 使える文字 | 長さ | 大文字と小文字 | 出典 |
|---|---|---|---|---|
| 英字、数字、ピリオド、アンダースコア | 30文字という説が広く引用されているが、Instagramのヘルプページには見当たらない | 区別しないものとして扱い、小文字で保存する | Instagramヘルプセンターがピリオド、数字、アンダースコアに言及 | |
| TikTok | 英字、数字、アンダースコア、ピリオド。ピリオドで終わることはできない | 2〜24文字 | URLでは区別しない(@TikTokはtiktokになる) | TikTokサポート、ユーザー名の変更 |
| YouTube | 対応言語の文字か数字、加えてアンダースコア、ハイフン、ピリオド、ラテン文字の中黒(先頭と末尾は不可) | 多くの文字体系で3〜30文字。漢字、ハングル、一部の文字体系ではより短い上限 | 区別しない | YouTubeヘルプ、ハンドルのガイドライン |
| X | 英字、数字、アンダースコア | 最大15文字。新しいユーザー名は4文字より長くする必要があるが、古い短いハンドルも存在する | 区別しない(x.com/JACKはx.com/jackに正規化される) | Xヘルプセンター、ユーザー名のルール |
正規化のアルゴリズム
- Step 1空白を除いてスキームを付ける
空白と紛れ込んだ山括弧を取り除きます。入力にスキームがなければ
https://を先頭に付け、instagram.com/natgeoも解析できるようにします。 - Step 2本物のURLパーサーで解析する
JavaScriptでは
new URL()、Pythonではurllib.parse.urlsplitを使い、文字列全体に正規表現をかけてはいけません。パーサーはポート、ユーザー情報、エンコードされた文字を処理し、https://instagram.com@evil.com/natgeoがInstagramとして通るのを防ぎます。 - Step 3ホストを正規化する
小文字にし、先頭の
www.、m.、mobile.を1つ取り除きます。そのうえで、ホストの許可リストと完全一致で照合します。instagram.com.evil.comにも一致してしまうincludes("instagram.com")は決して使わないでください。 - Step 4クエリ文字列とフラグメントを削除する
igsh、s、t、langなどのトラッキングパラメータで、URLが指すプロフィールが変わることはありません。 - Step 5パスを分割してデコードする
/で分割し、空のセグメントを除き(これで末尾のスラッシュが消えます)、それぞれをパーセントデコードします。不正なエンコーディングは無効な入力として扱います。 - Step 6プラットフォームごとに振り分ける
各プラットフォームの形を当てはめます。InstagramとXは1セグメント、TikTokは
@付きのセグメント、YouTubeは@handle、channel/UC...、user/name、c/nameです。予約語は弾きます。 - Step 7ハンドルを検証して小文字にする
ハンドルをプラットフォームの文字のルールで確認してから、小文字にします。YouTubeのチャンネルIDは大文字と小文字を区別するので、小文字にしてはいけません。
- Step 8正規URLを作る
1つの決まったホストで部品からURLを作り直し、同等の入力がすべてまったく同じ文字列になるようにします。
- Step 9短縮リンクは別に処理する
TikTokの短縮リンクを検出し、HTTPリクエストでリダイレクトをたどり、最終的なURLを再びパーサーに通します。解析を高速でテストしやすく保つため、この処理は純粋な関数の外に置きます。
JavaScriptとPythonのコード
どちらの関数も、同じ入力に対して同じオブジェクトを返します。ハンドルのURLには{ platform, handle, canonicalUrl }、YouTubeのチャンネルIDには{ platform, channelId, canonicalUrl }、YouTubeの2つの旧形式にはusernameかcustomName、プロフィールURLでないものにはnullです。どちらも依存パッケージは不要です。
JavaScript(Node 18以降)
const HOSTS = {
"instagram.com": "instagram",
"tiktok.com": "tiktok",
"youtube.com": "youtube",
"x.com": "x",
"twitter.com": "x",
};
const IG_RESERVED = new Set(["p", "reel", "reels", "tv", "stories", "explore", "accounts", "direct"]);
const X_RESERVED = new Set([
"home", "explore", "search", "i", "settings", "notifications", "messages",
"compose", "intent", "hashtag", "login", "signup", "logout", "tos", "privacy",
]);
const IG_HANDLE = /^[a-z0-9._]{1,30}$/;
const TIKTOK_HANDLE = /^[a-z0-9._]{2,24}$/;
const X_HANDLE = /^[a-z0-9_]{1,15}$/;
const YT_HANDLE = /^[\p{L}\p{M}\p{N}._\-·]{1,30}$/u;
const YT_CHANNEL_ID = /^UC[A-Za-z0-9_-]{22}$/;
const YT_LEGACY_NAME = /^[A-Za-z0-9._-]{1,100}$/;
export function parseProfileUrl(input) {
if (typeof input !== "string") return null;
let raw = input.trim().replace(/^<|>$/g, "");
if (!raw) return null;
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) raw = "https://" + raw;
let url;
try {
url = new URL(raw);
} catch {
return null;
}
if (url.protocol !== "https:" && url.protocol !== "http:") return null;
const host = url.hostname.toLowerCase().replace(/^(www|m|mobile)\./, "");
const platform = HOSTS[host];
if (!platform) return null;
let segments;
try {
segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
} catch {
return null; // malformed percent-encoding
}
const [first = "", second] = segments;
if (platform === "instagram") {
const handle = first.toLowerCase();
if (segments.length !== 1 || IG_RESERVED.has(handle) || !IG_HANDLE.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://www.instagram.com/${handle}/` };
}
if (platform === "tiktok") {
if (segments.length !== 1 || !first.startsWith("@")) return null;
const handle = first.slice(1).toLowerCase();
if (!TIKTOK_HANDLE.test(handle) || handle.endsWith(".")) return null;
return { platform, handle, canonicalUrl: `https://www.tiktok.com/@${handle}` };
}
if (platform === "x") {
const handle = first.toLowerCase();
if (segments.length !== 1 || X_RESERVED.has(handle) || !X_HANDLE.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://x.com/${handle}` };
}
// YouTube: /@handle[/tab], /channel/UC...[/tab], /user/name, /c/name
if (first.startsWith("@")) {
const handle = first.slice(1).toLowerCase();
if (!YT_HANDLE.test(handle) || /^[._\-·]|[._\-·]$/.test(handle)) return null;
return { platform, handle, canonicalUrl: `https://www.youtube.com/@${encodeURIComponent(handle)}` };
}
if (first === "channel" && second && YT_CHANNEL_ID.test(second)) {
return { platform, channelId: second, canonicalUrl: `https://www.youtube.com/channel/${second}` };
}
if ((first === "user" || first === "c") && second && segments.length === 2 && YT_LEGACY_NAME.test(second)) {
const key = first === "user" ? "username" : "customName";
return { platform, [key]: second, canonicalUrl: `https://www.youtube.com/${first}/${second}` };
}
return null; // videos, shorts, playlists, search and anything else
}
const SHORT_HOSTS = new Set(["vm.tiktok.com", "vt.tiktok.com"]);
export function isShortLink(input) {
try {
const url = new URL(/^https?:\/\//i.test(input) ? input : "https://" + input);
const host = url.hostname.toLowerCase();
return SHORT_HOSTS.has(host) || (host.replace(/^www\./, "") === "tiktok.com" && url.pathname.startsWith("/t/"));
} catch {
return false;
}
}
export async function resolveShortLink(input) {
const res = await fetch(/^https?:\/\//i.test(input) ? input : "https://" + input, {
redirect: "follow",
signal: AbortSignal.timeout(10_000),
});
await res.body?.cancel();
return res.url; // the final URL after every redirect
}resolveShortLinkは組み込みのfetchを使います。これはリダイレクトをたどり、最終的なアドレスをres.urlとして返します。isShortLink(link)がtrueのときはparseProfileUrl(await resolveShortLink(link))を呼び出します。短縮リンクの先がプロフィールではなく動画のこともあり、その場合パーサーは正しくnullを返します。
Python 3
import re
import unicodedata
import urllib.request
from urllib.parse import quote, unquote, urlsplit
HOSTS = {
"instagram.com": "instagram",
"tiktok.com": "tiktok",
"youtube.com": "youtube",
"x.com": "x",
"twitter.com": "x",
}
IG_RESERVED = {"p", "reel", "reels", "tv", "stories", "explore", "accounts", "direct"}
X_RESERVED = {
"home", "explore", "search", "i", "settings", "notifications", "messages",
"compose", "intent", "hashtag", "login", "signup", "logout", "tos", "privacy",
}
IG_HANDLE = re.compile(r"[a-z0-9._]{1,30}")
TIKTOK_HANDLE = re.compile(r"[a-z0-9._]{2,24}")
X_HANDLE = re.compile(r"[a-z0-9_]{1,15}")
YT_CHANNEL_ID = re.compile(r"UC[A-Za-z0-9_-]{22}")
YT_LEGACY_NAME = re.compile(r"[A-Za-z0-9._-]{1,100}")
YT_PUNCT = "._-·"
def _is_youtube_handle(handle):
if not 1 <= len(handle) <= 30 or handle[0] in YT_PUNCT or handle[-1] in YT_PUNCT:
return False
return all(
ch.isalnum() or ch in YT_PUNCT or unicodedata.category(ch).startswith("M")
for ch in handle
)
def parse_profile_url(value):
if not isinstance(value, str):
return None
raw = value.strip().strip("<>")
if not raw:
return None
if not re.match(r"^[a-z][a-z0-9+.-]*://", raw, re.I):
raw = "https://" + raw
try:
url = urlsplit(raw)
host = (url.hostname or "").lower()
except ValueError:
return None
if url.scheme.lower() not in ("http", "https"):
return None
host = re.sub(r"^(www|m|mobile)\.", "", host)
platform = HOSTS.get(host)
if not platform:
return None
try:
segments = [unquote(s, errors="strict") for s in url.path.split("/") if s]
except UnicodeDecodeError:
return None # malformed percent-encoding
first = segments[0] if segments else ""
second = segments[1] if len(segments) > 1 else None
if platform == "instagram":
handle = first.lower()
if len(segments) != 1 or handle in IG_RESERVED or not IG_HANDLE.fullmatch(handle):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.instagram.com/{handle}/"}
if platform == "tiktok":
if len(segments) != 1 or not first.startswith("@"):
return None
handle = first[1:].lower()
if not TIKTOK_HANDLE.fullmatch(handle) or handle.endswith("."):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.tiktok.com/@{handle}"}
if platform == "x":
handle = first.lower()
if len(segments) != 1 or handle in X_RESERVED or not X_HANDLE.fullmatch(handle):
return None
return {"platform": platform, "handle": handle, "canonicalUrl": f"https://x.com/{handle}"}
# YouTube: /@handle[/tab], /channel/UC...[/tab], /user/name, /c/name
if first.startswith("@"):
handle = first[1:].lower()
if not _is_youtube_handle(handle):
return None
return {"platform": platform, "handle": handle,
"canonicalUrl": f"https://www.youtube.com/@{quote(handle, safe='._-')}"}
if first == "channel" and second and YT_CHANNEL_ID.fullmatch(second):
return {"platform": platform, "channelId": second,
"canonicalUrl": f"https://www.youtube.com/channel/{second}"}
if first in ("user", "c") and second and len(segments) == 2 and YT_LEGACY_NAME.fullmatch(second):
key = "username" if first == "user" else "customName"
return {"platform": platform, key: second,
"canonicalUrl": f"https://www.youtube.com/{first}/{second}"}
return None # videos, shorts, playlists, search and anything else
SHORT_HOSTS = {"vm.tiktok.com", "vt.tiktok.com"}
def is_short_link(value):
url = urlsplit(value if re.match(r"^https?://", value, re.I) else "https://" + value)
host = (url.hostname or "").lower()
return host in SHORT_HOSTS or (
re.sub(r"^www\.", "", host) == "tiktok.com" and url.path.startswith("/t/")
)
def resolve_short_link(value, timeout=10):
if not re.match(r"^https?://", value, re.I):
value = "https://" + value
request = urllib.request.Request(value, headers={"User-Agent": "Mozilla/5.0"})
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.geturl() # the final URL after every redirectPythonの\wとstr.isalnum()はUnicodeの文字と数字に対応していますが、デーヴァナーガリーなどの文字体系で使われる結合記号には対応していません。そのため、YouTubeの確認ではUnicodeのMカテゴリの文字も受け付けています。
エッジケースとテスト
下の各行は、両方の実装が通過するテストスイートの一部です。
| 入力 | 出力 | 理由 |
|---|---|---|
instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== | instagram、natgeo、https://www.instagram.com/natgeo/ | スキームなし、大文字小文字の混在、共有パラメータ |
https://m.instagram.com/nat.geo_/#bio | instagram、nat.geo_ | モバイルのホストとフラグメント |
https://www.instagram.com/p/C9xYzAbCdEf/ | null | プロフィールではなく投稿 |
https://www.instagram.com/stories/natgeo/1234567890/ | null | パスにハンドルがあってもストーリーズ |
tiktok.com/@Khaby.Lame/ | tiktok、khaby.lame、https://www.tiktok.com/@khaby.lame | 末尾のスラッシュと大文字小文字 |
https://www.tiktok.com/@khaby.lame/video/7106594312292453675 | null | 動画のURL |
https://vm.tiktok.com/ZMabc123/ | null(先にリダイレクトをたどる) | ハンドルを含まない短縮リンク |
https://m.youtube.com/@GoogleDevelopers/videos?app=desktop | youtube、googledevelopers | モバイルのホスト、チャンネルのタブ、クエリ |
https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured | youtube、channelId UC_x5XG1OV2P6uZZ5FSM9Ttw | チャンネルIDは大文字小文字をそのまま保つ |
https://www.youtube.com/c/GoogleDevelopers | youtube、customName GoogleDevelopers | 旧形式のカスタムURL。IDを得るには検索が必要 |
https://www.youtube.com/@%E3%83%86%E3%82%B9%E3%83%88 | youtube、handle テスト | パーセントエンコードされたUnicodeのハンドル |
https://youtu.be/dQw4w9WgXcQ | null | チャンネルではなく動画 |
https://mobile.twitter.com/jack?s=21&t=abc | x、jack、https://x.com/jack | 古いドメインと共有パラメータ |
https://x.com/i/flow/login | null | アプリのルート |
https://x.com/this_handle_is_too_long | null | 15文字を超える |
https://instagram.com.evil.com/natgeo | null | よく似たホスト |
https://instagram.com@evil.com/natgeo | null | ユーザー情報を使ったトリック。本当のホストはevil.com |
@natgeo | null | ハンドルだけではプラットフォームが分からない |
テストケースはリポジトリに置き、CIで実行しましょう。プラットフォームがルートや共有パラメータを追加したら、1行追加すれば直し方は明らかです。
import assert from "node:assert/strict";
import { parseProfileUrl } from "./parse.mjs";
const cases = [
["instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA==", { platform: "instagram", handle: "natgeo", canonicalUrl: "https://www.instagram.com/natgeo/" }],
["https://www.instagram.com/reel/C9xYzAbCdEf/", null],
["https://www.instagram.com/explore/", null],
["tiktok.com/@Khaby.Lame/", { platform: "tiktok", handle: "khaby.lame", canonicalUrl: "https://www.tiktok.com/@khaby.lame" }],
["https://www.tiktok.com/@khaby.lame/video/7106594312292453675", null],
["https://vm.tiktok.com/ZMabc123/", null],
["https://m.youtube.com/@GoogleDevelopers/videos", { platform: "youtube", handle: "googledevelopers", canonicalUrl: "https://www.youtube.com/@googledevelopers" }],
["https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured", { platform: "youtube", channelId: "UC_x5XG1OV2P6uZZ5FSM9Ttw", canonicalUrl: "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw" }],
["https://www.youtube.com/user/GoogleDevelopers", { platform: "youtube", username: "GoogleDevelopers", canonicalUrl: "https://www.youtube.com/user/GoogleDevelopers" }],
["https://youtu.be/dQw4w9WgXcQ", null],
["https://twitter.com/Jack/", { platform: "x", handle: "jack", canonicalUrl: "https://x.com/jack" }],
["https://mobile.twitter.com/jack?s=21", { platform: "x", handle: "jack", canonicalUrl: "https://x.com/jack" }],
["https://x.com/jack/status/20", null],
["https://x.com/i/flow/login", null],
["https://instagram.com.evil.com/natgeo", null],
];
for (const [input, expected] of cases) {
assert.deepEqual(parseProfileUrl(input), expected, input);
}
console.log(`${cases.length} cases passed`);from parse_profile import parse_profile_url
CASES = [
("instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA==", {"platform": "instagram", "handle": "natgeo", "canonicalUrl": "https://www.instagram.com/natgeo/"}),
("https://www.instagram.com/reel/C9xYzAbCdEf/", None),
("tiktok.com/@Khaby.Lame/", {"platform": "tiktok", "handle": "khaby.lame", "canonicalUrl": "https://www.tiktok.com/@khaby.lame"}),
("https://vm.tiktok.com/ZMabc123/", None),
("https://m.youtube.com/@GoogleDevelopers/videos", {"platform": "youtube", "handle": "googledevelopers", "canonicalUrl": "https://www.youtube.com/@googledevelopers"}),
("https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured", {"platform": "youtube", "channelId": "UC_x5XG1OV2P6uZZ5FSM9Ttw", "canonicalUrl": "https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw"}),
("https://youtu.be/dQw4w9WgXcQ", None),
("https://twitter.com/Jack/", {"platform": "x", "handle": "jack", "canonicalUrl": "https://x.com/jack"}),
("https://x.com/i/flow/login", None),
("https://instagram.com.evil.com/natgeo", None),
]
for value, expected in CASES:
assert parse_profile_url(value) == expected, value
print(f"{len(CASES)} cases passed")元の入力と正規形の両方を保存する
正規化はもともと情報を落とす処理であり、ルールは時間とともに改善されていきます。両方の値を保存しましょう。
- `raw_url`:ユーザーやインポートから受け取ったそのままの値。後でより良いパーサーで処理し直せ、ユーザーがプロフィールを編集するときに表示する値にもなります。
- `platform`と`handle`または`channel_id`:部品の値で、
(platform, handle)に一意インデックスを張ります。これが重複排除と結合のキーです。 - `canonical_url`:作り直したURL。表示とプラットフォームの呼び出しに使います。
- `parsed_at`とパーサーのバージョン:古いバージョンのルールで正規化された行を見つけられるようにします。
ハンドルは永続的なものではありません。クリエイターはどのプラットフォームでもアカウント名を変えるので、昨年保存したハンドルが今は別人のものになっているかもしれません。YouTubeのチャンネルIDのように公式APIが安定したIDを提供している場合は、それを長期的なキーとして保存し、ハンドルは更新していく表示用の値として扱いましょう。その方法は関連ガイドハンドルからYouTubeのチャンネルIDを取得する方法で紹介しています。
1つのプロフィールから他のアカウントへ
プロフィールURLを正規化したら、次の問いはたいてい、同じクリエイターのほかのアカウントはどれか、です。sociallinks.devは、Instagram、TikTok、YouTube、XのプロフィールURLを1つ受け取り、そのクリエイターの他のプラットフォームのアカウントを、確信度スコアと一致ごとの根拠付きで返すAPIです。
よくある質問
ハンドルは保存する前に小文字にすべきですか?
検索用のキーについては、そうすべきです。YouTubeとXはハンドルが大文字と小文字を区別しないとしており、TikTokは@TikTokを@tiktokと同じアカウントとして扱います。Instagramは大文字と小文字のルールをドキュメント化していませんが、ユーザーネームは小文字で表示されるので、小文字にするのが安全な既定です。表示したい場合は、元の大文字小文字を元のURLに残しておきましょう。YouTubeのチャンネルIDは大文字と小文字を区別するので、小文字にしてはいけません。
投稿や動画のURLからハンドルを取り出せますか?
TikTokの動画(/@handle/video/id)とXの投稿(/handle/status/id)では、パスのハンドルは投稿者のものなので、それが目的なら取り出せます。Instagramの投稿とリールのURL(/p/と/reel/)にはハンドルが含まれていません。このガイドの関数は、どれもプロフィールのリンクではないので、すべてにnullを返します。
vm.tiktok.comのリンクはどう扱えばよいですか?
短縮リンクにはコードしか含まれていません。HTTPリクエストを送り、リダイレクトをたどって、最終的なURLを解析します。ネットワーク呼び出しであり、コードは期限切れになることもあるので、タイムアウトとキャッシュを付けた別の手順で行いましょう。
twitter.comは今も有効なプロフィールURLですか?
有効です。twitter.comのプロフィールURLはx.comにリダイレクトされるので、両方のドメインをXのプラットフォームとして扱い、x.comの形を正規形として保存します。
大きな正規表現を1つ使えばよいのでは?
文字列全体にかける1つの正規表現は読みにくく、よく似たホスト、ユーザー情報、エンコードされた文字で簡単にだまされます。まず標準ライブラリでURLを解析し、小さな正規表現はハンドルだけに使いましょう。
YouTubeの/c/や/user/のURLをチャンネルIDに変換するには?
/user/<name>のURLは、YouTube Data APIのforUsernameフィルタに対応します。/c/のカスタム名に対応するAPIフィルタはありません。両方のケースについては、ハンドルからYouTubeのチャンネルIDを取得する方法のガイドをご覧ください。