Blog / Entwickler-Guide
So parst und normalisierst du Social-Media-Profil-URLs (Instagram, TikTok, YouTube, X)
Creator fügen ihre Profil-Links in jeder denkbaren Form ein: mit und ohne www, aus der mobilen App mit Tracking-Parametern, als Kurzlinks, als alte twitter.com-URLs oder als Link zu einem Post statt zum Profil. Wenn dein Produkt diese Strings so speichert, wie sie ankommen, taucht derselbe Creator dreimal auf, und deine Joins laufen stillschweigend ins Leere. Dieser Leitfaden zeigt dir, wie du jede Profil-URL von Instagram, TikTok, YouTube oder X in eine Plattform, ein Handle oder eine Kanal-ID und eine kanonische URL verwandelst, mit getestetem Code in JavaScript und Python.
Warum das Normalisieren von Profil-URLs wichtig ist
Eine Profil-URL ist eine Kennung, und Kennungen funktionieren nur, wenn sie auf eine Art geschrieben werden. https://www.instagram.com/natgeo/, instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== und https://m.instagram.com/natgeo zeigen alle auf dasselbe Konto, aber als Strings sind es drei verschiedene Werte.
- Deduplizierung. Wenn sich ein Creator zweimal anmeldet oder zwei Teammitglieder denselben Influencer zu einer Kampagne hinzufügen, willst du einen Datensatz, nicht zwei.
- Joins. Dein CRM mit einem Export aus einem anderen Tool abzugleichen, funktioniert nur, wenn beide Seiten denselben Schlüssel nutzen.
- Schlechte Eingaben früh ablehnen. Ein Link zu einem Reel, einer Suchseite oder einem Login-Bildschirm ist kein Profil. Das bei der Eingabe abzufangen, ist billiger, als es später aufzuräumen.
- APIs aufrufen. Offizielle APIs wollen ein Handle oder eine ID, keine URL mit angehängten Tracking-Parametern.
Das Ziel ist eine kleine, strenge Funktion: Sie akzeptiert, was Nutzer wirklich einfügen, liefert für echte Profil-URLs ein normalisiertes Ergebnis und für alles andere null.
Die URL-Formen, die dir begegnen
Die Formen unten wurden im Oktober 2026 an den Live-Seiten geprüft. Plattformen ändern ihr Routing ohne Ankündigung, betrachte das also als Momentaufnahme und halte deine Testfälle aktuell.
- Profile liegen unter
https://www.instagram.com/<handle>/. Die Hostsinstagram.comohne Präfix undm.instagram.comleiten aufwww.instagram.comweiter. - Aus der App geteilte Links haben oft einen Parameter
?igsh=. Entferne den ganzen Query-String und das Fragment. - Posts sind
/p/<code>/, Reels sind/reel/<code>/und Storys sind/stories/<handle>/<id>/. Keins davon ist eine Profil-URL, auch wenn das Handle im Pfad steht. - App-Routen wie
/explore/,/accounts/und/direct/teilen sich das erste Pfadsegment mit Handles, du brauchst also eine Liste reservierter Wörter. Die Liste im Code unten ist nicht vollständig.
TikTok
- Profile sind
https://www.tiktok.com/@<handle>. Der Hosttiktok.comohne Präfix leitet aufwwwweiter. - Videos sind
/@<handle>/video/<id>. Das Handle ist der Autor, aber die URL ist ein Video-Link, deshalb lehnt der Parser unten sie ab. - Kurzlinks auf
vm.tiktok.com,vt.tiktok.comundwww.tiktok.com/t/<code>/enthalten gar kein Handle. Du musst der HTTP-Weiterleitung folgen, um zu sehen, wohin sie führen, und dann die finale URL parsen. - Ein Pfad ohne
@(zum Beispieltiktok.com/khaby.lame) ist keine Profil-URL.
YouTube
- Handle-URLs:
https://www.youtube.com/@<handle>. Laut YouTube-Hilfe wird ein Handle automatisch zur URL des Kanals. - Kanal-ID-URLs:
https://www.youtube.com/channel/UC.... Das ist die stabile Form. Kanal-IDs haben in der Praxis 24 Zeichen und beginnen mitUC. Google dokumentiert das Format nicht, die Prüfung unten ist also eine Plausibilitätsgrenze, keine Regel. - Alte Formen:
/user/<username>und/c/<customname>. Beide funktionieren heute noch, und laut Hilfe funktionieren alte personalisierte URLs weiter. Keine der beiden verrät dir die Kanal-ID ohne Abfrage. - Ein nacktes
youtube.com/<name>funktioniert bei manchen Kanälen ebenfalls noch, teilt sich aber den Namensraum mit Seitenrouten, deshalb akzeptiert der Parser unten es nicht. - Tabs wie
/@handle/videosund/channel/UC.../featuredgehören zum Kanal und können sicher akzeptiert werden. m.youtube.comleitet aufwww.youtube.comweiter. Video-Links (/watch?v=,/shorts/,youtu.be/<id>) sind keine Kanäle.
X (früher Twitter)
- Profile sind
https://x.com/<handle>.twitter.com,www.twitter.com,www.x.com,mobile.twitter.comundmobile.x.comleiten alle auf ein Profil aufx.comodertwitter.comweiter, behandle sie also als eine Plattform. - Posts sind
/<handle>/status/<id>. Wie TikTok-Videos nennen sie den Autor, sind aber keine Profil-URLs. - App-Routen wie
/home,/explore,/search,/settings,/notifications,/messagesund alles unter/i/sind keine Handles. - Geteilte Links hängen oft die Parameter
?s=und?t=an. Entferne sie.
Zeichenregeln für Handles je Plattform
Das Handle zu validieren, fängt Tippfehler und die meisten Nicht-Profil-Pfade ab. Nutze die Regeln der Plattformen, wo sie sie veröffentlichen, und halte die Prüfungen locker, wo nicht, damit du nie ein echtes Konto ablehnst.
| Plattform | Erlaubte Zeichen | Länge | Groß-/Kleinschreibung | Quelle |
|---|---|---|---|---|
| Buchstaben, Zahlen, Punkte, Unterstriche | 30 wird oft genannt; nicht in Instagrams Hilfeseiten gefunden | Als nicht unterscheidend behandeln; kleingeschrieben speichern | Das Instagram Help Center nennt Punkte, Zahlen und Unterstriche | |
| TikTok | Buchstaben, Zahlen, Unterstriche, Punkte; darf nicht mit einem Punkt enden | 2 bis 24 | In URLs nicht unterscheidend (@TikTok führt zu tiktok) | TikTok Support, Nutzernamen ändern |
| YouTube | Buchstaben oder Zahlen aus unterstützten Sprachen, dazu Unterstriche, Bindestriche, Punkte und lateinische Mittelpunkte (nicht am Anfang oder Ende) | 3 bis 30 für die meisten Schriften; kürzere Grenzen für Han, Hangul und einige andere Schriften | Nicht unterscheidend | YouTube-Hilfe, Richtlinien für Handles |
| X | Buchstaben, Zahlen und Unterstriche | Bis zu 15; neue Nutzernamen müssen länger als 4 sein, es gibt aber ältere kurze Handles | Nicht unterscheidend (x.com/JACK wird zu x.com/jack) | X Help Center, Regeln für Nutzernamen |
Ein Algorithmus zur Normalisierung
- Step 1Kürzen und ein Schema ergänzen
Entferne Leerzeichen und verirrte spitze Klammern. Wenn die Eingabe kein Schema hat, stell
https://voran, damitinstagram.com/natgeogeparst wird. - Step 2Mit einem echten URL-Parser parsen
Nutze
new URL()in JavaScript oderurllib.parse.urlsplitin Python, nie eine Regex über den ganzen String. Ein Parser kommt mit Ports, Benutzerinfos und kodierten Zeichen klar und verhindert, dasshttps://instagram.com@evil.com/natgeoals Instagram durchgeht. - Step 3Den Host normalisieren
Schreib ihn klein und entferne ein führendes
www.,m.odermobile.. Schlag ihn dann in einer exakten Positivliste von Hosts nach. Nutze nieincludes("instagram.com"), das auch aufinstagram.com.evil.compasst. - Step 4Query-String und Fragment entfernen
Tracking-Parameter wie
igsh,s,tundlangändern nie, auf welches Profil eine URL zeigt. - Step 5Den Pfad aufteilen und dekodieren
Teile an
/, verwirf leere Segmente (das entfernt abschließende Schrägstriche) und dekodiere jedes Segment. Behandle fehlerhafte Kodierung als ungültige Eingabe. - Step 6Nach Plattform verzweigen
Wende die Form jeder Plattform an: ein Segment bei Instagram und X, ein
@-Segment bei TikTok und@handle,channel/UC...,user/nameoderc/namebei YouTube. Lehne reservierte Wörter ab. - Step 7Handle validieren und kleinschreiben
Prüfe das Handle gegen die Zeichenregeln der Plattform und schreib es dann klein. Schreib YouTube-Kanal-IDs nicht klein: Sie unterscheiden Groß- und Kleinschreibung.
- Step 8Die kanonische URL bauen
Bau die URL aus den Teilen auf einem festen Host neu, damit jede gleichwertige Eingabe exakt denselben String ergibt.
- Step 9Kurzlinks separat behandeln
Erkenne TikTok-Kurzlinks, folge der Weiterleitung mit einer HTTP-Anfrage und schick die finale URL wieder durch den Parser. Halte das aus der reinen Funktion heraus, damit das Parsen schnell und testbar bleibt.
Der Code in JavaScript und Python
Beide Funktionen liefern für dieselbe Eingabe dieselben Objekte: { platform, handle, canonicalUrl } für Handle-URLs, { platform, channelId, canonicalUrl } für YouTube-Kanal-IDs, username oder customName für die zwei alten YouTube-Formen und null für alles, was keine Profil-URL ist. Keine braucht eine Abhängigkeit.
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 nutzt das eingebaute fetch, das Weiterleitungen folgt und die finale Adresse als res.url bereitstellt. Ruf parseProfileUrl(await resolveShortLink(link)) auf, wenn isShortLink(link) wahr ist. Ein Kurzlink kann auch zu einem Video statt zu einem Profil führen; dann liefert der Parser korrekt 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 redirectPythons \w und str.isalnum() decken Unicode-Buchstaben und -Ziffern ab, aber nicht die kombinierenden Zeichen von Schriften wie Devanagari. Deshalb akzeptiert die YouTube-Prüfung auch Zeichen aus den Unicode-Kategorien M.
Sonderfälle und Tests
Jede Zeile unten gehört zur Testsuite, die beide Implementierungen bestehen.
| Eingabe | Ausgabe | Warum |
|---|---|---|
instagram.com/NatGeo?igsh=MWQ1ZGUxMzBkMA== | instagram, natgeo, https://www.instagram.com/natgeo/ | Kein Schema, gemischte Schreibung, Teilen-Parameter |
https://m.instagram.com/nat.geo_/#bio | instagram, nat.geo_ | Mobiler Host und Fragment |
https://www.instagram.com/p/C9xYzAbCdEf/ | null | Ein Post, kein Profil |
https://www.instagram.com/stories/natgeo/1234567890/ | null | Eine Story, obwohl das Handle im Pfad steht |
tiktok.com/@Khaby.Lame/ | tiktok, khaby.lame, https://www.tiktok.com/@khaby.lame | Abschließender Schrägstrich und Schreibung |
https://www.tiktok.com/@khaby.lame/video/7106594312292453675 | null | Eine Video-URL |
https://vm.tiktok.com/ZMabc123/ | null (erst der Weiterleitung folgen) | Kurzlink ohne Handle |
https://m.youtube.com/@GoogleDevelopers/videos?app=desktop | youtube, googledevelopers | Mobiler Host, Kanal-Tab, Query |
https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw/featured | youtube, channelId UC_x5XG1OV2P6uZZ5FSM9Ttw | Die Kanal-ID behält ihre Schreibung |
https://www.youtube.com/c/GoogleDevelopers | youtube, customName GoogleDevelopers | Alte benutzerdefinierte URL; die ID braucht eine Abfrage |
https://www.youtube.com/@%E3%83%86%E3%82%B9%E3%83%88 | youtube, Handle テスト | Prozentkodiertes Unicode-Handle |
https://youtu.be/dQw4w9WgXcQ | null | Ein Video, kein Kanal |
https://mobile.twitter.com/jack?s=21&t=abc | x, jack, https://x.com/jack | Alte Domain und Teilen-Parameter |
https://x.com/i/flow/login | null | App-Route |
https://x.com/this_handle_is_too_long | null | Mehr als 15 Zeichen |
https://instagram.com.evil.com/natgeo | null | Ähnlich aussehender Host |
https://instagram.com@evil.com/natgeo | null | Trick mit Benutzerinfo; der echte Host ist evil.com |
@natgeo | null | Ein nacktes Handle hat keine Plattform |
Halte die Fälle in deinem Repository und lass sie in der CI laufen. Wenn eine Plattform eine Route oder einen Teilen-Parameter hinzufügt, ergänzt du eine Zeile, und die Korrektur liegt auf der Hand.
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")Speichere die Roheingabe und die kanonische Form
Normalisierung verliert bewusst Informationen, und deine Regeln werden mit der Zeit besser. Speichere beide Werte:
- `raw_url`: genau das, was der Nutzer oder der Import dir gegeben hat. So kannst du später einen besseren Parser erneut laufen lassen, und das zeigst du dem Nutzer, wenn er sein Profil bearbeitet.
- `platform` und `handle` oder `channel_id`: die Einzelteile, mit einem eindeutigen Index auf
(platform, handle). Das ist dein Schlüssel für Deduplizierung und Joins. - `canonical_url`: die neu gebaute URL, zur Anzeige und für Aufrufe bei der Plattform.
- `parsed_at` und eine Parser-Version: damit du Zeilen findest, die mit einer älteren Version deiner Regeln normalisiert wurden.
Handles sind nicht dauerhaft. Creator benennen Konten auf jeder Plattform um, und ein Handle, das du letztes Jahr gespeichert hast, gehört jetzt vielleicht jemand anderem. Wo eine offizielle API dir eine stabile ID gibt, etwa eine YouTube-Kanal-ID, speichere diese als langfristigen Schlüssel und behandle das Handle als Anzeigewert, den du aktualisierst. Der Begleitleitfaden dazu, wie du eine YouTube-Kanal-ID aus einem Handle bekommst, zeigt, wie das geht.
Von einem Profil zu den anderen
Sobald eine Profil-URL normalisiert ist, lautet die nächste Frage meist, welche anderen Konten zum selben Creator gehören. sociallinks.dev ist eine API, die eine Profil-URL von Instagram, TikTok, YouTube oder X nimmt und die Konten des Creators auf anderen Plattformen liefert, mit einem Confidence-Score und dem Beleg für jeden Treffer.
FAQ
Sollte ich Handles vor dem Speichern kleinschreiben?
Ja, für den Suchschlüssel. YouTube und X sagen, dass Handles nicht zwischen Groß- und Kleinschreibung unterscheiden, und TikTok führt @TikTok zum selben Konto wie @tiktok. Instagram dokumentiert keine Regel dazu, zeigt Nutzernamen aber kleingeschrieben an, Kleinschreibung ist also der sichere Standard. Behalte die ursprüngliche Schreibung in der Roh-URL, wenn du sie anzeigen willst. Schreib YouTube-Kanal-IDs nicht klein, sie unterscheiden Groß- und Kleinschreibung.
Kann ich das Handle aus einer Post- oder Video-URL herausziehen?
Bei TikTok-Videos (/@handle/video/id) und X-Posts (/handle/status/id) ist das Handle im Pfad der Autor, du kannst es also herausziehen, wenn du das willst. URLs von Instagram-Posts und Reels (/p/ und /reel/) enthalten das Handle nicht. Die Funktionen in diesem Leitfaden liefern für alle null, weil es keine Profil-Links sind.
Wie gehe ich mit vm.tiktok.com-Links um?
Kurzlinks enthalten nur einen Code. Stell eine HTTP-Anfrage, folge den Weiterleitungen und parse die finale URL. Mach das in einem separaten Schritt mit Timeout und Cache, denn es ist ein Netzwerkaufruf, und der Code kann ablaufen.
Ist twitter.com noch eine gültige Profil-URL?
Ja. Profil-URLs auf twitter.com leiten auf x.com weiter, behandle also beide Domains als Plattform X und speichere die x.com-Form als kanonisch.
Warum nicht eine große Regex nutzen?
Eine einzelne Regex über den ganzen String ist schwer zu lesen und leicht mit ähnlich aussehenden Hosts, Benutzerinfos oder kodierten Zeichen zu täuschen. Parse die URL zuerst mit der Standardbibliothek und nutze dann kleine Regexes nur für das Handle.
Wie mache ich aus einer YouTube-URL mit /c/ oder /user/ eine Kanal-ID?
URLs mit /user/<name> passen zum Filter forUsername der YouTube Data API. Für benutzerdefinierte /c/-Namen gibt es keinen API-Filter. Beide Fälle behandelt der Leitfaden dazu, wie du eine YouTube-Kanal-ID aus einem Handle bekommst.