一、背景
前两周已经将Python的解析EPUB文件学习,感觉已经基本了解的关键的技术点,很多的技术点也已经攻克,例如: - 如何使用日志 - 如何解压文件,尤其是解压(压缩包中的)单个文件 - 如何使用xpath查询,针对有命名空间的XML和没有命名空间的XML采用不同方法 - 如何调用Halo的管理接口进行信息更新维护
二、思路
- 先了解构建一个类包的结果及文件
- 在弄清楚如何包装公用的包结果
- 公用解析类设计
三、代码
3.1 文件结构

Vscode开发时会碰到调用自己开发的 包和模块 找不到的问题;
VScode Python no module,VSCode如何调用自己的模块。_Jacob-xyb的博客-CSDN博客

Vscode开发时用F5(运行调试时,没有使用pipenv的虚拟环境,而是直接调用了系统的python.exe )
❖ VSCode为Python配置Debug调试(virtualenv环境) - 简书 (jianshu.com)
3.2 再次重申注释的写法
- 类注释
- 属性注释
- 方法注释
- 如何根据注释生成文档
3.3 类属性
# XML 命名空间 (注意大小写问题)
NS = {
'xml': 'http://www.w3.org/XML/1998/namespace',
'epub': 'http://www.idpf.org/2007/ops',
'daisy': 'http://www.daisy.org/z3986/2005/ncx/',
.......
}
# xpath 元数据查询语句,这里的查询语句仅仅针对的是字符串格式的数据
OPF_XP_Query = {
"title" : "//dc:title/text()",
"author" : "//dc:creator/text()",
........
}
# XML Start
CONTAINER_PATH = 'META-INF/container.xml'
IMAGE_MEDIA_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml']
3.4 类初始化
《__init()》
def __init__(self, epubfn, epuboutpath='/tempepub', epubtemppath='abc'):
"""
构造函数, 例如:
epub = ParseEpub(f'd:/books/de/X.epub')
epub.readepub()
:Args:
- epubfn: epub文件路径 (必须)
- epuboutpath: epub文件处理结果的输出路径 (必须)
- epubtemppath: epub文件处理的缓存文件 (可选)
"""
# epub文件路径及文件名
self._epubfn = os.fspath(epubfn)
# epub处理的临时目录
self._tempepubpath = os.path.join(epuboutpath, str(time.time()))
# opf文件名
self._contentFileName = None
# ElementTree main object (OPF--XML元素树对象)
self._xml = None
# opf文件 ElementTree main object (OPF--XML元素树对象,适用解析待名称空间的标记)
self._opf_xml = None
# opf文件 ElementTree main object (OPF--HTML元素树对象)
self._opf_html = None
# epub中opf文件中的元数据节点
self._metadataNode = None
self._ncxFileName = None
# toc.ncx文件 ElementTree main object (toc.ncx--XML元素树对象,适用解析待名称空间的标记)
self._ncx_xml = None
# toc.ncx文件 ElementTree main object (toc.ncx--HTML元素树对象)
self._ncx_html = None
# xml 版本
self._version = "1.0"
# epub 版本
self._epub_version = "2.0"
# 日志处理对象
self._logger = None
#__init__
实际类的初始化操作方法:(这是最核心的部分哦)
def readepub(self):
"""
设置对象, 并对对象进行初始化操作
"""
self.setLoger() # 设置日志
self.exctKeyFile() #解压三个关键XML文件
try:
zfile = zipfile.ZipFile(self._epubfn)
self.findContent(zfile)
except FileNotFoundError:
print(f"错误: File '{self._epubfn}' not found. Bailing out.")
raise FileNotFoundError
#try
opffile = os.path.join(self._tempepubpath, self._contentFileName)
str_opf = open(opffile, 'r', encoding='utf-8').read()
# 将'content.opf' 文件读取作为XML主对象,用于后续信息的获取
self._xml = ET.fromstring(str_opf.encode("utf-8"))
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
self._opf_xml = etree.XML(str_opf.encode("utf-8"))
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
self._opf_html = etree.HTML(str_opf.encode("utf-8"))
self._metadataNode = self._opf_html.xpath("//metadata")
if self._metadataNode == None:
raise Exception("没有找到OPF元数据的XML标记")
#
if 'version' in self._xml.attrib:
self._version = self._xml.attrib['version']
#if
#获取目录TOC文件
ncxfile = os.path.join(self._tempepubpath,self._ncxFileName)
str_ncx = open(ncxfile, 'r', encoding='utf-8').read()
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
self._ncx_xml = etree.XML(str_ncx.encode("utf-8"))
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
self._ncx_html = etree.HTML(str_ncx.encode("utf-8"))
# 记录,epub文件对象初始化完成
mess_str = f"解析 {os.path.basename(self._epubfn)} 的OPF和NCX文件了: {self._contentFileName}; 完成解析epub对象初始化"
self._logger.logger.info(mess_str)
#readepub
3.5 类方法
3.6 解析类的调用方法
import os
from ep.epubparser import ParseEpub
# 测试使用epubparse这个类的功能是否正常
ef_path = "D:\\20_BND\\D_Download\\3-电子书籍\\0322-03-影响力经典版.epub"
# 文件处理的临时目录
ef_out_path = "res/mdout/"
ef = ParseEpub(os.fspath(ef_path),epuboutpath=ef_out_path)
ef.readepub()
print(ef.get_metedata("title"))
print(ef.get_metedata("author"))
print(ef.get_metedata("cover"))
print(ef.get_pubdate())
print(ef.get_ISBN())
# 处理完成后清理临时文件
ef.clear_temp()
四、问题记录/经验积累
4.1 压缩文件的open方法时很容易因为中文字符问题
报错: XML中fromstring()方法报错:不正确的xml格式文件
对策:这个问题基本很难解决,只能从思路上进行转换;先通过zipfile建关键的XML文件先解压出来,然后通过读取xml文件的方式就可以避免这个问题了;
原来的写法:(老外的代码,因为老外没有中文字符的问题,所以这样写)
with zfile.open(self._contentFileName) as file:
self._xml = ET.fromstring(file.read()) # 将'content.opf' 文件读取作为XML主对象,用于后续信息的获取
#with
zfile.close()
def exctKeyFile(self):
"""
将三个重要文件解压到一个临时目录(当前时间戳)中
"""
if not os.path.exists(self._tempepubpath) : os.mkdir(self._tempepubpath)
try:
zfile = zipfile.ZipFile(self._epubfn)
for f in zfile.namelist():
if (f.find('container.xml')>0) : zfile.extract(f, self._tempepubpath)
if (f.find('content.opf')>0) : zfile.extract(f, self._tempepubpath)
if (f.find('toc.ncx')>0) :
zfile.extract(f, self._tempepubpath)
self._ncxFileName = f
except FileNotFoundError:
print(f"错误: File '{self._epubfn}' not found. Bailing out.")
raise FileNotFoundError
#exctKeyFile
.................................
opffile = os.path.join(self._tempepubpath, self._contentFileName)
str_opf = open(opffile, 'r', encoding='utf-8').read()
# 将'content.opf' 文件读取作为XML主对象,用于后续信息的获取
self._xml = ET.fromstring(str_opf.encode("utf-8"))
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
self._opf_xml = etree.XML(str_opf.encode("utf-8"))
# 构造一个XML解析器,用于处理有名称空间(namespace)定义的标记
4.2 xml名称空间的大小写问题导致xpath查询不到结果
样例代码:NS 字典中的名称空间 用的大小的 “DC”; xpath查询语句中用的小写的 “dc” ;(测试了,必须保持一致)
报错:lxml.etree.XPathEvalError: Undefined namespace prefix
NS = {
'xml': 'http://www.w3.org/XML/1998/namespace',
'epub': 'http://www.idpf.org/2007/ops',
'daisy': 'http://www.daisy.org/z3986/2005/ncx/',
'opf': 'http://www.idpf.org/2007/opf',
'containerns': 'urn:oasis:names:tc:opendocument:xmlns:container',
'DC': 'http://purl.org/dc/elements/1.1/',
'xhtml': 'http://www.w3.org/1999/xhtml',
'ncx': 'http://www.daisy.org/z3986/2005/ncx/'
}
............
xpq_str = "//dc:title/text()"
aid = dom.xpath(xpq_str, namespaces=NS)
对策:名称空间 必须与Xpath中的查询串保持一致(* 与xml文件中的xmlns到不需要保持大小写一致)。
4.3 删除整个目录的报没有权限
使用import shutil的包,用rmtree(path) 解决问题:
def clear_temp(self):
"""
清除处理当个epub时产生的缓存文件
"""
f_path = os.path.abspath(self._tempepubpath)
if (os.path.exists(f_path)):
#os.remove(f_path)
shutil.rmtree(f_path)
#clear_temp
4.4 关于日志保存的目录
目前将日志文件的保存放在了一个固定的目录中,后续还需要完善该功能的灵活性。

🐤 Mobile : 159 6408 0134 ; E-Mail : chenliping@fangyuanmuban.com.cn