☰
Python文档批量处理实战:格式转换、文本清洗与办公自动化
2026/10/20 22:05:11 网站建设 项目流程

拿文档处理说事儿,很多人第一反应是:这还不简单,打开文件,复制粘贴,另存为,完事儿。真到手上批量处理几十个、上百个文件时,你很快会发现,事情完全不是想象中那样。编码乱码、标题格式不统一、PDF里文字顺序错乱、Word里的表格读不出来、来回手动整理想死的心都有。这时候,用Python写一个小脚本来做文档文章处理,几乎是所有干过这活儿的人都会走的一条路。

这篇文章我不想讲大而全的教程,而是从一个实际动手者的角度,把文档文章处理这个需求拆开来看:它到底在解决什么问题,选哪些工具顺手,核心流程怎么搭,实际操作中会踩哪些坑。内容覆盖纯文本、Markdown、Word、PDF等常见格式,适合正在做办公自动化的同学、需要批量整理文章的内容运营,以及正准备给手里的杂七杂八文档做“大扫除”的人。下面所有代码和步骤,都是可以直接照着跑的程度。

1. 文档文章处理:先搞清你属于哪种需求

1.1 三类最常见的“文档文章处理”场景

我接触过不少提这个需求的人,表面上看大家都是说“帮我处理一下文档”,但细聊下来,需求完全不同。大致能分成三类。

第一类是办公自动化。典型问题是:手里的报表是几十个Word文件,要提取里面的标题、日期、正文段落;或者说有一大堆PDF,需要批量把首页信息抽出来;又或者要把一堆旧文档从一种格式统一转成另一种格式。这类需求的核心是“提取”和“转换”,对文本精确度要求高,弄错一个数字可能就麻烦。

第二类是内容生产与整理。自媒体编辑、技术写作、课程运营这类人群,通常手里有一堆草稿、素材、历史文章,格式五花八门,有的在Markdown里,有的在Word里,还有从网页上复制下来带着一堆乱七八糟标签的文本。他们想要的不是简单提取,而是把内容整理成统一风格,批量做错别字检查、字数统计、重复内容删减。

第三类是数据分析与文本挖掘。这类人往往不关心单个文档长什么样,而是关心一堆文档拼起来之后的整体信息。比如把所有反馈文件合并,清洗掉噪音,再做分词、关键词统计。这类需求核心是“清洗”和“结构化”,格式对不对远没有内容能不能被机器读入重要。

三类场景的共性是:手动处理太累,容易漏,而且没法复现。今天花两小时整理好,明天又来一批新文件,又得重来。用程序处理的核心价值不是“自动”,而是“可复现、可批量、可审计”——同样的规则跑一百次都不会偷懒。

1.2 为什么偏偏选 Python

能处理文档的语言不止一种,但Python在这个领域的综合上手成本是最低的。理由不外乎三点:一是生态齐全,文本处理、PDF解析、Word读写、网页正文提取都有很成熟的现成库;二是代码表达力强,几行就能完成一个相对复杂的处理流程,不需要像其他语言那样写一大坨底层逻辑;三是社区资源足够密,绝大多数你遇到过的奇怪问题,搜一搜基本都有对应的处理思路。

另外一点很多人没注意,Python在“脏数据”场景下特别宽容。文档处理免不了遇到某个文件格式损坏、编码不对、中间夹着特殊字符,Python可以用异常捕获把这些情况拦下来,做成“跳过”或者“记录到日志里”,不会因为一个坏文件让整批任务崩掉。这是写业务脚本时很关键的能力。

当然,Python不是万能的。特别复杂的PDF版面还原、扫描件OCR、复杂表格结构抽取,还得配合专业工具甚至人工介入。但作为处理链条里的“调度中枢”和“清洗工厂”,它非常合适。

2. 环境准备与工具选型:基础不牢,后面全白搭

2.1 不同文档类型对应的核心库

先解决一个常见困惑:处理文档到底该用哪些库?答案取决于你手里的文件类型,不要贪多,只装用得上的。

文档类型推荐库主要用途
纯文本 / Markdown直接使用内置open()、re、pathlib读写、正则清洗、批量操作
Word .docxpython-docx读取段落、标题、表格,生成Word
老版 .docantiword命令行(不是Python库)虽然麻烦,但比硬写解析靠谱
PDF(文字版)PyMuPDF(fitz)为主,pdfplumber为辅提取文字、按页定位、处理表格
HTML网页BeautifulSoup4+lxml,或trafilaturaHTML清洗、正文抽取
通用文本匹配re+textdistance正则匹配、重复内容近似度
中文文本分析jieba、pycorrector分词、错别字检查(按需引入)

这里我想多说一句PDF的情况。很多朋友图省事用某种在线工具转换PDF,结果不是乱码就是版式位移。用PyMuPDF提取纯文字版PDF是目前体验相对稳定的方案,速度也快。如果遇到需要保留表格结构、按行坐标读取的情况,pdfplumber更合适,但它比PyMuPDF慢不少。一般建议先用PyMuPDF跑,遇到明显错乱再针对局部文件换工具,而不是上来就全量用最重的方案。

.doc这类老格式最好别直接读。python-docx只支持新版的.docx,旧版.doc本质上是二进制复合文档,直接用Python解析的成本极高。可行的路线是用antiword先转成纯文本,或者让用户先另存为新格式。这个我在后面常见问题里还会再次强调。

2.2 一套稳妥的环境搭建流程

我不会建议你直接往全局环境里装一堆库。文档处理涉及的库依赖复杂,尤其lxml、PyMuPDF这类带编译产物的包,全局环境下很容易和别的项目冲突。建议新建一个专用虚拟环境。

mkdir doc_handler cd doc_handler python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate

然后安装核心依赖:

pip install python-docx PyMuPDF beautifulsoup4 lxml trafilatura textdistance

如果你要处理PDF里的表格,再补充:

pip install pdfplumber

安装完成后,建议第一时间验证一下版本,免得后面API差异造成报错。

import docx import fitz from bs4 import BeautifulSoup print(docx.__version__ if hasattr(docx, "__version__") else "python-docx is ready") print(fitz.__doc__)

这一步看起来简单,但能省掉不少排查时间。尤其是PyMuPDF的版本迭代很快,不同版本之间提供的方法名略有差异,我在网上看到过不少“昨天还能跑今天报错”的案例,基本都跟升级后API变化有关。配好环境后,建议把用到的版本号记到requirements.txt里留个底。

3. 核心处理流程拆解:读取、清洗、结构化、输出

3.1 读取阶段:编码和文件类型是两个大坑

文档处理的第一个环节是读取,这里翻车率最高。纯文本文件最容易出问题的是编码。很多同事传来一个.txt,文件名上写着“UTF-8”,实际上是用Windows记事本另存为“ANSI”格式(即GBK),Python用默认的utf-8编码去读,直接抛UnicodeDecodeError。就算不抛错,读出来也是一堆乱码。

稳妥的做法是:优先用文件头部BOM判断,没有BOM时再用常见编码做尝试读取。

from pathlib import Path def read_text_file(filepath): path = Path(filepath) raw = path.read_bytes() if raw.startswith(b'\xef\xbb\xbf'): return raw.decode('utf-8-sig') if raw.startswith(b'\xff\xfe') or raw.startswith(b'\xfe\xff'): return raw.decode('utf-16') for enc in ('utf-8', 'gbk'): try: return raw.decode(enc) except UnicodeDecodeError: continue return raw.decode('utf-8', errors='replace')

这个函数的思路是:先看有没有BOM,有就直接按对应编码处理;没有就试探性解码。gbk在中文Windows环境里非常常见,放在utf-8后面备用比较合理。最后实在不行就用errors='replace',把个别无法识别的字符替换成占位符,保证程序不崩,至于字符丢失情况,我们再在日志里标记出来。

