epub文件需先解压再解析content.opf定位正文.xhtml路径,用lxml xpath提取语义化标签内文本,剔除脚注图片等冗余内容,逐文件流式处理并以utf-8-sig编码输出txt。

EPUB文件结构不拆开就别想读内容
EPUB本质是zip压缩包,里面包含HTML、CSS、OPF元数据等。直接用open()读取.epub文件只会得到二进制乱码——你得先解压,再定位到实际存正文的.xhtml或.html文件。
常见错误:有人用BeautifulSoup直接解析book.epub路径,报FileNotFoundError或解析出空文档。原因就是没解压,也没找对内嵌路径。
- 用
zipfile.ZipFile打开EPUB,检查namelist()里有没有content.opf(必须有)和*.xhtml类文件 - 从
content.opf里解析<spine></spine>节点,它按阅读顺序列出所有正文item ID;再查<manifest></manifest>把ID映射到真实文件路径(比如OEBPS/ch01.xhtml) - 跳过
cover.xhtml、title_page.xhtml等非正文页——它们常在<spine></spine>里被标记linear="no"
用lxml比BeautifulSoup快且更稳地提取文字
EPUB里的HTML常含大量冗余标签(<span></span>嵌套、<div class="footnote">、广告div)、换行符和空白字符。用<code>bs4默认解析容易漏掉<section></section>或<article></article>外的段落,而lxml支持XPath精准定位正文容器,且处理编码异常更鲁棒。
示例关键步骤:
from lxml import etree
parser = etree.HTMLParser(encoding='utf-8')
tree = etree.parse(html_path, parser)
# 取主内容区:优先试 //body, fallback到 //div[@id='content'] 或 //section
text_nodes = tree.xpath('//body//text() | //section//text() | //article//text()')
clean_text = '\n'.join([t.strip() for t in text_nodes if t.strip()])
- 避免用
tree.getroot().text——它只取根元素直系文本,丢弃子标签内文字 - 别依赖
class="chapter"这类CSS名——不同EPUB生成工具命名五花八门,用语义化标签(<h1></h1>后紧跟<p></p>)或位置逻辑更可靠 - 遇到
UnicodeDecodeError时,强制指定encoding='utf-8'或用chardet探测,但多数EPUB声明了UTF-8 meta
忽略图片、脚注、CSS样式,但别漏掉换行逻辑
TXT不是渲染格式,但人眼阅读仍需基本段落分隔。纯删所有标签会把“第一段
第二段”变成“第一段第二段”,而保留<p></p>、<div>、<code><section></section>的闭合结构能自然换行。
- 移除
<img>、<script></script>、<style></style>及其内容(用drop_tree()) - 脚注常以
<aside class="footnote"></aside>或<div epub:type="footnote">存在——直接剔除,否则TXT里混入“[1] 原文注释…”影响连贯性<li>用<code>text_content()前先调用normalize_space=True参数(lxml)或手动.replace('\xa0', ' ')处理不间断空格 - 章节标题建议保留,加前后空行:
'\n\n' + h1_elem.text.strip() + '\n\n' - 用
zipfile.ZipFile.open()直接读取单个HTML文件流,避免落地临时文件(减少IO和磁盘占用) - 输出TXT统一用
utf-8-sig编码(Windows记事本友好),避免BOM缺失导致中文乱码 - 文件名冲突:原EPUB中
OEBPS/01.xhtml和Text/Chapter1.xhtml都可能存正文——以content.opf中<spine></spine>顺序为准,别硬猜路径 - 遇到加密EPUB(含
encryption.xml)直接跳过并提示,不要尝试破解——标准EPUB不加密
批量转换时注意内存和编码边界问题
一个20MB EPUB解压后可能产生300个HTML文件,全读进内存再处理会OOM。应逐个解压→解析→写TXT→释放,而非一次性加载全部。
最易被忽略的是OPF文件里的toc.ncx或nav.xhtml路径——它们本身不含正文,但有人误当内容源去解析,结果转出一堆目录项。认准<spine></spine>里的<itemref></itemref>,这才是作者指定的阅读流。











