المدونة / دليل المطوّرين
كيف تحلل روابط الملفات الشخصية على وسائل التواصل وتوحّدها (Instagram وTikTok وYouTube وX)
يلصق صنّاع المحتوى روابط ملفاتهم الشخصية بكل شكل يمكن تخيله: مع www ودونها، ومن تطبيق الهاتف مع معاملات تتبع، وكروابط مختصرة، وكروابط twitter.com القديمة، أو كرابط إلى منشور بدلًا من الملف الشخصي. إذا كان منتجك يحفظ هذه النصوص كما تصل، فسيظهر صانع المحتوى نفسه ثلاث مرات وستفشل عمليات الربط بين الجداول دون أن تلاحظ. يوضح هذا الدليل كيف تحوّل أي رابط ملف شخصي على Instagram أو TikTok أو YouTube أو X إلى منصة، واسم مستخدم أو معرّف قناة، ورابط موحّد واحد، مع كود مختبر بلغتي JavaScript وPython.
لماذا يهم توحيد روابط الملفات الشخصية
رابط الملف الشخصي معرّف، والمعرّفات لا تعمل إلا إذا كُتبت بطريقة واحدة. https://www.instagram.com/natgeo/ وinstagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== وhttps://m.instagram.com/natgeo كلها تشير إلى الحساب نفسه، لكنها كنصوص ثلاث قيم مختلفة.
- إزالة التكرار. حين يسجل صانع محتوى مرتين، أو يضيف عضوان في الفريق المؤثر نفسه إلى حملة، فأنت تريد سجلًا واحدًا لا اثنين.
- الربط بين الجداول. مطابقة CRM الخاص بك مع ملف مصدّر من أداة أخرى لا تنجح إلا إذا استخدم الطرفان المفتاح نفسه.
- رفض المدخلات الخاطئة مبكرًا. رابط إلى Reel أو صفحة بحث أو شاشة تسجيل دخول ليس ملفًا شخصيًا. اكتشافه عند الإدخال أرخص من تنظيفه لاحقًا.
- استدعاء واجهات API. واجهات API الرسمية تريد اسم مستخدم أو معرّفًا، لا رابطًا ملحقة به معاملات تتبع.
الهدف دالة صغيرة وصارمة: تقبل ما يلصقه المستخدمون فعلًا، وتعيد نتيجة موحّدة لروابط الملفات الشخصية الحقيقية، وتعيد null لكل ما عدا ذلك.
أشكال الروابط التي ستراها
تم التحقق من الأشكال أدناه على المواقع الحية في أكتوبر 2026. المنصات تغيّر توجيه الروابط دون إشعار، لذلك تعامل مع هذا كلقطة في وقت محدد وأبقِ حالات الاختبار محدّثة.
- الملفات الشخصية على
https://www.instagram.com/<handle>/. النطاقانinstagram.comالمجرد وm.instagram.comيعيدان التوجيه إلىwww.instagram.com. - الروابط المشاركة من التطبيق تحمل غالبًا المعامل
?igsh=. احذف سلسلة الاستعلام كاملة والجزء بعد العلامة #. - المنشورات
/p/<code>/، وReels/reel/<code>/، والقصص/stories/<handle>/<id>/. لا شيء منها رابط ملف شخصي، حتى حين يظهر اسم المستخدم في المسار. - مسارات التطبيق مثل
/explore/و/accounts/و/direct/تشترك مع أسماء المستخدمين في الجزء الأول من المسار، لذلك تحتاج إلى قائمة كلمات محجوزة. القائمة في الكود أدناه ليست شاملة.
TikTok
- الملفات الشخصية
https://www.tiktok.com/@<handle>. النطاقtiktok.comالمجرد يعيد التوجيه إلىwww. - الفيديوهات
/@<handle>/video/<id>. اسم المستخدم هو صاحب الفيديو، لكن الرابط رابط فيديو، لذلك يرفضه المحلل أدناه. - الروابط المختصرة على
vm.tiktok.comوvt.tiktok.comوwww.tiktok.com/t/<code>/لا تحمل اسم مستخدم إطلاقًا. عليك أن تتبع إعادة توجيه HTTP لترى إلى أين تقود، ثم تحلل الرابط النهائي. - المسار دون
@(مثلtiktok.com/khaby.lame) ليس رابط ملف شخصي.
YouTube
- روابط اسم المستخدم:
https://www.youtube.com/@<handle>. يقول مركز مساعدة YouTube إن اسم المستخدم يصبح تلقائيًا رابط القناة. - روابط معرّف القناة:
https://www.youtube.com/channel/UC.... هذه الصيغة الثابتة. معرّفات القنوات عمليًا من 24 حرفًا تبدأ بـUC. لا توثّق Google الصيغة، لذلك الفحص أدناه حد للتحقق المعقول لا قاعدة. - الصيغ القديمة:
/user/<username>و/c/<customname>. كلتاهما ما زالت تعمل اليوم، ويقول مركز المساعدة إن الروابط المخصصة القديمة تستمر في العمل. ولا تخبرك أي منهما بمعرّف القناة دون بحث. - الصيغة المجردة
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، تذكر صاحب المنشور لكنها ليست روابط ملفات شخصية. - مسارات التطبيق مثل
/homeو/exploreو/searchو/settingsو/notificationsو/messagesوأي شيء تحت/i/ليست أسماء مستخدمين. - الروابط المشاركة تضيف غالبًا المعاملين
?s=و?t=. احذفهما.
قواعد أحرف اسم المستخدم في كل منصة
التحقق من اسم المستخدم يكتشف الأخطاء الإملائية ومعظم المسارات التي ليست ملفات شخصية. استخدم قواعد المنصات نفسها حيث تنشرها، وأبقِ الفحوص مرنة حيث لا تنشرها، حتى لا ترفض حسابًا حقيقيًا أبدًا.
| المنصة | الأحرف المسموحة | الطول | حالة الأحرف | المصدر |
|---|---|---|---|---|
| حروف، وأرقام، ونقاط، وشرطات سفلية | 30 هو الرقم المتداول، ولم نجده في صفحات مساعدة Instagram | تعامل معه كغير مميز لحالة الأحرف، واحفظه بأحرف صغيرة | يذكر مركز مساعدة Instagram النقاط والأرقام والشرطات السفلية | |
| TikTok | حروف، وأرقام، وشرطات سفلية، ونقاط، ولا ينتهي بنقطة | من 2 إلى 24 | غير مميز لحالة الأحرف في الروابط (@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حلّل بمحلل روابط حقيقي
استخدم
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لا تغيّر أبدًا الملف الشخصي الذي يشير إليه الرابط. - Step 5قسّم المسار وفك ترميزه
قسّم عند
/، واحذف الأجزاء الفارغة (هذا يزيل الشرطات المائلة في النهاية)، وفك ترميز النسبة المئوية لكل جزء. وتعامل مع الترميز المشوّه كمدخل غير صالح. - Step 6وجّه حسب المنصة
طبّق شكل كل منصة: جزء واحد على Instagram وX، وجزء يبدأ بـ
@على TikTok، و@handleأوchannel/UC...أوuser/nameأوc/nameعلى YouTube. وارفض الكلمات المحجوزة. - Step 7تحقق من اسم المستخدم وحوّله إلى أحرف صغيرة
افحص اسم المستخدم وفق قواعد أحرف المنصة، ثم حوّله إلى أحرف صغيرة. لا تحوّل معرّفات قنوات YouTube إلى أحرف صغيرة: فهي تميز بين الأحرف الكبيرة والصغيرة.
- Step 8ابنِ الرابط الموحّد
أعد بناء الرابط من أجزائه على نطاق ثابت واحد، حتى ينتج كل مدخل مكافئ النص نفسه تمامًا.
- Step 9تعامل مع الروابط المختصرة منفصلة
اكتشف روابط TikTok المختصرة، وتتبع إعادة التوجيه بطلب HTTP، وأعد الرابط النهائي إلى المحلل. أبقِ هذا خارج الدالة الخالصة حتى يبقى التحليل سريعًا وقابلًا للاختبار.
الكود بلغتي JavaScript وPython
تعيد الدالتان الكائنات نفسها للمدخل نفسه: { platform, handle, canonicalUrl } لروابط أسماء المستخدمين، و{ platform, channelId, canonicalUrl } لمعرّفات قنوات YouTube، وusername أو customName لصيغتي YouTube القديمتين، و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. استدعِ parseProfileUrl(await resolveShortLink(link)) حين تكون isShortLink(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 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 | رابط فيديو |
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 | معرّف القناة يحتفظ بحالة أحرفه |
https://www.youtube.com/c/GoogleDevelopers | youtube، customName GoogleDevelopers | رابط مخصص قديم، يحتاج إلى بحث للحصول على المعرّف |
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. حين تضيف منصة مسارًا أو معامل مشاركة، تضيف صفًا واحدًا ويصبح الإصلاح واضحًا.
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`: الرابط المعاد بناؤه، للعرض ولاستدعاء المنصة.
- `parsed_at` ورقم إصدار المحلل: حتى تعثر على الصفوف التي وحّدها إصدار أقدم من قواعدك.
أسماء المستخدمين ليست دائمة. صنّاع المحتوى يغيرون أسماء حساباتهم على كل منصة، واسم المستخدم الذي حفظته العام الماضي قد يخص شخصًا آخر الآن. حيث تمنحك API رسمية معرّفًا ثابتًا، مثل معرّف قناة YouTube، احفظه كمفتاح طويل الأمد وتعامل مع اسم المستخدم كقيمة عرض تحدّثها. يوضح الدليل المرافق عن الحصول على معرّف قناة YouTube من اسم المستخدم الطريقة.
من ملف شخصي واحد إلى البقية
بعد توحيد رابط الملف الشخصي، السؤال التالي عادة هو: ما الحسابات الأخرى التي تخص صانع المحتوى نفسه؟ sociallinks.dev واجهة API تأخذ رابط ملف شخصي واحد على Instagram أو TikTok أو YouTube أو X وتعيد حسابات صانع المحتوى على المنصات الأخرى، مع درجة ثقة ودليل لكل تطابق.
الأسئلة الشائعة
هل أحوّل أسماء المستخدمين إلى أحرف صغيرة قبل حفظها؟
نعم لمفتاح البحث. يقول YouTube وX إن أسماء المستخدمين لا تميز بين الأحرف الكبيرة والصغيرة، وTikTok يقود @TikTok إلى الحساب نفسه مثل @tiktok. لا يوثّق Instagram قاعدة لحالة الأحرف، لكن أسماء المستخدمين تُعرض بأحرف صغيرة، لذلك التحويل إلى أحرف صغيرة هو الخيار الآمن. احتفظ بالحالة الأصلية في الرابط الخام إذا أردت عرضها. ولا تحوّل معرّفات قنوات YouTube إلى أحرف صغيرة، فهي تميز بين الأحرف.
هل يمكنني استخراج اسم المستخدم من رابط منشور أو فيديو؟
في فيديوهات TikTok (/@handle/video/id) ومنشورات X (/handle/status/id) اسم المستخدم في المسار هو صاحب المحتوى، لذلك يمكنك استخراجه إذا كان هذا ما تريده. أما روابط منشورات Instagram وReels (/p/ و/reel/) فلا تتضمن اسم المستخدم. والدوال في هذا الدليل تعيد null لها جميعًا لأنها ليست روابط ملفات شخصية.
كيف أتعامل مع روابط vm.tiktok.com؟
الروابط المختصرة لا تحتوي إلا على كود. أرسل طلب HTTP، وتتبع إعادة التوجيه، وحلّل الرابط النهائي. افعل ذلك في خطوة منفصلة مع مهلة زمنية وذاكرة مؤقتة، لأنه استدعاء شبكة وقد تنتهي صلاحية الكود.
هل ما زال twitter.com رابط ملف شخصي صالحًا؟
نعم. روابط الملفات الشخصية على twitter.com تعيد التوجيه إلى x.com، لذلك تعامل مع النطاقين كمنصة X واحفظ صيغة x.com كصيغة موحّدة.
لماذا لا أستخدم تعبيرًا نمطيًا واحدًا كبيرًا؟
التعبير النمطي الواحد على النص كاملًا صعب القراءة وسهل الخداع بالنطاقات الشبيهة أو معلومات المستخدم أو الأحرف المرمّزة. حلّل الرابط بالمكتبة القياسية أولًا، ثم استخدم تعبيرات نمطية صغيرة على اسم المستخدم فقط.
كيف أحوّل رابط YouTube من نوع /c/ أو /user/ إلى معرّف قناة؟
روابط /user/<name> تقابل فلتر forUsername في YouTube Data API. ولا يوجد فلتر في API لأسماء /c/ المخصصة. راجع الدليل عن الحصول على معرّف قناة YouTube من اسم المستخدم للحالتين.