读取普通纯文本还有一个隐蔽问题:行尾符。Windows下是\r\n,Linux下是\n,如果直接按行切分,可能带着一个\r干扰后面的正则匹配。通常在读取后统一做一次归一化:

text = text.replace('\r\n', '\n').replace('\r', '\n')

这一步很便宜,但能让后面处理少很多没头没尾的怪问题。

对于 Word 和 PDF 文档,编码问题不明显,但要区分“读取文本”和“读取结构”。比如python-docx读取.docx时,表面上是读段落,实际上每个段落还有样式、层级关系。如果你只想要纯文本,直接遍历paragraphs取text即可;如果你要保留标题层级,就得把style.name一并取出来。PDF 更麻烦,它是按页面、按文本块组织的,读取时还要考虑坐标顺序,这个下面单独讲。

3.2 清洗阶段:文本清洗怎么做才不掉东西

清洗是所有环节里最考验细致程度的。处理不好,轻则多余的空行没去掉,重则把文章里正常需要的符号、结构给删没了。

先建立一个原则:清洗过程必须尽量把操作拆成单步,且每步只做一件明确的事。我在实际项目里总结的一套最小清洗流程是这样的:

  • 统一换行符
  • 去掉“零宽字符”和不可见控制字符
  • 合并连续空行为一个,去除行首行尾空白
  • 清理全角空格、不间断空格
  • 按需替换错误标点
  • 去掉网址里多余参数(可选)

前四步几乎是所有场景都必须做的。代码可以这样写:

import re ZERO_WIDTH_CHARS = re.compile(r'[\u200b\u200c\u200d\ufeff]') CONTROL_CHARS = re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f]') NBSP = re.compile(r'\u00a0') def clean_text(text: str) -> str: text = text.replace('\r\n', '\n').replace('\r', '\n') text = ZERO_WIDTH_CHARS.sub('', text) text = CONTROL_CHARS.sub('', text) text = NBSP.sub(' ', text) lines = [line.strip() for line in text.split('\n')] cleaned_lines = [] blank_count = 0 for line in lines: if line: blank_count = 0 cleaned_lines.append(line) else: blank_count += 1 if blank_count <= 1: cleaned_lines.append('') text = '\n'.join(cleaned_lines).strip() return text

注意我这里的空行合并逻辑:连续多个空行只保留一个。如果你处理的是Markdown,空行在块级语法里是有含义的,所以不能全部删掉,保留一个空行最稳妥。CONTROL_CHARS这个正则不要误删\n和\t,所以我排除了这几个常用字符。

关于标点替换,我踩过一次坑。有些从网页复制的文稿里,中文引号被转成了英文引号,逗号有的是中文全角,有的是英文半角。如果后面要做分词或者统计,最好统一成中文标点;但如果做的是搜索引擎输入,反而可能保留原样更好。所以这一步要当作业务规则来定义,不要一把梭。建议把规则集中放在一个配置列表里,方便后面改:

PUNCT_RULES = [ (',', ','), (';', ';'), ('!', '!'), ('?', '?'), (':', ':'), ] def normalize_punct(text: str) -> str: for old, new in PUNCT_RULES: text = text.replace(old, new) return text

这里需要提醒:不要把这种替换用在代码块或网址里,否则会破坏内容。所以更严谨的做法是先临时用占位符保护代码块,清洗完再还原。

3.3 结构化阶段:标题识别、分块、切句

清洗完的文本是一长串,但很多时候我们需要的是“按文章结构”来管理。比如生成目录、统计每个章节字数、按标题拆分成独立文档。

结构化第一步是标题识别。不同来源规则不同:

  • Markdown 文件里,标题是#开头的一行,正则很好写:^#{1,6}\s+
  • Word 文档里,标题通常对应样式名里的“标题1”“标题2”
  • 纯文本里,往往是“第X章”“一、”这类编号前缀

