ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GATT1994中文版PDF获取与Word兼容处理:法律文本工程化落地指南

GATT1994中文版PDF获取与Word兼容处理:法律文本工程化落地指南 简介这份PDF文档是关税及贸易总协定GATT1994的中文整理版采用Word兼容格式面向国际贸易法研究者、外贸从业者、法学专业学生及备考相关资格考试的人群帮助读者便捷查阅GATT1994原始条款理解多边贸易体制的法律框架。资源包内仅含1个PDF文件大小约239KB轻量易存适合在电脑或移动设备上随时翻阅、检索与引用。文档完整收录协定正文涵盖一般最惠国待遇、减让表、国内税与国内规章的国民待遇等核心条款并涉及优惠待遇范围、贸易障碍取消及关税费用优惠安排等具体规定可作为研究WTO规则、撰写论文或处理贸易实务的参考底本。目前已有429人学习下载适合需要反复研读条文、对照案例分析的读者使用。1. GATT1994 中文版 PDF 的获取与 Word 兼容处理一份法律文本的工程化落地做涉外合规、国际贸易法务或者关务系统的朋友大概率都遇到过同一个场景手头需要一份 GATT1994 的中文文本要求能检索、能复制、能批注、能转成结构化数据入库但拿到的往往是一个扫描件 PDF 或者排版混乱的网页存档。GATT1994 作为 WTO 协定附件一A 的核心组成部分其条文被各国贸易救济调查、关税谈判、原产地规则制定反复引用文本的准确性和可编辑性直接决定了后续工作的效率。这个标题指向的其实是一个很具体的工程问题如何把一份「Word 兼容版」的 GATT1994 中文 PDF 变成真正能在本地跑通检索、比对、入库的可用资产。适合做合规系统、法律知识库、贸易政策研究的人也适合需要把条约文本喂给检索或比对工具的技术同学。2. GATT1994 文本的结构特征与 Word 兼容版 PDF 的选型逻辑2.1 为什么 GATT1994 的文本处理比一般法律文件更麻烦GATT1994 不是一份单一文件它由四部分构成GATT1947 的条文本身、WTO 成立前生效的各类法律文件如关税减让议定书、加入议定书、乌拉圭回合对 GATT1947 的谅解以及 GATT1994 的马拉喀什议定书。中文版本在翻译和编排上存在多个来源不同来源的条款编号、注释位置、附件归属都有差异。更麻烦的是GATT1994 的条文里大量使用交叉引用比如「按本协定第 XX 条」「依第 II 条第 2 款」这些引用在纯文本里是字符串在结构化数据里必须变成可跳转的锚点。如果 PDF 只是扫描图OCR 出来的文本会把「第 II 条」识别成「第 II 条」还是「第 II 条」全看运气条款编号一旦错位后续所有引用链就断了。Word 兼容版 PDF 的价值就在这里。所谓 Word 兼容通常指 PDF 内部保留了文本层和段落标记用 Adobe Acrobat、WPS 或者 LibreOffice 打开时能直接选中文字、复制段落而不是整页一张图。这类 PDF 的来源一般是出版社排版文件导出或者官方文本经过 Word 排版后另存为 PDF。它的文本流是连续的段落边界清晰标题层级虽然不一定带样式标签但通过字号和加粗能推断出来。选型时优先看三个指标文本层是否可选中、段落是否按条拆分、页眉页脚是否混入正文。如果 PDF 打开后选中一段文字复制出来是乱序的那说明文本流是碎的处理成本会高很多。2.2 判断一份 GATT1994 中文 PDF 是否值得动手的四个检查点拿到文件后别急着写代码先做四件事。第一用pdftotext -layout跑一遍看输出的文本里「第 X 条」是否独立成行如果条款标题和正文黏在一起说明排版时没有用段落分隔。第二检查页码和页眉是否出现在文本流中间很多 PDF 的页眉是「GATT1994 中文版」加页码这些内容会污染正文。第三搜索「附件」和「注释」看它们是否被正确放在对应条款之后而不是全部堆在文件末尾。第四用pdfinfo看 PDF 的 Producer 字段如果是「Microsoft Word」或者「WPS」导出的文本层质量通常较好如果是「ScanSnap」或者「ABBYY」之类的扫描仪软件那基本就是 OCR 结果需要额外校对。# 检查 PDF 文本层质量和元数据 pdfinfo GATT1994中文整理版.pdf | grep -E Producer|Pages|Encrypted # 提取纯文本保留布局观察条款标题是否独立成行 pdftotext -layout GATT1994中文整理版.pdf gatt_raw.txt # 统计「第.*条」出现次数和目录对比 grep -cE ^第[一二三四五六七八九十百]条 gatt_raw.txtpdfinfo的 Producer 字段能告诉你这份 PDF 是原生导出还是扫描后 OCR。pdftotext -layout保留原始空格布局适合判断段落结构。grep统计条款数量是为了和官方目录做交叉验证如果数量对不上说明提取过程中丢了内容或者把注释里的「第 X 条」也算进去了。注意-layout模式有时会在行尾加多余空格后续清洗时用sed s/[[:space:]]*$//去掉。2.3 Word 兼容版 PDF 转结构化文本的两种路径选择路径一PDF → 文本 → 正则解析 → JSON/Markdown。适合条款结构规整、编号连续的版本。核心是用正则把「第 X 条」「第 X 款」「第 X 项」切成层级再按标题层级重建树。路径二PDF → Worddocx→ 样式解析 → 结构化。适合 PDF 本身是从 Word 导出的情况用pdf2docx或者 LibreOffice 转成 docx 后标题样式Heading 1/2/3能直接映射条款层级比正则更稳。我一般优先走路径二因为 GATT1994 的条款层级深正则写多了容易在「第 II 条」和「第 II 条第 2 款」之间翻车。但路径二依赖 PDF 的文本层质量如果转出来的 docx 里标题样式全丢了就得退回路径一。from pdf2docx import Converter # 将 Word 兼容版 PDF 转为 docx保留段落和样式 cv Converter(GATT1994中文整理版.pdf) cv.convert(gatt1994.docx, start0, endNone) cv.close() # 用 python-docx 读取标题层级 from docx import Document doc Document(gatt1994.docx) for para in doc.paragraphs: if para.style.name.startswith(Heading): print(para.style.name, |, para.text[:60])pdf2docx的convert方法会尽量还原 PDF 的段落和样式但 GATT1994 里有些条款标题用的是加粗正文而不是 Heading 样式转出来可能变成 Normal。这时候需要看para.style.name和para.runs[0].bold组合判断。start和end参数可以只转特定页适合先拿几页试效果。如果转出来的 docx 里 Heading 数量远少于预期说明 PDF 的样式信息在导出时丢了路径二不可行。3. 从 PDF 到可检索文本提取、清洗与条款切分的完整操作3.1 用 pdfplumber 做精细提取表格、脚注和跨页条款的处理pdftotext快但粗遇到 GATT1994 里的表格比如关税减让表引用和脚注比如条款的补充注释容易丢内容。pdfplumber能按字符坐标提取适合处理跨页条款和表格。GATT1994 的正文里有一些条款是跨页的比如第 XX 条可能从第 5 页底部延续到第 6 页顶部pdftotext会在页边界处断行pdfplumber可以通过extract_text()的layout参数控制是否保留换行再结合页码信息把跨页段落拼回去。import pdfplumber with pdfplumber.open(GATT1994中文整理版.pdf) as pdf: full_text [] for i, page in enumerate(pdf.pages): # 提取文本x_tolerance 控制字符间距y_tolerance 控制行间距 text page.extract_text(x_tolerance2, y_tolerance3) # 去掉页眉页脚通常页眉在顶部 50pt 以内页脚在底部 50pt 以内 words page.extract_words() body_words [w for w in words if 50 w[top] page.height - 50] body_text .join(w[text] for w in body_words) full_text.append(f--- PAGE {i1} ---\n{body_text}) with open(gatt_clean.txt, w, encodingutf-8) as f: f.write(\n.join(full_text))x_tolerance和y_tolerance是pdfplumber的关键参数值越小越严格适合字符间距均匀的排版值太大会把不同列的文本混在一起。extract_words()返回每个词的坐标用top过滤页眉页脚比用正则匹配「第 X 页」更可靠因为页码格式可能不统一。注意 GATT1994 的注释有时用小字号排在页面底部如果页脚过滤范围设得太大会把注释一起删掉建议先打印几页的words坐标分布再定阈值。3.2 条款切分的正则设计处理「第 X 条」「第 X 款」「第 X 项」的嵌套GATT1994 的层级是部分Part→ 条Article→ 款Paragraph→ 项Subparagraph。中文版里「条」用「第 X 条」「款」用「第 X 款」或者直接用阿拉伯数字加括号「项」用「(a)(b)(c)」或者「甲乙」。正则要能区分这些层级同时不能把正文里的交叉引用比如「按第 II 条第 2 款」误判为标题。我的做法是标题行必须满足「行首 第 X 条 行尾无句号」或者「行首 数字 顿号 标题文字」交叉引用通常出现在句子中间用^锚定行首就能过滤掉大部分。import re with open(gatt_clean.txt, r, encodingutf-8) as f: text f.read() # 匹配「第 X 条」作为条款标题要求行首且后面不跟「规定」「所指」等词 article_pattern re.compile( r^第([一二三四五六七八九十百])条\s*(.*?)$, re.MULTILINE ) # 匹配「第 X 款」通常以数字开头如「1.」「2.」 paragraph_pattern re.compile( r^(\d)\.\s(.*?)$, re.MULTILINE ) articles [] current_article None for line in text.split(\n): line line.strip() m article_pattern.match(line) if m: if current_article: articles.append(current_article) current_article {article: m.group(1), title: m.group(2), content: []} elif current_article is not None: current_article[content].append(line) if current_article: articles.append(current_article) print(f共切分出 {len(articles)} 条) for a in articles[:3]: print(a[article], a[title], len(a[content]))article_pattern里的^和re.MULTILINE保证只匹配行首的「第 X 条」避免把正文里的引用切出来。(.*?)捕获条款标题GATT1994 里有些条款标题是空的比如第 I 条就是「普遍最惠国待遇」标题直接跟在条号后面有些是换行写的需要根据实际文本调整。paragraph_pattern匹配阿拉伯数字开头的款但要注意 GATT1994 里有些条款的款是用「(a)(b)」标记的这种需要单独处理。切分完后用len(articles)和官方目录对比GATT1994 正文共 38 条加上附件和注释总数应该在 40 左右。3.3 清洗规则去掉页码残留、合并断行、统一标点提取出来的文本里常见的脏数据有三类页码残留比如「- 5 -」或者「5」单独成行、断行一个句子被硬回车切成两行、标点不统一中文引号和英文引号混用。页码残留用正则^\s*-?\s*\d\s*-?\s*$匹配整行只有数字或数字加横线的行直接删掉。断行合并要看上下文如果一行以逗号、顿号、分号结尾下一行开头是小写字母或者中文大概率是断行合并时去掉换行符如果一行以句号结尾下一行是「第 X 条」那可能是新段落不能合并。标点统一用str.maketrans把英文引号、括号转成中文但要注意 GATT1994 里的条款编号「第 II 条」用的是罗马数字不能把「II」转成中文「二」。import re def clean_gatt_text(raw: str) - str: lines raw.split(\n) cleaned [] for line in lines: # 去掉纯页码行 if re.match(r^\s*-?\s*\d\s*-?\s*$, line): continue # 去掉页眉标记 if --- PAGE in line: continue cleaned.append(line.strip()) # 合并断行如果上一行不以句号、问号、感叹号结尾且下一行不以「第」开头 merged [] for i, line in enumerate(cleaned): if not line: continue if merged and not re.search(r[。]$, merged[-1]) and not re.match(r^第[一二三四五六七八九十百]条, line): merged[-1] line else: merged.append(line) text \n.join(merged) # 统一标点英文引号转中文 trans str.maketrans({: “, : ”, : ‘, : ’}) text text.translate(trans) return text with open(gatt_clean.txt, r, encodingutf-8) as f: raw f.read() result clean_gatt_text(raw) with open(gatt_final.txt, w, encodingutf-8) as f: f.write(result)合并断行的逻辑是如果上一行末尾没有句号、问号、感叹号、分号且下一行不是以「第 X 条」开头就把下一行拼到上一行。这个规则能处理大部分正文断行但 GATT1994 里有些条款的款标题比如「1.」后面直接跟正文如果上一行是「1.」且没有标点会被误合并所以加一个判断如果上一行匹配^\d\.$不合并。标点转换只处理引号括号和破折号保持原样因为 GATT1994 里有些括号是条款编号的一部分转成中文括号反而会破坏结构。4. 避坑与排查GATT1994 文本处理中五个真实翻车记录4.1 现象提取出的条款数量比官方目录少了好几条原因pdftotext默认不处理 PDF 里的文本框和浮动元素GATT1994 中文版里有些条款的标题被放在文本框里或者用了分栏排版pdftotext按阅读顺序提取时会跳过这些内容。另外如果 PDF 有加密权限限制pdftotext可能只输出部分页面。解决先用pdfinfo确认Encrypted字段如果是yes用qpdf --decrypt解密后再提取。对于文本框改用pdfplumber的extract_text()并设置layoutTrue或者用page.extract_words()按坐标排序后手动拼接。提取完后用grep -cE ^第[一二三四五六七八九十百]条统计条款数和官方目录逐条核对缺哪条就单独去 PDF 里定位那一页看是不是排版问题。4.2 现象条款编号里的罗马数字被错误转成了阿拉伯数字或中文数字原因清洗时用了全局的标点转换或者数字转换把「第 II 条」里的「II」当成英文引号或者普通字母处理了。GATT1994 里罗马数字用于条款编号第 I 条到第 XXXVIII 条也用于款项编号比如「第 II 条第 2 款」这些罗马数字必须保持原样。解决在清洗规则里加白名单用正则第\s*[IVXLCDM]\s*条匹配罗马数字条款匹配到的行不做数字转换。如果已经转错了用re.sub把「第 2 条」这种误转的改回「第 II 条」但前提是你有一份正确的条款编号映射表。更稳妥的做法是清洗阶段只处理标点和空白不做任何数字转换罗马数字和阿拉伯数字的转换留到结构化阶段按字段类型处理。4.3 现象跨页条款被切成两段合并后语义错乱原因GATT1994 里有些长条款跨页pdftotext在页边界处插入换行清洗时如果无脑合并会把上一页末尾的句子和下一页开头的句子拼在一起但下一页开头可能是另一个条款的标题或者页眉残留。解决在合并前先标记页边界。用pdfplumber提取时记录每页的起止行号合并断行时只在同一页内合并跨页的行先检查下一页第一行是否以「第 X 条」开头如果是不合并如果不是再检查上一页最后一行是否以句号结尾不是才合并。这个逻辑写起来有点绕但能避免大部分跨页错乱。我一般会在清洗后的文本里保留--- PAGE N ---标记结构化阶段再决定是否去掉。4.4 现象注释和附件被混入正文导致条款内容多出无关段落原因GATT1994 的注释Ad Note和附件Annex在 PDF 里可能和正文用同样的字号排版pdftotext不区分层级提取出来就是连续文本。如果正则切分时只按「第 X 条」切注释会被归到上一条的content里。解决在切分前先识别注释和附件的起始标记。GATT1994 的注释通常以「注释」或「对第 X 条的注释」开头附件以「附件」或「ANNEX」开头。用正则^(注释|对第.*条的注释|附件|ANNEX)匹配到这些行时把后续内容归入独立的notes或annexes列表不混入articles。如果 PDF 里注释是用小字号排的可以用pdfplumber的chars属性按字号过滤但这种方法依赖 PDF 的字体信息不是所有版本都可靠。4.5 现象转成 docx 后标题样式全丢Heading 数量为零原因PDF 导出时没有保留 Word 的样式标签或者pdf2docx的版本对中文 PDF 的样式还原支持不好。有些 GATT1994 中文版是扫描后 OCR 再排版的PDF 里根本没有样式信息。解决先检查 PDF 的 Producer 字段如果是扫描仪软件直接放弃路径二走正则解析。如果是 Word 导出但样式丢了用pdf2docx时加multi_processingTrue和cpu_count参数提高还原率或者改用 LibreOffice 的命令行转换libreoffice --headless --convert-to docx GATT1994中文整理版.pdf。LibreOffice 对中文 PDF 的段落还原有时比pdf2docx好。如果都不行就回到pdfplumber按字号和加粗判断标题层级字号大于正文且加粗的行大概率是条款标题。5. 把 GATT1994 文本变成可检索知识库索引、比对与版本管理5.1 用 SQLite FTS5 建全文索引支持条款级检索结构化后的 GATT1994 文本最适合放进 SQLite 的 FTS5 虚拟表按条款建索引支持中文分词和短语查询。FTS5 默认的分词器对中文是按字切分检索「最惠国待遇」会匹配到包含这些字的条款但也会匹配到无关内容。更好的做法是用jieba分词后把词序列存进 FTS 表或者用 SQLite 的unicode61分词器加tokenchars参数。下面是一个可复现的建库脚本把前面切分出的articles列表写入数据库。import sqlite3 import json conn sqlite3.connect(gatt1994.db) cur conn.cursor() # 创建主表和 FTS5 索引表 cur.execute( CREATE TABLE IF NOT EXISTS articles ( id INTEGER PRIMARY KEY, article_num TEXT, title TEXT, content TEXT, part TEXT ) ) cur.execute( CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts USING fts5( article_num, title, content, contentarticles, content_rowidid, tokenizeunicode61 ) ) # 假设 articles 是前面切分出的列表 for i, a in enumerate(articles): content \n.join(a[content]) cur.execute( INSERT INTO articles (article_num, title, content, part) VALUES (?, ?, ?, ?), (a[article], a[title], content, a.get(part, )) ) cur.execute( INSERT INTO articles_fts (rowid, article_num, title, content) VALUES (?, ?, ?, ?), (i1, a[article], a[title], content) ) conn.commit() # 检索示例查找包含「最惠国待遇」的条款 cur.execute( SELECT article_num, title FROM articles_fts WHERE articles_fts MATCH 最惠国待遇 ORDER BY rank LIMIT 5 ) for row in cur.fetchall(): print(row) conn.close()FTS5 的contentarticles表示外部内容表索引和主表分离更新主表后需要手动同步 FTS 表或者用触发器自动同步。tokenizeunicode61对中文是按字切分检索「最惠国」会匹配到「最惠国待遇」和「最惠国关税」等召回率高但精度一般。如果要做精确短语检索用MATCH 最惠国待遇加双引号。ORDER BY rank按相关性排序FTS5 的rank是内置的 BM25 变体适合快速排序。5.2 版本比对用 difflib 找出不同来源 GATT1994 文本的差异GATT1994 中文版有多个来源条款措辞可能有细微差异。做合规系统时需要知道当前用的版本和官方版本差在哪。用 Python 的difflib按条款比对输出差异行再人工判断是排版差异还是实质修改。下面是一个按条款比对的脚本输入是两个版本的articles列表输出差异报告。import difflib def compare_articles(articles_a, articles_b): # 按条款号建字典 dict_a {a[article]: a for a in articles_a} dict_b {a[article]: a for a in articles_b} all_articles sorted(set(dict_a.keys()) | set(dict_b.keys())) report [] for art in all_articles: a dict_a.get(art) b dict_b.get(art) if not a: report.append(f条款 {art}: 仅在版本 B 中存在) continue if not b: report.append(f条款 {art}: 仅在版本 A 中存在) continue text_a \n.join(a[content]) text_b \n.join(b[content]) if text_a text_b: continue diff list(difflib.unified_diff( text_a.splitlines(), text_b.splitlines(), lineterm, n1 )) report.append(f条款 {art} 差异:\n \n.join(diff[:20])) return report # 假设 articles_v1 和 articles_v2 是两个版本的切分结果 # report compare_articles(articles_v1, articles_v2) # print(\n.join(report))difflib.unified_diff的n1表示只显示差异行前后各 1 行上下文适合快速定位。text_a.splitlines()按行比对如果两个版本的断行方式不同会产生大量伪差异所以比对前要确保两个版本都经过了同样的清洗和断行合并。差异报告里如果出现「第 II 条」变成「第 2 条」这种说明其中一个版本的罗马数字被转错了需要先修正再比对。这个脚本只做文本级比对不做语义比对实质修改需要人工判断。5.3 用 Markdown 重建条款层级方便版本管理和协作结构化后的 GATT1994 文本导出成 Markdown用#表示部分##表示条###表示款这样在 Git 里做版本管理时diff 会按层级显示比纯文本 diff 清晰得多。导出脚本用articles列表生成 Markdown条款标题作为##款内容作为段落注释和附件单独放在文件末尾。def export_markdown(articles, notesNone, annexesNone): lines [# GATT1994 中文版\n] for a in articles: lines.append(f## 第 {a[article]} 条 {a[title]}\n) for para in a[content]: if para.strip(): lines.append(para.strip() \n) if notes: lines.append(# 注释\n) for n in notes: lines.append(n \n) if annexes: lines.append(# 附件\n) for an in annexes: lines.append(an \n) return \n.join(lines) # with open(gatt1994.md, w, encodingutf-8) as f: # f.write(export_markdown(articles))Markdown 的层级用##和###不要用####因为 GATT1994 的条款层级最多到项###足够。条款标题里的罗马数字保持原样不要转成阿拉伯数字。导出后在 Git 里提交后续如果官方文本更新重新跑一遍提取和导出git diff就能看到哪些条款变了。这个做法比维护一个二进制 PDF 靠谱得多也方便多人协作时合并修改。5.4 一个我常用的验证习惯用条款交叉引用做一致性检查GATT1994 里大量交叉引用比如「按本协定第 XX 条」「依第 II 条第 2 款」。结构化后用正则提取所有交叉引用检查被引用的条款是否存在、编号是否匹配。如果引用指向一个不存在的条款号说明切分或清洗过程中丢了内容。这个检查能抓住大部分提取错误比人工核对快得多。import re def check_cross_references(articles): article_nums {a[article] for a in articles} ref_pattern re.compile(r第([一二三四五六七八九十百])条) missing [] for a in articles: content \n.join(a[content]) refs ref_pattern.findall(content) for ref in refs: if ref not in article_nums: missing.append((a[article], ref)) return missing # missing check_cross_references(articles) # for src, dst in missing: # print(f条款 {src} 引用了不存在的第 {dst} 条)这个检查的误报率取决于正则的精度如果正文里出现「第 X 条」但不是交叉引用比如「本条」会被误报。改进方法是只匹配「第 X 条」前后有「按」「依」「根据」「见」等动词的上下文。我一般会先跑一遍全量把误报的条款号加入白名单再跑第二遍。这个习惯帮我抓过好几次提取丢页的问题尤其是 PDF 中间某页是图片的情况。希望帮到你。本文还有配套的精品资源点击获取
返回列表