最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Python開發(fā)的PDF拆分工具

 更新時間:2025年12月24日 10:17:58   作者:楊利杰YJlio  
本文介紹了作者開發(fā)的一個用于拆分PDF文件的Python工具,該工具可以自動識別章節(jié)標(biāo)題、按章節(jié)拆分PDF并創(chuàng)建文件夾,支持整章導(dǎo)出和單頁導(dǎo)出,作者詳細(xì)描述了工具的使用方法、依賴環(huán)境、代碼實現(xiàn)原理以及常見問題的解決方法,并附帶了完整代碼,需要的朋友可以參考下

1. 我寫這個工具的原因

我經(jīng)常會把電子書交給 AI 做總結(jié)/問答,但很多 PDF 體積大、頁數(shù)多,如果我想按章節(jié)拆開再喂給 AI,手動操作會非常耗時間。

所以我用 Python 寫了一個小工具,實現(xiàn)了:

  • 自動識別“第X章”標(biāo)題(優(yōu)先書簽,沒書簽再掃正文)
  • 按章節(jié)自動創(chuàng)建文件夾(文件夾命名帶序號,方便排序)
  • 支持整章導(dǎo)出 + 單頁導(dǎo)出(每頁單獨 PDF,便于上傳/AI 處理)

2. 最終效果長什么樣(我想要的輸出)

我拆分后的輸出結(jié)構(gòu)大概是這樣:

輸出目錄

01_第1章_xxx/

  • 01_第1章_xxx.pdf(整章)
  • p0001.pdf p0002.pdf ...(單頁)

02_第2章_xxx/

  • 02_第2章_xxx.pdf
  • p00xx.pdf ...

一句話:我既保留“整章”,也能拿到“每一頁”。

3. 使用方法(我實際是這樣跑的)

我把腳本直接丟進(jìn) PyCharm(你原文寫的 ptcharm 我這里統(tǒng)一寫成 PyCharm)運行即可。

3.1 我做的第 1 步:選擇 PDF 文件

我點擊按鈕 「1. 請選擇你的 PDF 文件」,選中要處理的 PDF。

3.2 我做的第 2 步:選擇輸出位置并開始拆分

我點擊 「2. 請選擇輸出目錄并開始拆分」,選擇一個輸出文件夾,工具就會開始處理,并在下方日志區(qū)域輸出進(jìn)度。

你原來的兩張圖建議保留在這里(效果最直觀)

4. 環(huán)境與依賴(我用的配置)

  • Windows 10/11 均可
  • Python 3.x(建議 3.8+)
  • 依賴庫:pypdf

我安裝依賴的方式:

pip install pypdf

注意:如果 PDF 是掃描版圖片(沒有可提取的文字),正文識別可能會失敗,這種情況需要先 OCR,否則工具無法“讀到章節(jié)標(biāo)題”。

5. 我在代碼里做了什么(原理簡述)

為了讓工具對不同 PDF 更“穩(wěn)”,我設(shè)計了兩套識別策略:

5.1 優(yōu)先方案:從 PDF 書簽(outline)識別章節(jié)

如果 PDF 自帶書簽(目錄),我就直接讀取書簽并找出匹配 “第X章” 的標(biāo)題,然后拿到對應(yīng)頁碼作為章節(jié)起點。

優(yōu)點:速度快、準(zhǔn)確率高。

5.2 備用方案:掃描正文文本猜章節(jié)起始頁

如果沒有書簽,我會逐頁提取文本,然后用正則匹配 第X章,并做了兩層過濾:

  • 過濾“目錄頁”(包含 目錄/Contents 等)
  • 過濾類似 標(biāo)題......頁碼 的目錄行

優(yōu)點:即使沒書簽,只要正文可提取文字,也能拆分。

6. 我可以調(diào)的關(guān)鍵參數(shù)(想改行為就看這里)

代碼最上面配置區(qū)我留了兩個重點:

  • CHAPTER_PATTERN:章節(jié)匹配正則(默認(rèn)支持:第1章 / 第 1 章 / 第一章 / 第十四章)
  • EXPORT_SINGLE_PAGES:是否導(dǎo)出單頁 PDF(默認(rèn) True)

如果你不想生成單頁 PDF,把 EXPORT_SINGLE_PAGES = False 就行。