处理时不要只靠一种规则,建议把所有候选规则都跑一遍,再结合行长度做判断。举个例子:

import re MD_HEADING = re.compile(r'^(#{1,6})\s+(.*)$') CN_NUM_HEADING = re.compile(r'^(第\s*[一二三四五六七八九十百千0-9]+\s*[章篇节])\s*(.*)$') CN_SECTION_HEADING = re.compile(r'^([一二三四五六七八九十]+、)\s*(.*)$') def detect_heading(line: str): for pattern in (MD_HEADING, CN_NUM_HEADING, CN_SECTION_HEADING): m = pattern.match(line) if m: return (len(m.group(1)), m.group(2).strip()) return None

这个函数返回的是(层级, 标题文字)。如果没有匹配到,返回None。需要注意的是不要只看行首正则,还要过滤“只有标题没人超过50个字”之类的可疑行,否则有些索引行也被误判成标题。

结构化第二步是分块。分块有两种思路。一种是按标题层级把内容拆成树状结构;另一种是固定长度切块,比如按段落、按句长切。如果只是做批处理整理,按标题拆成列表就够了。

def split_by_heading(text: str): blocks = [] current_heading = None current_level = 0 current_content = [] for line in text.split('\n'): heading = detect_heading(line) if heading: if current_heading is not None: blocks.append({ "level": current_level, "title": current_heading, "content": '\n'.join(current_content).strip() }) current_heading = heading[1] current_level = heading[0] current_content = [] else: current_content.append(line) if current_heading is not None: blocks.append({ "level": current_level, "title": current_heading, "content": '\n'.join(current_content).strip() }) return blocks

结构化第三步是切句。基础方案是按中文句末标点切分。但不建议直接用re.split把句子全拆开,因为会破坏引号里的对话和省略号。我一般会保留简单规则:句号、问号、感叹号后跟着换行或空格时才切。

SENTENCE_BOUNDARY = re.compile(r'(?<=[。!?!?])\s*') def split_sentences(paragraph: str): parts = SENTENCE_BOUNDARY.split(paragraph) return [p.strip() for p in parts if p.strip()]

这个正则用的是零宽后行断言,只切在标点之后、空白之前的位置,能保留中文引号内部的完整句子。注意,它不会处理省略号“……”结尾的句子,所以如果文本里省略号很多,可以在这个正则里再加一个“…”的边界条件。

4. 实操案例:批量整理一整个文件夹的文章

4.1 案例背景与脚本设计

拿一个很常见的需求来演示:某内容团队把一个文件夹里散落的.txt、.md、.docx文件,统一转成带标题结构的纯文本,并且生成一份总目录和字数统计。这个需求我做过不止一次,看起来很基础,但代码里包含的异常处理、编码处理、文件类型分流,是几乎所有文档处理脚本都要用到的骨架。

我先给出脚本整体设计。数据源目录假设是./articles_input,里面可能有子目录,输出目录是./articles_output。脚本要做四件事:

  1. 递归扫描所有.txt、.md、.docx文件
  2. 按不同扩展名调用对应读取函数
  3. 清洗后按文章原始结构输出成统一格式
  4. 生成一个output_index.csv,记录文件名、标题、字数、输出路径

设计上一定要把“读取”和“处理”分开。因为不同类型的文件读取方式差异大,但进入清洗阶段后,它们就统一成纯文本了,这样后续逻辑不用为每种格式写一遍。

4.2 核心代码和关键写法说明

主循环如下:

from pathlib import Path import csv from docx import Document INPUT_DIR = Path("./articles_input") OUTPUT_DIR = Path("./articles_output") OUTPUT_DIR.mkdir(parents=True, exist_ok=True) SUPPORTED_SUFFIX = {".txt", ".md", ".docx"} def read_docx(filepath: Path) -> str: doc = Document(str(filepath)) parts = [] for para in doc.paragraphs: text = para.text.strip() style = para.style.name if para.style else "" if not text: parts.append("") continue if style.startswith("Heading"): level = int(style.replace("Heading ", "").strip() or "1") prefix = "#" * level + " " parts.append(prefix + text) else: parts.append(text) return "\n".join(parts) def extract_title(filepath: Path, text: str) -> str: lines = [l.strip() for l in text.split("\n") if l.strip()] head = detect_heading(lines[0]) if lines else None if head: return head[1] return filepath.stem def process_one_file(filepath: Path): suffix = filepath.suffix.lower() if suffix == ".docx": raw_text = read_docx(filepath) else: raw_text = read_text_file(filepath) raw_text = raw_text.replace("\r\n", "\n").replace("\r", "\n") cleaned = clean_text(raw_text) title = extract_title(filepath, cleaned) output_name = filepath.stem + ".txt" output_path = OUTPUT_DIR / output_name output_path.write_text(cleaned, encoding="utf-8") return filepath.name, title, len(cleaned), str(output_path)

这段代码有几个设计要点:

  • read_docx里把Word的“标题1”“标题2”样式转成了Markdown的#前缀,这样后续就能统一走detect_heading逻辑。
  • extract_title优先看正文第一个有没有标题,找不到才用文件名。这个顺序很重要,因为很多文件正文标题和文件名不一样。
  • 读取纯文本时统一走前面写的read_text_file,编码兼容性才有保障。
  • 写入输出时强制用utf-8,避免再次出现编码问题。你如果要给别人用,可以再补一个“自动给文件名加序号”的操作,防止不同子目录同名文件互相覆盖。

批量处理部分用Path.rglob遍历,并对每个文件加try和日志:

import logging logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s") rows = [] for filepath in sorted(INPUT_DIR.rglob("*")): if filepath.suffix.lower() not in SUPPORTED_SUFFIX: continue try: row = process_one_file(filepath) rows.append(row) logging.info("已处理: %s", filepath.name) except Exception as exc: logging.warning("处理失败 %s: %s", filepath, exc)

日志里只记录文件名和异常内容,不打印整个堆栈。大批量跑的时候一堆堆栈信息反而没用,你只需要知道哪一个文件挂了、什么原因。

4.3 输出结果示例

跑完脚本后,输出目录里每个文件变成了一致风格的.txt,下面是一个整理后的段落结构示例:

# 月报数据说明 本月数据已统一整理,整体趋势如下。 ## 访问量 访问量较上月增长明显。 ## 转化率 转化率保持稳定。

然后再用一份CSV把处理结果汇总起来:

filename,title,chars,output a.txt,月报数据说明,152,./articles_output/a.txt b.md,常见问题汇总,439,./articles_output/b.txt c.docx,发布规范,872,./articles_output/c.txt

这个CSV的好处是:后面不管是要做字数汇总、重复检查还是往Excel里导入,都有据可查。如果你需要把多个文件合并成一个总文件,也很简单,直接在循环里把每篇文章用两个换行分隔后追加到一个大文件即可。

5. 常见问题与排查技巧实录

5.1 高频问题速查表

下面这些问题是文档处理里重复出现率最高的,我把它们整理成速查表。遇到问题先对号入座,能省下大量搜索时间。

症状常见原因处理建议
打开txt文件乱码文件实际是GBK/ANSI,被按UTF-8读取用前面read_text_file的兼容逻辑,增加编码探测
UnicodeDecodeError文件包含非法字节序列读取时用errors='replace',同时记录文件名
用python-docx打不开文件文件其实是.doc或已损坏先确认扩展名,建议另存为.docx
PDF提取出的文字顺序错乱PDF是双栏排版或包含复杂图层用PyMuPDF的get_text("dict")按坐标排序,或者换pdfplumber
正则把Markdown标题误删了清洗时没有保留#结构清洗前用占位符保护标题行,或者清洗时跳过^#{1,6}\s+行
批处理跑一半崩溃某个坏文件触发了未捕获异常给每个文件包一层try-except,错误写日志
输出文件多了看不见的空字符原文本包含零宽空格用ZERO_WIDTH_CHARS正则清除
处理.rar/.zip压缩包当前脚本没有解压逻辑先用zipfile或命令行解压,再进入处理主流程

