尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

用API抓包替代Selenium:腾讯文档批量导出实战

发布时间:2026/9/29 1:40:34

资讯中心
01
ARTICLE

用API抓包替代Selenium:腾讯文档批量导出实战

用API抓包替代Selenium:腾讯文档批量导出实战
去年年底我接到一个活儿要把团队在腾讯文档里攒了两百多个表格、文档和幻灯片统一备份到本地。一开始我老老实实用Selenium写自动化脚本结果越写越痛苦等待元素加载、处理弹窗、维护选择器、控制浏览器版本每天都像在修一个永远修不完的补丁。直到有一天我打开了浏览器开发者工具盯着Network面板看了半小时突然意识到一个事——前端页面厉害归厉害但它所有的操作最终都要落到那些HTTP请求上那我为什么不直接分析这些请求绕过浏览器自己构造同样的调用呢这个思路后来帮我用一百多行Python替代了六百多行Selenium代码批量导出的速度从每晚跑两小时变成三分钟跑完。这篇就把完整过程写出来包括前端API的调用链路、鉴权机制、导出任务的处理逻辑、限流规避以及我在这个过程里踩过的坑。1. 被Selenium折磨一个月后我盯上了浏览器Network面板1.1 Selenium方案哪里让人崩溃先交代下背景我的需求其实很简单把指定目录下的在线文档、表格、幻灯片全部下载为Office格式到本地。听起来是不是比写爬虫简单多了但真用Selenium去操作腾讯文档网页版你会发现处处是坑。页面组件全是动态渲染表格内容只有滚动到可视区域才会加载Selenium的find_element时有超时点击邮件通知、历史版本这类弹窗经常把界面遮住更麻烦的是登录态维护——扫码登录后Cookie有时效挂在服务器上跑定时任务根本没法自动续期。我试过把webdriver.Chrome的user-data-dir指向本地Chrome配置试图借用浏览器登录态结果频繁的浏览器升级又把ChromeDriver版本兼容性问题扯了出来。很多人说Selenium是万金油什么网页都能自动化但“能跑”和“能稳定跑”是两码事。尤其面对单页应用整个页面状态都在JavaScript里维护Selenium每执行一步都在和前端渲染线程抢时间。我用显式等待解决了80%的超时但剩下20%的偶发失败依然无法根除而且每次跑完两百多个文件要将近两小时完全是不可忍受的。1.2 一次抓包改变思路真正让我转变思路的是一个偶然动作。当时为了排查某个文档下载失败的原因我打开了Chrome开发者工具切到Network面板勾选了Fetch/XHR过滤然后手动在页面上点了一次“导出为xlsx”。我清楚看到浏览器发出了一串请求——先是一个创建导出任务的接口接着是状态轮询最后是一个下载地址。全程没有出现过任何一次对后端页面的真实访问所有数据交换都是标准JSON。这时候我脑子里冒出来一个想法如果页面操作的本质就是这十几个HTTP请求那我只需要模拟这些请求就能复刻整个导出流程。浏览器只是负责渲染和交互的壳子真正干活的从来都是API。Selenium最大的问题就在于它把大量的计算资源浪费在了“模拟人眼可见的界面”上而这些界面交互哪怕全部跳过对最终结果也没有任何影响。顺着这个思路我开始系统抓包分析腾讯文档Web端的API结构。这个过程不算难但也绝对没有网上传的那么简单。下面把这套API调用逻辑完整拆一遍。2. 腾讯文档前端API的调用链路拆解2.1 鉴权设计Cookie、CSRF与Referer第一步要解决的问题是鉴权。腾讯文档的网页版登录态依赖两样东西一个是主Cookie另一个是请求头里携带的csrfToken。主Cookie在登录之后由服务器下发包含若干个关键字段浏览器会把它自动附加到同域请求上。关键是CSRF Token——它通常藏在页面的全局JS变量里也可能是某个meta标签里腾讯文档的做法是通过一个专门的初始化接口动态下发。我抓包时发现几乎所有写操作创建导出任务、修改文件属性的请求头都带了一个x-csrf-token字段而请求体里也会带上相同的token。这两个值必须一致否则服务端直接拒绝。这个token本身有过期时间好在有效期足够长实测几小时内不会失效跑批量任务基本够用。还有一个细节容易被忽略导出请求的Referer必须设置为腾讯文档的域名页面。服务端会校验Referer来源如果Referer是空的或者非法域名会返回403。这个校验在早期版本里没有后来某次改版加上去的。我在脚本里统一设置了headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://docs.qq.com/, x-csrf-token: csrf_token, }2.2 文档列表接口与元数据结构导出的第一步是先拿到你要导出的文档列表。如果你手动在网页上管理文档分页浏览会反复请求一个列表接口。这个接口的URL路径类似/cgi-proxy/document_list实际路径会随版本调整参数是start、limit、type等分页和筛选条件返回的是JSON结构里面包含了文档的唯一标识fileId、标题、类型、更新时间、创建时间等字段。我需要的核心字段是fileId因为在腾讯文档的API体系里所有针对单个文件的操作都依赖这个ID而不是文档标题。标题只是给人看的程序里必须用ID来定位文件。同一个接口也支持按目录过滤比如我只想导出某个文件夹下的文档传对应的folderId即可。列表接口返回的分页逻辑需要注意默认limit最大是100超过了要加分页参数逐页拉取否则服务端会截断数据。我在脚本里做了个循环一页一页拉直到拉到的条数小于limit就认为到底了。元数据结构里值得关注的还有fileType字段它区分了doc、sheet、slide等不同类型。后面调用导出接口时不同的文件类型对应的导出格式不一样——文档可以导出docx、pdf、md表格可以导xlsx、csv幻灯片是pptx、pdf。2.3 导出引擎创建任务、轮询结果、下载文件文档列表拿到之后真正的核心环节是导出。腾讯文档的导出机制不是简单的“传ID返回文件”而是三步走创建任务、异步轮询、下载文件。创建任务阶段脚本向/cgi-proxy/export/create发送POST请求请求体里包含fileId、exportType等参数。exportType决定了你要导出成什么格式比如表格的xlsx对应某个枚举值pdf对应另一个值。服务端收到请求后不会立刻生成文件返回而是返回一个taskId表示导出任务已经在后台队列里了。轮询结果阶段脚本拿着taskId去请求/cgi-proxy/export/progress这个接口会返回任务当前状态包括等待中、处理中、成功、失败几种状态。由于超大文件导出需要一定时间轮询不能只等一次要隔几秒查一次直到状态变为成功或失败。轮询间隔建议不要低于2秒短了容易被限流。下载文件阶段当轮询接口返回成功时响应里会带一个临时的文件下载URL。这个URL通常是某个对象存储域名下的签名地址带有有效期一般是10到30分钟。拿到URL后直接用requests下载即可。注意这个URL一旦过期就必须重新发起导出任务所以下载逻辑里的一个判断要点是及时拿、及时下不要在拿到URL后拖延。3. 手写一个批量导出脚本3.1 代码分层设计搞清了API的调用链写代码就成了力气活。我的脚本分了三层第一层是会话层负责处理Cookie、CSRF Token的获取与刷新第二层是接口层封装了列表、创建任务、轮询、下载四个核心函数第三层是调度层遍历文档列表对每个文件执行完整的导出流程。这种分层不是为了炫技而是为了让排错更简单。接口层出了问题可以直接在REPL里单测某个函数会话层如果过期有独立的重试逻辑调度层负责控制节奏同时承担日志记录。3.2 准备登录态与CSRF Token第一次拿Cookie没有特别巧妙的办法我是在浏览器里手动登录腾讯文档然后从开发者工具里把Cookie字符串复制到脚本的配置文件里。这种做法在日常运维中完全可以接受关键是脚本要支持Cookie的快速替换以及遇到401/403时能明确提示用户去更新Cookie。CSRF Token我没用解析HTML的方式去抠而是直接请求一个初始化接口拿JSON。实测下来这个接口在页面加载时会被调用返回一个token字段直接从JSON里取值即可def fetch_csrf_token(session: requests.Session) - str: r session.get(https://docs.qq.com/cgi-proxy/init_data) r.raise_for_status() return r.json()[token]3.3 核心代码文档列表获取列表接口的封装要点在于分页。我把start和limit作为参数传入每次最多拉100条循环拉取直到不满一页def fetch_doc_list(session, start0, limit100): params { start: start, limit: limit, type: all, } r session.get(https://docs.qq.com/cgi-proxy/document_list, paramsparams) r.raise_for_status() return r.json()返回的JSON里docs数组包含每个文档的fileId、title、fileType等字段。我在调度层维护一个待导出的任务队列把title和fileId打包成tuple存进去。这里有个小坑文件名里可能包含/、\、:等非法字符Windows本地保存时必须做清洗否则open()会直接抛异常。3.4 核心代码导出任务创建与轮询下载创建任务的接口我封装成下面这个函数。export_type参数需要按文件类型映射文档类通常传1代表docx、传2代表pdf表格类传1代表xlsx幻灯片类传1代表pptx。具体枚举值以抓包为准不同版本可能有变化。稳妥做法是先抓一次包看你常用的导出格式对应哪个值写死到配置里。EXPORT_TYPE_MAP { doc: {word: 1, pdf: 2}, sheet: {excel: 1, pdf: 2}, slide: {ppt: 1, pdf: 2}, } def create_export_task(session, file_id, export_type): payload { fileId: file_id, exportType: export_type, } r session.post(https://docs.qq.com/cgi-proxy/export/create, jsonpayload) r.raise_for_status() return r.json()[taskId]轮询函数的要点是设置合理的超时上限。我默认最多轮询30次每次间隔3秒总共最多等90秒。如果一个文件超过90秒还没导出完成大概率是文件太大了我会在日志里记录一条警告把它归入“慢任务”列表下一轮再单独处理def wait_export_done(session, task_id, max_retry30, interval3): for _ in range(max_retry): r session.get(https://docs.qq.com/cgi-proxy/export/progress, params{taskId: task_id}) r.raise_for_status() data r.json() if data[status] success: return data[downloadUrl] if data[status] failed: raise RuntimeError(data.get(message, export failed)) time.sleep(interval) raise TimeoutError(export task timeout)下载的逻辑不多说拿到downloadUrl后直接用session.get流式下载写入本地文件。注意文件名后缀要和导出的格式一致否则打开文件时系统会不认识。4. 请求频率与异常处理的经验边界4.1 限流规则推测与请求退避直接拿API刷接口和用Selenium操作页面有一个本质区别——API请求频率的“人味”很重。你用人手点网页每秒最多点几次而脚本可以一秒发几十个请求。服务端对异常高频请求有明显识别策略我在测试阶段用无节制的循环跑过百来个请求之后就触发了一次临时封锁表现是连续十几个请求返回429过十分钟才恢复。建议的节流策略是每次创建导出任务之前强制sleep(1)到sleep(2)列表接口拉取控制在每页之间隔0.5秒轮询接口本身就自带3秒间隔不用额外加锁。这样跑一百个文件的整体耗时才几分钟完全够用没必要冒着被封的风险去压缩时间。另一个容易忽略的点是请求顺序。正常用户的操作习惯是“看一眼列表点一个文档导出等结果再返回列表”请求序列是间隔性重复的。脚本如果一口气连发几十个创建任务请求再统一轮询服务器端的反自动化策略很容易告警。我把调度逻辑设计成“逐个导出”而非“并发导出”——每个文件完成下载后再处理下一个。虽然牺牲了并发效率但胜在安全稳定。4.2 常见HTTP错误码与处理下表是我在实际运行中遇到的HTTP状态码和对应的处理方式状态码含义处理方案401登录态过期提示用户重新获取Cookie退出当前任务403Referer被校验拦截检查请求头里的Referer是否设置正确404请求路径变了或fileId无效更新接口路径核对fileId429触发限流退避60秒后重试最多重试3次500服务端异常重试2次仍失败则跳过该文件记录日志我特意强调一下403的处理经验。早期脚本总是偶发性的返回403排查了半小时发现是某个请求漏设了Referer而漏设的请求刚好是创建任务的请求。因为列表接口的Referer校验不严格但导出相关的接口校验很严格所以同样的header配置在不同接口上效果不同。统一的做法是在构造requests.Session时就通过session.headers.update()设置全局默认请求头而不是在每次请求时单独传。4.3 大文件与特殊格式的处理腾讯文档对超大文件的导出有独立的限制。我遇到过一个几百MB的表格创建任务后轮询了五分钟才成功下载时还要处理网络中断重连。所以下载函数不建议用response.text或一把梭的content要用流式写入def download_file(session, url, dest_path): with session.get(url, streamTrue) as r: r.raise_for_status() with open(dest_path, wb) as f: for chunk in r.iter_content(chunk_size8192): if chunk: f.write(chunk)流式下载的好处是边下边写盘不会因为文件过大把内存撑爆。对于超过500MB的文件建议额外加一个断点续传逻辑不过实测腾讯文档的下载URL过期时间够长正常网络下不至于传一半就失效。特殊格式方面腾讯文档里有一部分文件可能是“在线表格”之外的特殊类型比如智能表格、脑图、流程图。这三类文件在列表接口里的fileType字段和普通类型不同导出接口也不支持转成Office格式只能导出为对应的私有格式。我在脚本里加了一个格式白名单遇到不在白名单里的文件类型直接跳过避免浪费任务额度。5. API方案与Selenium方案实测数据对比用API方案替换Selenium之后我做了一个简单的对比测试。同一个文件夹下123个文档含表格、文档、幻灯片三种类型分别用两种方案跑结果如下指标Selenium方案API方案总耗时约1小时47分钟约4分钟代码量682行156行失败率约8%偶发元素未找到约1%偶发限流浏览器依赖依赖Chrome和ChromeDriver版本匹配无内存占用单个浏览器进程约500MB约150MB维护成本每次页面改版都要修选择器接口路径偶尔变动频率较低这个对比能清楚看出来两者的本质区别Selenium是在和前端渲染层较劲而API方案直接和后端数据层对话。后者的效率优势不是一点点而是数量级的差异。当然API方案也有它的短板。它依赖对接口路径和参数结构的理解一旦腾讯文档后端调整接口格式脚本就要跟着改。我在使用过程中遇到过两次接口调整一次是列表接口的路径改版一次是导出任务轮询接口的返回字段从status改成state。这种维护成本还是远低于Selenium那种“页面稍微加个弹窗就要重写选择器”的成本。6. 踩坑记录与边界意识6.1 我踩过的三个坑第一个坑是Cookie过期没有预警机制。脚本跑到第160个文件时突然全部401当时没做错误中断后面几十个文件全白跑了。后来我加了元组返回重试逻辑——检测到401立刻停止当前任务循环并输出醒目的提示日志。第二个坑是文件名清洗。某个文档标题是“年度/Q1汇报/最终版”保存到Windows本地时Open报错排查了半天才发现是标题里的斜杠搞的鬼。后来我写了个sanitize_filename函数把所有/\:*?|字符统一替换成下划线。第三个坑是并发误解。我想当然地用ThreadPoolExecutor同时并发导出多个文件结果触发了一次挺严重的限流。后来改为单线程顺序执行速度虽然慢了但整体稳定性大幅提升。对于脚本工具来说稳比快重要。6.2 合规意识与使用边界最后说一点合规的事。这套思路本质上是对腾讯文档公开Web API的分析和调用它适用于处理你自己拥有权限的文档。如果你拿别人的文档链接去调用会收到权限错误因为鉴权阶段就过不去——这也说明服务端对文档权限的控制是严格的。脚本的去向应该是辅助你管理自己的文件比如批量备份、批量转换格式而不是破解他人文档的访问控制。在使用过程中我建议做好三件事第一控制请求频率不要让脚本的行为特征明显偏离正常用户第二只读取和处理你具备权限的文档不尝试越权访问第三把脚本的使用范围限定在个人备份、企业内部数据打通等正当场景。6.3 后续还能怎么扩展这套API分析思路其实不止能用来导出文档。文档列表接口拿到的fileId还能用于其他操作比如批量修改标题、批量移动文件到指定文件夹、批量设置权限等等。如果你熟悉了腾讯文档的接口风格会发现它整体的设计思路和很多云端协作平台类似列表接口、任务接口、异步轮询接口、下载接口这四个环节几乎能套用到所有云文档产品上。以后再遇到其他平台的批量导出需求用同样的抓包分析方法就可以快速搞定。我个人的习惯是把这套脚本做成一个命令行工具支持指定目录导出、格式选择、增量备份通过比对导出时间戳跳过未修改的文件。增量备份这个功能特别实用每周跑一次定时任务只会导出新增和修改过的文档大幅减少接口调用次数。如果你有跨平台使用的需求把脚本里的会话层用环境变量管理就能很方便地部署在Linux服务器或容器里定期执行。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。