7. 常見問題(我自己踩過的坑)

Q1:提示“未識別到任何章節(jié)標(biāo)題”

通常是三類原因:

  1. PDF 沒書簽 + 正文提取不到文本(掃描版)
  2. 章節(jié)標(biāo)題不是“第X章”這種格式(需要改正則)
  3. 章節(jié)標(biāo)題出現(xiàn)在頁面太靠后(正文識別里有閾值 m.start() > 400

解決思路:我會先確認(rèn) PDF 是不是可復(fù)制文字;不行就 OCR;標(biāo)題格式不一致就改正則。

Q2:為什么我的章節(jié)起始頁不準(zhǔn)?

如果 PDF 正文排版很特殊(比如章標(biāo)題不在頁首、頁眉重復(fù)干擾),可能會誤判。
這種情況我會優(yōu)先找“帶書簽”的版本,或者適當(dāng)調(diào)整正文識別的閾值。

Q3:輸出文件名為什么會有下劃線?

我在 sanitize_filename() 里把 Windows 不允許的字符替換成 _,避免出現(xiàn)“無法創(chuàng)建文件”的問題。

8. 完整代碼(可直接復(fù)制運行)

我把完整代碼放在這里,復(fù)制到 PyCharm 直接運行即可。

# -*- coding: utf-8 -*-
import re
from pathlib import Path
from pypdf import PdfReader, PdfWriter

import tkinter as tk
from tkinter import filedialog, messagebox

# ================= 配置區(qū)域 =================

# 章節(jié)標(biāo)題匹配規(guī)則(適合:第1章 / 第 1 章 / 第一章 / 第十四章 等)
CHAPTER_PATTERN = re.compile(
    r"第\s*[一二三四五六七八九十百千0-9]+\s*章[^\n\r]*"
)

# 是否額外按“頁”拆成單頁 PDF
EXPORT_SINGLE_PAGES = True

# ==========================================

# GUI 全局對象占位
root = None
log_text = None

def sanitize_filename(name: str) -> str:
    """
    去掉 Windows 不支持的文件名字符。
    """
    return re.sub(r'[\\/:*?"<>|]', "_", name)


# ---------- 方案一:優(yōu)先從 PDF 書簽(outline) 中找章節(jié) ----------

def find_chapters_from_outline(reader: PdfReader):
    """
    從 PDF 書簽(outline) 中找出章節(jié):
      - 遍歷所有書簽
      - 標(biāo)題里匹配 CHAPTER_PATTERN(第X章……)
      - 獲取對應(yīng)頁碼
    返回: [{title: '第1章 xxx', start: 0}, ...]
    """
    chapters = []

    # 兼容不同版本的 pypdf:有的叫 outline,有的叫 outlines
    try:
        outlines = reader.outline
    except Exception:
        try:
            outlines = reader.outlines
        except Exception:
            outlines = None

    if not outlines:
        return []

    def walk(items):
        for item in items:
            # 子列表:繼續(xù)遞歸
            if isinstance(item, list):
                walk(item)
            else:
                # 嘗試拿書簽標(biāo)題
                try:
                    title = item.title
                except AttributeError:
                    title = str(item)
                if not isinstance(title, str):
                    title = str(title)

                # 標(biāo)題里不含“第X章”就跳過
                if not CHAPTER_PATTERN.search(title):
                    continue

                # 拿到書簽指向的頁碼
                try:
                    page_num = reader.get_destination_page_number(item)
                except Exception:
                    continue

                chapters.append({"title": title.strip(), "start": page_num})

    walk(outlines)

    # 去重、排序(同一頁只保留一個章節(jié))
    unique = {}
    for ch in chapters:
        if ch["start"] not in unique:
            unique[ch["start"]] = ch

    chapters = sorted(unique.values(), key=lambda c: c["start"])
    return chapters


# ---------- 方案二:從正文文本中猜章節(jié)(備用) ----------

def find_chapters_from_text(reader: PdfReader):
    """
    掃描整個 PDF 正文,猜每一章的“起始頁”以及章節(jié)標(biāo)題。
    規(guī)則大致是:
      - 排除“目錄/contents”頁面
      - 一頁內(nèi)允許有多個“第X章”,逐個判斷
      - 只要某個匹配出現(xiàn)在頁面較前面,且所在行不像目錄行(標(biāo)題 + ...... + 頁碼) 就認(rèn)為是章節(jié)開始
    返回: [{title: '第1章 xxx', start: 0}, ...]
    """
    chapters = []
    num_pages = len(reader.pages)

    for i in range(num_pages):
        page = reader.pages[i]
        text = page.extract_text() or ""
        if not text.strip():
            continue

        # 跳過目錄頁(粗略判斷即可)
        head = text[:100]
        if "目錄" in head or "Contents" in head or "CONTENTS" in head:
            continue

        # 遍歷此頁所有匹配 "第X章"
        for m in CHAPTER_PATTERN.finditer(text):
            # 要求標(biāo)題出現(xiàn)在頁面比較靠前的位置
            if m.start() > 400:   # 這個閾值可以按需要微調(diào)
                continue

            # 找到這一行的文本內(nèi)容
            lines = text.splitlines()
            line_of_match = ""
            char_pos = 0
            for line in lines:
                next_pos = char_pos + len(line) + 1  # 粗略算上換行
                if m.start() < next_pos:
                    line_of_match = line
                    break
                char_pos = next_pos

            # 目錄行一般是:標(biāo)題 + 一串點 + 頁碼
            # 例如:第1章 人際關(guān)系的構(gòu)成..................1
            if re.search(r"[\.·…]{3,}\s*\d+\s*$", line_of_match):
                # 像目錄的行,忽略
                continue

            title = m.group(0).strip()

            # 同一頁只認(rèn)一個章節(jié)起點
            if not any(ch["start"] == i for ch in chapters):
                chapters.append({"title": title, "start": i})
                break  # 當(dāng)前頁已經(jīng)找到一個章節(jié)了,后面不再找

    return chapters


# ---------- 包一層:帶 logger 的 find_chapters ----------

def find_chapters(reader: PdfReader, logger=print):
    chapters = find_chapters_from_outline(reader)
    if chapters:
        logger("? 使用 PDF 書簽識別章節(jié)")
        return chapters

    logger("?? 此 PDF 沒有可用書簽,改用正文文本識別章節(jié)")
    chapters = find_chapters_from_text(reader)
    return chapters


def fill_chapter_ranges(chapters, num_pages):
    """
    根據(jù) start 頁自動計算每章的 end 頁。
    修改 chapters 列表,增加 end 字段。
    """
    for idx, ch in enumerate(chapters):
        start = ch["start"]
        if idx < len(chapters) - 1:
            end = chapters[idx + 1]["start"] - 1
        else:
            end = num_pages - 1
        ch["end"] = end
    return chapters


def split_pdf_by_chapters(pdf_path, output_root, logger=None):
    """
    真正拆分 PDF 的函數(shù),所有信息通過 logger 輸出到日志區(qū)域
    """
    if logger is None:
        logger = print

    pdf_path = Path(pdf_path)
    output_root = Path(output_root)

    if not pdf_path.exists():
        msg = f"PDF 文件不存在: {pdf_path}"
        logger(msg)
        raise FileNotFoundError(msg)

    reader = PdfReader(str(pdf_path))
    num_pages = len(reader.pages)

    book_name = pdf_path.name
    logger(f"開始處理:{book_name}")
    logger(f"總頁數(shù):{num_pages}")

    # 1. 找章節(jié)
    chapters = find_chapters(reader, logger=logger)
    if not chapters:
        msg = "未識別到任何章節(jié)標(biāo)題,請檢查:PDF 是否有書簽/正文是否能提取文字/正則是否合適。"
        logger(msg)
        raise ValueError(msg)

    logger(f"共識別到 {len(chapters)} 章:")
    for idx, ch in enumerate(chapters, start=1):
        logger(f"  第{idx}章 → {ch['title']}(起始頁:{ch['start'] + 1})")

    # 2. 填充每章的結(jié)束頁
    chapters = fill_chapter_ranges(chapters, num_pages)

    # 3. 創(chuàng)建輸出根目錄
    output_root.mkdir(parents=True, exist_ok=True)
    logger(f"輸出目錄:{output_root}")

    # 4. 按章節(jié)導(dǎo)出
    for idx, ch in enumerate(chapters, start=1):
        title = ch["title"]
        start_page = ch["start"]         # 0-based
        end_page = ch["end"]             # 0-based
        page_count = end_page - start_page + 1

        safe_title = sanitize_filename(title)
        chapter_dir = output_root / f"{idx:02d}_{safe_title}"
        chapter_dir.mkdir(parents=True, exist_ok=True)

        logger("")
        logger(f"==== 處理章節(jié) {idx}: {title} ====")
        logger(f"頁碼范圍: {start_page + 1} - {end_page + 1}(共 {page_count} 頁)")
        logger(f"章節(jié)輸出目錄: {chapter_dir}")

        # 4.1 導(dǎo)出“整章一個 PDF”
        chapter_writer = PdfWriter()
        for p in range(start_page, end_page + 1):
            chapter_writer.add_page(reader.pages[p])

        chapter_pdf_path = chapter_dir / f"{idx:02d}_{safe_title}.pdf"
        with open(chapter_pdf_path, "wb") as f:
            chapter_writer.write(f)

        logger(f"  ? 已生成整章 PDF: {chapter_pdf_path.name}")

        # 4.2 可選:每一頁單獨導(dǎo)出
        if EXPORT_SINGLE_PAGES:
            for p in range(start_page, end_page + 1):
                writer = PdfWriter()
                writer.add_page(reader.pages[p])

                # 頁碼用 1 開始,且補零對齊,例如 p0001.pdf
                page_label = f"p{p + 1:04d}.pdf"
                single_page_path = chapter_dir / page_label
                with open(single_page_path, "wb") as f:
                    writer.write(f)
            logger("  ? 已生成單頁 PDF 文件(按頁命名)")

    logger("")
    logger("?? 拆分完成!")


# ========= GUI 部分(兩個按鈕 + 日志框) =========

selected_pdf_file = ""
selected_output_dir = ""

def append_log(msg: str):
    """寫日志到 Text,并自動滾動"""
    if log_text is None:
        print(msg)
        return
    log_text.config(state="normal")
    log_text.insert(tk.END, msg + "\n")
    log_text.see(tk.END)
    log_text.config(state="disabled")
    # 刷新一下界面,讓日志滾動更及時
    if root is not None:
        root.update_idletasks()


def choose_pdf():
    """按鈕1:選擇 PDF 文件"""
    global selected_pdf_file
    path = filedialog.askopenfilename(
        title="請選擇 PDF 文件",
        filetypes=[("PDF 文件", "*.pdf"), ("所有文件", "*.*")]
    )
    if path:
        selected_pdf_file = path
        label_pdf.config(text=f"已選擇 PDF:{path}")
        append_log(f"已選擇 PDF 文件:{path}")


def choose_output_and_run():
    """按鈕2:選擇輸出目錄并開始拆分"""
    global selected_output_dir, selected_pdf_file

    if not selected_pdf_file:
        messagebox.showwarning("提示", "請先選擇 PDF 文件!")
        return

    path = filedialog.askdirectory(title="請選擇輸出目錄")
    if not path:
        return

    selected_output_dir = path
    label_output.config(text=f"輸出目錄:{path}")
    append_log("")
    append_log(f"輸出目錄設(shè)置為:{path}")
    append_log("開始拆分,請稍候...\n")

    # 清理一下舊的錯誤提示
    try:
        split_pdf_by_chapters(selected_pdf_file, selected_output_dir, logger=append_log)
        messagebox.showinfo("完成", "拆分完成!\n請到輸出目錄查看各章節(jié)文件夾。")
    except Exception as e:
        append_log(f"? 出錯:{e}")
        messagebox.showerror("錯誤", f"處理過程中出現(xiàn)錯誤:\n{e}")


if __name__ == "__main__":
    # 創(chuàng)建窗口
    root = tk.Tk()
    root.title("PDF 章節(jié)拆分工具")
    root.geometry("400x400")   # 固定為 400x400 的窗口
    root.resizable(False, False)

    # 上半部分:按鈕區(qū)域
    btn_frame = tk.Frame(root)
    btn_frame.pack(padx=10, pady=10, fill="x")

    # 按鈕1:選擇 PDF
    btn_pdf = tk.Button(btn_frame, text="1. 請選擇你的 PDF 文件", command=choose_pdf)
    btn_pdf.pack(fill="x")

    label_pdf = tk.Label(btn_frame, text="尚未選擇 PDF 文件", anchor="w")
    label_pdf.pack(fill="x", pady=(5, 10))

    # 按鈕2:選擇輸出目錄 + 開始拆分
    btn_output = tk.Button(btn_frame, text="2. 請選擇輸出目錄并開始拆分", command=choose_output_and_run)
    btn_output.pack(fill="x")

    label_output = tk.Label(btn_frame, text="尚未選擇輸出目錄", anchor="w")
    label_output.pack(fill="x", pady=(5, 0))

    # 下半部分:日志輸出區(qū)域(帶滾動條)
    log_frame = tk.Frame(root)
    log_frame.pack(padx=10, pady=10, fill="both", expand=True)

    log_text = tk.Text(log_frame, state="disabled")
    log_text.pack(side="left", fill="both", expand=True)

    scrollbar = tk.Scrollbar(log_frame, command=log_text.yview)
    scrollbar.pack(side="right", fill="y")

    log_text.config(yscrollcommand=scrollbar.set)

    append_log("日志初始化完成。")
    append_log("提示:先選擇 PDF 文件,再選擇輸出目錄開始拆分。")

    root.mainloop()

到此這篇關(guān)于使用Python開發(fā)的PDF拆分工具的文章就介紹到這了,更多相關(guān)Python PDF拆分工具內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python用字符組成圖像代碼實例

    python用字符組成圖像代碼實例

    大家好,本篇文章主要講的是python用字符組成圖像代碼實例,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • Python3 中文文件讀寫方法

    Python3 中文文件讀寫方法

    下面小編就為大家分享一篇Python3 中文文件讀寫方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • 淺談Python中os模塊及shutil模塊的常規(guī)操作

    淺談Python中os模塊及shutil模塊的常規(guī)操作

    這篇文章主要介紹了淺談Python中os模塊及shutil模塊的常規(guī)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-04-04
  • 詳解python字符串相關(guān)str

    詳解python字符串相關(guān)str

    這篇文章主要為大家介紹了python字符串相關(guān)str,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • Opencv圖像處理方法最全總結(jié)

    Opencv圖像處理方法最全總結(jié)

    這篇文章主要給大家介紹了關(guān)于Opencv圖像處理方法的相關(guān)資料,OpenCV是一個開源的計算機視覺庫,提供了很多圖像處理、計算機視覺和機器學(xué)習(xí)等方面的函數(shù)和工具,被廣泛應(yīng)用于各種計算機視覺領(lǐng)域的研究和應(yīng)用中,需要的朋友可以參考下
    2024-06-06
  • 淺談Python traceback的優(yōu)雅處理

    淺談Python traceback的優(yōu)雅處理

    這篇文章主要介紹了淺談Python traceback的優(yōu)雅處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-08-08
  • python模擬登陸、POST/GET請求方式

    python模擬登陸、POST/GET請求方式

    這篇文章主要介紹了python模擬登陸、POST/GET請求方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • Python3.8安裝Pygame教程步驟詳解

    Python3.8安裝Pygame教程步驟詳解

    這篇文章主要介紹了Python3.8安裝Pygame教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • pydev使用wxpython找不到路徑的解決方法

    pydev使用wxpython找不到路徑的解決方法

    pydev使用wx庫開發(fā)的過程中,import時碰到wx可以識別,但是其它很多函數(shù)和變量上面全部是紅叉,即無法識別
    2013-02-02
  • Python sort 自定義函數(shù)排序問題

    Python sort 自定義函數(shù)排序問題

    這篇文章主要介紹了Python sort 自定義函數(shù)排序問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09

最新評論

香港 | 凯里市| 明溪县| 夏河县| 金平| 青铜峡市| 定陶县| 康平县| 香港 | 深州市| 南汇区| 科技| 绥阳县| 饶平县| 牡丹江市| 台南县| 开远市| 池州市| 昌平区| 贵溪市| 甘肃省| 桦甸市| 天柱县| 马公市| 文安县| 祁东县| 乌恰县| 武隆县| 青河县| 连山| 天峨县| 德令哈市| 句容市| 高唐县| 开平市| 云浮市| 海南省| 自治县| 广安市| 宾川县| 蒙城县|