5.2 排查思路与避坑经验

先说编码问题。很多人以为用自动检测编码的库就能解决,我在实践中的体验是:这类库对中文文本的准确率没那么高,尤其是长文本里夹杂了大量ASCII符号时。所以我的建议是“BOM优先、常见编码其次、检测兜底”。不要一上来就跑chardet,先按文件头部特征判断,再尝试解码,这样速度快,也稳定。

再说PDF。PDF读取最大的坑是“看着有字,提取出来没字”,或者“提取出来顺序错乱”。如果是图片型PDF,也就是扫描件或照片,任何文本提取库都无能为力,必须OCR。好在现在OCR工具成熟,可以先把PDF每页渲染成图片,再用OCR识别。但这是另一套方案了。文字顺序错乱多半是双栏版式问题,PyMuPDF的基础get_text()是按内部逻辑顺序返回的,有时不是我们预期的“从左到右,从上到下”,需要按文本块的坐标排序。简单的处理办法:

import fitz def extract_pdf_sorted(path): doc = fitz.open(path) full_text = [] for page in doc: blocks = page.get_text("blocks") # (x0, y0, x1, y1, text, ...) blocks.sort(key=lambda b: (round(b[1], 1), b[0])) full_text.append("\n".join(b[4].strip() for b in blocks if b[4].strip())) return "\n".join(full_text)

blocks里的坐标排序,先按 y 轴(行)再按 x 轴(列),通常能解决大部分双栏问题。但碰上复杂表格区域,这种方式还是不够,得靠pdfplumber的extract_table之类功能。所以每次处理PDF前,先确认“需要的是全文,还是特定结构的表格”,再决定技术路线。

还有一个很容易忽视的点:Word文档里的内容不只分布在段落里,还有表格、页眉页脚、文本框。python-docx的paragraphs属性只覆盖“正文段落”,表格里的文字要单独遍历doc.tables。如果你的业务方说“文档里有个表格也要处理”,而你只处理了段落,那结果必然缺数据。我一般会写一个函数把段落和表格合并读取:

def read_docx_all(doc): parts = [] for para in doc.paragraphs: parts.append(para.text) for table in doc.tables: for row in table.rows: for cell in row.cells: parts.append(cell.text) return "\n".join(parts)

当然这个函数会丢失表格的二维结构,只适合纯文本抽取。如果你要保留表格结构,建议输出成CSV或HTML表格。

6. 再往前走一步:正文提取、去重与质量检查

6.1 从网页批量提取文章正文

批量处理本地文件只是需求的一部分,很多人的文章其实是散落在网页端的。这时候就需要从HTML里抽取正文。用BeautifulSoup可以做针对性的抽取,比如抓<article>或者.content这些容器。但不同网站的页面结构千差万别,靠手写选择器维护成本太高。

更务实的方式是用专门做正文抽取的库。trafilatura就是专门干这个的,它能自动检测网页正文区域,剥离导航、广告、评论区,输出相对干净的文本。用法非常简单:

import trafilatura downloaded = trafilatura.fetch_url("https://example.com/some-article") content = trafilatura.extract(downloaded, output_format="txt") with open("output.txt", "w", encoding="utf-8") as f: f.write(content)

这类库适合批量抓取正文的场景。不过别指望它100%准确,比如网页有弹窗遮挡、正文在iframe里、或者用了动态加载技术的页面,它就抓不到了。面对那种页面,还得用浏览器自动化工具,但成本和复杂度会高很多。我的原则是:能静态抽取的先静态抽取,只有少数整页动态渲染的站点才考虑上浏览器方案。

