一个基于 微信小程序 + FastAPI 的短视频去水印解析工具,支持抖音无水印视频提取、快手和小红书视频解析。适合个人学习、给家人使用(比如帮长辈下载无水印视频)。
- 支持 抖音官方API无水印解析(非简单的 playwm→play 替换,是真无水印)
- 支持 快手、小红书 视频解析
- 支持 第三方API兜底(bugpk.com免费API),自有解析失败时自动切换
- 微信小程序一键粘贴、解析、预览、下载保存到相册
- Python FastAPI 后端,轻量高效,可本地运行或部署到云服务器
- 完整签名算法实现(SM3 + RC4 + Base64),从开源项目翻译为 Python
- 微信小程序前端
- FastAPI Python 后端
- 阿里云 ECS + Nginx + systemd(可选部署)
微信小程序 抖音去水印 视频解析 短视频下载 无水印视频 FastAPI Python 快手解析 小红书解析 douyin watermark-removal
qushuiyin/
├─ api/ # 小程序请求封装
├─ app.js
├─ app.json
├─ app.less
├─ config.js # 小程序后端地址
├─ pages/
│ └─ home/
│ ├─ index.js
│ ├─ index.json
│ ├─ index.less
│ └─ index.wxml
├─ server/
│ ├─ app.py # FastAPI 应用入口
│ ├─ main.py # 本地启动入口
│ ├─ config.py # 环境变量配置
│ ├─ api/ # 路由层
│ ├─ parsers/ # 平台解析器
│ │ ├─ douyin.py # 抖音基础解析器(分享页HTML提取)
│ │ ├─ douyin_api.py # 抖音官方API解析器(无水印核心)
│ │ ├─ douyin_v2.py # 抖音v2解析器(备用方案)
│ │ ├─ douyin_sign.py # 抖音签名算法(SM3+RC4+Base64)
│ │ ├─ kuaishou.py # 快手解析器
│ │ └─ xiaohongshu.py # 小红书解析器
│ ├─ schemas/ # 请求与响应模型
│ ├─ services/ # 业务逻辑
│ │ ├─ parser_service.py # 解析调度服务
│ │ ├─ third_party_service.py # 第三方API兜底服务
│ │ ├─ downloader_service.py # 下载代理服务
│ │ └─ url_service.py # URL提取与解析服务
│ └─ utils/ # 工具函数
│ ├─ http_client.py # HTTP客户端(含防盗链Referer)
│ └─ text_extractor.py # 文本提取工具
├─ deploy/
│ ├─ nginx.conf
│ ├─ qushuiyin.service
│ └─ run_nohup.sh
├─ docs/
│ └─ TROUBLESHOOTING.md
├─ .env.example
└─ requirements.txt
建议使用 Python 3.11 或 3.12。
Windows:
py -3.11 -m venv .venv
.venv\Scripts\activate
pip install -U pip
pip install -r requirements.txt
Copy-Item .env.example .envmacOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -r requirements.txt
cp .env.example .envpython -m server.main启动成功后访问:
http://127.0.0.1:8001/healthhttp://127.0.0.1:8001/docs
在微信开发者工具中导入当前目录:
F:\Pyhton_Project\WeChatProject\qushuiyin
config.js 默认配置为:
export default {
baseUrl: 'http://127.0.0.1:8001',
};在微信开发者工具里,仅本地测试时开启:
- 不校验合法域名
- 不校验 TLS 版本
- 不校验 HTTPS 证书
这样小程序就可以直接请求本地 FastAPI 服务。
POST /api/parse
请求体:
{
"text": "这里放分享文案或链接"
}返回示例:
{
"success": true,
"data": {
"platform": "douyin",
"platform_label": "抖音",
"title": "视频标题",
"author": "作者昵称",
"cover_url": "https://...",
"video_url": "https://...",
"download_url": "http://127.0.0.1:8000/api/download?token=xxx",
"preview_video_url": "http://127.0.0.1:8001/api/media?token=xxx",
"raw_url": "https://v.douyin.com/xxxx/",
"resolved_url": "https://www.douyin.com/video/xxxx"
}
}GET /api/download?token=xxx
说明:
- 后端会代理拉取视频并流式返回
- 小程序只请求你自己的后端域名即可
- 从分享文案中提取第一条链接
- 跟随重定向拿到真实页面地址
- 根据域名识别平台
- 请求平台分享页 HTML
- 从页面
meta或内嵌 JSON 中提取标题、作者、封面、视频地址 - 构造下载代理地址返回给小程序
说明:
- 解析依赖平台当前公开页面结构
- 平台改版后,优先更新对应 parser 文件中的提取规则
以下以 Ubuntu 22.04 为例。
sudo apt update
sudo apt install -y python3 python3-pip python3-venv nginx git创建部署目录:
sudo mkdir -p /opt/qushuiyin
sudo chown -R $USER:$USER /opt/qushuiyin
cd /opt/qushuiyin上传代码后执行:
python3 -m venv venv
source venv/bin/activate
pip install -U pip
pip install -r requirements.txt
cp .env.example .env编辑 .env:
APP_ENV=production
APP_HOST=0.0.0.0
APP_PORT=8000
APP_BASE_URL=https://your-domain.com
CORS_ORIGINS=https://servicewechat.com,https://your-domain.comsource /opt/qushuiyin/venv/bin/activate
cd /opt/qushuiyin
python -m server.main另开终端测试:
curl http://127.0.0.1:8000/health在阿里云控制台放行:
22:SSH80:HTTP443:HTTPS
如果只让 Nginx 对外,8000 不需要开放公网。
mkdir -p /opt/qushuiyin/logs
chmod +x /opt/qushuiyin/deploy/run_nohup.sh
cd /opt/qushuiyin
./deploy/run_nohup.sh查看进程:
ps -ef | grep server.main查看日志:
tail -f /opt/qushuiyin/logs/server.outsudo cp deploy/qushuiyin.service /etc/systemd/system/qushuiyin.service
sudo systemctl daemon-reload
sudo systemctl enable qushuiyin
sudo systemctl start qushuiyin
sudo systemctl status qushuiyin查看日志:
sudo journalctl -u qushuiyin -fsudo cp deploy/nginx.conf /etc/nginx/sites-available/qushuiyin
sudo ln -s /etc/nginx/sites-available/qushuiyin /etc/nginx/sites-enabled/qushuiyin
sudo nginx -t
sudo systemctl reload nginx记得把配置里的 your-domain.com 改成你自己的域名。
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
sudo certbot renew --dry-run微信小程序后台需要配置:
request合法域名:https://your-domain.comdownloadFile合法域名:https://your-domain.com
说明:
- 本地开发可关闭校验
- 正式发布必须是 HTTPS 域名
- 域名通常需要备案
优先修改:
建议每个平台保留几条你自己的测试样本链接。
先检查:
/health是否正常- 解析前的
raw_url - 展开后的
resolved_url video_url是否能在浏览器中直接访问
更详细排查见 docs/TROUBLESHOOTING.md
- 仅处理你本人拥有权利或明确获得授权的素材
- 不建议公开运营、收费、裂变传播
- 平台规则和页面结构会变化,解析功能需要持续维护
本章节记录从"分享页HTML提取"到"调用官方API获取真无水印地址"的完整踩坑过程,供后续维护参考。
抖音视频有两种地址:
| 类型 | 来源字段 | URL特征 | 水印情况 |
|---|---|---|---|
| 有水印 | video.download_addr |
路径含 /mps/logo/ 或 watermark=1 |
带抖音平台水印 |
| 无水印 | video.bit_rate[].play_addr |
路径含 /tos/cn/tos-cn-ve-15/ |
纯净视频 |
核心思路:调用抖音官方 /aweme/v1/web/aweme/detail/ 接口,传入合法的 a_bogus 签名参数,获取视频完整元数据,从中提取 play_addr 作为无水印地址。
抖音官方接口需要 msToken、ttwid Cookie 和 a_bogus 签名三个关键参数。其中 a_bogus 的生成算法是最大难点。
参考开源项目 jiuhunwl/short_videos 的 Cloudflare Workers 版本,其核心算法涉及:
- SM3 哈希:对时间戳、用户代理等参数做摘要
- RC4 加密:使用密钥
chr(121)对中间结果加密 - 自定义 Base64:抖音使用变种的 Base64 编码(字母表顺序有调整)
关键踩坑点:
# 错误:RC4 输入用 bytes
bb_bytes = bytes(bb) # ❌ 导致签名无效
# 正确:模拟 JavaScript 的 String.fromCharCode,取模 65536
bb_str = "".join(chr(c & 0xFFFF) for c in bb) # ✅
return rc4_encrypt(bb_str, chr(121))完整实现见 server/utils/douyin_sign.py。
用户粘贴链接
↓
提取 aweme_id(从短链重定向后的长链接中正则提取)
↓
生成 msToken(随机字符串 + 时间戳)
↓
获取 ttwid Cookie(访问抖音首页获取,失败则用默认值)
↓
生成 a_bogus 签名(基于 URL 参数、Cookie、User-Agent)
↓
调用 /aweme/v1/web/aweme/detail/?aweme_id=xxx&msToken=xxx&a_bogus=xxx
↓
从返回 JSON 中提取 video.play_addr.url_list[0](无水印)
↓
从 video.download_addr.url_list[0](有水印,用于对比/兜底)
↓
构造下载代理链接返回给前端
常见误区:以为把 URL 中的 playwm 替换成 play 就能去水印。实际上抖音早已升级,分享页提取的地址即使替换后仍然带水印。
正确区分方法(通过官方API返回的数据):
# 有水印地址:download_addr,路径特征含 /mps/logo/
watermark_url = detail["video"]["download_addr"]["url_list"][0]
# 无水印地址:bit_rate 或 play_addr,路径特征含 /tos/cn/tos-cn-ve-15/
no_watermark_url = detail["video"]["bit_rate"][0]["play_addr"]["url_list"][0]下载时的表现:
- 有水印视频:播放时右下角有抖音Logo,左上角或右下角会跳动
- 无水印视频:纯净画面,无任何平台标识
微信小程序 wx.request 返回的数据结构容易嵌套多层,前端解析时必须兼容多种情况:
// 容易出错的写法:直接假设 response.data.data 存在
const result = response.data.data; // ❌ 可能报错
// 正确的兼容写法
let result;
try {
if (response && response.data && response.data.data) {
result = response.data.data; // 标准结构
} else if (response && response.data) {
result = response.data; // 少一层
} else {
result = response; // 直接就是数据
}
} catch (e) {
result = response;
}抖音、快手等平台的视频CDN会校验 Referer 头。如果直接用浏览器或小程序下载,会返回 403。
解决方案:后端代理下载时带上对应平台的 Referer。
# server/utils/http_client.py
headers = {}
if "douyin" in url.lower():
headers["Referer"] = "https://www.douyin.com/"
elif "kuaishou" in url.lower():
headers["Referer"] = "https://www.kuaishou.com/"前端不直接请求视频源地址,而是请求后端的 /api/download 或 /api/media 代理接口。
当自有解析失效时,可调用第三方免费API作为备选方案。
配置(.env):
BUGPK_API_ENABLED=true请求方式:
curl 'https://api.bugpk.com/api/douyin?url=https%3A%2F%2Fv.douyin.com%2Fxxxxx%2F' \
-H 'Referer: https://api.bugpk.com/doc-douyin.html' \
-H 'X-Requested-With: XMLHttpRequest'注意事项:
- 该API不需要 API Key
- 返回字段名与自有解析不同,需要做字段映射
- 作为
fallback使用,优先走自有解析
解析抖音视频涉及多个网络请求,容易超时。
优化前的问题:
- 前端超时 20 秒 → 经常
request: fail - 后端解析完成后还额外发送
verify_no_watermarkHEAD 请求 → 增加 5-10 秒
优化措施:
- 前端超时改为 60 秒:
api/request.js中timeout: 60000 - 去掉冗余的
verify_no_watermark验证:官方API返回的play_addr本身就是无水印地址,直接信任即可
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
request: fail |
后端没启动 | python -m server.main |
request: fail |
微信校验域名 | 开发者工具勾选"不校验合法域名" |
Error: timeout |
解析耗时超过前端超时 | 增加 timeout 到 60000ms |
Error: timeout |
后端 verify_no_watermark 额外请求 |
去掉该验证逻辑 |
| 解析成功但视频有水印 | 使用了 download_addr |
改用 bit_rate[].play_addr |
| 视频无法下载/播放 | CDN 防盗链 | 后端代理加 Referer |
| 复制链接无效 | 前端解析了错误字段 | 检查 no_watermark_video_url 字段 |
| 端口冲突 | 上次进程未退出 | taskkill /F /IM python.exe |
| 文件 | 作用 | 修改场景 |
|---|---|---|
| server/parsers/douyin_api.py | 抖音官方API解析核心 | 官方接口改版、字段结构调整 |
| server/utils/douyin_sign.py | 签名算法 | 签名失效、API返回签名错误 |
| server/services/parser_service.py | 解析调度与兜底逻辑 | 切换解析策略、调整超时 |
| server/services/third_party_service.py | 第三方API集成 | bugpk.com接口变更 |
| server/utils/http_client.py | HTTP客户端 | CDN Referer变更、代理配置 |
| api/request.js | 前端请求封装 | 调整超时、修改header |
| pages/home/index.js | 前端页面逻辑 | 数据解析、UI交互 |
| config.js | 后端地址配置 | 切换环境、修改端口 |
本章节记录快手、小红书两个平台从"仅能解析"到"支持无水印"的优化过程。
快手和小红书的解析器只做了基础的 HTML 页面提取,存在以下问题:
- 快手:只提取了一个视频地址,没有区分有水印和无水印
- 小红书:同样没有区分水印版本,且页面提取规则不够全面
- 第三方兜底:bugpk.com API 只对接了抖音端点,快手和小红书无法使用兜底
- 调度逻辑:非抖音平台一律标记
no_watermark_verified = False,前端无法确认是否无水印
快手分享页对 User-Agent 敏感,使用移动端 UA 只能拿到精简页面,缺少视频地址字段。
优化:改用 PC 端 Chrome UA + Referer,获取包含完整 JSON 数据的页面。
html = await self.fetch_html(resolved_url, headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
"Referer": "https://www.kuaishou.com/",
})快手页面 JSON 中包含两种视频地址:
| 字段 | 说明 | 水印情况 |
|---|---|---|
srcNoMark |
无水印播放地址 | 无水印 |
photoUrl |
有水印播放地址 | 带快手Logo |
mainMvUrls |
主视频地址列表 | 通常有水印 |
优化:分别提取 srcNoMark(无水印)和 photoUrl(有水印),填充到 ParsedVideo 的对应字段。
srcNoMark(无水印)→ photoUrl → mainMvUrls → hevc.url → og:video meta
如果 srcNoMark 不存在,依次尝试其他字段,确保至少能拿到一个可播放地址。
与快手类似,小红书也需要 PC 端 UA 才能获取完整页面数据。
小红书的视频地址结构:
| 字段 | 说明 | 水印情况 |
|---|---|---|
masterUrl |
原始视频地址 | 无水印(小红书视频通常无平台水印) |
backupUrls |
备用地址列表 | 无水印 |
originVideoKey |
原始视频Key | 需拼接域名 |
og:video |
meta标签 | 可能有水印 |
优化:优先提取 masterUrl 作为无水印地址,og:video 作为有水印兜底。
masterUrl → h264[].masterUrl → originVideoKey → backupUrls → videoUrl → og:video
bugpk.com 为每个平台提供独立的 API 端点:
BUGPK_API_ENDPOINTS = {
"douyin": "https://api.bugpk.com/api/douyin",
"kuaishou": "https://api.bugpk.com/api/kuaishou",
"xiaohongshu": "https://api.bugpk.com/api/xiaohongshu",
}根据分享链接的域名自动识别平台,选择对应的 API 端点:
def _detect_platform(self, source_url: str) -> str:
domain = urlparse(source_url).hostname
if "kuaishou" in domain:
return "kuaishou"
if "xiaohongshu" in domain:
return "xiaohongshu"
return "douyin"三个平台的 API 返回格式统一为 {code, msg, data: {title, cover, url, ...}},使用同一个适配方法处理,但保留平台标识。
# 优化前:非抖音平台一律不验证
elif platform != "douyin" and parsed_video:
no_watermark_verified = False# 优化后:快手/小红书有无水印地址则标记验证通过
elif platform in ("kuaishou", "xiaohongshu") and parsed_video:
if parsed_video.no_watermark_video_url:
no_watermark_verified = True
# 无水印地址为空时,自动调用第三方API兜底
elif self.third_party_service.is_configured():
third_party_result = await self.third_party_service.parse(raw_url)
if third_party_result:
parsed_video = third_party_result
parse_source = "fallback"
no_watermark_verified = True关键改进:
- 快手/小红书自有解析成功且有无水印地址 → 标记
verified = True - 自有解析未获取到无水印地址 → 自动调用 bugpk.com 对应平台 API 兜底
- native 模式也支持验证状态标记
| 平台 | 耗时 | 无水印URL来源 | 解析来源 | 验证状态 |
|---|---|---|---|---|
| 快手 | 18.4秒 | v23-3.kwaicdn.com |
native | ✅ 通过 |
| 小红书 | 8.0秒 | sns-video-zl.xhscdn.com |
native | ✅ 通过 |
快手和小红书的 CDN 也有防盗链机制,已在 http_client.py 中统一处理:
if "kuaishou" in url.lower():
headers["Referer"] = "https://www.kuaishou.com/"
elif "xiaohongshu" in url.lower():
headers["Referer"] = "https://www.xiaohongshu.com/"| 特性 | 抖音 | 快手 | 小红书 |
|---|---|---|---|
| 解析方式 | 官方API + 签名 | 页面HTML提取 | 页面HTML提取 |
| 无水印来源 | bit_rate[].play_addr |
srcNoMark |
masterUrl |
| 有水印来源 | download_addr |
photoUrl |
og:video(meta) |
| 第三方兜底 | bugpk.com 抖音端点 | bugpk.com 快手端点 | bugpk.com 小红书端点 |
| 防盗链Referer | douyin.com |
kuaishou.com |
xiaohongshu.com |
| 是否需要签名 | 是(a_bogus) | 否 | 否 |
| 平均耗时 | 5-10秒 | 15-20秒 | 5-10秒 |