ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

短视频去水印小程序开发:JavaScript解析与下载保存实战

短视频去水印小程序开发:JavaScript解析与下载保存实战 简介这是一套基于JavaScript开发的短视频去水印微信小程序完整源码面向计算机相关专业在校学生、教师及企业员工也适合小程序开发入门者进阶学习可作为毕业设计、课程设计或项目初期立项演示。项目实现多平台短视频去水印解析、无水印视频下载、积分签到、历史解析记录查询与下载、福利广告接入页、帮助页及个人信息页等模块代码经测试运行成功答辩评审平均分达96分。资源包共70个文件约280KB包含11个js逻辑脚本、8个wxml页面结构、12个wxss样式文件、13个json配置文件以及24个png图片资源和license、README说明文档目录涵盖pages、utils、colorui组件等模块结构清晰便于二次开发。目前已有75人学习下载。下载后可参考README了解运行方式在现有代码基础上修改扩展功能用于学习小程序开发流程、页面布局与接口请求封装思路。1. 短视频去水印小程序从解析到落地的完整技术路径刷到一条想保存的短视频点开保存却发现右下角挂着平台水印或者干脆提示「该内容不支持下载」——这是很多人做内容搬运、素材收集时最常遇到的场景。基于 JavaScript 实现的短视频去水印微信小程序核心要解决的就是这件事用户粘贴一条分享链接小程序在服务端完成解析拿到无水印的视频直链再交给用户保存到相册。整套方案的技术栈并不复杂前端是微信小程序原生开发后端用 Node.js 承接解析逻辑中间靠一个 HTTP 接口打通。它适合两类人一类是想自己搭一个工具自用或给团队用的开发者另一类是想拿一个完整小程序项目练手、顺便搞懂「小程序请求后端 下载保存」这条链路的新手。这篇文章不讲空泛概念直接把解析接口怎么写、小程序端怎么调、下载保存怎么绕坑讲清楚源代码和文档说明的落地方式也会一并交代。2. 去水印的技术原理与解析链路拆解2.1 水印到底加在哪一层要理解去水印先得搞清楚水印是怎么加上去的。绝大多数短视频平台的水印并不是烧录在视频像素里的而是在视频转码分发阶段由服务端在视频帧上叠加一层图层或者直接在播放器层面做覆盖渲染。前者是「硬水印」后者是「软水印」。硬水印一旦合成进视频文件理论上无法无损去除只能靠裁剪或模糊覆盖软水印则不同它往往只存在于某个特定清晰度的播放地址上而平台同时还会保留一份不带水印的原始视频源。去水印小程序能生效的前提就是平台存在这样一份「无水印源」。常见的做法是用户分享出来的链接指向的是带水印的播放页但这个播放页的 HTML 或接口返回数据里藏着另一个字段——通常是play_addr或download_addr之类的键——它对应的视频地址就是无水印版本。解析的本质就是从分享链接出发请求平台的重定向页面从返回的 JSON 或 HTML 里把那个真正的视频直链抠出来。这里有个容易翻车的点不同平台、甚至同一平台不同时期这个字段名和嵌套层级都会变。所以解析逻辑不能写死得做成可配置的规则表每个平台一条规则规则里描述「请求哪个 URL、用什么方法、从返回结果的哪个路径取视频地址」。2.2 解析服务的接口设计后端是整个方案的核心。我一般用 Node.js Express 起一个轻量服务对外暴露一个 POST 接口接收前端传来的分享链接返回解析后的视频直链和标题。下面是一个最小可运行的解析服务骨架// server.js const express require(express); const axios require(axios); const app express(); app.use(express.json()); // 平台解析规则表每个平台一条key 为平台标识 const rules { douyin: { // 从分享文本中提取真实 URL 的正则 urlPattern: /https?:\/\/v\.douyin\.com\/[\w-]/, // 请求该 URL 时跟随重定向拿到最终页面 async parse(realUrl) { const res await axios.get(realUrl, { headers: { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) }, maxRedirects: 5, }); // 从页面 HTML 中匹配视频信息 JSON const match res.data.match(/window\._ROUTER_DATA\s*\s*(\{.?\});/s); if (!match) throw new Error(未匹配到视频数据); const data JSON.parse(match[1]); // 逐层取到无水印地址路径随平台版本变化需按实际调整 const item data.loaderData[video_(id)/page].videoInfoRes.item_list[0]; return { title: item.desc, cover: item.video.cover.url_list[0], videoUrl: item.video.play_addr.url_list[0].replace(playwm, play), }; }, }, }; app.post(/api/parse, async (req, res) { const { shareText } req.body; if (!shareText) return res.status(400).json({ code: 1, msg: 缺少分享链接 }); // 遍历规则表找到能匹配的平台 for (const [platform, rule] of Object.entries(rules)) { const m shareText.match(rule.urlPattern); if (!m) continue; try { const result await rule.parse(m[0]); return res.json({ code: 0, platform, data: result }); } catch (e) { return res.status(500).json({ code: 2, msg: 解析失败 e.message }); } } res.status(400).json({ code: 1, msg: 暂不支持该平台链接 }); }); app.listen(3000, () console.log(parse server running on 3000));这段代码的逻辑很直白先定义规则表每个平台一条规则规则里包含「怎么从分享文本里抠出真实 URL」和「怎么请求并提取视频地址」两个能力。接口收到请求后遍历规则表命中就执行解析返回统一结构的数据。参数上有几个关键点。User-Agent必须伪装成移动端否则平台可能返回桌面版页面数据结构完全不同。maxRedirects要设够因为分享短链通常要跳转两三次才能到真实页面。正则urlPattern要按平台实际分享格式调整抖音的短链是v.douyin.com开头快手的格式又不一样。最关键的videoUrl那一行很多平台的播放地址里带playwmplay with watermark标识把它替换成play就能拿到无水印版本——这是最常见的做法但不是所有平台都适用得实测。提示解析规则是整套方案里最脆弱的部分平台一改版就可能失效。建议把规则表抽成独立配置文件方便不改主逻辑就能更新。2.3 小程序端如何调用解析接口小程序端要做的事很简单一个输入框让用户粘贴分享文本一个按钮触发请求拿到结果后展示封面和标题再提供一个保存按钮。核心代码如下// pages/index/index.js Page({ data: { shareText: , videoInfo: null, loading: false }, onInput(e) { this.setData({ shareText: e.detail.value }); }, async onParse() { if (!this.data.shareText) { return wx.showToast({ title: 请先粘贴链接, icon: none }); } this.setData({ loading: true }); try { const res await wx.request({ url: https://your-domain.com/api/parse, method: POST, data: { shareText: this.data.shareText }, header: { content-type: application/json }, }); if (res.data.code 0) { this.setData({ videoInfo: res.data.data }); } else { wx.showToast({ title: res.data.msg, icon: none }); } } catch (e) { wx.showToast({ title: 网络异常, icon: none }); } finally { this.setData({ loading: false }); } }, });wx.request的url必须是你自己备案过的域名且要在小程序后台的「开发设置 - 服务器域名」里配置 request 合法域名否则真机上会直接报错。method用 POST 是因为分享文本可能很长放 URL 参数里容易超长。header里指定 JSON 格式和后端express.json()中间件对应。拿到videoInfo之后页面用image组件展示封面用text展示标题保存按钮触发下载逻辑。这里要注意小程序的wx.downloadFile只能下载配置过 downloadFile 合法域名的地址而视频直链的域名往往和你的接口域名不同所以要么把视频域名也加进白名单要么让后端做一个中转下载接口把视频流代理回来。前者简单但受平台域名变化影响后者稳定但消耗服务器带宽按实际情况选。3. 从零跑通环境搭建、接口联调与下载保存3.1 本地开发环境与项目结构先把环境搭起来。你需要 Node.js建议 16 以上、微信开发者工具以及一个能跑 Node 服务的本地或云服务器。项目结构我一般这样组织project/ ├── server/ # 后端解析服务 │ ├── server.js # 入口 │ ├── rules/ # 各平台解析规则 │ │ └── douyin.js │ └── package.json ├── miniprogram/ # 小程序前端 │ ├── pages/index/ │ ├── app.js │ └── app.json └── docs/ # 文档说明 └── README.md后端初始化就三步npm init -y、npm install express axios、node server.js。小程序端用微信开发者工具新建项目选「不使用云服务」把miniprogram目录指进去即可。app.json里要配好页面路径和窗口样式project.config.json里填上你的 AppID。如果你还没有 AppID可以用测试号先跑通逻辑但测试号不支持真机预览和发布正式上线必须用注册好的小程序账号。3.2 接口联调与跨域处理本地开发时小程序开发者工具请求localhost是通的但真机不行。所以联调阶段建议用内网穿透工具把本地服务暴露成一个公网 HTTPS 地址填到wx.request的url里同时在小程序后台把这个域名加进 request 合法域名。后端这边要处理跨域。虽然小程序请求不受浏览器同源策略限制但如果你同时想用浏览器调试接口就得加 CORS 头// 在 server.js 的 app.use 之后加上 app.use((req, res, next) { res.header(Access-Control-Allow-Origin, *); res.header(Access-Control-Allow-Headers, Content-Type); res.header(Access-Control-Allow-Methods, POST, GET, OPTIONS); if (req.method OPTIONS) return res.sendStatus(204); next(); });这段中间件的作用是给所有响应加上跨域头并直接放行 OPTIONS 预检请求。Access-Control-Allow-Origin生产环境建议改成你的小程序域名不要用*。联调时最常见的报错是request:fail url not in domain list这说明域名没配或配错了。另一个高频问题是40029 invalid code那是登录相关接口的和解析无关别搞混。3.3 视频下载与保存到相册拿到视频直链后保存到相册分两步先wx.downloadFile下载到本地临时文件再wx.saveVideoToPhotosAlbum存进相册。代码如下// 保存视频到相册 async onSave() { const { videoUrl } this.data.videoInfo; wx.showLoading({ title: 下载中 }); try { const dl await wx.downloadFile({ url: videoUrl }); if (dl.statusCode ! 200) throw new Error(下载失败); await wx.saveVideoToPhotosAlbum({ filePath: dl.tempFilePath }); wx.showToast({ title: 已保存到相册 }); } catch (e) { // 用户拒绝授权时会走到这里 if (e.errMsg e.errMsg.includes(auth deny)) { wx.showModal({ title: 需要相册权限, content: 请在设置中开启保存到相册的权限, success: (r) { if (r.confirm) wx.openSetting(); }, }); } else { wx.showToast({ title: 保存失败, icon: none }); } } finally { wx.hideLoading(); } }wx.downloadFile的url必须是 HTTPS且域名在 downloadFile 合法域名列表里。statusCode要判断有些平台会对非浏览器请求返回 403。saveVideoToPhotosAlbum第一次调用会弹授权框用户拒绝后必须引导去wx.openSetting手动开启否则后续调用一直失败。这个授权逻辑是新手最容易漏的漏了就会出现「代码没错但就是存不了」的玄学现象。注意wx.downloadFile有大小限制默认单次下载不超过 200MB超长视频可能失败。如果目标视频普遍较大建议后端做分片或直接返回一个可复制的直链让用户用浏览器下载。4. 避坑与排查那些让你白干半天的细节4.1 解析突然失效接口返回空数据现象昨天还能正常解析的链接今天接口返回「未匹配到视频数据」。原因平台改版了页面结构或接口字段名原来的正则或 JSON 路径取不到值了。解决把解析规则抽成独立配置文件每次失效只改配置不改主逻辑。同时加一层日志把平台返回的原始 HTML 或 JSON 落盘方便快速定位字段变化。我一般会在解析失败时把原始响应存一份到本地文件改规则时直接对着看。4.2 真机上请求接口报域名不合法现象开发者工具里一切正常真机预览时接口请求直接失败提示url not in domain list。原因小程序真机请求强制走 HTTPS且域名必须在后台白名单里开发者工具的「不校验合法域名」选项在真机上不生效。解决把接口域名和视频下载域名都加进小程序后台的「服务器域名」配置确保是 HTTPS 且已备案。本地调试阶段用内网穿透拿到的临时域名也要加进去但注意这类域名可能不稳定正式上线必须换成自己的域名。4.3 保存到相册没反应也不报错现象点了保存按钮loading 转了一圈就没了相册里没有视频控制台也没有明显报错。原因大概率是用户之前拒绝过相册授权saveVideoToPhotosAlbum静默失败了而代码里没有处理auth deny分支。解决在catch里判断errMsg是否包含auth deny是的话弹窗引导用户去wx.openSetting开启权限。另外可以在页面onLoad时用wx.getSetting提前查一下授权状态给用户一个提示。4.4 下载的视频带水印或清晰度不对现象解析出来的视频能下载但水印还在或者清晰度明显比原视频低。原因取到的地址是带水印的播放地址或者取的是低清晰度版本。很多平台的返回数据里有多个清晰度档位每个档位对应不同的 URL。解决在解析规则里优先取最高清晰度的无水印地址。通常返回数据里会有一个数组按清晰度排序取第一个或标记为origin的那个。如果替换playwm为play后水印还在说明这个平台的机制不是简单替换得换思路比如从分享页的og:video标签里取。4.5 接口被平台限流或封 IP现象解析请求频繁失败返回 403 或验证码页面。原因短时间内大量请求同一平台触发了风控。解决在服务端加请求频率限制同一 IP 或同一用户每分钟最多解析几次。同时准备多个 User-Agent 轮换必要时加代理池。但代理池成本和维护复杂度都高个人项目建议先做频率限制够用。5. 进阶技巧让解析更稳、更快、更好维护解析规则的可维护性是这套方案能不能长期跑下去的关键。我踩过的最大坑就是把所有平台的解析逻辑写在一个大文件里平台一改版就得翻半天代码。后来改成每个平台一个独立模块导出一个统一的parse函数主服务只负责调度。这样新增平台就是加一个文件改版就是改一个文件互不影响。另一个技巧是加缓存。同一条分享链接短时间内可能被多次解析没必要每次都请求平台。我一般用内存缓存比如node-cache存 10 分钟key 用分享链接的 hash命中直接返回。这能显著降低被限流的概率也加快了响应速度。验证解析是否正常我习惯写一个简单的测试脚本把几个典型平台的分享链接跑一遍看返回结构对不对// test/parse.test.js const axios require(axios); const cases [ { name: 抖音, text: 7.43 复制打开抖音看看【某某】的作品 https://v.douyin.com/xxxxx/ }, // 其他平台链接按实际补充 ]; (async () { for (const c of cases) { try { const res await axios.post(http://localhost:3000/api/parse, { shareText: c.text }); console.log(c.name, res.data.code 0 ? 通过 : 失败 res.data.msg); } catch (e) { console.log(c.name, 请求异常, e.message); } } })();这个脚本的作用是快速回归每次改完规则跑一遍确认没有把其他平台搞挂。参数上没什么特别的就是把真实分享文本填进去看code是否为 0。最后说一个关于「文档说明」的落地建议。很多人拿到一个带文档的项目文档写得像流水账看完还是不知道怎么跑。我写文档的习惯是第一段写「这个项目解决什么问题」第二段写「跑起来需要哪三步」第三段写「常见报错和对应解法」。三步之内必须能让一个新手把服务跑起来否则文档就是失败的。源代码的价值不在于代码本身多优雅而在于别人能不能照着跑通、改得动。这套方案我前后迭代过几版最大的教训是别指望一套解析规则能一劳永逸。平台在变规则就得跟着变所以架构上一定要把「易变的」和「不变的」分开。不变的是一套请求-解析-返回的流程易变的是每个平台的具体规则。把易变的部分隔离好这个项目就能一直维护下去。希望帮到你。本文还有配套的精品资源点击获取
返回列表