feat(video-analysis): 抖音免浏览器解析链路 + 快手支持 + 小红书加固 + 发送防重复
- 抖音(2026-10-13):删除 SSR + playwright 旧链路(douyin_ssr.py 移除, douyin_api.py 精简为下载工具),新增 douyin_web.py(x-tt-argus 网关头 + web detail 接口 + 最高码率梯/多 CDN 回退下载)与 douyin_sign.py (纯 Python a_bogus 签名,vendor 自 f2(Apache-2.0),依赖 gmssl SM3); 实测 10/10 成功率、单作品 <1s(旧浏览器链路 5-15s 且随机撞验证码) - 快手(2026-10-08):kuaishou_content.py 分享页 INIT_STATE 静态解析(无登录态), 清晰度梯最佳档 + 图集 URL 组装,入口/策略/Web 面板同步接入 - 小红书:按链接来源定国际/国内站优先序、视频 backupUrls 逐候选回退、 图片 urlDefault→infoList WB_DFT→url 三档回退 - 长视频防重复发送(2026-10-13):媒体发送带 per-call _timeout(默认 300s), 超时=结果未知不再降级重试;auto/active 两条 handler 互斥 - Web 解析封面抽帧 ffmpeg 加 CREATE_NO_WINDOW(pythonw 下不再弹 cmd 黑窗) - 依赖:+gmssl;测试:新增 test_douyin_web / test_kuaishou_parse / test_video_cleanup, 扩充 test_video_policy / test_video_sender_link / test_video_group_file / test_video_media_naming Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
a31aab00e8
commit
a789a44f5d
25 files changed
+3536
-895
No files matched your search
@@ -118,7 +118,7 @@ def short_time_code(at: Optional[float] = None) -> str:
|
||||
_JUNK_AUTHOR_IDS = {"", "0", "none", "null", "na", "nan", "undefined"}
|
||||
|
||||
#: 抓取层拿不到昵称时的兜底串(slugify 后)—— 它们等于"没有作者信息"
|
||||
_PLACEHOLDER_AUTHORS = {"未知作者", "小红书用户", "b站用户"}
|
||||
_PLACEHOLDER_AUTHORS = {"未知作者", "小红书用户", "b站用户", "快手用户"}
|
||||
|
||||
|
||||
def _clean_author_id(author_id: Optional[str]) -> str:
|
||||
@@ -174,9 +174,18 @@ def build_author_dir(
|
||||
return f"未知作者_{short_time_code(at)}"
|
||||
|
||||
|
||||
def build_work_stem(title: Optional[str]) -> str:
|
||||
"""作品名做文件名/子目录名:slugify(沿用 15 字上限),空则 `作品`"""
|
||||
return slugify(title or "", max_length=15) or "作品"
|
||||
def build_work_stem(title: Optional[str], *, source: Optional[str] = None) -> str:
|
||||
"""作品名做文件名/子目录名:slugify(沿用 15 字上限),空则 `作品_{来源短码}`
|
||||
|
||||
标题为空(或只有 `#话题`,slugify 后为空)时**不能**固定用 `作品`:同一作者的
|
||||
多个无名作品会全挤在一个名字下,只能靠 4 位**时间**短码区分,每次重解析名字都
|
||||
变,S3 key 与群文件成员名都不可复现。改用 `作品_{short_source_code(source)}`:
|
||||
同一来源稳定、不同来源不撞,与 `未知作者_{来源码}` 同一套约定。
|
||||
`source` 也没给才退回固定的 `作品`(保住旧契约)。
|
||||
"""
|
||||
return slugify(title or "", max_length=15) or (
|
||||
f"作品_{short_source_code(source)}" if source else "作品"
|
||||
)
|
||||
|
||||
|
||||
def unique_media_path(path: Path, *, at: Optional[float] = None) -> Path:
|
||||
@@ -227,7 +236,7 @@ def media_rel_dir_of(file_path: Path | str) -> str:
|
||||
return "/".join(parts[:-1]) if len(parts) > 1 else ""
|
||||
|
||||
|
||||
def parse_netscape_cookies(file_path: str) -> List[dict]:
|
||||
def parse_netscape_cookies(file_path: str | Path) -> List[dict]:
|
||||
"""解析 Netscape 格式 cookies 文件"""
|
||||
cookies = []
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
|
||||
Reference in new issue
Block a user