"""链接解析:把用户随手粘的东西还原成 aweme_id。 支持的输入形态: 7xxxxxxxxxxxxxxxxxx 裸 id https://v.douyin.com/xxxxxxx/ 分享短链(需要一次网络跳转) 4.53 复制打开抖音... https://v.douyin.com/xxx/ ... 整段分享口令 https://www.douyin.com/video/7xxxxxx https://www.douyin.com/note/7xxxxxx 图文 https://www.douyin.com/?modal_id=7xxxxxx https://www.iesdouyin.com/share/video/7xxxxxx/ """ from __future__ import annotations import re from typing import Optional import requests # 从一段中文分享口令里把 url 抠出来 URL_RE = re.compile(r"https?://[-\w./?=&%#:]+") SHORT_HOSTS = ("v.douyin.com", "v.iesdouyin.com") # 按优先级尝试的 id 提取规则 ID_PATTERNS = ( re.compile(r"/(?:video|note|slides|share/video|share/note)/(\d{15,25})"), re.compile(r"[?&](?:modal_id|aweme_id|item_ids|vid)=(\d{15,25})"), ) BARE_ID_RE = re.compile(r"^\d{15,25}$") class ParseError(ValueError): """输入里找不到可用的 aweme_id。""" def _first_url(text: str) -> Optional[str]: m = URL_RE.search(text) return m.group(0) if m else None def _extract_id(url: str) -> Optional[str]: for pat in ID_PATTERNS: m = pat.search(url) if m: return m.group(1) return None def resolve_short_link(url: str, session: requests.Session, timeout: float = 15.0) -> str: """跟随 v.douyin.com 短链跳转,返回落地长链。""" resp = session.get(url, timeout=timeout, allow_redirects=True) return resp.url def extract_aweme_id(raw: str, session: Optional[requests.Session] = None) -> str: """从任意输入中解析出 aweme_id,必要时联网还原短链。""" raw = raw.strip() if BARE_ID_RE.match(raw): return raw url = _first_url(raw) if not url: raise ParseError(f"输入里没找到链接也不是纯数字 id: {raw[:80]!r}") aid = _extract_id(url) if aid: return aid if any(h in url for h in SHORT_HOSTS): if session is None: session = requests.Session() session.headers["User-Agent"] = ( "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) " "AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 " "Mobile/15E148 Safari/604.1" ) final = resolve_short_link(url, session) aid = _extract_id(final) if aid: return aid raise ParseError(f"短链 {url} 跳转到 {final},但里面没有 aweme_id") raise ParseError(f"无法从 {url} 中解析出 aweme_id")