PDF文档转换成mobi格式,并解决排版问题

0. 前言

正式介绍之前,先回答下面几个问题:
在这里插入图片描述

1. 为什么要将PDF转换成mobi?
想要将PDF转换成mobi格式,初衷在于想在kindle上面看一些从网上获取到的PDF文档。直接将PDF导入kindle本来也可以,但是效果不是很好——要么竖着看,但是字体很小;要么横着看,字体会大一些,但是总感觉比较别扭,而且PDF的一页需要在kindle上翻3页。
kindle支持azw3、mobi等格式,但是不支持直接将azw3格式的文档直接导入到kindle,所以需要将PDF文档转换成mobi格式

2. 为什么不直接用在线转换工具?
其实网上有很多工具支持将PDF转换成mobi格式,但是效果都很差:

  1. 章节标题和正文内容没有区别;
  2. 正文内容格式混乱,在kindle上看是以PDF的一行进行的分段,行首也没有空格
    在这里插入图片描述

3. 将PDF转换成mobi格式,我大概怎么做?
将PDF转换成mobi格式,我主要是借助于calibre工具:
在这里插入图片描述

4. 转换效果如何?
转换之后效果如下图,其中对章节标题和段落划分进行了处理:
在这里插入图片描述

1. 下载和安装calibre

calibre下载地址:
https://calibre-ebook.com/download

根据自己的系统下载安装即可

2. PDF导入calibre,并转换为azw3格式

打开calibre,点击菜单栏的“添加数据”,选择PDF格式文件,点击“Open”
在这里插入图片描述
在calibre的主窗口中选中刚导入的图书,点击菜单栏的“转换书籍”,在弹出的转换窗口,将输出格式选择为“AZW3”,然后点击“确定”。

转换过程可能会持续一小段时间,看calibre主窗口右下角的任务执行结束即转换完成
在这里插入图片描述
【注】这里有一个坑,图书转换的设置,目录针对章节有一个默认的最大值,需要根据图书的实际情况进行调整。我一般将两个值分别设置为1000,50
在这里插入图片描述

3. 编辑电子书,获取HTML内容和图片

在calibre主窗口选中目标电子书,点击菜单栏的“编辑书籍”打开编辑书籍窗口

在编辑书籍窗口,选中文本下的“part0000.html”,点击右键,选择“导出part0000.html”

同样的方式,全选图片下的所有图片,然后导出
在这里插入图片描述

4. 程序处理HTML文档

由上一步可以看到,直接由PDF转换得到的HTML文档,PDF里面的每一行转换之后在HTML文档中都独立成了一段,而且段首也没有空格,章节标题也没有突出展示。
在这里插入图片描述
针对这个问题,于是自己写了一段Java代码,主要是根据语义对文档内容进行分段,同时将标题突出,然后将每一张的起始位置独立成页。

具体的代码见文末,里面也有比较详细的注释,有一些特殊情况的处理可以根据自己的要求适当修改代码。
在这里插入图片描述

5. 将HTML文档导入calibre,并转换成azw3格式

首先,将calibre中已有的同名电子书删掉,操作方式是:在calibre主窗口,选中要删除的电子书,右键,选择“移除书籍” > “移除选定书籍”,之后在弹出窗口做二次确认
在这里插入图片描述
然后,将处理之后的HTML文档导入到calibre,操作方式同第2章。

最后,由于直接导入的HTML文档是ZIP格式,不能直接进行编辑,所以需要再次将文档转换成azw3格式,操作方式同第3章。可以看到,导入HTML文档之后,图书的封面丢失了,可以在转换的时候,直接修改封面图(可以用第3章导出保存的图片),如下图:
在这里插入图片描述

6. 编辑azw3文档

这一步是非必须的,但是如果电子书中有插图,那么就需要在这一步进行补充。另外也可以在文件预览中检查各个HTML文件,如有必要,也可以进行简单编辑。

进入编辑操作同第3章所介绍,这里主要介绍一下补充图片。

在编辑书籍窗口,点击菜单栏的“新增文件”按钮。

