在线文档内容提取与转换Word:JS与Python双方案详解

📅 发布时间:2026/10/17 14:17:57
在线文档内容提取与转换Word:JS与Python双方案详解
很多人都有过这种经历在网上看到一份整理得很好的文档想复制几段存进笔记结果不是要会员就是要下载券甚至鼠标右键都被禁掉。我以前也常卡在这一步后来干脆自己动手写了两个小工具一个塞在浏览器控制台里临时抓取页面上的纯文本另一个用 Python 跑一遍直接帮你生成 Word 文档。今天把思路、代码和踩过的坑一次性整理出来希望能帮到有同样需求的朋友。先说明一下下面提到“某文库平台”的地方指的就是市面上常见的在线文档分享站点。我不会强调具体名字也不鼓励绕过任何付费和会员机制。以下操作仅面向已经能在页面上正常阅读、但复制不方便的文档用途限定在个人学习、笔记整理和资料归档。技术本身是中性的怎么用是每个开发者自己的选择。1. 先理清楚文库文档下载到底卡在哪1.1 在线文库页面的典型结构很多在线文库页面看起来是一篇完整的文章但实际上由很多模块拼起来顶部标题、作者信息、目录索引、正文内容、底部广告、相关推荐。正文部分往往不是一次性渲染出来的有的做成“第1页、第2页、第3页”这样手动翻页有的往下滚动时自动加载有的甚至把文字藏在图片里。这些不同的结构直接决定了后续用 JS 还是用 Python 去抓取时的难度。我通常做的第一步是打开开发者工具查看正文区域对应的 HTML 节点。一般文字都在div、span、p这些标签里如果能直接在 DOM 中看到完整的句子那基本就可以用脚本提取。如果看到一堆canvas或者背景图说明页面走的是图片化渲染脚本就只能做到 OCR 的层面了复杂度会上升一截。对大多数文库页面来说前一种情况最常见所以后面的方法也主要针对这种结构。1.2 两种技术方案的适用场景对比在动手之前我先用表格对比一下浏览器 JS 脚本和 Python 爬虫的差别这能帮你决定该用哪套方案。对比维度浏览器控制台 JS 脚本Python 爬虫脚本运行环境任何浏览器无需安装本机 Python 环境上手难度低适合临时用中等需要装依赖批量处理不适合需要手动打开页面适合可循环抓取多篇生成 Word不方便通常只复制文本方便直接生成 docx 文件翻页处理手动翻页多次执行自动拼接可循环反爬应对依赖浏览器环境较容易被当正常访问需要处理请求头、频率等我平时是这样选的如果只是临时想存一篇文档直接在浏览器里执行 JS 脚本就行全程不用切工具几秒钟拿到纯文本。如果是要整理一批文档或者说想把结果干净地存成 Word 文件那就走 Python一套流程跑完省心很多。1.3 为什么选择这两条路线选择浏览器 JS 脚本最重要的原因是没有开发环境成本。浏览器本身就是世界上最普及的运行时打开控制台就能写代码特别适合处理“一次性、快进快出”的需求。而且浏览器已经帮我们处理了登录态、Cookie、JS 渲染这些麻烦事脚本只需要关心页面上的文本节点就够了。选择 Python则是因为我最终需要的是“可以直接编辑的 Word 文档”。Python 生态里的python-docx可以很方便地创建段落、设置标题样式和保存文件天然适合做文本整理和数据清洗。另外 Python 脚本跑起来可以加翻页循环、错误重试、频率控制这些都是手动操作很难做到的事情。两条路线各有各的舒适区放在一起刚好覆盖了多数需求。2. 动手前必须看懂的提取原理与细节2.1 JS 脚本提取文本的底层逻辑浏览器内执行 JS 提取文本本质上就是读取页面的 DOM 树。document.body.innerText是最直接的方式它会返回当前页面中所有可见的文本内容并且会把 HTML 标签之间的换行自动转换成\n。好处是简单坏处是会把导航栏、侧边栏、推荐列表里的文字也一并捞进来后续需要靠正则清洗。如果只想提取正文区域可以给脚本加一个范围限定。比如页面正文放在某个特定class的容器里就可以用document.querySelector找到这个容器再读取它的innerText。这样拿到的文本干净很多也更容易直接使用。需要注意的是很多文库页面会做懒加载滚动到哪一页哪一页的内容才插入 DOM。所以执行脚本之前最好先把页面从头到尾滚动一遍让所有分页内容都加载出来否则只会抓到当前可见的几段。2.2 Python 爬虫如何拿到完整正文Python 爬虫的思路稍微绕一点先用requests把目标页面的 HTML 抓下来再用BeautifulSoup解析 DOM定位正文节点然后把文字提取出来。和浏览器脚本相比它不需要看到页面渲染效果只要接口返回的 HTML 里有数据就能直接处理。关键点有三个第一请求头必须伪装成正常浏览器至少要带上User-Agent否则部分站点会直接拒绝连接。第二页面可能出现编码识别错误遇到乱码时要手动指定编码比如resp.encoding resp.apparent_encoding。第三分页内容可能是独立请求加载的这时候需要找到真正的分页接口分析 URL 里的页码参数再循环请求。判断分页是否结束通常看循环请求后返回的文本有没有新增内容或者页面提示是否到达末页。2.3 从纯文本到 Word 文档的格式处理把文本抓到以后最容易被低估的就是排版清洗。从网页上提取出来的文本往往有大量多余空行、行首空格、重复的标签文本直接塞进 Word 会很难看。我一般会先做几轮处理把连续两个以上的换行符合并成一个把每行首尾的空格去掉删除空字符串行再根据句号、问号、感叹号来断句合并段落。python-docx里面正文可以用doc.add_paragraph添加标题可以用doc.add_heading添加。如果原始文档里有小标题可以在清洗阶段先识别出来比如判断行尾是否没有标点、是否为孤行文本等。设置行距、首行缩进也很方便用paragraph.paragraph_format.first_line_indent和line_spacing就能搞定。这一步做得好不好直接决定生成的 Word 是“能看”还是“能用”。3. 手把手实操两个脚本直接照着改3.1 方案A浏览器控制台 JS 脚本手动抓取这个脚本我平时用得最勤。操作步骤很简单打开文库文档页面按下 F12 打开开发者工具切到 Console 面板把下面这段代码粘贴进去回车执行。(function () { // 先尝试只取正文区域选择器需要按实际页面修改 const article document.querySelector(.reader-word-layer, .content, .doc-content); const rawText article ? article.innerText : document.body.innerText; // 清洗去掉空行和首尾空格 const lines rawText.split(\n); const cleaned lines .map(line line.replace(/\s/g, ).trim()) .filter(line line.length 0) .join(\n); // 复制到剪贴板 if (navigator.clipboard) { navigator.clipboard.writeText(cleaned).then(() { console.log(复制成功总计字符数, cleaned.length); }); } else { // 老版本浏览器降级方案 const textarea document.createElement(textarea); textarea.value cleaned; document.body.appendChild(textarea); textarea.select(); document.execCommand(copy); document.body.removeChild(textarea); console.log(复制成功总计字符数, cleaned.length); } })();这段脚本做的事情很简单优先读取指定区域里的文本如果找不到正文容器就退回到整页文本然后按换行切分逐行处理空白丢掉空行再拼起来复制到剪贴板。我用下来最舒服的一点是它不会把页面上一堆“广告”“推荐阅读”之类的东西带进笔记里基本能保证复制出来的内容是可以直接用的。但有一个地方必须提醒如果文档分了多页这脚本只能抓到当前已经加载到 DOM 里的内容。解决办法是手动翻页或者在翻页后重新执行一次脚本把几次复制的结果手动粘到一起。追求全自动的话还是看下面 Python 方案。3.2 方案BPython 脚本全流程自动化批量处理或者想直接生成 Word 的时候我用的是 Python。先安装三个依赖包pip install requests beautifulsoup4 python-docx下面是简化但可用的核心脚本思路是循环请求每一页内容解析正文节点清洗后写入同一个 Word 文档。import re import time import requests from bs4 import BeautifulSoup from docx import Document from docx.shared import Pt from docx.enum.text import WD_LINE_SPACING HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_page(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding if resp.status_code ! 200: raise Exception(f请求失败状态码: {resp.status_code}) return resp.text def parse_text(html): soup BeautifulSoup(html, html.parser) # 根据实际页面选择正文容器 content soup.select_one(.reader-word-layer, .content, .doc-content) if not content: content soup.body text content.get_text(\n) # 清洗文本 lines text.split(\n) lines [re.sub(r\s, , line).strip() for line in lines] lines [line for line in lines if line] return \n.join(lines) def save_to_word(text, output_path): doc Document() for line in text.split(\n): p doc.add_paragraph(line) p.paragraph_format.first_line_indent Pt(24) p.paragraph_format.line_spacing_rule WD_LINE_SPACING.ONE_POINT_FIVE doc.save(output_path) print(已保存, output_path) # 示例循环抓取多页文本 def main(): base_url https://wenku.example.com/view/xxxx?page{page} all_texts [] for page in range(1, 6): url base_url.format(pagepage) html fetch_page(url) text parse_text(html) if not text: break all_texts.append(text) print(f第 {page} 页抓取完成字符数 {len(text)}) time.sleep(2) # 控制频率降低对方服务器压力 full_text \n.join(all_texts) save_to_word(full_text, output.docx) if __name__ __main__: main()脚本的核心逻辑就是三步请求页面、解析正文、清洗保存。需要特别注意的是base_url里的{page}占位符实际页面中分页参数的名称可能是page、pn、index也可能是 POST 请求体里的字段。这个没有统一标准需要打开开发者工具的 Network 面板点一下第2页看看请求参数长什么样然后把代码改成对应格式。另外time.sleep(2)这个等待时间是我常用的默认值。太快容易触发风控太慢又浪费效率两秒算是一个适合多数站点的折中值。3.3 参数设置与翻页策略详解很多初学者在这里栽跟头明明脚本没问题但只抓到第一页内容。为什么因为文库平台的分页通常有两种形态。一种是服务端分页点击下一页时 URL 改变返回的是新 HTML这种情况下把页码参数替换成循环变量就行。另一种是前端动态加载点击下一页时 URL 不变而是发一个 AJAX 请求返回 JSON 或一小段 HTML。这种情况下直接循环 URL 是没用的必须模拟那个 AJAX 请求。怎么判断属于哪种打开开发者工具切到 Network 标签点击下一页观察名称列里新增的请求。如果顶部文档 URL 变了那就是服务端分页如果只是多了一个带page或callback参数的请求那就是动态加载。对于后者需要把那个请求的完整 URL、请求头、请求体复制出来在脚本里模拟。还有一个细节分页结束的判断不能只看“有没有新文本”。有些页面最后一页会重复显示第一页内容或者返回一段页脚信息。我通常的做法是记录上一次抓到的文本内容如果连续两次相同就认为到底了直接跳出循环。这个改动虽然只有几行却能避免 Word 文档里出现大段重复内容。4. 避坑指南常见问题与排查实录4.1 只抓到标题正文没抓到这是最典型的问题。我第一次写脚本时就遇到过控制台里明明能看到整个页面但打印出来的innerText只有标题和导航栏。后来发现原因是正文是用懒加载渲染的页面没有滚动到底部正文节点还没进入 DOM。解决方法也很简单执行脚本前先把页面从上到下滚动一遍或者用window.scrollTo(0, document.body.scrollHeight)强制滚动。如果滚动后还是不行就要考虑正文是不是放在 iframe 里。如果是 iframe浏览器控制台里需要先切换到对应的 frame 上下文再执行脚本Python 方案里则需要单独请求 iframe 的 URL而不是主文档 URL。刚才还遇到过一种情况页面显示了“正在加载中.…”的字样文字被脚本生成了。这种时候等待时间不够无论 JS 还是 Python都需要增加等待或者重试机制。4.2 翻页后内容重复或丢失内容重复通常是因为选择器范围太大。比如document.body.innerText会把页面里所有隐藏但是存在于 DOM 的旧分页文本也读取出来导致下一页和上一页内容混在一起。解决方法是尽可能缩小正文区域确保每一页只读取当前页对应的容器。内容丢失则往往是因为循环速度太快页面还没渲染完成就请求下一页。Python 脚本里可以通过增加time.sleep的时长来处理同时加入重试逻辑如果某一次解析出来的文本长度为0就重新请求一次最多重试3次。这个策略在稳定性上很有帮助基本能避免偶发的网络抖动。4.3 生成 Word 后排版混乱怎么补救如果你把抓到的纯文本一键写入 Word大概率会发现两个问题一是很多短行被单独拆成段落看起来像散文不像文章二是标题和正文的样式完全一样找不到阅读的节奏感。我自己的处理步骤是先合并短行在清洗阶段把连续几个较短的行合并成一个段落以句号、问号、感叹号为断句依据。然后根据行文特征识别标题比如字数不超过20字、不包含句末标点、后面跟着较长正文段落的行就给它们应用heading样式。最后统一设置正文字体和行距。这样生成的 Word 才真正适合后续编辑而不是千篇一律的文本堆叠。如果你想更快一点也可以先把所有文本存成 txt用 Word 自带的“样式”功能手动调整。但既然已经走到 Python 这一步了顺手加些排版规则并不复杂建议一步到位。4.4 关于版权与使用边界的个人建议工具写出来之后用什么场景、怎么用比工具本身更重要。我第一次写完脚本后确实有段时间看到什么文档都想批量拉下来有种“集邮”的快感。后来冷静下来一想很多文档是别人花时间整理甚至付费购买的批量下载并公开传播实际上是在伤害原作者的利益。所以我现在的做法是只对允许免费阅读的页面做文本提取只用于做笔记、整理学习资料绝不用于商业用途也没有必要去碰那些明显属于会员专享的内容。合规底线其实是给自己省麻烦。爬虫不是不能写关键是要尊重对方的服务条款和网络空间的安全边界。一个稳定的长期方案一定是对双方都友好的方案。另外如果只是想把某篇文档转成 Word 自用手动复制粘贴也不是不行。我写脚本的原因只是懒得重复劳动而不是想通过技术手段突破平台限制。这个心态摆正了后续遇到问题也会更坦荡。最后再分享一点个人体会这套 JS 加 Python 的组合拳我前前后后用了大概一年小修小补了很多次。最大的感受是没有一劳永逸的脚本因为页面结构会改接口参数会变唯一不变的是处理思路——先看页面结构再选择提取方式最后做好清洗和保存。你现在照着文章里的代码能跑通不代表过几个月还能跑通关键是要学会自己打开开发者工具去定位问题。我自己手头也维护着一个把结果生成 Word 的小模板偶尔同事问起怎么存文档我就把方案发给他们。绝大多数人用到的功能其实就两个复制当前页文本和批量生成 Word。这两件事都不难难的是愿意花半小时把随机抓来的乱码整理成可阅读的文档。希望这篇文章能帮你少走点弯路。