385 lines
22 KiB
Python
385 lines
22 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
辅行诀五脏用药法要-药性探真 资料库 ETL 第 1 步(v3)
|
||
01_来源数据 -> 02_加工数据
|
||
|
||
只做"数据清理",不做释义抽取 / 跨库关联 / 主题分析(用户 2026-09-15 指令)。
|
||
|
||
清理项:
|
||
A 页面残留物:页眉(含缺书名号的嵌入形式)、独立页码、行尾空格、全角空格
|
||
B 断行错位接合:段落被页眉/页码打断处自动接合
|
||
C 形近错字 / 异体字 / 书名误字修正(逐条附核校依据)
|
||
E 半角标点规范化(邻接汉字时)
|
||
F 存疑登记:无法确证处不改动原文,逐条附疑正与核校来源
|
||
|
||
输出:
|
||
02_加工数据/药性探真_第一章_清洗版.md
|
||
02_加工数据/文本清洗报告.md (全部核对数据**按章/节列明**)
|
||
02_加工数据/人工核对清单.md (按章/节列明的待人工确认项)
|
||
02_加工数据/章节结构_第一章.json (清理配套定位索引)
|
||
幂等:可重复运行,输出覆盖生成。
|
||
"""
|
||
import os, re, json, hashlib
|
||
|
||
BASE = os.path.expanduser("~/Documents/ai_agent_scraper_study/data/辅行诀五脏用药法要-药性探真")
|
||
SRC = os.path.join(BASE, "01_来源数据/药性探真_第一章_原始_20260915.md")
|
||
OUT_MD = os.path.join(BASE, "02_加工数据/药性探真_第一章_清洗版.md")
|
||
OUT_REPORT = os.path.join(BASE, "02_加工数据/文本清洗报告.md")
|
||
OUT_CHECK = os.path.join(BASE, "02_加工数据/人工核对清单.md")
|
||
OUT_STRUCT = os.path.join(BASE, "02_加工数据/章节结构_第一章.json")
|
||
|
||
PAGE_HEADER_RE = re.compile(r'^\s*《?辅行诀五脏用药法要》?\s*药性探真\s*》?\s*$')
|
||
PAGE_NUM_RE = re.compile(r'^\s*\d{1,3}\s*$')
|
||
CJK = r'[\u4e00-\u9fff]'
|
||
|
||
# C 类:形近错字 / 异体字 / 书名误字(逐条核校)
|
||
FIXES = [
|
||
("先骋通使", "先聘通使", "《本经》牡桂条作「为诸药先聘通使」;下文自称「先聘」,本书自证"),
|
||
("越洲山阴", "越州山阴", "陆佃籍贯,《宋史·陆佃传》作越州山阴"),
|
||
("桜", "梫", "《纲目》桂条引《尔雅》「梫者,能侵害他木也」(梫,音寝)"),
|
||
("国老之誊", "国老之誉", "文义:众药之王、和国老之誉;「誊」形近误"),
|
||
("酒皰皷鼻", "酒疱皶鼻", "《本经》栀子条作「面赤,酒疱皶鼻」"),
|
||
("淚出", "泪出", "异体字正字,全书他处均用「泪」"),
|
||
("微字为蒼之讹字", "微字为苍之讹字", "《纲目》人参释名,简体正字"),
|
||
("天元幻大论", "天元纪大论", "《素问》篇名作「天元纪大论」"),
|
||
("白戸浆", "白酨浆", "酨即酢浆;《辅行诀》原书作「白酨浆」,本书小标题亦作白酨浆"),
|
||
]
|
||
|
||
# F 类:存疑不改(原文保留)
|
||
SUSPECT_RULES = [
|
||
(r'菌,蕗。从竹,雨声', "《说文》「箘,箘簬。从竹,囷声。一曰博棋也」;疑「菌蕗」「雨声」为形近致误"),
|
||
(r'大则性环详而缓', "《本草衍义》枳实条:「大则其性详而缓」,疑衍「环」字"),
|
||
(r'决泄愤,地方但', "《本草衍义》:「皆取其疏通决泄、破结实之义。他方但导败风壅之气……」;"
|
||
"此处「愤,地方但」疑为OCR丢字串行,待补正"),
|
||
(r'由于积分布甚广', "据上下文(论枳之品种)疑当作「枳分布甚广」"),
|
||
(r'传动产隻者为雄', "《竹谱》竹根雌雄辨:疑当作「其节单者为雄」(「隻」为「单」之误)"),
|
||
(r'[((]?[))]', "原文含存疑符号(?),系作者或OCR存疑处,保留原文"),
|
||
(r'[①②③④⑤⑥⑦⑧⑨⑩]', "脚注标记,对应脚注正文在本次来源中缺失,待补"),
|
||
]
|
||
|
||
|
||
def clean_text(raw: str):
|
||
"""返回 (清洗文本, 事件列表, 删除统计)。事件均锚定到【清洗版行号】。"""
|
||
recs = [] # [{"text":..., "fixes":[...], "punct":[...]}]
|
||
removed = {"页眉": 0, "页码": 0}
|
||
for line in raw.split("\n"):
|
||
if PAGE_HEADER_RE.match(line):
|
||
removed["页眉"] += 1
|
||
continue
|
||
if PAGE_NUM_RE.match(line):
|
||
removed["页码"] += 1
|
||
continue
|
||
new = line.replace("\u3000", "").rstrip()
|
||
rec = {"text": new, "fixes": [], "punct": []}
|
||
for bad, good, why in FIXES:
|
||
if bad in new:
|
||
rec["fixes"].append({"原": bad, "改": good, "依据": why})
|
||
new = new.replace(bad, good)
|
||
# 保护「附录N」「第N章/节」后的分隔空格,避免与 OCR 断行空格一并折叠
|
||
new = re.sub(r'(附录[一二三四五六七八九十]|第[一二三四五六七八九十]+[章节])[ \t]+',
|
||
lambda m: m.group(1) + "\x01", new)
|
||
prev = None
|
||
while prev != new: # 中文词间半角空格(OCR断行残留)
|
||
prev = new
|
||
new = re.sub(r'(?<=[\u4e00-\u9fff])\s+(?=[\u4e00-\u9fff])', '', new)
|
||
new = re.sub(r'(?<=[\u4e00-\u9fff])\s+(?=[,。、;:?!“”()《》])', '', new)
|
||
new = re.sub(r'(?<=[,。、;:?!“”()《》])\s+(?=[\u4e00-\u9fff])', '', new)
|
||
# E 类:半角标点 ↔ 全角(邻接汉字或全角标点)
|
||
if re.search(CJK, line):
|
||
for _pass in range(2): # 二次通过:括号先转全角后再判后随标点
|
||
for asc, full in ((",", ","), (";", ";"), (":", ":"), ("?", "?"), ("!", "!")):
|
||
new2 = re.sub(r'(?<=[\u4e00-\u9fff),。、;:?!”“》)])\s*' + re.escape(asc), full, new)
|
||
if new2 != new:
|
||
rec["punct"].append({"原": asc, "改": full}); new = new2
|
||
for asc, full, side in (("(", "(", "后"), (")", ")", "前")):
|
||
pat = (r'\s*' + re.escape(asc) + r'(?=' + CJK + r')') if side == "后" else \
|
||
(r'(?<=' + CJK + r')\s*' + re.escape(asc))
|
||
new2 = re.sub(pat, full, new)
|
||
if new2 != new:
|
||
rec["punct"].append({"原": asc, "改": full}); new = new2
|
||
# 转换后再次折叠全角标点两侧空格
|
||
new = re.sub(r'(?<=[\u4e00-\u9fff])\s+(?=[,。、;:?!“”()《》])', '', new)
|
||
new = re.sub(r'(?<=[,。、;:?!“”()《》])\s+(?=[\u4e00-\u9fff])', '', new)
|
||
new = re.sub(r'(?=[,。、;:?!])\s+', '', new)
|
||
new = new.replace("\x01", " ") # 还原受保护的分隔空格
|
||
rec["text"] = new.rstrip()
|
||
recs.append(rec)
|
||
|
||
# 合并连续空行
|
||
tmp = []
|
||
prev_blank = False
|
||
for r in recs:
|
||
if r["text"] == "":
|
||
if prev_blank:
|
||
continue
|
||
prev_blank = True
|
||
else:
|
||
prev_blank = False
|
||
tmp.append(r)
|
||
|
||
# B 类:断行错位接合
|
||
out, joins = [], []
|
||
i = 0
|
||
while i < len(tmp):
|
||
cur = tmp[i]
|
||
if (cur["text"] and not cur["text"].startswith("#") and len(cur["text"]) > 8
|
||
and re.search(CJK + r'$', cur["text"])):
|
||
j = i + 1
|
||
while j < len(tmp) and tmp[j]["text"] == "":
|
||
j += 1
|
||
if j < len(tmp) and re.match(CJK, tmp[j]["text"]) and not tmp[j]["text"].startswith("#"):
|
||
merged = {"text": cur["text"] + tmp[j]["text"],
|
||
"fixes": cur["fixes"] + tmp[j]["fixes"],
|
||
"punct": cur["punct"] + tmp[j]["punct"]}
|
||
joins.append({"接合前": cur["text"][-20:], "接合后": tmp[j]["text"][:20],
|
||
"_before_line": i + 1, "_cont_line": j + 1})
|
||
out.append(merged)
|
||
i = j + 1
|
||
continue
|
||
out.append(cur)
|
||
i += 1
|
||
|
||
# 落定清洗版行号 + 事件汇总
|
||
lines, events = [], []
|
||
for n, rec in enumerate(out, start=1):
|
||
lines.append(rec["text"])
|
||
for f in rec["fixes"]:
|
||
events.append(dict(类型="C形近错字", 行=n, **f))
|
||
for p in rec["punct"]:
|
||
events.append(dict(类型="E标点规范化", 行=n, 原=p["原"], 改=p["改"], 依据="半角标点邻接汉字"))
|
||
for s in SUSPECT_RULES:
|
||
for m in re.finditer(s[0], rec["text"]):
|
||
events.append(dict(类型="F存疑", 行=n,
|
||
原=f"…{rec['text'][max(0,m.start()-15):m.end()+15]}…", 改="(保留原文)", 依据=s[1]))
|
||
for jn in joins:
|
||
events.append(dict(类型="B断行接合", 行=jn["_before_line"],
|
||
原=f"…{jn['接合前']} ‖ {jn['接合后']}…",
|
||
改="接合为一段", 依据=f"段落被页眉/页码打断,接续于原第{jn['_cont_line']}行"))
|
||
|
||
text = re.sub(r'\n{3,}', '\n\n', "\n".join(lines)).strip() + "\n"
|
||
# 去重:同行同类型同说明
|
||
seen, uniq = set(), []
|
||
for e in events:
|
||
k = (e["类型"], e["行"], e["依据"])
|
||
if k not in seen:
|
||
seen.add(k); uniq.append(e)
|
||
uniq.sort(key=lambda x: (x["行"], {"B断行接合": 0, "C形近错字": 1, "E标点规范化": 2, "F存疑": 3}[x["类型"]]))
|
||
return text, uniq, removed
|
||
|
||
|
||
def parse_structure(text: str):
|
||
lines = text.split("\n")
|
||
nodes = []
|
||
for i, l in enumerate(lines, start=1):
|
||
if not l.startswith("#"):
|
||
continue
|
||
title = l.lstrip("#").strip()
|
||
if re.match(r'^第[一二三四五六七八九十]+章', title):
|
||
lvl, typ = 1, "章"
|
||
elif re.match(r'^第[一二三四五六七八九十]+节', title):
|
||
lvl, typ = 2, "节"
|
||
elif re.match(r'^(附[::]|\d+\.\s)', title):
|
||
lvl, typ = 4, "附注"
|
||
else:
|
||
lvl, typ = 3, "条目"
|
||
nodes.append({"标题": title, "起始行": i, "类型": typ, "_lvl": lvl})
|
||
for n, node in enumerate(nodes):
|
||
node["结束行"] = len(lines)
|
||
for m in range(n + 1, len(nodes)):
|
||
if nodes[m]["_lvl"] <= node["_lvl"]:
|
||
node["结束行"] = nodes[m]["起始行"] - 1
|
||
break
|
||
GATE = {"辛": ("肝", "木", "春"), "咸": ("心", "火", "夏"), "甘": ("脾", "土", "长夏"),
|
||
"酸": ("肺", "金", "秋"), "苦": ("肾", "水", "冬")}
|
||
chapters = []
|
||
for node in nodes:
|
||
if node["类型"] == "章":
|
||
chapters.append(dict(node, 节=[]))
|
||
elif node["类型"] == "节":
|
||
m = re.search(r'([辛咸甘酸苦])味?门', node["标题"])
|
||
node["味门"] = m.group(1) if m else None
|
||
if m:
|
||
node["本脏"], node["本行"], node["脏象法象"] = GATE[m.group(1)]
|
||
if chapters:
|
||
chapters[-1]["节"].append(dict(node, 条目=[]))
|
||
else:
|
||
m = re.match(r'^[一二三四五六七八九十]+、\s*(.+?)\s*[((](.+?)[))]\s*$', node["标题"])
|
||
if m:
|
||
node["药名"], node["五行互含名位"] = m.group(1), m.group(2)
|
||
else:
|
||
node["药名"] = re.sub(r'^[一二三四五六七八九十]+、\s*', '', node["标题"])
|
||
node["五行互含名位"] = None
|
||
if chapters and chapters[-1].get("节"):
|
||
chapters[-1]["节"][-1]["条目"].append(node)
|
||
for c in chapters:
|
||
for s in c["节"]:
|
||
s["药物条目"] = [e for e in s["条目"] if e["类型"] == "条目"]
|
||
s["附属标题"] = [e["标题"] for e in s["条目"] if e["类型"] == "附注"]
|
||
return chapters
|
||
|
||
|
||
def scan_missing_assets(text: str):
|
||
"""登记悬空图片引用(OCR 流水线遗留,图片本体未随源文件提供)。"""
|
||
lines = text.split("\n")
|
||
out = []
|
||
for i, l in enumerate(lines, start=1):
|
||
for m in re.finditer(r'!\[\]\(([^)\n]+)\)', l):
|
||
out.append({"行": i, "文件名": os.path.basename(m.group(1)), "原始引用": m.group(0)[:80],
|
||
"上文": lines[i-3][:60] if i >= 3 else ""})
|
||
return out
|
||
|
||
|
||
def main():
|
||
raw = open(SRC, encoding="utf-8").read()
|
||
text, events, removed = clean_text(raw)
|
||
chapters = parse_structure(text)
|
||
missing_assets = scan_missing_assets(text)
|
||
|
||
def wf(path, content):
|
||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||
with open(path, "w", encoding="utf-8") as f:
|
||
f.write(content); f.flush(); os.fsync(f.fileno())
|
||
|
||
wf(OUT_MD, text)
|
||
struct = {
|
||
"来源文件": os.path.basename(SRC), "来源文件md5": hashlib.md5(raw.encode()).hexdigest(),
|
||
"清洗版文件": os.path.basename(OUT_MD), "清洗版md5": hashlib.md5(text.encode()).hexdigest(),
|
||
"原始字符数": len(raw), "清洗后字符数": len(text),
|
||
"说明": "行号均指清洗版;本文件仅作清理配套定位索引,未做释义抽取与跨库关联",
|
||
"章": chapters,
|
||
}
|
||
wf(OUT_STRUCT, json.dumps(struct, ensure_ascii=False, indent=2) + "\n")
|
||
|
||
ch = chapters[0]
|
||
secs = ch["节"]
|
||
n_drug = sum(len(s["药物条目"]) for s in secs)
|
||
cnt = lambda t: sum(1 for e in events if e["类型"] == t)
|
||
|
||
# 每节归属
|
||
def owner(line):
|
||
for s in secs:
|
||
if s["起始行"] <= line <= s["结束行"]:
|
||
return s
|
||
return None
|
||
per_sec = {s["标题"]: [] for s in secs}
|
||
for e in events:
|
||
o = owner(e["行"])
|
||
if o is not None:
|
||
per_sec[o["标题"]].append(e)
|
||
|
||
def ev_table(evs):
|
||
rows = ["| 行 | 类型 | 原文 | 处理后 | 依据 |", "|----|------|------|--------|------|"]
|
||
for e in evs:
|
||
rows.append(f"| {e['行']} | {e['类型']} | {e['原'].replace('|','|')} | "
|
||
f"{e['改'].replace('|','|')} | {e['依据']} |")
|
||
return rows
|
||
|
||
rep = ["# 《辅行诀五脏用药法要》药性探真 第一章 — 文本清洗报告", "",
|
||
"> 本库现阶段只做数据清理。用户 2026-09-15 指令:以第一章为源,待全文内容完整后再做加工/跨库关联/分析。", "",
|
||
"## 0 概览", "",
|
||
f"- 来源:`01_来源数据/{os.path.basename(SRC)}`(md5 `{struct['来源文件md5']}`)",
|
||
f"- 清洗版:`02_加工数据/{os.path.basename(OUT_MD)}`(md5 `{struct['清洗版md5']}`)",
|
||
f"- 字符数:{len(raw)} → {len(text)};行数:{raw.count(chr(10))} → {text.count(chr(10))}",
|
||
f"- 结构:{ch['标题']} ── {len(secs)} 节 / 药物条目 {n_drug} 味", "",
|
||
"| 清理类别 | 处数 | 说明 |", "|---------|------|------|",
|
||
f"| A 页面残留物 | {removed['页眉'] + removed['页码']} | 页眉 {removed['页眉']} 行、独立页码 {removed['页码']} 行;另有行尾/全角/词间空格全量清理 |",
|
||
f"| B 断行错位接合 | {cnt('B断行接合')} | 段落被页眉打断处复原成整段 |",
|
||
f"| C 形近错字/异体字 | {cnt('C形近错字')} | 每条附核校依据,见下逐节明细 |",
|
||
f"| E 半角标点规范化 | {cnt('E标点规范化')} | ASCII , ; : ( ) 邻接汉字时改全角 |",
|
||
f"| F 存疑(未改原文) | {cnt('F存疑')} | 逐条列出疑正与核校来源,见 `人工核对清单.md` |", "",
|
||
"### 各节清理量一览", "",
|
||
"| 节 | 行范围 | 药物条目 | B 接合 | C 错字 | E 标点 | F 存疑 |",
|
||
"|----|--------|---------|--------|--------|--------|--------|"]
|
||
for s in secs:
|
||
evs = per_sec[s["标题"]]
|
||
c = lambda t: sum(1 for e in evs if e["类型"] == t)
|
||
rep.append(f"| {s['标题']} | {s['起始行']}–{s['结束行']} | {len(s['药物条目'])} | "
|
||
f"{c('B断行接合')} | {c('C形近错字')} | {c('E标点规范化')} | {c('F存疑')} |")
|
||
|
||
rep += ["", "---", "", "## 1 逐节核对明细(按章 / 节列明)", "",
|
||
f"### {ch['标题']}(清洗版行 {ch['起始行']}–{ch['结束行']})", ""]
|
||
for s in secs:
|
||
evs = per_sec[s["标题"]]
|
||
gate = f"味门{s['味门']}·{s['本脏']}{s['本行']}·法{s['脏象法象']}" if s.get("味门") else "从火土同治论心病(草木方例用药简释)"
|
||
rep += [f"#### {s['标题']}", "",
|
||
f"- 定位:清洗版行 {s['起始行']}–{s['结束行']};{gate};药物条目 {len(s['药物条目'])} 味",
|
||
f"- 本味药:{'、'.join(e['药名'] for e in s['药物条目'])}",
|
||
f"- 清理量:B 接合 {sum(1 for e in evs if e['类型']=='B断行接合')} / "
|
||
f"C 错字 {sum(1 for e in evs if e['类型']=='C形近错字')} / "
|
||
f"E 标点 {sum(1 for e in evs if e['类型']=='E标点规范化')} / "
|
||
f"F 存疑 {sum(1 for e in evs if e['类型']=='F存疑')}", ""]
|
||
if evs:
|
||
rep += ev_table(evs) + [""]
|
||
else:
|
||
rep += ["- 本节无需修正条目。", ""]
|
||
|
||
rep += ["---", "", "## 2 章节结构(清洗版行号)", "",
|
||
"| 章 | 节 | 味门 | 本脏/行/时 | 起始行 | 结束行 | 药物条目 | 附属标题 |",
|
||
"|----|----|------|-----------|--------|--------|---------|---------|"]
|
||
for s in secs:
|
||
gate = f"{s.get('本脏','—')}/{s.get('本行','—')}/{s.get('脏象法象','—')}" if s.get("味门") else "—"
|
||
rep.append(f"| {ch['标题']} | {s['标题']} | {s.get('味门') or '—'} | {gate} | {s['起始行']} | {s['结束行']} | "
|
||
f"{len(s['药物条目'])} | {';'.join(s['附属标题']) or '—'} |")
|
||
rep += ["", "## 3 缺失内容登记(来源不完整,非错字问题)", "",
|
||
"| 项 | 情况 | 位置 | 处理 |", "|----|------|------|------|",
|
||
f"| 悬空图片引用 | {len(missing_assets)} 处(OCR 流水线遗留,图片本体未随源文件提供) | "
|
||
+ ";".join(f"行 {m['行']}({m['文件名'][:12]}…)" for m in missing_assets) + " | 原文保留引用,待补图 |",
|
||
"| 脚注正文 | 正文存 ① ② 等标记 5 处(桂枝、人参、升麻、白醪法 2 处),脚注正文未随文录入 | 见上方逐节明细 F 类 | 待补后回填 |",
|
||
"| 第二章及以后各章 | 本次来源仅第一章「五脏补泻草木方例 用药释义」 | — | 待补 |",
|
||
"| 附录一至四 | 目录列有:整订稿/略论《汤液经法》与张仲景论著的关系/张大昌先生《处方正范》/张大昌先生《三十六脉名义略述》 | 来源文件首 3 行 | 待补 |", "",
|
||
"## 4 药物条目清单(章节 → 药名 → 五行互含名位,原样保留)", "",
|
||
"| 节 | 序 | 药名 | 五行互含名位 | 清洗版行范围 |", "|----|----|------|-------------|-------------|"]
|
||
for s in secs:
|
||
for k, e in enumerate(s["药物条目"], start=1):
|
||
rep.append(f"| {s['标题']} | {k} | {e['药名']} | {e['五行互含名位'] or '—'} | {e['起始行']}–{e['结束行']} |")
|
||
rep += ["", "> 说明:标题中的「五行互含名位」为原书表述(如「生姜木中火、干姜木中水」),清洗阶段原样保留,不作拆分或解释。"]
|
||
wf(OUT_REPORT, "\n".join(rep) + "\n")
|
||
|
||
chk = ["# 人工核对清单 — 《辅行诀五脏用药法要》药性探真 第一章", "",
|
||
"> 用途:需对照纸质原书/影印本确认的条目。核对清单**按章/节列明**,本阶段不改动清洗版正文。",
|
||
"> 清洗流程与全部修正记录见 `文本清洗报告.md`。", "",
|
||
f"## {ch['标题']}", ""]
|
||
for s in secs:
|
||
evs = [e for e in per_sec[s["标题"]] if e["类型"] == "F存疑"]
|
||
chk += [f"### {s['标题']}(行 {s['起始行']}–{s['结束行']})", ""]
|
||
if evs:
|
||
chk += ["| 行 | 片段 | 疑正 / 核校来源 |", "|----|------|----------------|"]
|
||
for e in evs:
|
||
chk.append(f"| {e['行']} | {e['原'].replace('|','|')} | {e['依据']} |")
|
||
else:
|
||
chk.append("- 无存疑项。")
|
||
chk.append("")
|
||
chk += ["---", "", "## 缺失内容登记(本次来源不完整,非错字问题)", "",
|
||
"| 项 | 情况 | 处理 |", "|----|------|------|",
|
||
f"| 悬空图片引用 | {len(missing_assets)} 处:"
|
||
+ ";".join(f"行 {m['行']} `{m['文件名']}`" for m in missing_assets)
|
||
+ "(OCR 流水线遗留,图片本体未随源文件提供) | 待补图,补后替换为真实路径 |",
|
||
"| 脚注正文 | 正文存 ① ② 等标记 5 处(桂枝、人参、升麻、白醪法 2 处),脚注正文未随文录入 | 待补后回填 |",
|
||
"| 第二章及以后各章 | 本次来源仅第一章「五脏补泻草木方例 用药释义」 | 待补 |",
|
||
"| 附录一至四 | 目录列有:整订稿/略论《汤液经法》与张仲景论著的关系/张大昌先生《处方正范》/张大昌先生《三十六脉名义略述》 | 待补 |",
|
||
"| 表格 | 第一章正文不含表格 | — |",
|
||
"| 图版 | 正文有 2 处图片引用,图片本体缺失(见上「悬空图片引用」) | 如有影印本可另补 |", "",
|
||
"## 书目信息(供核对)", "",
|
||
"- 书名:《辅行诀五脏用药法要》药性探真",
|
||
"- 著者:衣之镖 撰著;丛书:张大昌先生弟子个人专著",
|
||
"- 出版:学苑出版社,2014 年 1 月第 1 版,ISBN 978-7-5077-4414-9",
|
||
"- 本次来源:`01_来源数据/药性探真_第一章_原始_20260915.md`(仅第一章)"]
|
||
wf(OUT_CHECK, "\n".join(chk) + "\n")
|
||
|
||
print("清洗版:", OUT_MD, os.path.getsize(OUT_MD), "bytes")
|
||
print("结构 :", OUT_STRUCT, os.path.getsize(OUT_STRUCT), "bytes")
|
||
print("报告 :", OUT_REPORT, os.path.getsize(OUT_REPORT), "bytes")
|
||
print("核对单:", OUT_CHECK, os.path.getsize(OUT_CHECK), "bytes")
|
||
print(f"页眉/页码删除 {removed} | B接合 {cnt('B断行接合')} | C错字 {cnt('C形近错字')} | "
|
||
f"E标点 {cnt('E标点规范化')} | F存疑 {cnt('F存疑')}")
|
||
print(f"节 {len(secs)} 个 / 药物条目 {n_drug} 味")
|
||
for s in secs:
|
||
evs = per_sec[s["标题"]]
|
||
print(f" {s['标题']} 行{s['起始行']}–{s['结束行']} 药物{len(s['药物条目'])} "
|
||
f"| 清理项 {len(evs)} 条:" + "、".join(f"{e['类型'][:1]}@{e['行']}" for e in evs))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|