前段时间开始读蔡东藩先生的《中国历代通俗演义》,其中《前汉演义》读了差不多半年了,读到第68回的时候,突然奇想,与其每次遇到不懂的文言文查询,不如试试AI一键获取会怎么样呢?因此采用Cursor的Auto模式开始进行批量生成。 当然由于内容过多,目前生成的效果还来不及二次校验(只校验了一小部分),但后续会继续阅读时会一直校验,先以本博文记录下AI生成及目录。
代码生成过程
整体流程:维基文库 API 拉 HTML → BeautifulSoup 取回目与正文段 → zhconv 转简体 → 写入 Jekyll Markdown;# 译文 占位符后续由 Cursor 中 AI(可拆多批/子智能体)按第 68 回式述要补全。
环境与依赖
在仓库根目录执行(示例为 Python 3.9+):
pip install zhconv beautifulsoup4 lxml
说明:
- zhconv:繁体转大陆简体(
zh-cn)。 - beautifulsoup4 + lxml:解析
action=parse返回的 HTML。 - 请求维基 API 时必须带 非空
User-Agent(脚本中已写固定标识),否则易被 403。
脚本位置与用法
仓库内脚本路径:scripts/fetch_qianhan_chapters.py。
# 默认仅拉第 26~67 回(与早期批处理一致,避免误覆盖全书)
python3 scripts/fetch_qianhan_chapters.py
# 指定区间(示例:全书 1~100 回;会覆盖已存在的同名 md)
python3 scripts/fetch_qianhan_chapters.py --start 1 --end 100
# 仅补某一回
python3 scripts/fetch_qianhan_chapters.py --start 69 --end 69
写入路径:_posts/前汉演义/2026-04-18-前汉演义XX.md(XX 为两位回次)。再次运行会整文件覆盖,若已手写译文请先备份或改脚本逻辑。
完整脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Fetch 前漢演義 chapters from zh.wikisource.org, convert to zh-cn, write Jekyll posts."""
from __future__ import annotations
import argparse
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from pathlib import Path
from typing import List, Optional, Tuple
from bs4 import BeautifulSoup
import zhconv
UA = "KwanWaiPangBlogBot/1.0 (https://github.com/KwanWaiPang/kwanwaipang.github.io; zhconv+read-only)"
ROOT = Path(__file__).resolve().parents[1]
OUT_DIR = ROOT / "_posts" / "前汉演义"
def api_parse(page: str) -> str:
q = urllib.parse.urlencode(
{"action": "parse", "page": page, "prop": "text", "format": "json"}
)
url = f"https://zh.wikisource.org/w/api.php?{q}"
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=60) as resp:
data = json.loads(resp.read().decode("utf-8"))
if "error" in data:
raise RuntimeError(data["error"])
return data["parse"]["text"]["*"]
def _parse_nav_subtitle(nav_text: str) -> Optional[str]:
"""从导航块文字解析副标题;维基文库各回版式略有出入。"""
flat = re.sub(r"\s+", " ", nav_text.replace("\n", " "))
patterns = [
r"前漢演義\s*(第.+?回)\s*(.+?)\s*作者",
r"前漢演義\s*(第.+?回)\s*(.+?)(?=\s*[◄►])",
r"前漢演義\s*(第.+?回)\s*(.+?)(?=\s*→|\s*←)",
r"前漢演義\s*(第.+?回)\s*(.+?)(?=\s*姊妹计划)",
]
for pat in patterns:
m = re.search(pat, flat)
if m:
return m.group(2).strip()
return None
def extract_title_and_paragraphs(html: str) -> Tuple[str, List[str]]:
soup = BeautifulSoup(html, "lxml")
po = soup.select_one(".mw-parser-output")
if not po:
raise RuntimeError("no .mw-parser-output")
subtitle = ""
paragraphs: List[str] = []
for child in po.children:
name = getattr(child, "name", None)
if not name:
continue
if name in ("div", "table") and not subtitle:
nav_text = child.get_text(" ", strip=True)
if "前漢演義" in nav_text and "回" in nav_text:
sub = _parse_nav_subtitle(nav_text)
if sub:
subtitle = sub
continue
if name == "p":
t = child.get_text()
if not t or not t.strip():
continue
paragraphs.append(t.strip())
if not subtitle:
blob = po.get_text(" ", strip=True)[:1200]
subtitle = _parse_nav_subtitle(blob) or ""
if not subtitle:
raise RuntimeError("subtitle not found")
return subtitle, paragraphs
def to_cn(s: str) -> str:
return zhconv.convert(s, "zh-cn")
def cn_num(n: int) -> str:
"""阿拉伯数字转中文数字(用于 Jekyll title 中的回次,1–100)。"""
digits = "零一二三四五六七八九"
if n < 10:
return digits[n]
if n == 10:
return "十"
if n < 20:
return "十" + digits[n - 10]
if n < 100:
tens, ones = divmod(n, 10)
return digits[tens] + "十" + (digits[ones] if ones else "")
if n == 100:
return "一百"
return str(n)
def build_markdown(chapter: int, subtitle_tw: str, paras_tw: List[str]) -> str:
subtitle = to_cn(subtitle_tw)
body_lines = []
for p in paras_tw:
body_lines.append(to_cn(p))
body = "\n\n".join(" " + ln.replace("\n", "") for ln in body_lines)
title = f'读书笔记之——《前汉演义》第{cn_num(chapter)}回:{subtitle}'
wikilink = f"https://zh.wikisource.org/wiki/%E5%89%8D%E6%BC%A2%E6%BC%94%E7%BE%A9/%E7%AC%AC{chapter:03d}%E5%9B%9E"
return f"""---
layout: post
title: "{title}"
date: 2026-04-18
tags: [Books]
comments: true
author: kwanwaipang
# toc: false
excerpt: ""
---
<!-- * 参考资料:[蔡东藩《前汉演义》](https://zh.wikisource.org/wiki/%E5%89%8D%E6%BC%A2%E6%BC%94%E7%BE%A9) · [维基文库:本回全文]({wikilink}) -->
---
# 原文
{body}
---
# 译文
(译文生成中)
"""
def main(argv=None) -> None:
p = argparse.ArgumentParser(description="Fetch 前漢演義 chapters from zh.wikisource.org")
p.add_argument(
"--start",
type=int,
default=26,
help="起始回次(含)。默认 26 与历史批处理一致;全书请用 1",
)
p.add_argument(
"--end",
type=int,
default=67,
help="结束回次(含)。默认 67;全书至 100 请写 100",
)
args = p.parse_args(argv)
start, end = args.start, args.end
if start > end:
print("error: --start must be <= --end", file=sys.stderr)
sys.exit(1)
OUT_DIR.mkdir(parents=True, exist_ok=True)
for n in range(start, end + 1):
page = f"前漢演義/第{n:03d}回"
print("fetch", page)
html = api_parse(page)
sub, paras = extract_title_and_paragraphs(html)
md = build_markdown(n, sub, paras)
path = OUT_DIR / f"2026-04-18-前汉演义{n:02d}.md"
path.write_text(md, encoding="utf-8")
time.sleep(0.35)
if __name__ == "__main__":
main()
抓取逻辑摘要
| 步骤 | 说明 |
|---|---|
| API | https://zh.wikisource.org/w/api.php?action=parse&page=前漢演義/第NNN回&prop=text&format=json |
| 回目副题 | 从 .mw-parser-output 首块 div 或 table 导航文字中正则提取;部分回次无「作者」字样时换用 ◄► 等分隔模式;仍失败则在前 1200 字内再扫 |
| 正文 | 收集导航块之后所有顶层 <p>,段首统一加全角空格 |
| 简体 | 副题与正文均经 zhconv.convert(..., "zh-cn") |
| 节流 | 每回 sleep(0.35),减轻对维基服务器压力 |
译文部分(AI)
脚本生成的 # 译文 初值为 (译文生成中)。实际生产时在 Cursor 用 Auto / Agent,按回次分批撰写白话述要(对齐 _posts/前汉演义/2026-04-18-前汉演义68.md:开篇说明、若干 ##、诗联要点、## 回末史论(白话)),再写回对应 md。与抓取脚本解耦,避免一次对话超长。
多智能体(可选)
大批量回次时,可在 Cursor 中并行派发多个子任务,各负责一段回次区间,分别改文件。示意截图仍如下(界面随版本可能略有差异):
书籍目录及索引
- 第一回:移花接木计献美姬 用李代桃欢承淫后
- 第二回:诛假父纳言迎母 称皇帝立法愚民
- 第三回:封泰岱下山避雨 过湘江中渡惊风
- 第四回:误椎击逃生遇异士 见图谶遣将造长城
- 第五回:信佞臣尽毁诗书 筑阿房大兴土木
- 第六回:坑深谷诸儒毙命 得原璧暴主惊心
- 第七回:寻生路徐巿垦荒 从逆谋李斯矫诏
- 第八回:葬始皇骊山成巨冢 戮宗室豻狱构奇冤
- 第九回:充屯长中途施诡计 杀将尉大泽揭叛旗
- 第十回:违谏议陈胜称王 善招抚武臣独立
- 第十一回:降真龙光韬泗水 斩大蛇夜走丰乡
- 第十二回:戕县令刘邦发迹 杀郡守项梁举兵
- 第十三回:说燕将厮卒救王 入赵宫叛臣弑主
- 第十四回:失兵机陈王毙命 免子祸婴母垂言
- 第十五回:从范增访立楚王孙 信赵高冤杀李丞相
- 第十六回:驻定陶项梁败死 屯安阳宋义丧生
- 第十七回:破釜沈舟奋身杀敌 损兵折将畏罪乞降
- 第十八回:智郦生献谋取要邑 愚胡亥遇弑毙斋宫
- 第十九回:诛逆阉难延秦祚 坑降卒直入函关
- 第二十回:宴鸿门张樊保驾 焚秦宫关陕成墟
- 第二十一回:烧栈道张良定谋 筑郊坛韩信拜将
- 第二十二回:用秘计暗渡陈仓 受密嘱阴弑义帝
- 第二十三回:下河南陈平走谒 过洛阳董老献谋
- 第二十四回:脱楚厄幸遇戚姬 知汉兴拼死陵母
- 第二十五回:木罂渡军计擒魏豹 背水列阵诱斩陈余
- 第二十六回:随何传命招英布 张良借箸驳郦生
- 第二十七回:纵反间范增致毙 甘替死纪信被焚
- 第二十八回:入内帐潜夺将军印 救全城幸得舍人儿
- 第二十九回:贪功得祸郦生就烹 数罪陈言汉王中箭
- 第三十回:斩龙且出奇制胜 划鸿沟接眷修和
- 第三十一回:大将奇谋鏖兵垓下 美人惨别走死江滨
- 第三十二回:即帝位汉主称尊 就驿舍田横自刭
- 第三十三回:劝移都娄敬献议 伪出游韩信受擒
- 第三十四回:序侯封优待萧丞相 定朝仪功出叔孙通
- 第三十五回:谋弑父射死单于 求脱围赂遗番后
- 第三十六回:宴深宫奉觞祝父寿 系诏狱拼死白王冤
- 第三十七回:议废立周昌争储 讨乱贼陈豨败走
- 第三十八回:悍吕后毒计戮功臣 智陆生善言招蛮酋
- 第三十九回:讨淮南箭伤御驾 过沛中宴会乡亲
- 第四十回:保储君四皓与宴 留遗嘱高祖升遐
- 第四十一回:折雄狐片言杜祸 看人彘少主惊心
- 第四十二回:媚公主腼颜拜母 戏太后嫚语求妻
- 第四十三回:审食其遇救谢恩人 吕娥姁挟权立少帝
- 第四十四回:易幼主诸吕加封 得悍妇两王枉死
- 第四十五回:听陆生交欢将相 连齐兵合拒权奸
- 第四十六回:夺禁军捕诛诸吕 迎代王废死故君
- 第四十七回:两重喜窦后逢兄弟 一纸书文帝服蛮夷
- 第四十八回:遭众忌贾谊被迁 正阃仪袁盎强谏
- 第四十九回:辟阳侯受椎毙命 淮南王谋反被囚
- 第五十回:中行说叛国降虏庭 缇萦女上书赎父罪
- 第五十一回:老郎官犯颜救魏尚 贤丞相当面劾邓通
- 第五十二回:争棋局吴太子亡身 肃军营周亚夫守法
- 第五十三回:呕心血气死申屠嘉 主首谋变起吴王濞
- 第五十四回:信袁盎诡谋斩御史 遇赵涉依议出奇兵
- 第五十五回:平叛军太尉建功 保孱王邻封乞命
- 第五十六回:王美人有缘终作后 栗太子被废复蒙冤
- 第五十七回:索罪犯曲全介弟 赐肉食戏弄条侯
- 第五十八回:嗣帝祚董生进三策 应主召申公陈两言
- 第五十九回:迎母姊亲驰御驾 访公主喜遇歌姬
- 第六十回:因祸为福仲卿得官 寓正于谐东方善辩
- 第六十一回:挑嫠女即席弹琴 别娇妻入都献赋
- 第六十二回:厌夫贫下堂致悔 开敌衅出塞无功
- 第六十三回:执国法王恢受诛 骂座客灌夫得罪
- 第六十四回:遭鬼祟田蚡毙命 抚夷人司马扬镳
- 第六十五回:窦太主好淫甘屈膝 公孙弘变节善承颜
- 第六十六回:飞将军射石惊奇 愚主父受金拒谏
- 第六十七回:失俭德故人烛隐 庆凯旋大将承恩
- 第六十八回:舅甥踵起一战封侯,父子败谋九重讨罪
- 第六十九回:勘叛案重兴大狱 立战功还挈同胞
- 第七十回:贤汲黯直谏救人 老李广失途刎首
- 第七十一回:报私仇射毙李敢 发诈谋致死张汤
- 第七十二回:通西域复灭南夷 进神马兼迎宝鼎
- 第七十三回:信方士连番被惑 行封禅妄想求仙
- 第七十四回:东征西讨绝域穷兵 先败后成贰师得马
- 第七十五回:入虏庭苏武抗节 出朔漠李陵败降
- 第七十六回:巫盅狱丞相灭门 泉鸠里储君毙命
- 第七十七回:悔前愆痛下轮台诏 授顾命嘱遵负扆图
- 第七十八回:六龄幼女竟主中宫 廿载使臣重还故国
- 第七十九回:识诈书终惩逆党 效刺客得毙番王
- 第八十回:迎外藩新主入都 废昏君太后登殿
- 第八十一回:谒祖庙骖乘生嫌 嘱女医入宫进毒
- 第八十二回:孝妇伸冤于公造福 淫妪失德霍氏横行
- 第八十三回:泄逆谋杀尽后族 矫君命歼厥渠魁
- 第八十四回:询宫婢才识酬恩 擢循吏迭闻报绩
- 第八十五回:两疏见机辞官归里 三书迭奏罢兵屯田
- 第八十六回:逞淫谋番妇构衅 识子祸严母知几
- 第八十七回:杰阁图形名标麟史 锦车出使功让蛾眉
- 第八十八回:宠阉竖屈死萧望之 惑谗言再贬周少傅
- 第八十九回:冯婕妤挺身当猛兽 朱子元仗义救良朋
- 第九十回:斩郅支陈汤立奇功 嫁匈奴王嫱留遗恨
- 第九十一回:赖直谏太子得承基 宠正宫词臣同抗议
- 第九十二回:识番情指日解围 违妇言上书惹祸
- 第九十三回:惩诸舅推恩赦罪 嬖二美夺嫡宣淫
- 第九十四回:智班伯借图进谏 猛朱云折槛留旌
- 第九十五回:泄机谋鸩死许后 争座位怒斥中官
- 第九十六回:忤重闱师丹遭贬 害故妃史立售奸
- 第九十七回:莽朱博附势反亡身 美董贤阖家同邀宠
- 第九十八回:良相遭囚呕血致毙 幸臣失势与妇并戕
- 第九十九回:献白雉罔上居功 惊赤血杀儿构狱
- 第一百回:窃国权王莽弑帝 投御玺元后覆宗