腾讯文档API逆向实战:Python批量导出文档,告别Selenium
发布时间:2026/9/29 23:43:45来源:尧图网络
但凡你在公司里做过一次“帮我把这几十份腾讯文档汇总到本地”的需求应该都能体会什么叫头皮发麻。一份份打开链接、复制正文、整理格式纯手工操作耗掉一整天中间手一抖还容易漏数据。尤其遇到电脑QQ进不去腾讯文档、小程序白屏的时候更是欲哭无泪。以前我第一反应是上Selenium写脚本启动浏览器自动点鼠标后来发现这条路越走越堵慢、脆、还容易被风控。后来我换了个思路——直接抓取并分析腾讯文档网页端的API接口绕开浏览器UI用纯HTTP请求完成列表抓取、批量导出速度和稳定性完全不是一个量级。这篇就把这个“不用Selenium也能批量导出腾讯文档”的完整方案拆开讲清楚带点逆向分析的方法论保证你看完能自己跑起来。1. 为什么弃用Selenium改走API逆向这条路1.1 Selenium批量导出的三个硬伤先说结论Selenium不是不能用而是在“批量”这两个字面前它天生就有短板。第一个问题是慢。Selenium本质是驱动一个真实的浏览器去执行操作每次打开一个文档浏览器都要加载HTML、CSS、JavaScript、图片、字体网络请求没个几十上百个不算完。叠加页面渲染、异步加载、动画过渡一个在线文档从打开到可以复制内容几秒钟根本搞不定。如果导出文档数量是几十上百个那整个流程就是一个漫长的煎熬跑一半断了更是心态爆炸。第二个问题是脆。浏览器自动化对页面结构极其敏感腾讯文档这种纯前端SPA应用改版和灰度发布都很频繁今天跑得好好的XPath/CSS选择器明天可能就失效了。更别提偶尔跳出来的引导弹窗、登录态过期、加载超时这些异常情况写进脚本里就是无穷无尽的分支判断维护成本直接起飞。第三个问题也是最致命的问题容易触发风控。自动化浏览器在服务端眼里有明显特征比如navigator.webdriver标记、异常的时间行为模式、缺少真实用户交互轨迹等。当你用Selenium高频访问、连续导出时大概率会碰到滑块验证、服务降级甚至临时封禁。我见过很多人的自动化脚本跑着跑着就卡在验证码环节最后又得手动介入等于白折腾。1.2 API方案的核心思路与优势其实回过头想一下浏览器里做的任何事情底层都是HTTP请求。你在网页上点“导出为Word”本质上就是前端向后端某个接口发了一个请求后端排队处理完再返回文件地址。Selenium只是在帮我们模拟“点击”这个动作而已但“点击”背后的接口调用我们完全可以自己用代码直接发。这就是API逆向方案的核心逻辑把人和浏览器之间的交互降维成代码和后端接口之间的直连。这样做的好处非常直接速度飞快不用加载页面、不用跑JS渲染一次HTTP请求只要几百毫秒批量任务几分钟就能跑完。稳定性极强不依赖DOM结构页面怎么改版都不影响只要接口没变就能继续用。可控性高接口返回的是结构化JSON拿到直接清洗、入库、重命名数据链路非常干净。风控友好只要频率控制得当接口调用的行为模式和真实用户差距不大不容易触发验证。所以结论很明确批量操作在线文档这类场景API方案是比Selenium更优选的技术路线。下面我就从抓包定位接口开始一步步讲完整套流程。2. 逆向前的准备与抓包定位2.1 环境与工具清单在动手之前先把工具准备好。我用的组合是Python 3.8以上版本核心依赖只有requests库轻量到不需要虚拟环境都能玩。Chrome浏览器直接用自带的开发者工具DevTools抓包不需要额外装Fiddler或Charles。一个已经登录腾讯文档网页版的账号建议在Chrome的普通窗口里登录别用无痕模式。如果你的抓包经验比较少这里给你一个建议先别急着看接口先在网页里手动操作一遍“打开文档列表页——翻页——导出一个文档到本地”把整个链路走通再去看Network面板里产生了哪些请求。这样你心里有数抓包时才不会乱。2.2 用DevTools定位文档列表接口打开腾讯文档网页版首页按F12进入开发者工具切到Network面板然后在小箭头里勾选Fetch/XHR把请求过滤成只有异步接口。这一步是抓包的关键因为页面里的图片、CSS、JS请求实在太多全部展示根本看不过来只过滤XHR接口剩下的就是前端调后端的数据请求我们关心的东西全在这里面。接下来在文档列表区域翻页或者滚动加载观察Network面板里新冒出来的XHR请求。找返回体是JSON、里面包含文档标题、链接、更新时间这类字段的请求。通常这种接口的URL路径里会带有list、feed、doc等关键词一眼看过去就很有辨识度。点击请求条目后重点看四块内容Request URL完整接口地址。MethodGET还是POST这决定你拼参数的方式。Query String Parameters / Payload请求携带的参数比如分页页码、每页数量。Request Headers里面有Cookie、User-Agent、Referer这些都是后续代码里必须带上的。找到目标接口后不要急着写代码先用浏览器的复制功能把请求复制为cURL格式在终端里跑一遍确认能拿到数据再进下一步。2.3 Cookie与登录态的理解打开DevTools的Application面板找到Storage下的Cookies能看到当前站点下所有Cookie键值对。千万别小看这一步API调用的鉴权全靠它。腾讯文档网页端的接口认证走的是Cookie体系常见的几个关键字段包括skey、p_skey、uin等。你用requests库发请求时只要把Cookie字符串原样带过去服务端就认为你是那个已登录的用户。我见过不少人试图用requests模拟登录输入账号密码然后硬闯结果一头撞上滑块验证和短信校验。没必要抓包拿Cookie已经是最稳的方式。Cookie过期了怎么办重新打开一次文档首页Network里多半会刷新请求再从Application面板复制一份新的就行。3. 核心接口解析与反向流程3.1 文档列表接口的字段与分页规则在我当时抓包的过程中文档列表接口的形态大致如下不同账号和版本可能略有差异接口地址以你抓到的为准GET https://docs.qq.com/cgi-bin/doc_feed/list请求参数一般包括page页码从1开始。page_size每页条数常见值是20或50。有时还需要type或folder_id来区分全部文档、我创建的、共享给我的。响应JSON里通常有一个列表字段里面每一项对应一个文档包含doc_id文档的唯一ID后续导出接口要用。title文档标题。url文档访问链接。modify_time最后修改时间。拿到这个接口后就能一次性把账号下有权限访问的所有文档列表捞出来。注意分页遍历时边界条件比如最后一页返回的列表为空时就应该停止循环不然会白白多发一个请求。3.2 导出接口的异步任务机制文档列表搞定了重头戏在导出接口。这里有一个必须理解的概念导出是异步任务。你在网页上点击“导出为Word”后页面不会傻等在那里而是先向后端提交一个导出申请后端收到申请后创建一个任务ID然后把任务丢到队列里慢慢处理。前端拿到任务ID后每隔几秒轮询一次任务状态等状态变成“已完成”再拿下载地址去下载文件。为什么腾讯文档要把导出设计成异步因为文档转换是个吃资源的操作尤其是有几十页内容、大量图片的复杂文档转换耗时可能超过几秒甚至更长。如果做成同步接口HTTP请求会长时间占用连接用户体验极差服务端也更难做负载均衡。所以用异步任务是最合理的设计。那么我们在代码里也要遵循这个流程提交导出任务 → 间隔轮询任务状态 → 任务完成后获取下载链接 → 下载文件。导出接口的大致形态如下POST https://docs.qq.com/cgi-bin/export/start请求体里需要带上doc_id和format字段format的值通常有docx、xlsx、pdf等。腾讯文档支持导出的格式和文档类型有关在线表格导出Excel在线文档导出Word不是所有格式都能随意混用。提交成功后响应里会返回一个task_id然后轮询对应接口直到状态变为成功拿到download_url。3.3 常见返回状态码的解读在调试接口的过程中最有价值的技能是看懂返回状态码。这里整理一下最常见的几种状态码含义排查方向200请求成功正常处理400参数错误检查参数名、请求格式是否与抓包一致403无权限或风控检查Cookie是否过期、Referer是否缺失、触发频率限制404接口地址错误确认URL是否完整、是否带错路径429请求过于频繁降低频率加延时或停止一段时间有时候即使返回200响应里也可能带一个retcode失败码千万不要只看HTTP状态码就认为一切正常。建议每次请求都打印出返回的JSON内容人工确认字段合理性后再继续。4. 完整批量导出脚本实现4.1 最小可用的Python脚本框架理论知识讲完直接上代码。下面的脚本是一个可运行的框架接口地址和参数名我用的是示意写法你实际使用的时候一定要替换成自己抓包看到的值。import requests import time import json from pathlib import Path # 1. 初始化会话把浏览器的Cookie原样带入 session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://docs.qq.com/desktop, Origin: https://docs.qq.com, }) cookie_str 你的Cookie字符串用分号和空格分隔每对键值 for item in cookie_str.split(; ): key, value item.split(, 1) session.cookies.set(key, value) # 2. 获取文档列表 def get_doc_list(page1, page_size20): url https://docs.qq.com/cgi-bin/doc_feed/list params {page: page, page_size: page_size} resp session.get(url, paramsparams, timeout15) resp.raise_for_status() data resp.json() # 下面这个字段路径要以实际返回为准 return data.get(data, {}).get(list, []) # 3. 提交导出任务 def submit_export(doc_id, fmtdocx): url https://docs.qq.com/cgi-bin/export/start payload {doc_id: doc_id, format: fmt} resp session.post(url, jsonpayload, timeout15) resp.raise_for_status() result resp.json() if result.get(retcode) ! 0: raise RuntimeError(f提交导出任务失败: {result}) return result[data][task_id] # 4. 轮询导出任务状态 def wait_for_export(task_id, max_wait120): url https://docs.qq.com/cgi-bin/export/query waited 0 while waited max_wait: resp session.get(url, params{task_id: task_id}, timeout15) result resp.json() status result.get(data, {}).get(status) if status done: return result[data][download_url] elif status failed: raise RuntimeError(f导出任务失败: {result}) time.sleep(3) waited 3 raise TimeoutError(f导出任务超时: {task_id}) # 5. 下载文件 def download_file(url, save_path): resp session.get(url, timeout60) resp.raise_for_status() Path(save_path).write_bytes(resp.content) # 6. 批量导出主流程 def batch_export(page_start1, page_end5, fmtdocx): save_dir Path(exported_docs) save_dir.mkdir(exist_okTrue) for page in range(page_start, page_end 1): print(f正在处理第 {page} 页) docs get_doc_list(pagepage) if not docs: print(当前页无数据提前结束) break for doc in docs: doc_id doc[doc_id] title doc[title] safe_title title.replace(/, _).replace(\\, _) print(f导出文档: {title}) try: task_id submit_export(doc_id, fmtfmt) download_url wait_for_export(task_id) file_path save_dir / f{safe_title}.{fmt} download_file(download_url, file_path) print(f保存成功: {file_path}) except Exception as e: print(f导出失败: {title}, 错误: {e}) # 两个文档之间留一点随机间隔降低风控概率 time.sleep(1 (hash(doc_id) % 3)) if __name__ __main__: batch_export(page_start1, page_end3, fmtdocx)这个脚本把整个链路拆成了五个函数会话初始化、获取列表、提交导出、轮询状态、下载文件。你在自己电脑上跑的时候只需要替换cookie_str、接口地址、参数路径三处地方就能用。4.2 运行效果与耗时对比我拿自己账号下的一批文档做过对比测试结果非常直观用Selenium自动化导出10个文档平均每个要等页面加载加转换耗时大概5到8分钟用API方案跑同样的10个文档从提交任务到全部下载完成总耗时通常不会超过1分钟。这里面最大的时间差来自Selenium每个文档都要打开一次页面渲染而API方案全程只是发几个轻量请求。当然导出任务真正的耗时还是取决于文档本身的复杂度。几十页的大文档服务端转换本身就要花几秒到十几秒这个没法绕过去。但相比Selenium省掉的页面加载和渲染时间已经非常可观了。还有一个容易忽略的点get_doc_list遍历分页时如果某页返回了空列表要主动break不然脚本会以为是网络问题反复重试最后一页浪费时间。另外建议把任务进度和失败记录输出到日志文件里这样就算批量任务中断了也能知道哪些文档成功、哪些需要重跑而不是两眼一抹黑。4.3 频率控制与礼貌抓取接口方案的性能虽然好但不代表可以肆无忌惮地高并发。这里给你几个我实操下来觉得比较合适的控制策略每个文档导出完成后至少间隔1到3秒再提交下一个任务既照顾了服务端压力也让请求节奏更接近真人操作。单次批量任务不要贪多建议控制在50个文档以内跑完一批歇几分钟再跑下一批。如果发现响应里出现验证码相关字段或429状态码立刻停止脚本等待至少半小时再重试。一句话总结接口逆向是为了提升效率不是为了压垮目标服务。用温和的频率去跑反而能跑得更长久。5. 常见问题与排查技巧实录5.1 接口返回400或403的排查清单批量导出跑起来之后最先遇到的基本都是权限和参数问题。我整理了一张根据错误现象快速定位的排查表现象可能原因解决方案返回403Cookie过期或被服务端判定失效重新打开文档首页复制最新Cookie返回403缺少Referer或Origin头在headers里补上https://docs.qq.com返回400请求参数名或格式和抓包不一致回到DevTools逐字段比对Payload返回400提交的任务格式不支持当前文档类型表格导xlsx文档导docx别混用返回200但retcode非0业务级拒绝比如无导出权限检查该文档是否共享给当前账号碰到400的时候最直接的办法是把抓包时浏览器发出的完整请求复制成cURL在Postman里跑一遍再把Postman自动生成的代码和你的脚本对比参数差异一眼就能看出来。5.2 导出任务一直处于处理中状态如果wait_for_export轮询了很久状态始终不是done或failed大概率是以下三种情况第一种文档太大或太复杂服务端转换本身就需要很长时间超出你的轮询等待上限。可以把max_wait调大到300秒或者把轮询间隔从3秒改成5秒减少请求频率。第二种当前账号对该文档没有导出权限。有些文档虽然能打开阅读但所有者关闭了导出权限这种情况下导出任务会一直卡住。判断方法很简单网页上手点一次导出看客户端的表现。第三种并发任务太多导致服务端排队积压。如果你同时开了多个脚本在跑任务建议先停掉其他任务让服务端处理完再继续。5.3 下载文件失败或文件损坏任务状态变成done拿到download_url后下载这一步也有几个常见的坑。最典型的问题是下载时遇到302跳转。服务端返回的download_url有可能是一个临时跳转地址需要跟随跳转才能拿到真实文件。requests库默认是跟随重定向的但如果自己指定了allow_redirectsFalse就会拿到一个空的响应体。保险起见用默认行为就行。另一个坑是文件以二进制形式写入时编码问题。文件下载要始终用resp.content而不是resp.text因为resp.text会按文本解码Word或Excel这类二进制文件转了文本再写回去文件就废了。直接用Path.write_bytes可以避免这个问题。文件名里如果包含/、\、:这些非法字符在Windows下保存会直接报错我在脚本里先做了清洗替换这个细节一定要记得。5.4 触发风控后的应对方法最后聊一下最让人头疼的风控问题。即便你频率控制得再好长时间大范围导出还是有概率触发服务端的异常检测。一旦发现请求开始返回验证码链接、滑块页面或要求重新登录第一步是立刻停止脚本不要心存侥幸继续重试只会加重限制。停半小时以上换回浏览器手动操作几次把账号活跃度恢复一下再继续。第二降低单批任务量。把原来一次跑100个文档改成一次跑20个跑完歇几分钟再跑下一批。这样单次任务数据量小行为特征更接近正常用户。第三尽量在工作时间跑和生活作息的真实用户保持一致。凌晨三四点批量导出大量文档这个行为特征本身就非常可疑。我自己踩过不少坑之后对风控的心态已经变成尊重规则、控制节奏、留下余量。批量导出本来就是为了提升效率没必要为了快那几分钟把自己账号搭进去。最后再分享一个我从实战里摸索出来的小技巧Cookie过期后不用重新登录账号再进页面抓包。你只需要在浏览器里刷新一下文档首页Network面板的第一个XHR请求里通常就带上了最新有效的Cookie直接复制过来就能继续跑脚本。这个操作比重新登录、重新走验证流程省事得多尤其是挂着几个脚本账号场景下能帮你少很多麻烦。整体来说这套API逆向前期需要一点耐心去抓包和分析但一旦链路跑通后续同类需求基本就是改改参数直接复用性价比极高。
网站建设高端定制企业官网