Skip to content

Latest commit

 

History

History
366 lines (271 loc) · 11.2 KB

File metadata and controls

366 lines (271 loc) · 11.2 KB

Epub 转文本切分系统 - AI 对话式开发实录

本文档完整记录了通过与 AI 对话进行 Agentic Coding 开发 EpubToSplitTxt 项目的全过程,展示了人机协作编程的真实体验。


项目起源

初始需求: 将网络小说《蛊真人》的 Epub 电子书转换为按章节切分的纯文本文件,为后续 TTS(文本转语音)处理做准备。

核心挑战:

  • Epub 内部是 HTML 格式,需要清洗
  • 《蛊真人》结构特殊:按"卷-节"组织,每卷从"第一节"开始
  • 需要保持正确的阅读顺序
  • 文件命名要兼顾语义和排序

对话开发历程

第 0 轮:基于 Rules 生成项目骨架

背景: 在开始对话之前,我先定义了一份项目规则文件 epub_processing.md,作为 AI 的开发指南。

Rules 核心内容:

## Codebase
- 语言: C# (.NET 10 Preview)
- 职责边界:
  - EpubConverter: 负责解析 .epub 文件结构并提取纯文本
  - TextSplitter: 负责基于正则规则扫描文本,并按章节物理切割文件
- 文件命名规范: 切分后的文件名必须包含序列号以保持阅读顺序

## Dependencies
- VersOne.Epub:3.2.1 - Epub 解析库
- HtmlAgilityPack:1.11.59 - HTML 标签清洗

## Config
- Splitter:ChapterRegex - 章节匹配正则
- Splitter:MinChapterLength - 最小章节长度
- Paths:RawEpubFolder / SplitOutputFolder - 路径配置

## AI Coding Rules
- 正则必须使用 RegexOptions.Compiled
- 禁止一次性 File.ReadAllText,必须流式处理
- 输出必须使用 UTF-8 (without BOM) 编码
- 文件名必须清洗非法字符

AI 基于 Rules 生成了:

  1. EpubToSplitTxt.csproj - 项目配置,引入指定依赖
  2. Program.cs - 主程序入口
  3. EpubConverter.cs - Epub 解析模块
  4. TextSplitter.cs - 章节切分模块
  5. AppSettings.cs - 配置管理
  6. appsettings.json - 配置文件

学到的:通过预定义 Rules,AI 能一次性生成符合规范的完整项目骨架,而不是从零开始摸索。


第 1 轮:项目结构完善

我说:

用 global using,然后把 bin 和 obj 文件全部忽略,还有就是为什么没有解决方案文件

AI 做了什么:

  1. 创建 GlobalUsings.cs,集中管理常用命名空间
  2. 创建 .gitignore,忽略 bin/obj 等编译产物
  3. 生成 EpubToSplitTxt.sln 解决方案文件

学到的:AI 能快速理解 .NET 项目的最佳实践,一次性完成多个配置任务。


第 2 轮:配置路径问题

我说:

@appsettings.json 这个好像不生效

问题根源: Directory.GetCurrentDirectory() 返回的是 Shell 当前目录,而不是程序所在目录。

AI 的修复:

// 修复前:使用当前工作目录
SetBasePath(Directory.GetCurrentDirectory())

// 修复后:使用程序所在目录
SetBasePath(AppContext.BaseDirectory)

学到的:描述问题现象,AI 能准确定位 .NET 配置加载的常见陷阱。


第 3 轮:自定义输入目录

我说:

不用编译 [WARN] 在 bin\Debug\net9.0\RawEpub 中未找到任何 .epub 文件 我不要用编译文件的那个文件夹,我这里不是有一个文件夹吗

AI 理解了: 我想用项目根目录下的 RawEpub 文件夹,而不是 bin 目录。

解决方案: 配置文件中使用相对路径 ../../../RawEpub,并在代码中正确解析。


第 4 轮:针对《蛊真人》的正则优化

我展示了书中内容:

类似这种章节: 序:不是走向成功,就是走向毁灭 第一节:纵身亡魔心仍不悔

AI 更新了正则配置:

{
  "ChapterRegex": "(^第[0-9一二三四五六七八九十百千]+章.*)|(^序[::].*)|(^序章.*)|(^楔子.*)",
  "SectionRegex": "(^第[0-9一二三四五六七八九十百千]+节.*)|(^内容简介.*)"
}

同时实现了:如果内容为空则不生成 txt 文件。


第 5 轮:发现缺失内容

我说:

其实根本不完整 这个文件是完整的,下面是最后一小节:第三百六十八节:方源、巨阳战星宿

问题诊断: 源文件有 2335 节,但只生成了 2158 个文件,缺失 177 节!

AI 的排查过程:

  1. 添加调试计数器统计识别的节数
  2. 对比发现确实少了很多节
  3. 检查正则表达式...

根本原因: 正则 [0-9一二三四五六七八九十百千] 缺少 "零""两" 字符!

像 "第一百零一节"、"第两百节" 这样的标题无法匹配。

修复:

- [0-9一二三四五六七八九十百千]+节
+ [0-9一二三四五六七八九十百千零两]+节

学到的:中文数字的完整字符集容易遗漏,AI 能通过数据对比快速定位问题。


第 6 轮:两级目录结构

我说:

不对,还有内容没有分割处理,它是分章然后再分小节的,你可以把每章放到一个文件夹中,里面是那些小节

AI 重构了 TextSplitter:

  • 章 → 文件夹
  • 节 → 文件夹内的独立文件

输出结构:

SplitOutput/蛊真人/
├── 01.第一章:前言/
│   ├── 001.前言.txt
│   └── 002.内容简介.txt
├── 02.第二章:纵身亡魔心仍不悔/
│   ├── 003.第一节:纵身亡魔心仍不悔.txt
│   ├── 004.第二节:逆光阴五百年觉悟.txt
│   └── ...

第 7 轮:按"第一节"分章

我说:

你能根据每个第一节帮我分割吗,然后根据上下文以及语义取标题

AI 分析了源文本: 发现有 6 个 "第一节",意味着全书分 6 卷。

新策略:

  1. 遇到 "第一节" 开始新章
  2. 用第一节标题冒号后的内容作为章名
    • 第一节:纵身亡魔心仍不悔 → 章名 纵身亡魔心仍不悔

第 8 轮:文件命名格式

我说:

每一节前面还是要这样命名:第一百三十节:"父子"相见 然后每一章按照顺序排序:第一章:前言

AI 理解了双重需求:

  1. 节文件保留完整标题格式
  2. 章文件夹用 "第X章:" 前缀

但遇到排序问题: 中文数字在文件系统中按字符排序(第一、第七、第三...)

我说:

可以试试,你给我看看改动

最终方案: 阿拉伯数字前缀 + 中文标题

01.第一章:前言
02.第二章:纵身亡魔心仍不悔
...
003.第一节:纵身亡魔心仍不悔.txt
004.第二节:逆光阴五百年觉悟.txt

兼顾了 正确排序语义可读性


技术架构

Program.cs (主程序入口)
    │
    ├── EpubConverter (Epub 解析模块)
    │   ├── VersOne.Epub 3.3.0 - 读取 Epub 结构
    │   └── HtmlAgilityPack 1.11.59 - 清洗 HTML 标签
    │
    ├── TextSplitter (章节切分模块)
    │   ├── 按"第一节"识别新章
    │   ├── 正则匹配节标题
    │   └── 中文数字转换
    │
    └── AppSettings (配置管理)
        └── appsettings.json

处理流水线

┌─────────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────────┐
│  Epub 文件  │ → │ EpubConverter │ → │  全本 TXT   │ → │ TextSplitter │
└─────────────┘    └──────────────┘    └─────────────┘    └──────────────┘
                          │                                      │
                   解析 HTML 结构                         按"第一节"分章
                   清洗标签/实体                          正则匹配节标题
                   合并为纯文本                           序号+语义命名
                                                                 ↓
                                                    ┌──────────────────┐
                                                    │  章节文件夹集合  │
                                                    └──────────────────┘

关键代码片段

第一节分章逻辑

// 第一节作为新章的开始标志
_firstSectionRegex = new Regex(@"^第一节[::]", RegexOptions.Compiled);

if (isFirstSection)
{
    // 保存当前章
    SaveCurrentChapter();
    
    // 用第一节标题的冒号后内容作为章名
    currentChapterTitle = ExtractChapterTitle(trimmedLine);
    // "第一节:纵身亡魔心仍不悔" → "纵身亡魔心仍不悔"
}

中文数字转换

private string ConvertToChineseNumber(int number)
{
    string[] digits = { "零", "一", "二", "三", "四", "五", "六", "七", "八", "九" };
    
    if (number < 10) return digits[number];
    if (number < 20) return "十" + (number % 10 > 0 ? digits[number % 10] : "");
    // ... 百、千的处理
}

文件名清洗

private string SanitizeFileName(string fileName)
{
    // 移除文件系统非法字符: \ / : * ? " < > |
    char[] invalidChars = Path.GetInvalidFileNameChars();
    foreach (char c in invalidChars)
    {
        fileName = fileName.Replace(c, '_');
    }
    return fileName;
}

最终成果

指标 数值
识别章数 7 章
识别节数 2336 节
平均每节字数 3168 字
处理耗时 ~5 秒

输出目录结构:

SplitOutput/蛊真人/
├── 01.第一章:前言/
│   ├── 001.前言.txt
│   └── 002.内容简介....txt
├── 02.第二章:纵身亡魔心仍不悔/
│   ├── 003.第一节:纵身亡魔心仍不悔.txt
│   ├── 004.第二节:逆光阴五百年觉悟.txt
│   └── ... (共 199 节)
├── 03.第三章:黄龙江上竹筏倾/
├── 04.第四章:凤九歌/
├── 05.第五章:回归狐仙福地/
├── 06.第六章:一切都是天意/
└── 07.第七章:新天外之魔/
    └── 2336.第三百六十八节:方源、巨阳战星宿.txt

开发感悟

AI 对话开发的优势

  1. 快速迭代: 描述需求 → AI 实现 → 运行验证 → 发现问题 → 继续对话,循环只需几分钟

  2. 问题诊断: 当我说"不完整",AI 能添加调试代码、对比数据、定位到正则缺少"零两"字符

  3. 渐进式完善: 从基础功能开始,通过持续对话逐步添加:

    • 配置化 → 两级目录 → 第一节分章 → 命名格式优化
  4. 知识补充: AI 自动处理了我没想到的细节:

    • UTF-8 无 BOM 编码
    • 正则超时保护
    • 流式读取大文件

人机协作的关键

  • 明确表达期望: "我要这样..." 而不是模糊描述
  • 提供具体示例: 展示书中实际内容,比抽象描述更有效
  • 及时验证反馈: 运行后告诉 AI 结果对不对

项目信息

项目 说明
开发时间 2025-12-13 ~ 2025-12-14
开发方式 Agentic Coding
对话轮次 ~15 轮核心交互
技术栈 .NET 9.0 / C#
核心依赖 VersOne.Epub, HtmlAgilityPack

本文档由人类与 AI 协作编写,记录真实的对话开发过程。