使用ebooklib制作符合epub3规范的epub文件

发布于:2025-02-11 ⋅ 阅读:(128) ⋅ 点赞:(0)

扫描的pdf文档在电子设备上阅读时受到了很多限制,在OCR质量越来越高的今天,完全可以考虑将一本扫描的pdf电子书改造成epub电子书。改造过程大致如下:

1、OCR

可以用AI或者一些大厂的免费API接口进行识别,可参阅自己写个OCR工具,某捷休想再从我这里收费_如何自建ocr识别库-CSDN博客

2、校稿

一般扫描质量较高的文档OCR的准确率极高,但会有一些常见错误,例如用VBA将word文档处理成支持弹出式注释的epub文档可用的html内容-CSDN博客一文中的例子中的带圈数字注释引用和注释编号经常会有缺漏和错误的,引号不能配对等。我们可以将识别出的文本复制到Word中,注释引用和注释编号缺漏的地方可以一律用①补上(可以考虑用Autohotkey做个键盘映射,便于输入①),然后在后续处理过程中将其调整准确。可以用下面的宏辅助检查分段选择范围内的引号配对情况:

Sub 检查引号配对()
    Dim aPara As Paragraph, b As Boolean
    Dim regLQuote, regRQuote As RegExp, mLQuote, mRQuote As IMatchCollection2
    Set regLQuote = CreateObject("VBScript.RegExp")
    Set regRQuote = CreateObject("VBScript.RegExp")
    With regLQuote
        .Global = True
        .Pattern = "“"
    End With
    With regRQuote
        .Global = True
        .Pattern = "”"
    End With
    
    For Each aPara In Selection.Range.Paragraphs
        Set mLQuote = regLQuote.Execute(aPara.Range.Text)
        Set mRQuote = regRQuote.Execute(aPara.Range.Text)
        If mLQuote.Count <> mRQuote.Count Then
            b = True
            aPara.Range.Select
            Exit For
        End If
    Next
    If b Then
        MsgBox "请检查选定段落中的引号配对错误"
    Else
        MsgBox "本次选择范围内未发现引号配对错误"
    End If
End Sub

3、编辑文档的大纲结构

与直接编辑HTML相比,在Word中编辑文档的大纲结构十分方便。以处理《何逊集校注》为例,我们在Word中做如下图所示的处理(图中误将诗内容样式为“列表段落”输入为“标题段落”):

4、在每个标题3前面插入分节符,便于进一步遍历和处理文档。宏代码如下:

Sub 在符合条件的段落前插入分节符()
    Dim pos As Long, styleName$
    styleName = "标题 3"
    
    With Selection
        .HomeKey wdStory '光标回到文档开头,此时Selection.Start为0
        Do
            pos = .Start '先记录光标位置
            .GoTo wdGoToHeading, wdGoToNext, 1 '向后移动到下一个标题,以标题为对象遍历文档
            If .Start = pos Then Exit Do ' 光标位置不变则已遍历完所有标题,退出循环
            
            If .Paragraphs(1).Style = styleName Then
                .InsertBreak Type:=wdSectionBreakContinuous ' 连续型分节符
            End If
            
        Loop
    End With
 
End Sub

5、利用文档的特点,将Word文件的文本内容改为HTML。

以如上的何逊集校注为例,可以先用下面的VBA代码插入符合epub3规范的弹出式注释相关HTML标签:

Sub 何逊集改为html字串()
    
    Dim regEx As RegExp, aSec As Section
    Dim matches As IMatchCollection2
    Dim aPara, prePara As Paragraph, b As Boolean, c%, i%
    Dim paraTxtRng, tmpRng As Range, chapter%, href$
    
    Set regEx = CreateObject("VBScript.RegExp")
    regEx.Global = True
    
    Set prePara = ActiveDocument.Paragraphs(1)
    For Each aSec In ActiveDocument.Sections
        i = 0 ' 校注内容的注释编号,每节恢复为0
        chapter = chapter + 1
        
        For Each aPara In aSec.Range.Paragraphs
            ' 象《七召》這種將全文分成多個部分的,每分一部分将章节号增加1
            ' 如果最终epub文件每首诗做成一个html文件,其实就无需考虑章节问题
            If prePara.Style = "正文" And aPara.Style = "列表段落" Then
                i = 0
                chapter = chapter + 1
            End If
            href = "c" & Format(chapter, "000") & "_"
            If aPara.Style = "列表段落" Then ' 处理诗内容中的注释引用
                regEx.Pattern = "[\u2460-\u2473]" ' 带圈数字序号1~20
                Set tmpRng = aPara.Range
                Set matches = regEx.Execute(aPara.Range.Text)
                For c = 0 To matches.count - 1
                    With tmpRng.Find
                        .Text = matches.Item(c)
                        .Wrap = wdFindContinue
                        .Execute
                    End With
                    ' 由于使用了循环变量作为输入的序号,所以校稿时无需考虑注释引用编号的正确性
                    tmpRng.Text = "<a id=""ref_" & href & Trim(Str(c + 1)) & """ epub:type=""noteref"" href=""#" & _
                                href & Trim(Str(c + 1)) & """><sup>" & "[" & Format(c + 1, "00") & "]" & "</sup></a>"
                Next c
            ElseIf aPara.Style = "正文" Then
                regEx.Pattern = "^[\u2460-\u2473]"
                ' 检查正文段落开头是否是带圈数字序号,不是就不处理
                If regEx.test(aPara.Range.Text) Then
                    i = i + 1
                    ' 注意修改段落内容时为避免副作用,应该将分段符排除在修改范围之外
                    Set paraTxtRng = ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)
                    ' 这里的HTML标签内容主要考虑了符合epub3规范弹出式注释的编写要求
                    paraTxtRng.Text = "<aside epub:type=""footnote"" id=""" & href & Trim(Str(i)) & _
                        """ class=""comment""><a href=""#ref_" & href & Trim(Str(i)) & """>" & _
                        "[" & Format(i, "00") & "]</a>" & Mid(aPara.Range.Text, 2, Len(aPara.Range.Text) - 2) & "</aside>"
                End If
            End If
            If Len(aPara.Range.Text) > 1 Then Set prePara = aPara
        Next aPara
    Next aSec
    
End Sub

再根据各段落的性质将各段落用相关HTML标签包裹:

Sub 为段落包裹html标签()
    Dim aPara As Paragraph, paraTxtRng As Range
    For Each aPara In ActiveDocument.Paragraphs
        If Len(aPara.Range.Text) > 1 And Left(aPara.Range.Text, 1) <> "<" Then
            Set paraTxtRng = ActiveDocument.Range(aPara.Range.Start, aPara.Range.End - 1)
            If aPara.Style = "标题 2" Then
                paraTxtRng.Text = "<h2>" & paraTxtRng.Text & "</h2>"
            ElseIf aPara.Style = "标题 3" Then
                paraTxtRng.Text = "<h3>" & paraTxtRng.Text & "</h3>"
            ElseIf aPara.Style = "标题 4" Then
                paraTxtRng.Text = "<h4>" & paraTxtRng.Text & "</h4>"
            ElseIf aPara.Style = "列表段落" Then
                paraTxtRng.Text = "<p class=""content"">" & paraTxtRng.Text & "</p>"
            ElseIf aPara.Style = "正文" Then
                paraTxtRng.Text = "<p class=""normal"">" & paraTxtRng.Text & "</p>"
            End If
        End If
    Next
End Sub

6、将Word中处理好的内容复制粘贴到任何支持正则表达式的文本处理软件中,例如Emeditor,通过将“<h3>”替换为“</section>\n<section><h3>”,然后删除第一个“</section>”,在最后补上一个“</section>”,将每首诗连带其题解和校注内容全部包裹到一对“<section>”和“</section>”之中。将原来在Word文档中添加的“<br>”标签也替换为“</p><p class="content">”,使其达到真正的分段效果。处理完成后保存此文本文件,例如命名“何逊集校注.htm”。

7、使用下面的Python代码,将上一步处理的文本文件分解成每首诗一个符合epub3规范弹出式注释要求的HTML文件:

import os

from bs4 import BeautifulSoup

# HTML模板
html_template = """
<html xml:lang="zh-CN" xmlns="http://www.w3.org/1999/xhtml"  xmlns:epub="http://www.idpf.org/2007/ops">
<head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <title>Title</title>
    <link href="styles.css" rel="stylesheet" /> 
</head>
<body>
</body>
</html>
"""
path = 'htmldemo'
orig_file = '何逊集校注.htm'

with open(os.path.join(path, orig_file), 'r', encoding='utf-8') as f:
    content = f.read()

    soup_content = BeautifulSoup(content, 'html.parser')

    i = 0
    # 将内容添加到HTML模板的<body>中
    for element in soup_content.find_all('section'):
        # 创建BeautifulSoup对象
        soup_template = BeautifulSoup(html_template, 'html.parser')
        h3 = element.find('h3')
        title = h3.get_text()
        soup_template.title.string = title
        soup_template.body.append(element)

        # 在文件名前加上序号做前缀,便于排序
        i = i + 1
        with open(os.path.join(path, 'output', f'c{i:0>3}_{title}.html'), 'w', encoding='utf-8') as html:
            html.write(soup_template.prettify())

8、然后可以用sigil等epub文件创建工具利用分割的HTML文件制作epub了。但是对于程序员来说,不如利用ebooklib库编个Python程序来处理:

import os
from ebooklib import epub


def make_epub(input_folder, output_file):
    # 创建一个Epub书籍对象
    book = epub.EpubBook()

    # 添加书名、作者等信息
    book.set_identifier(output_file.split('.')[0])  # 使用输出文件名作为标识符
    book.set_title('何逊集校注')
    book.set_language('zh-CN')
    # 设置作者
    book.add_author("一只虎")

    # 添加一个导航项
    book.add_item(epub.EpubNcx())
    nav = book.add_item(epub.EpubNav())

    # 用于存储CSS和JS文件的路径
    css_files = []
    js_files = []

    # 准备css文件和js文件
    for root, dirs, files in os.walk(input_folder):
        for file in files:
            if file.endswith(".css"):
                file_path = os.path.join(root, file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    css = epub.EpubItem(uid=file, file_name=file, content=content, media_type='text/css')
                    css_files.append(css)
            elif file.endswith(".js"):
                file_path = os.path.join(root, file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    js = epub.EpubItem(uid=file, file_name=file, content=content,
                                       media_type='application/javascript')
                    js_files.append(css)

    # 存储文本文件
    chapter_files = []
    # 添加目录文件
    chapter_files.append(nav)

    # 遍历文件夹中的所有文件
    for root, dirs, files in os.walk(input_folder):
        for file in files:
            if file.endswith(".html"):
                file_path = os.path.join(root, file)
                chapter = epub.EpubHtml(title=file, file_name=file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    chapter.content = f.read()
                # 添加CSS和JS文件到书籍中
                for css in css_files:
                    chapter.add_item(css)

                for js in js_files:
                    chapter.add_item(js)
                # 文本文件加入章节
                book.add_item(chapter)
                chapter_files.append(chapter)
                # 添加目录项,可以将链接文本进行处理(这里删除了文件名前缀及文件扩展名)
                book.toc.append((epub.Link(file, file[5:-5]), file))

    # 设置书籍的spine(书脊),对于epub3.0规范来说不设置的话制作出的书籍无法阅读
    book.spine = chapter_files

    # 添加CSS和JS文件到书籍中
    for css in css_files:
        book.add_item(css)

    for js in js_files:
        book.add_item(js)

    # 假设封面图片在与html文件同一文件夹下,名为cover.jpg
    cover_image_path = os.path.join(input_folder, "cover.jpg")
    if os.path.exists(cover_image_path):
        with open(cover_image_path, 'rb') as f:
            cover_image = epub.EpubImage(uid="cover", file_name="cover.jpg", media_type="image/jpeg", content=f.read())
        book.set_cover("cover.jpg", cover_image.content)
        book.add_item(cover_image)


    # 保存书籍为EPUB文件
    epub.write_epub(output_file, book, {})

if __name__ == '__main__':
    # 输入文件夹和输出文件名
    input_folder = 'htmldemo/output'
    output_file = 'htmldemo/何逊集校注.epub'

    # 调用函数生成EPUB文件
    make_epub(input_folder, output_file)

上面的代码包含了插入css、js和封面图片的内容,但是没有相关内容也不影响上面的程序成功执行。使用ebooklib要注意的是:

1)插入的css和js不但要添加到书籍中,还要添加到HTML文件中,否则样式表和js文件不会起作用。当然,添加到HTML文件中时ebooklib只是在相应的文件中插入一些<link>及<script>标签来引用相关文件。

2)必须让书籍的“spine”指向文本文件的列表,否则epub文件无法阅读,添加的目录文件也应该加入文本文件列表,否则目录文件无法阅读。

上面的用ebooklib生成epub文件的代码中,用下面的代码添加目录和导航元素是官方文档上的写法:

book.add_item(epub.EpubNcx())
book.add_item(epub.EpubNav())

但是这种写法有时候会爆出下面的错误:

File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\epub.py", line 1746, in write_epub
    epub.write()
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\epub.py", line 1369, in write
    self._write_items()
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\epub.py", line 1356, in _write_items
    self.out.writestr('%s/%s' % (self.book.FOLDER_NAME, item.file_name), self._get_nav(item))
                                                                         ^^^^^^^^^^^^^^^^^^^
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\epub.py", line 1212, in _get_nav
    inserted_pages = get_pages_for_items([item for item in self.book.get_items_of_type(ebooklib.ITEM_DOCUMENT) \
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\utils.py", line 119, in get_pages_for_items
    pages_from_docs = [get_pages(item) for item in items]
                       ^^^^^^^^^^^^^^^
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\utils.py", line 96, in get_pages
    body = parse_html_string(item.get_body_content())
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\ebooklib\utils.py", line 48, in parse_html_string
    html_tree = html.document_fromstring(s, parser=utf8_parser)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "E:\projects\OpenCVStudy\.venv\Lib\site-packages\lxml\html\__init__.py", line 738, in document_fromstring
    raise etree.ParserError(
lxml.etree.ParserError: Document is empty

这应该是因为自动生成目录失败导致的“Document is empty”错误,只要注释掉

book.add_item(epub.EpubNav())

即可成功生成文件,但是这样生成的文件没有导航元素,有些手机或平板电子书阅读App会找不到目录。将上面的代码改成下面这样,手动生成nav.xhtml的内容并添加进书籍,就既可以避免出现错误,又为书籍添加导航目录:

import os
from ebooklib import epub


def make_epub(input_folder, output_file):
    # 创建一个Epub书籍对象
    book = epub.EpubBook()

    # 添加书名、作者等信息
    book.set_identifier(output_file.split('.')[0])  # 使用输出文件名作为标识符
    book.set_title('何逊集校注')
    book.set_language('zh-CN')
    # 设置作者
    book.add_author("一只虎")

    # 用于存储CSS和JS文件的路径
    css_files = []
    js_files = []

    # 准备css文件和js文件
    for root, dirs, files in os.walk(input_folder):
        for file in files:
            if file.endswith('.css'):
                file_path = os.path.join(root, file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    css = epub.EpubItem(uid=file, file_name=file, content=content, media_type='text/css')
                    css_files.append(css)
            elif file.endswith('.js'):
                file_path = os.path.join(root, file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                    js = epub.EpubItem(uid=file, file_name=file, content=content,
                                       media_type='application/javascript')
                    js_files.append(css)

    # 存储文本文件
    chapter_files = []
    # 遍历文件夹中的所有文件
    for root, dirs, files in os.walk(input_folder):
        for file in files:
            if file.endswith('.html'):
                file_path = os.path.join(root, file)
                chapter = epub.EpubHtml(title=file[5:-5], file_name=file)
                with open(file_path, 'r', encoding='utf-8') as f:
                    chapter.content = f.read()

                # 添加CSS和JS文件链接到HTML页面中
                for css in css_files:
                    chapter.add_item(css)
                for js in js_files:
                    chapter.add_item(js)

                # 在书籍中添加处理好的HTML页面
                book.add_item(chapter)
                chapter_files.append(chapter)
                # 添加目录项,可以将链接文本进行处理(这里删除了文件名前缀及文件扩展名)
                book.toc.append((epub.Link(file, file[5:-5]), file))



    # 处理导航
    book.add_item(epub.EpubNcx())
    # 生成目录文件,uid='nav'是符合epub3规范的目录文件属性
    nav = epub.EpubHtml(uid='nav', title='目录', file_name='nav.xhtml', media_type='application/xhtml+xml')
    # 根据HTML文件列表生成目录导航字符串
    nav_content = ''
    for chapter in chapter_files:
        nav_content += f'<a href="{chapter.file_name}">{chapter.title}</a><br>'
    # 设置目录导航文件的内容,不设置的话book.add_item(nav)会报错Document is empty
    nav.content = nav_content
    book.add_item(nav)

    # 添加CSS和JS文件到书籍中
    for css in css_files:
        book.add_item(css)

    for js in js_files:
        book.add_item(js)

    # 假设封面图片在与html文件同一文件夹下,名为cover.jpg
    cover_image_path = os.path.join(input_folder, 'cover.jpg')
    if os.path.exists(cover_image_path):
        with open(cover_image_path, 'rb') as f:
            cover_image = epub.EpubImage(uid='cover', file_name='cover.jpg',
                                         media_type='image/jpeg', content=f.read())
        # 下面这行代码不注释,会报cover.jpg文件名重复错误,应该是book.set_cover()也会做添加资源的动作
        # book.add_item(cover_image) 
        book.set_cover('cover.jpg', cover_image.content)
   
    # 设置书籍的spine(书脊),将封面与目录也添加进来。对于epub3.0规范来说不设置的话制作出的书籍无法阅读。列表中可以用书籍章节的uid,例如下面的封面。
    book.spine = ['cover', nav] + chapter_files
    # 保存书籍为EPUB文件
    epub.write_epub(output_file, book, {})

if __name__ == '__main__':
    # 输入文件夹和输出文件名
    input_folder = 'E:\\projects\\OpenCVStudy\\htmldemo\\output'
    output_file = 'E:\\projects\\OpenCVStudy\\htmldemo\\何逊集校注.epub'

    # 调用函数生成EPUB文件
    make_epub(input_folder, output_file)


网站公告

今日签到

点亮在社区的每一天
去签到