尧图精选

法律.rar文本语料清洗指南:解压、乱码修复、条文切分与BM25检索

🕒 发布时间:2026/10/2 11:12:14 📁 来源:尧图网络
简介这是一份面向法律智能问答应用开发者的完整项目资料包围绕四万条法律问答数据的清洗、相似匹配与Web服务搭建展开适合有一定Python基础、希望入门中文自然语言处理或构建法律咨询机器人的人群。压缩包共25个文件主要包含Python数据分析脚本、预训练中文BERT模型文件、Web前端页面与交互脚本、Visio流程设计图以及CSV格式的法律问答数据集。其中模型与前端模板可直接复用能够快速启动演示应用。资源整体约371.77MB已有139人学习下载。通过阅读代码和数据分析过程可以掌握数据预处理、句子相似度计算、模型加载与调优、前后端联调等关键环节的实施思路流程图为理解系统整体工作流提供了直观参考从用户提问、语义匹配到答案返回的完整链路一目了然。 法律.rar 这类资源拿到手最先要解决的其实不是“内容”而是文件形态、编码和完整性。半个 G 到一个多 G 的压缩包里面经常混着 txt、docx、pdf 甚至嵌套的 zip文件名还常常是乱码直接解压再按目录去读基本等于把整理资料的耐心提前耗尽。这篇文章不会讲具体法条内容而是把“法律.rar”当作一份需要落地的文本语料从解压前的检查、清洗、切分、去重、建立检索索引一直讲到每条数据的边界条件。适合两类人做文档检索或 NLP 的工程师以及需要从本地资料库快速查旧条文、旧裁定的业务人员。2. 包内摸底先看清单、编码和完整性再谈清洗2.1 为什么第一步不是解压而是“看压缩包的目录”很多人拿到法律.rar的第一反应是双击解压到桌面然后发现几千个文件堆在一起。真正的问题是你根本不知道里面是什么级别的资料。可能是散装 txt可能是已经做成目录结构的标准文本也可能混了大量扫描版 PDF后者在文本提取阶段就会卡住。我一般会把“看目录”当作第一步而且是在不解压的情况下连清单带校验一起看。这一步能直接回答三个问题文件总量有多大、扩展名分布怎么样、有没有分卷。压缩包本身不是黑匣子rarfile 库可以把每个文件的文件名、大小、CRC 校验值都读出来CRC 值先记下来解压完成后能用来做一致性核对。2.2 搭好拆包环境rarfile unrar 的组合与常见报错Python 侧最顺手的库是rarfile但它并没有内置 RAR 解压实现还是得依赖外部 unrar 工具。在 Windows 上装了 WinRAR 后用它的 unrar.exe在 Linux 上装unrar或p7zip都行。环境变量路径写错了最常见会直接抛找不到解压工具的异常。import rarfile from collections import Counter, defaultdict # 指定外部 unrar 工具路径Windows 下常写成 # rarfile.UNRAR_TOOL rC:\Program Files\WinRAR\UnRAR.exe rarfile.UNRAR_TOOL /usr/bin/unrar def inspect_archive(path: str): stat defaultdict(Counter) with rarfile.RarFile(path) as rf: for f in rf.infolist(): if f.isdir(): continue name f.filename ext name.rsplit(., 1)[-1].lower() if . in name else (noext) stat[ext][count] 1 stat[ext][size] f.file_size print(f{f.file_size:10} {f.CRC:08X} {name}) return stat if __name__ __main__: stat inspect_archive(法律.rar) for ext, s in stat.items(): print(f{ext:10} {s[count]:5d} 个 {s[size] / 1024 / 1024:.1f} MB)这段脚本里有两个点值得注意。一个是f.isdir()rarfile 继承了 zipfile 的文件信息模型目录项也会出现在 infolist 里不跳过会把目录当空文件处理另一个是f.CRC它是 RAR 头里记录的 32 位校验值打印出来只为了留个底真正做一致性比对还是等解压完成后用输出文件的 md5 去比。第一次跑这种脚本输出里如果出现大量.doc和.pdf后面清洗的工作量基本可以提前确认了。2.3 文件名乱码的根因与修复脚本解压后的文件名变成“锟斤拷”或一堆 ASCII 符号几乎每个拆包的人都遇到过。原因在压缩工具旧版 RAR 在中文 Windows 下写文件名时按 GBK 编码而 Python 的 rarfile 在读取头信息时默认按 Unicode 处理两端对不上名字就花了。def decode_rar_filename(raw: str) - str: # raw 是 rarfile 解出来的字符串 # 中文本地环境常见场景实际上是 cp437/latin-1 读出来的 GBK 字节 try: return raw.encode(cp437).decode(gbk) except (UnicodeDecodeError, UnicodeEncodeError): return raw这段代码的思路是先还原出原始字节再用 GBK 解一次。典型的例子是“ǎ”被拆成“æ³å”这种一翻就能回到正确中文。要注意的是较新的 RAR5 文件头里带了 Unicode 标记这招不一定生效所以我只把它放在清洗脚本的最前面作为第一道兜底。文件名能正确显示了后面的清洗脚本才好往下写。3. 把 rar 变成结构化条文库解压、清洗与切分流水线3.1 解压策略整包解压还是按需抽取目录摸底做完之后才考虑解压。对于几百 MB 的包我通常是整包解压到独立目录因为后续文本切分还是要扫所有文件但如果包里明显混着大量不需要碰的 PDF就按扩展名过滤避免让 OCR 之类的重活拖垮整条流水线。import os import rarfile def extract_selected(archive_path: str, out_dir: str, exts(.txt, .doc, .docx)): os.makedirs(out_dir, exist_okTrue) with rarfile.RarFile(archive_path) as rf: for f in rf.infolist(): if f.isdir(): continue if not f.filename.lower().endswith(exts): continue rf.extract(f.filename, pathout_dir) print(extract done:, out_dir)这里的rf.extract(f.filename, pathout_dir)是 rarfile 的常规用法第二个参数是目标目录。解压时如果文件名是加密的还需要传入密码参数pwd。实际上很多法条资料包都会套一层密码密码错误时 rarfile 抛的不是权限异常而是一个 BadRarFile排查时要能认出这个表象。3.2 文本清洗的两个硬指标全角半角统一与不可见字符解压完的文件不能直接当语料用尤其是从网页或其他工具里导出的文本全角引号、全角数字、行尾的软回车、零宽空格都混在里面。法律文本还很爱用全角括号和全角逗号如果不统一分词效果和后续关键词匹配都会出差错。import re def clean_legal_text(raw: str) - str: # 1. 全角转半角全角字符在 UFF01~UFF5E映射到 U21~U7E out [] for ch in raw: code ord(ch) if code 0x3000: # 全角空格 code 0x20 elif 0xFF01 code 0xFF5E: # 全角 ASCII 可视字符 code - 0xFEE0 out.append(chr(code)) text .join(out) # 2. 去掉控制字符和零宽字符 text re.sub(r[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\u200b-\u200d\ufeff], , text) # 3. 合并多余的空白和空行 text re.sub(r[ \t], , text) text re.sub(r\n{3,}, \n\n, text) return text.strip()参数说明0xFEE0是全角 ASCII 区与半角区之间的偏移量因为全角字符按顺序排布所以直接减这个偏移量就能整体映射回去。控制字符范围里比较容易漏的是\u200b零宽空格和\ufeffBOM后者出现在文件开头时经常让首条记录多出一个隐形字符。这段清洗我一般放在所有格式转换之后、分词之前顺序反了的话docx 里带的 XML 残留会把正则彻底带偏。3.3 按“第X条”切分条文保留可追溯的元信息法律文本最直观的结构化维度是“章”和“条”。把每个完整文件按条文粒度切出来后续检索的召回粒度才够细。切分规则看着简单但真写起来要照顾到中文数字和阿拉伯数字混编的情况。import re from pathlib import Path ARTICLE_RE re.compile(r^(第[一二三四五六七八九十百零0-9][条章])\s*(.*)$) def split_articles(text: str): records [] cur_title, cur_body , [] for line in text.splitlines(): m ARTICLE_RE.match(line) if m: if cur_title: records.append({title: cur_title, body: \n.join(cur_body)}) cur_title m.group(1) cur_body [m.group(2)] if m.group(2) else [] else: cur_body.append(line) if cur_title: records.append({title: cur_title, body: \n.join(cur_body)}) return records这段函数的关键点在正则表达式^第[一二三四五六七八九十百零0-9][条章]锚定了行首又同时接受中文数字和阿拉伯数字。切分后每条记录带title和body两个字段前者是“第X条”这种索引值后者是正文。后续做召回时title 字段权重应该比 body 高因为条文号本身是最强的索引键。处理大文件时建议按文件为单位调用这个函数再存入 jsonl别把所有文件一次性拼成超大字符串再切内存会先爆。4. 为条文建立可检索索引法律分词、停用词与去重4.1 为什么通用分词在这里“翻车”jieba 默认词典面向新闻和日常文本法律文本里大量术语和固定搭配会被切得七零八落。“违约责任”可能被切成“违约”和“责任”“不可抗力”可能被切成两个词。搜索引擎可以靠索引召回兜底但本地检索不行——查“违约责任”时如果索引里根本没有“违约责任”这个完整 tokenBM25 这类方法很难把它和“违反合同义务应承担的责任”关联起来。所以我会先准备一个法律领域的自定义词典不需要很大几百条足够。把高频出现的法律术语、文书类型、法条引用方式都加进去。格式是每行一个词后面跟词频和词性违约责任 10 n 法定代表人 10 n 合同义务 8 n 不可抗力 5 n 裁判文书 10 n词频权重在这里不用太精确优先让长词在 match 时可以整体命中。加载后检查一下效果import jieba jieba.load_userdict(legal_dict.txt) text 一方不履行合同义务或者履行合同义务不符合约定的应当承担违约责任。 print(list(jieba.lcut(text))) # 预期输出里能看到“合同义务”“违约责任”被保留为整体如果切出来还是把“违约责任”拆开多半是自定义词典里边的词频被默认词典压过了把词频改成默认词典里的更高值会好很多。不过切分词表只是第一步真正影响检索效果的是停用词列表。4.2 停用词不要随便删法律文本里的语义边界通用停用词表看到“不得”“应当”“以下”直接删但这种做法放在法律文本上会出事。“应当”在法律语义中是义务性描述“不得”是禁止性描述把它们从索引里删掉用户查“禁止”或者“必须”时召回结果会偏移。我用的清洗规则是只删语气助词和连接词比如“的、了、与、和、在”这类通用高频字动词性的约束词全部保留。stop_words set(的 了 吗 呢 与 和 在 中.split()) def tokenize(doc: str): tokens [] for w in jieba.cut(doc): if w.strip() and w not in stop_words: tokens.append(w.strip()) return tokens注意上一章清洗完的文本这里直接分词就行不用再做统一。此外像“第X条”这种条文标题里带数字的词我不会把数字单独拆成 token否则检索“第三编”时会出现奇怪的偏斜。4.3 全文去重的三种做法精确、近似到语义法律.rar 这类资料包收集时间跨度大同一个文件在不同文件夹里重复出现很常见重复比例超过 20% 都不意外。第一阶段先做精确去重这是成本和收益比最高的一步import hashlib import re def normalize_for_dedup(text: str) - str: # 去除所有空白和标点只保留中文字符用于比较 return re.sub(r[\W_], , text).lower() def file_md5(path: str, block_size2 ** 20) - str: h hashlib.md5() with open(path, rb) as f: while True: block f.read(block_size) if not block: break h.update(block) return h.hexdigest()这里的normalize_for_dedup做了两件事去掉所有非汉字内容再把剩下的统一小写。这样“第一章”和“第 1 章”这类格式差异不至于产生两个 hash。精确去重之后如果还发现大量“看起来像但 hash 对不上”的文档才轮到近似去重通常是基于 MinHash 或 SimHash 来算文本间相似度。这一步在几千条记录规模下不会太慢量级再往上走就建议先用 minhash 做粗筛再用向量相似做精排。绝大多数本地资料场景到精确去重已经足够不必一上来就上向量化。5. 避坑记录拆法律.rar 时最容易踩的五个问题5.1 文件名变成“锟斤拷”或“æ³å”现象解压出来的文件名在 Windows 资源管理器里是乱码在命令行里更难看。原因RAR 文件头里的文件名按 GBK 编码写入但解压工具没有识别到对应代码页把字节当成了 cp437 或 UTF-8 去解码。更糟的是有些压缩包在生成时就已经把文件名写坏解压工具怎么调都救不回来。解决在解压时用decode_rar_filename先做一次转换或者用带编码选项的压缩工具重压一遍。如果包是别人打包的重压不现实只能靠转换函数兜底。5.2 解压中段报 “Cannot find RAR archive”现象解压到 80% 时突然报找不到归档文件或者只解出部分文件。原因早年网盘分享又喜欢拆分的习惯把大文件拆成.part1.rar、.part2.rar多个分卷下载时只下到部分分卷或者重命名漏掉.part2。rarfile 在遇到缺少分卷时会直接中断而不是自动跳过。解决先把所有分卷放在同一目录确认命名连续再用unrar t 法律.rar完整跑一遍完整性验证。校验通过后再用 Python 解压。如果校验失败但业务上急着用用unrar lb打印卷成员列表人工看缺哪块。5.3 docx 被当成纯文本清洗后全是 XML现象清洗脚本跑完输出里出现大量/w:p和w:r。原因docx 本质是 zip 容器直接按文本模式读取会把打包的 XML 当成正文。解决用docx2txt转纯文本后再进清洗函数而不是自己手撕 XML。老式.doc文件更麻烦需要调用antiword或者用 LibreOffice 批量转换soffice --headless --convert-to txt:Text --outdir ./converted ./原始目录/*.doc这条命令是一次性把所有.doc转成 txt转换完后注意文件名可能会加后缀用--outdir指定独立输出目录避免覆盖原始文件。5.4 正则按“第X条”切分时误伤正文句子现象正文里出现“第一百七十九条”整段被当成条文标题单独切开。原因正则^第[一二三四五六七八九十百零0-9][条章]只锚定了行首老文本里有些正文段落恰好以“第X条”开头尤其是转述条文的时候。解决加一个长度限制条文标题部分一般不超过 12 个汉字。还要检查匹配到条文标题后下一行是否真的像正文。我一般会在切分循环里加一个判断如果匹配到的“第X条”后面没有内容且下一条记录里也没有有效行就把它合并回前一条记录的 body。5.5 全量加载导致内存溢出现象清洗脚本跑在 4G 内存的机器上处理到一半报 MemoryError。原因用Path.read_text()读整个文件列表再拼成一个大字符串几千个文件同时进内存。jieba 分词还会把临时 DAG 结构再翻几倍。解决改成逐文件、逐条切分、逐条写出的流水线核心是记忆“只保留当前文件的结果”。如果确实需要做大数组排序比如全文相似度计算先对文本做分段并切片写入临时文件。6. 进阶用法清洗完成之后用 BM25 在本地做条文快速召回6.1 从 TF-IDF 换到 BM25 的理由条文清洗成结构化记录之后最实用的场景是快速召回给你一句案情描述找出最相关的法律条文。TF-IDF 对短文本没问题但法律条文长度差异很大有的条文几十字有的几百字直接用 TF-IDF 会让长条文在归一化时被惩罚过度。BM25 的 k1 和 b 参数能调节词频饱和度与文档长度惩罚对长短混合的语料更稳。rank_bm25 已经实现了经典参数版本开箱即用。6.2 一个可直接跑的召回脚本import pickle import jieba from rank_bm25 import BM25Okapi # docs 是切分清洗后的记录列表每条包含 title 和 body with open(legal_records.jsonl, r, encodingutf-8) as f: docs [json.loads(line) for line in f if line.strip()] corpus_tokens [] for d in docs: corpus_tokens.append( list(jieba.lcut(d[title] d[body])) ) bm25 BM25Okapi(corpus_tokens, k11.5, b0.75) def recall(query, topk5): q_tokens list(jieba.lcut(query)) scores bm25.get_scores(q_tokens) top_idx sorted(range(len(scores)), keylambda i: scores[i], reverseTrue)[:topk] for i in top_idx: print(fscore{scores[i]:.3f} | {docs[i][title]} | {docs[i][body][:40]}) recall(一方不履行合同义务另一方可以要求赔偿损失吗)脚本里的 k1 和 b 是 BM25 的两个核心参数常见默认 k11.5、b0.75。k1 越高词频对分数的影响越早进入饱和b 越高对长文本的惩罚越重。法律条文整体偏长如果发现长条文总是排在前面可以把 b 调高一点比如 0.8发现有价值的短条文反而排后则下调到 0.6。调参时先跑一次基准查询再小步改不要一上来就动默认值。跑通这段脚本后整个法律.rar就成了一个可检索的本地知识库。从那以后我每次拿到这类“xx.rar”资源都会强制先走一遍清单检查、编码判断、CRC 校验再决定要不要写清洗脚本。宁可多花十分钟摸底也不想再被半截文件和一整屏乱码浪费两个小时。希望这篇拆包经验能帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →