Блог / Руководство для разработчиков
Как разбирать и нормализовать URL профилей в соцсетях (Instagram, TikTok, YouTube, X)
Авторы вставляют ссылки на профили в любом мыслимом виде: с www и без, из мобильного приложения с параметрами отслеживания, как короткие ссылки, как старые URL twitter.com или как ссылку на публикацию вместо профиля. Если ваш продукт хранит эти строки как есть, один и тот же автор появляется трижды, а объединения данных незаметно промахиваются. В этом руководстве мы показываем, как превратить любой URL профиля в Instagram, TikTok, YouTube или X в платформу, никнейм или ID канала и один канонический URL, с проверенным кодом на JavaScript и Python.
Зачем нормализовать URL профилей
URL профиля это идентификатор, а идентификаторы работают, только когда записаны одним способом. https://www.instagram.com/natgeo/, instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== и https://m.instagram.com/natgeo ведут на один аккаунт, но как строки это три разных значения.
- Дедупликация. Когда автор регистрируется дважды или два человека из команды добавляют одного инфлюенсера в кампанию, вам нужна одна запись, а не две.
- Объединения. Сопоставить вашу CRM с выгрузкой из другого инструмента можно, только если обе стороны используют один ключ.
- Ранний отсев плохого ввода. Ссылка на Reels, страницу поиска или экран входа это не профиль. Поймать её при вводе дешевле, чем чистить потом.
- Вызовы API. Официальным API нужен никнейм или ID, а не URL с параметрами отслеживания.
Цель: небольшая строгая функция, которая принимает то, что пользователи реально вставляют, возвращает нормализованный результат для настоящих URL профилей и null для всего остального.
Какие виды URL вам встретятся
Форматы ниже проверены на работающих сайтах в октябре 2026 года. Платформы меняют маршрутизацию без предупреждения, поэтому считайте это снимком и поддерживайте тестовые случаи в актуальном состоянии.
- Профили находятся по адресу
https://www.instagram.com/<handle>/. Хостыinstagram.comиm.instagram.comперенаправляют наwww.instagram.com. - Ссылки, которыми делятся из приложения, часто содержат параметр
?igsh=. Отбрасывайте всю строку запроса и фрагмент. - Публикации это
/p/<code>/, Reels это/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 канала. - URL с ID канала:
https://www.youtube.com/channel/UC.... Это постоянная форма. На практике ID каналов состоят из 24 символов и начинаются сUC. 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, поэтому считайте их одной платформой. - Публикации:
/<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
Используйте
new URL()в JavaScript илиurllib.parse.urlsplitв Python, но никогда не регулярное выражение по всей строке. Парсер обрабатывает порты, данные пользователя и закодированные символы и не даётhttps://instagram.com@evil.com/natgeoпройти как Instagram. - Step 3Нормализуйте хост
Переведите его в нижний регистр и уберите один ведущий
www.,m.илиmobile.. Затем найдите его в точном списке разрешённых хостов. Никогда не используйтеincludes("instagram.com"): это совпадёт и сinstagram.com.evil.com. - Step 4Отбросьте строку запроса и фрагмент
Параметры отслеживания, такие как
igsh,s,tиlang, никогда не меняют профиль, на который ведёт URL. - Step 5Разделите и раскодируйте путь
Разделите по
/, отбросьте пустые сегменты (так уходят слеши в конце) и раскодируйте percent-encoding в каждом. Некорректную кодировку считайте недопустимым вводом. - Step 6Маршрутизируйте по платформе
Применяйте формат каждой платформы: один сегмент в Instagram и X, сегмент с
@в TikTok и@handle,channel/UC...,user/nameилиc/nameна YouTube. Отклоняйте зарезервированные слова. - Step 7Проверьте никнейм и переведите в нижний регистр
Сверьте никнейм с правилами символов платформы, затем переведите его в нижний регистр. Не меняйте регистр ID каналов YouTube: они чувствительны к регистру.
- Step 8Соберите канонический URL
Соберите URL из частей на одном фиксированном хосте, чтобы любой равнозначный ввод давал ровно одну и ту же строку.
- Step 9Обрабатывайте короткие ссылки отдельно
Распознавайте короткие ссылки TikTok, проходите по перенаправлению HTTP-запросом и снова пропускайте итоговый URL через парсер. Держите это вне чистой функции, чтобы разбор оставался быстрым и тестируемым.
Код на JavaScript и Python
Обе функции возвращают одинаковые объекты для одного и того же ввода: { platform, handle, canonicalUrl } для URL с никнеймом, { platform, channelId, canonicalUrl } для ID каналов YouTube, username или customName для двух старых форм YouTube и null для всего, что не является URL профиля. Ни одной не нужны зависимости.
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. Вызывайте parseProfileUrl(await resolveShortLink(link)), когда isShortLink(link) возвращает true. Короткая ссылка может вести и на видео, а не на профиль, и тогда парсер правильно вернёт 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 redirectВ Python \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 в percent-encoding |
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. Когда платформа добавляет маршрут или параметр шеринга, вы добавляете одну строку, и исправление становится очевидным.
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`: части URL с уникальным индексом по
(platform, handle). Это ваш ключ для дедупликации и объединений. - `canonical_url`: собранный заново URL для отображения и обращения к платформе.
- `parsed_at` и версия парсера: чтобы находить строки, нормализованные старой версией правил.
Никнеймы не вечны. Авторы переименовывают аккаунты на всех платформах, и никнейм, который вы сохранили в прошлом году, теперь может принадлежать другому человеку. Там, где официальный API даёт постоянный ID, например ID канала YouTube, храните его как долгосрочный ключ, а никнейм считайте отображаемым значением, которое вы обновляете. Как это сделать, показано в соседнем руководстве о том, как получить ID канала YouTube по никнейму.
От одного профиля к остальным
Когда URL профиля нормализован, следующий вопрос обычно такой: какие ещё аккаунты принадлежат тому же автору. sociallinks.dev это API, которое принимает один URL профиля в Instagram, TikTok, YouTube или X и возвращает аккаунты автора на других платформах с оценкой уверенности и доказательствами для каждого совпадения.
Частые вопросы
Нужно ли переводить никнеймы в нижний регистр перед сохранением?
Для ключа поиска да. YouTube и X говорят, что никнеймы не зависят от регистра, а TikTok открывает по @TikTok тот же аккаунт, что и по @tiktok. Instagram не документирует правило о регистре, но имена пользователей отображаются строчными буквами, поэтому нижний регистр это безопасный вариант по умолчанию. Если хотите показывать исходное написание, сохраните его в исходном URL. Не меняйте регистр ID каналов YouTube: они чувствительны к регистру.
Можно ли извлечь никнейм из URL публикации или видео?
Для видео в TikTok (/@handle/video/id) и публикаций в X (/handle/status/id) никнейм в пути принадлежит автору, так что его можно извлечь, если вам нужно именно это. URL публикаций и Reels в Instagram (/p/ и /reel/) никнейма не содержат. Функции в этом руководстве возвращают null для всех них, потому что это не ссылки на профили.
Что делать со ссылками vm.tiktok.com?
Короткие ссылки содержат только код. Сделайте HTTP-запрос, пройдите по перенаправлениям и разберите итоговый URL. Делайте это отдельным шагом с тайм-аутом и кешем, потому что это сетевой вызов, а срок действия кода может истечь.
twitter.com всё ещё допустимый URL профиля?
Да. URL профилей twitter.com перенаправляют на x.com, поэтому считайте оба домена платформой X и храните форму x.com как каноническую.
Почему бы не использовать одно большое регулярное выражение?
Одно регулярное выражение по всей строке трудно читать и легко обмануть похожими хостами, данными пользователя или закодированными символами. Сначала разберите URL стандартной библиотекой, а затем применяйте небольшие регулярные выражения только к никнейму.
Как превратить URL YouTube вида /c/ или /user/ в ID канала?
URL /user/<name> соответствуют фильтру forUsername в YouTube Data API. Для персональных имён /c/ фильтра в API нет. Оба случая разобраны в руководстве о том, как получить ID канала YouTube по никнейму.