בלוג / מדריך למפתחים
איך לפרק ולנרמל כתובות פרופיל ברשתות חברתיות (Instagram, TikTok, YouTube, X)
יוצרים מדביקים את קישורי הפרופיל שלהם בכל צורה אפשרית: עם www ובלי, מהאפליקציה בנייד עם פרמטרי מעקב, כקישורים מקוצרים, ככתובות twitter.com ישנות, או כקישור לפוסט במקום לפרופיל. אם המוצר שלכם שומר את המחרוזות כפי שהן מגיעות, אותו יוצר מופיע שלוש פעמים והצירופים (joins) שלכם מחטיאים בשקט. המדריך הזה מראה איך להפוך כל כתובת פרופיל של 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פרקו עם מפרק 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אף פעם לא משנים על איזה פרופיל כתובת מצביעה. - 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\w ו-str.isalnum() ב-Python מכסים אותיות וספרות של Unicode, אבל לא את הסימנים המצטרפים שמשמשים מערכות כתב כמו דוונאגרי, ולכן בדיקת YouTube מקבלת גם תווים בקטגוריות M של Unicode.
מקרי קצה ובדיקות
כל שורה בהמשך היא חלק מחבילת הבדיקות ששני המימושים עוברים.
| קלט | פלט | למה |
|---|---|---|
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) שם המשתמש בנתיב הוא של הכותב, אז אפשר לחלץ אותו אם זה מה שאתם רוצים. כתובות של פוסטים ו-Reels ב-Instagram (/p/ ו-/reel/) לא כוללות את שם המשתמש. הפונקציות במדריך הזה מחזירות null לכולן, כי הן אינן קישורי פרופיל.
איך מטפלים בקישורי vm.tiktok.com?
קישורים מקוצרים מכילים רק קוד. שלחו בקשת HTTP, עקבו אחרי ההפניות ופרקו את הכתובת הסופית. עשו את זה בשלב נפרד עם זמן קצוב ומטמון, כי זו קריאת רשת והקוד יכול לפוג.
האם twitter.com הוא עדיין כתובת פרופיל תקפה?
כן. כתובות פרופיל של twitter.com מפנות ל-x.com, אז התייחסו לשני הדומיינים כפלטפורמת X ושמרו את צורת x.com כקנונית.
למה לא להשתמש בביטוי רגולרי אחד גדול?
ביטוי רגולרי אחד על כל המחרוזת קשה לקריאה וקל להטעות אותו עם שרתים מתחזים, פרטי משתמש או תווים מקודדים. פרקו קודם את הכתובת עם הספרייה הסטנדרטית, ואז השתמשו בביטויים רגולריים קטנים על שם המשתמש בלבד.
איך הופכים כתובת /c/ או /user/ של YouTube למזהה ערוץ?
כתובות /user/<name> ממופות למסנן forUsername של YouTube Data API. אין מסנן API לשמות מותאמים של /c/. ראו את המדריך על קבלת מזהה ערוץ YouTube משם משתמש לשני המקרים.