Web 数据抓取 · 接口逆向 · Node.js 实战
一次数据导出任务:原本走浏览器模拟点击路线耗了 5 小时,后来通过抓包发现后台接口、改用纯 HTTP 调用,8 分钟搞定 429 条记录。本文记录完整思路转折、接口调用方案和踩坑细节。
需求:将某语音转文字平台(SaaS 服务)上的数百条录音记录批量导出为「音频 MP3 + 转写 Word」,文件名统一加日期前缀并按录制时间排序。后续还需要清理旧文件释放存储空间,但不能误删未整理的数据。
关键约束:
一开始用的是浏览器模拟操作方案:打开每个记录的详情页、从 DOM 提取数据、触发下载。这条路线遇到了一系列典型问题:
问题 | 影响 |
|---|---|
需要逐个打开详情页 | 429 条记录就要加载 429 个页面 |
SPA 渲染等待 | 每页需要等待 JS 渲染完成才能提取数据 |
弹窗拦截 | 程序化的 window.open() 经常被浏览器拦截 |
选择器不稳定 | 页面改版后选择器失效,脚本中断 |
登录态维护 | Cookie 过期后需要重新登录 |
结果:跑了 5 小时还没跑完一半,而且中途因为各种异常断了好几次。
注意到该平台的网页端本身就有"勾选 50 条批量下载"的功能——这说明后台一定有批量操作的接口。既然网页能调,脚本也能直接调。
思路转变:不该教脚本怎么"假装是人",而是找到网页在用什么接口,然后直接复用那些接口。
打开浏览器开发者工具(F12)→ Network 面板 → 在网页上手动触发一次列表加载/下载操作,观察实际发出的请求。
本次抓包发现的三个核心接口:
用途 | 方法 | 路径模式 | 说明 |
|---|---|---|---|
列表分页 | POST | /api/v2/records/list | 游标分页,支持拉全量 |
音频下载 | GET | /api/v1/audio/{id}/url | 返回带签名的临时下载地址 |
转写文本 | GET | /api/v1/transcript/{id}/text | 返回 JSON 格式的逐字稿 |
注:以上路径为示例模式,实际路径因平台而异。关键是先抓包确认真实接口,不要猜测。
大多数 SaaS 平台的网页端认证基于 Cookie。获取方式:
安全提醒: Cookie 含敏感登录态,脚本中不应硬编码。建议运行时动态获取,用完即弃。
切换到纯接口调用后,核心流程大幅简化:
获取 Cookie → 循环拉列表分页 → 对每条记录并行请求音频URL和转写文本 → 下载写入本地文件
分页接口采用游标模式(cursor pagination),每次请求返回一页数据和下一页游标,直到游标为空表示结束。
// 伪代码:游标分页拉全量 let cursor = null; const allRecords = []; do { const resp = await httpPost(LIST_API, { cursor, pageSize: 50 }); allRecords.push(...resp.records); cursor = resp.nextCursor; } while (cursor);
注意:必须正确处理末页条件。 有些接口末页返回空数组,有些返回 hasMore: false,有些返回 nextCursor: null。处理不当会导致死循环(本次踩过这个坑,脚本跑到 60 多页才停)。
429 条记录,每条需要 2~3 次 API 请求(音频 URL + 转写文本 + 下载)。全部串行太慢,全部并发又可能触发限流。
折中方案:使用并发池(concurrency pool),控制同时请求数:
// 伪代码:并发池控制 const pool = new ConcurrencyPool({ maxConcurrency: 5 }); for (const record of allRecords) { await pool.add(() => downloadRecord(record)); } await pool.done();
原始文件名格式五花八门,需要统一解析出录制日期:
原始格式 | 示例 | 解析结果 |
|---|---|---|
中文日期 | 2022年9月3日录音 | 2022-09-03 |
横杠分隔 | 20251210_文件名 | 2025-12-10 |
点分分隔 | 2025.6.12 | 2025-06-12 |
紧凑数字 | 20221103 | 2022-11-03 |
无日期 | 会议录音001 | 使用创建时间补 |
日期正则要点: 必须加前缀约束(如年份以 19/20 开头)和月日范围校验(月 1-12,日 1-31),否则手机号之类的数字串会被误判为日期。
备份完成后清理旧数据释放空间。这里的核心挑战是如何只删该删的、绝不误删。
利用业务规则实现天然隔离:该平台的"未整理"数据在 PC 端不可见,也不出现在接口返回的列表中。因此通过接口筛选删除目标时,未整理数据天然不会被选中。
大多数平台提供两步删除机制:
操作 | 语义 | 说明 |
|---|---|---|
回收(recycle) | 移入回收站 | 可恢复,通常 7 天后自动清除 |
彻底删除(delete) | 从回收站永久删除 | 不可恢复 |
建议: 脚本默认只执行回收操作(soft delete),且必须先 dry-run 输出清单让用户确认。
按文件名中的录制日期过滤(不能用同步时间,那个是操作当天的日期)。只删同时满足以下条件的记录:
坑 | 阶段 | 解法 | |
|---|---|---|---|
1 | 浏览器自动化跑了 5 小时没跑完 | 方案选择 | 放弃浏览器自动化,改用纯 API |
2 | 分页死循环跑到 60 页 | 接口调用 | 末页必须正确判断退出条件 |
3 | 手机号被日期正则误匹配 | 命名解析 | 加年份前缀 + 月日范围双重约束 |
4 | 旧数据漏删——用了同步时间而非录制日期 | 过滤逻辑 | 统一按文件名中的日期过滤 |
5 | 有备份的记录被判无备份 | 命名一致性 | 多脚本的命名函数必须字符级一致 |
6 | 点分日期格式未被识别 | 正则覆盖 | 补充 \d{4}.\d{1,2}.\d{1,2} 模式 |
7 | 接口超时导致脚本挂死 | 工程实践 | 所有 HTTP 请求设置超时(建议 20s) |
1. 先抓包再动手
任何网页数据操作任务,第一步不是写爬虫代码,而是打开 DevTools 看它实际调了什么接口。有现成的 API 就直连,效率差几个数量级。
2. 破坏性操作必须分层
把流程拆成"只读导出"和"写删除"两步,删除步骤默认 dry-run、强制备份校验。这一条能避免绝大多数灾难性误操作。
3. 跨脚本的一致性靠拷贝不靠记忆
如果多个脚本共享同一套命名/解析逻辑,从一个已验证的脚本原样复制函数代码,不要凭记忆重写。本次多个 bug 的根因都是"记得大概但参数顺序写错了"。
4. 利用业务规则做安全兜底
了解平台的业务规则(如"未整理数据不在列表中"),有时比写更多的校验代码更有效。
指标 | 数值 |
|---|---|
总记录数 | 429 条 |
导出文件数 | 776 个(387 mp3 + 389 docx) |
纯 API 耗时 | 约 8 分钟 |
清理旧记录 | 约 307 条 |
释放空间 | 约 70GB |
误删数量 | 0 条 |
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。