【开发学习】python开发学习构建解析公用类包

一、背景

前两周已经将Python的解析EPUB文件学习,感觉已经基本了解的关键的技术点,很多的技术点也已经攻克,例如: - 如何使用日志 - 如何解压文件,尤其是解压(压缩包中的)单个文件 - 如何使用xpath查询,针对有命名空间的XML和没有命名空间的XML采用不同方法 - 如何调用Halo的管理接口进行信息更新维护

二、思路

  • 先了解构建一个类包的结果及文件
  • 在弄清楚如何包装公用的包结果
  • 公用解析类设计

三、代码

3.1 文件结构

image-20220402205016802

Vscode开发时会碰到调用自己开发的 包和模块 找不到的问题;

VScode Python no module,VSCode如何调用自己的模块。_Jacob-xyb的博客-CSDN博客

image-20220402205554015

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