6.2 近似去重:别被“看起来不同”骗了

文章整理到一定规模,重复问题就会出现。完全相同的重复很容易查,比较哈希值即可。麻烦的是“几乎一样”的文章:广告词换了几句、首段加了个词、图片URL不一样,正文主体完全重复。这时候需要用到近似去重。

比较基础的方案是用textdistance这个库,计算文本之间的编辑距离或Jaccard相似度。但如果文本数量大,两两比较的复杂度会爆炸。更常用的做法是先把每篇文章抽成一组“关键特征”,比如百字连续片段、章节标题序列、正文前N个句子,再用这些特征做碰撞检测。

from textdistance import jaccard def ngram_set(text, n=3): text = re.sub(r'\s+', '', text) return {text[i:i+n] for i in range(len(text)-n+1)} def similarity(a, b): a_set = ngram_set(a) b_set = ngram_set(b) union = len(a_set | b_set) if union == 0: return 0.0 return len(a_set & b_set) / union

这里有几点很重要。一个是先做清洗再做去重,尤其是去掉多余空格和标点,否则长得完全一样但换行方式不同的文章也会被误判为低相似度。另一个是阈值的设定,我一般会先跑一小批样本,把相似度分布打出来,再确定阈值,不要随手定90%。因为中文文本里,即使是很短的一篇文章,3-gram的相似度也可能比较低,但是核心章节如果一样,相似度就会很高。

6.3 文本质量检查:字数、段落、错别字

整理完的文章拿去做发布前,质量检查也是一个很实际的需求。基础指标包括:总字数、段落数、平均句长、最长段落、疑似空段落数。这些用纯Python就能统计。

def text_statistics(text): paragraphs = [p.strip() for p in text.split('\n') if p.strip()] sentences = [] for p in paragraphs: sentences.extend(split_sentences(p)) total_chars = len(re.sub(r'\s+', '', text)) avg_para_chars = total_chars / len(paragraphs) if paragraphs else 0 avg_sentence_len = total_chars / len(sentences) if sentences else 0 return { "total_chars": total_chars, "paragraph_count": len(paragraphs), "sentence_count": len(sentences), "avg_para_chars": round(avg_para_chars, 1), "avg_sentence_len": round(avg_sentence_len, 1), "max_para_chars": max(len(re.sub(r'\s+', '', p)) for p in paragraphs) if paragraphs else 0, }

这个统计可以快速帮你发现“某篇文章有几段超长文字没分段”“某段疑似把两个段落黏在一起了”等问题。

错别字检查属于进阶需求,传统词典法覆盖不了所有问题。pycorrector这类工具适合做个粗检测,但它对专业术语、人名地名的误报率不低,不能直接自动改。我的做法是:先让它跑一遍,把疑似错别字和原句输出到Excel里,再由人工确认。自动改错的风险就在于有些词在特定上下文里其实是正确的,比如“交付”和“交手”,只凭词典判断很不可靠。

最后说几个小经验

做文档处理这类脚本,我最大的体会是:千万不要一上来就写一个巨复杂的函数,试图把所有格式、所有规则、所有清洗步骤都塞进去。正确顺序是先拿三五个真实文件,手动跑通“读取到输出”的最小流程,确认业务方认可输出结果,再逐步增加文件类型和清洗规则。这套思路帮我躲过了很多后期推倒重来的局面。

另一个很有用的技巧是文件处理前后都要留原始备份。脚本跑之前把输入目录压缩留档,哪怕处理出问题也能随时回到原始状态。尤其是处理Word和PDF这类无法完美还原格式的文件时,贸然覆盖源文件相当于自断退路。

最后一个小技巧:脚本里多打印“当前处理到第几个文件、这个文件多少字、是否成功”,很多人批量处理几百个文件时只关心最终统计,结果跑到一半某个文件挂掉也没发现。加日志看起来笨,实际救命的次数远比你想象得多。希望这批经验能帮你少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询