然后在弹出框选择“导入文件”,选择需要导入的图片。注意,文件的路径最终要是“images/xx”格式。

最后点击“确定”。

如果有多张图片,则重复上面的操作。目前calibre不支持批量导入。

编辑完成之后,点击编辑书籍窗口菜单栏的“保存”按钮进行保存。
在这里插入图片描述

7. 将azw3文档转换成mobi格式

在calibre主窗口,选中电子书,点击“转换书籍”,在弹出窗口中,输入格式选择“AZW3”
,输出格式选择“MOBI”。点击“确定”。然后等转换任务结束。
在这里插入图片描述
转换结束之后,在calibre主窗口,选中电子书,在右侧边栏,点击“点击打开”即可获取到mobi格式的电子书。然后将其共享至kindle就可以了。
在这里插入图片描述

8. 附录

推荐的PDF电子书网址:
http://www.80pdf.com/
(大家有其他好的电子书链接也欢迎评论区共享。)

HTML格式文档处理代码:

package com.amwalle.walle.util;
import org.springframework.util.StringUtils;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PDFConverter {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要转换的文件路径: ");
        String filePath = scanner.nextLine();
        System.out.println(filePath);
        convert(filePath);
    public enum HeaderLevel {
        Part("H1", ".*第.*部分.*"),
        Chapter("H2", "(.*第.*章.*)|(.*[1-9].*)");
        private String headerLevel;
        private String expression;
        HeaderLevel(String headerLevel, String expression) {
            this.headerLevel = headerLevel;
            this.expression = expression;
        public static String getHeaderLevel(String input) {
            if (StringUtils.isEmpty(input) || (!input.contains("</a>") && !input.contains("<p class=\"calibre1\">"))) {
                return null;
            String dummy = preHandleHeader(input);
            for (HeaderLevel level : HeaderLevel.values()) {
                Pattern pattern = Pattern.compile(level.expression);
                Matcher matcher = pattern.matcher(dummy);
                if (matcher.find()) {
                    return level.headerLevel;
            return null;
        private static String preHandleHeader(String input) {
            String dummy = input;
            if (input.contains("</a>") && input.contains("</p>")) {
                dummy = dummy.substring(dummy.indexOf("</a>") + 4, dummy.indexOf("</p>"));
            } else if (input.contains("<p class=\"calibre1\">") && input.contains(




    
"</p>")) {
                dummy = dummy.substring(dummy.indexOf("<p class=\"calibre1\">") + 20, dummy.indexOf("</p>"));
            dummy = dummy.replaceAll("<b class=\"calibre3\"> </b>", "");
            dummy = dummy.replaceAll("<b class=\"calibre3\">", "");
            dummy = dummy.replaceAll(" </b>", "");
            dummy = dummy.replaceAll("</b>", "");
            dummy = removeSpace(dummy);
            return dummy;
        public String getHeaderLevel() {
            return headerLevel;
        public void setHeaderLevel(String headerLevel) {
            this.headerLevel = headerLevel;
        public String getExpression() {
            return expression;
        public void setExpression(String expression) {
            this.expression = expression;
    public static void convert(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.isFile() || !file.canRead() || !file.getName().endsWith(".html")) {
            System.out.println("请输入正确的html文件路径,并保证文件可读!");
        String title = file.getName();
        title = title.replace(".html", "");
        File outFile = new File(filePath.replace(".html", "1.html"));
        if (outFile.exists()) {
            outFile.delete();
            outFile.createNewFile();
        try (Scanner scanner = new Scanner(file, "UTF-8"); BufferedWriter outWriter = new BufferedWriter(new FileWriter(outFile))) {
            Set<String> catalog = new HashSet<>();
            StringBuffer output = new StringBuffer();
            boolean isIndentationNeeded = false;
            int count = 0;
            while (scanner.hasNextLine()) {
                count++;
                // 有些文件太大,需要在中间存一下文件
                if (count >= 10000) {
                    outWriter.append(output);
                    count = 0;
                    output.delete(0, output.length());
                String line = scanner.nextLine();
                // 目录前面的内容(封面、版权等)
                if (catalog.isEmpty() && !line.startsWith("<p class=\"calibre1\"><a href=\"")) {
                    output.append(line).append("\n");
                    continue;
                // 处理目录:目录数据存set用于后续增加标题格式
                if (line.endsWith("</a></p>")) {
                    if (catalog.isEmpty()) {
                        output.delete(output.length() - 1, output.length());
                        String header = output.toString();
                        String catalogTitle = header.substring(header.lastIndexOf("\n") + 1);
                        // 格式化目录标题,增加目录前的分页
                        if (catalogTitle.contains("目录") || catalogTitle.contains("Contents")) {
                            catalogTitle = "<h1>" + catalogTitle + "</h1>" + "\n";
                            output.delete(header.lastIndexOf("\n") + 1, output.length());
                            output.append(addPagination(title));
                            output.append(catalogTitle);
                        } else




    
 {
                            output.append("\n");
                            output.append(addPagination(title));
                    line = line.replaceAll("part0000\\.html", "");
                    output.append(line).append("\n");
                    String temp = line.substring(line.lastIndexOf("\">") + 2, line.indexOf("</a></p>"));
                    temp = removeSpace(temp);
                    catalog.add(temp);
                    continue;
                if (StringUtils.isEmpty(line)) {
                    output.append("\n");
                    continue;
                // 处理章节标题
                if (isLineATitle(line, catalog)) {
                    isIndentationNeeded = true;
                    String titleLevel = HeaderLevel.getHeaderLevel(line);
                    if (HeaderLevel.Part.headerLevel.equals(titleLevel)) {
                        output.append(addPagination(title));
                        output.append("<h1>").append(line).append("</h1>").append("\n");
                        continue;
                    if (HeaderLevel.Chapter.headerLevel.equals(titleLevel)) {
                        output.append(addPagination(title));
                        output.append("<h2>").append(line).append("</h2>").append("\n");
                        continue;
                    output.append("<h3>").append(line).append("</h3>").append("\n");
                    continue;
                // 首行缩进
                if (isIndentationNeeded) {
                    line = line.replace("<p class=\"calibre1\">", "<p class=\"calibre1\" style=\"text-indent:2em;\">");
                    line = line.replace("</p>", "");
                    output.append(line).append("\n");
                    isIndentationNeeded = false;
                    continue;
                // 文本内容分段
                if (isParagraphEnd(line)) {
                    isIndentationNeeded = true;
                    line = line.replace("<p class=\"calibre1\">", "");
                    line = line.replace("</p>", "");
                    output.append(line).append("\n");
                    continue;
                // 普通行处理:去掉换行
                if (line.startsWith("<p class=\"calibre1\">") && line.endsWith("</p>")) {
                    line = line.replace("<p class=\"calibre1\">", "");
                    line = line.replace("</p>", "");
                    output.append(line);
                    continue;
                output.append(line).append("\n");
            outWriter.append(output);
            System.out.println("********************************************");
            System.out.println("Done! 生成的新文件路径: ");
            System.out.println(outFile.getPath());
        } catch (FileNotFoundException e) {
            System.out.println("File convert failed!");
            e.printStackTrace();
    private static String removeSpace(String input) {
        String result = input.trim().replaceAll("\\.", "").replaceAll("·", "");
        result = result.replaceAll("\\s*", "").replaceAll("\\u00a0", "").replaceAll((char) 12288 + "", "");
        return result;
    private static String addPagination(String title) {
        return "\n" +
                "</body>\n" +
                "</html>\n" +
                "<html xml:lang=\"zh-cn\" xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
                "<head>\n" +
                "<title>" + title + "</title>\n" +
                "  <link rel=\"stylesheet\" type=\"text/css\" href=\"../styles/0002.css\"/>\n" +
                "  <link rel=\"stylesheet\" type=\"text/css\" href=\"../styles/0001.css\"/>\n" +
                "</head>\n" +
                "<body style=\"font-family:songti;\">\n" +
                "\n";
    private static boolean isLineATitle(String input, Set<String> catalog) {
        if (StringUtils.isEmpty(input) || (!input.contains("</a>") && !input.contains("<p class=\"calibre1\">"))) {
            return false;
        String dummy = HeaderLevel.preHandleHeader(input);
        for (String catalogEntry : catalog) {
            if (StringUtils.isEmpty(catalogEntry)) {
                continue;
            if (catalogEntry.equals(dummy)) {
                return true;
        return false;
    private static boolean isParagraphEnd(String input) {
        Pattern pattern = Pattern.compile(".*(\\w</p>)$");
        Matcher matcher = pattern.matcher(input);
        if (matcher.matches()) {
            return false;
        // 句子结尾的标点符号:./!/?/……/"」 (英文+中文)
        Pattern pattern1 = Pattern.compile(".*(([\\.|!|\\?|\"|\\u3002|\\uff01|\\uff1f|\\u2026|\\u201d|\\u300d])|(\\. ))</p>$");
        Matcher matcher1 = pattern1.matcher(input);
        return matcher1.matches();
                                    它的文件PDF功能支持同时上传多个mobi文件进行换操作,整个换过程只需要7s左右便可完成,并且换后的文件能够很好将原文件的排版、内容保留下来,不会出现格式错乱、文件损坏乱码的情况。这是一款开源的电子书管理器,能够帮助我们进行浏览图书、格式变、生成电子书等操作,其中它的格式换功能可以帮助我们将epub、azw3、mobipdf、rtf等格式的文件进行互相换。这是一款电脑自带的格式换工具,我们只需要通过修改文件后缀名的方式就可以实现格式换的操作了。【推荐软件】格式换工具。
                                    PDF to MOBI是一款免费的电子书和文档换软件,简单好用,可将PDF文件换为MOBI电子书格式MOBI格式是亚马逊Kindle阅读设备的专属格式。没有复杂的设置和选项,只需点击几次鼠标即可完成换。真的很方便,有需要的可以来下载使用的!
mobi格式
亚马逊推出kindle以来,这个阅读器的专用格式mobi就很流行,但是只能在kindle上阅读,比较遗憾。现在亚马逊终于终于推出了一
                                    最近有个小伙伴私信问我PDF文件MOBI怎么?这里给大家科普一下,MOBI文件是网络一种流行的电子书格式,几乎和EPUB(Electronic Publication的缩写,意为:电子出版)文件格式一样只是MOBI格式文件的体积会稍大一些,可以用亚马逊设备或者在电脑上用kindle打开。详细的介绍大家可以在搜索引擎查询,可能很多朋友不太了解这种电子书怎么换甚至没有接触过,今天给大家分享一个方法。...
                                    kindle如何导入azw3格式的电子书
在手机上将下载好的azw3格式的电子书用kindle软件打开我发现并不可行,显示不支持该格式,那么kindle不支持azw3格式吗?
答案当然是否定的,kindle可以打开azw3格式的文件但需要将该电子书放入kindle默认路径中,这个路径是: /sdcard/Andriod/data/com.amazon.kindle/files,我们将azw3格式的...
                                    [最新更新,于2012-03-10]
对于pdf文件是文字编码方式(非图片),可以用用下面的方式生成mobi格式,且可以修改!!
通过mobipocket creator把pdf为prc,中间不设置任何信息,直接build就行!
然后在生成的文件夹里找到 xxx.html,也就是说在生成prc文件的过程中这个工具还生成了相应的html!html格式就很好修改了。用Editplus修改目录显
                                    蓝桥杯给了一个kindle,然而我看的书大多数是pdf的,直接放上去效果很不好,就找了这个教程,也算能凑合看了,当然还是花钱买的书看起来更舒服。
因为KINDLEPDF扫描文档的渲染、对比度、读取等都有先天不足,就算用上多看系统,效果也是不理想,所以用KINDLEPDF绝对是一种折磨。在原版系统下,我们如何体验工具在KINDLE的魅力呢。本人参考网上所说和自己实践得出相对好的方法。理论上就是