Tworzenie spisu treści i łączenie rozdziałów w PDF to ważny element automatyzacji pracy z dużymi dokumentami. Jeśli generujesz raporty, dokumentację techniczną, książki, oferty, materiały szkoleniowe albo zbiory załączników, sam plik PDF bez zakładek szybko staje się niewygodny. Użytkownik musi przewijać dziesiątki lub setki stron, aby znaleźć właściwy rozdział.
Python pozwala zautomatyzować ten proces. Za pomocą biblioteki pypdf możesz scalać wiele plików PDF, dodawać zakładki, tworzyć strukturę outline, budować hierarchiczny spis treści i porządkować rozdziały w jednym dokumencie. Z kolei reportlab przydaje się wtedy, gdy chcesz wygenerować osobną stronę ze spisem treści.
W tym artykule zobaczysz, jak tworzyć zakładki w PDF, jak budować zagnieżdżony outline, jak łączyć rozdziały w jeden dokument i jak dodać stronę ze spisem treści.
Kurs Python od podstaw — PyStart
Zacznij programować w Pythonie od zera. Praktyczny kurs wideo z ćwiczeniami — bez wcześniejszego doświadczenia.
- ✓24 lekcje wideo + 80 ćwiczeń
- ✓Realne bazy z e-commerce
- ✓Społeczność i code-review
Czym jest spis treści w PDF?
W PDF możesz spotkać dwa rodzaje spisu treści.
Pierwszy to zakładki, nazywane też outline. Są widoczne w panelu bocznym czytnika PDF i pozwalają szybko przejść do wybranej strony. To najwygodniejsza forma nawigacji w dużych dokumentach.
Drugi to strona ze spisem treści umieszczona na początku dokumentu. Wygląda jak klasyczny spis treści w książce: tytuł rozdziału, kropki prowadzące i numer strony.
Najlepsze dokumenty często mają oba elementy: stronę TOC oraz zakładki w panelu bocznym. Dzięki temu PDF jest czytelny zarówno przy drukowaniu, jak i przy pracy na komputerze.
Instalacja bibliotek
Do pracy z zakładkami i scalaniem PDF użyj pypdf. Do tworzenia strony ze spisem treści przyda się reportlab.
Bash1pip install pypdf reportlab
pypdf odpowiada za manipulację istniejącymi plikami PDF, a reportlab za generowanie nowych stron PDF od podstaw.
Dodawanie prostych zakładek do PDF
Najprostszy scenariusz to dodanie zakładek do istniejącego dokumentu. Zakładki wskazują konkretne strony. W pypdf numery stron są indeksowane od zera, więc pierwsza strona ma indeks 0.
Dla wygody funkcja poniżej przyjmuje numerację od 1, czyli taką, jaką widzi użytkownik w czytniku PDF.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44from pypdf import PdfReader, PdfWriter def add_bookmarks(input_pdf: str, output_pdf: str, bookmarks: dict[str, int]): """ Dodaje zakładki do dokumentu PDF. bookmarks: { "Wprowadzenie": 1, "Rozdział 1": 5 } """ reader = PdfReader(input_pdf) writer = PdfWriter() for page in reader.pages: writer.add_page(page) total_pages = len(reader.pages) for title, page_number in bookmarks.items(): page_index = page_number - 1 if 0 <= page_index < total_pages: writer.add_outline_item(title, page_index) else: print(f"Pominięto zakładkę '{title}': strona {page_number} nie istnieje.") with open(output_pdf, "wb") as file: writer.write(file) print(f"Dodano {len(bookmarks)} zakładek do pliku: {output_pdf}") add_bookmarks( "dokument.pdf", "dokument_z_zakladkami.pdf", { "Wprowadzenie": 1, "Rozdział 1": 5, "Rozdział 2": 15, "Zakończenie": 30 } )
To dobre rozwiązanie, gdy masz gotowy PDF i znasz strony, od których zaczynają się rozdziały.
Hierarchiczne zakładki w PDF
W większych dokumentach sama płaska lista zakładek może nie wystarczyć. Lepszy efekt daje struktura hierarchiczna: część, rozdział, podrozdział.
Przykład:
1 2 3 4 5 6Część I Rozdział 1.1 Rozdział 1.2 Podrozdział 1.2.1 Część II Rozdział 2.1
W pypdf można tworzyć takie zagnieżdżenie przez przekazanie rodzica w parametrze parent.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70from pypdf import PdfReader, PdfWriter def add_nested_bookmarks(input_pdf: str, output_pdf: str, structure: dict): """ Dodaje hierarchiczne zakładki do dokumentu PDF. structure: { "Wprowadzenie": 1, "Część I": (5, { "Rozdział 1.1": 5, "Rozdział 1.2": 10 }) } """ reader = PdfReader(input_pdf) writer = PdfWriter() for page in reader.pages: writer.add_page(page) total_pages = len(reader.pages) def add_item(title: str, page_number: int, parent=None): page_index = page_number - 1 if not 0 <= page_index < total_pages: print(f"Pominięto '{title}': strona {page_number} nie istnieje.") return None return writer.add_outline_item(title, page_index, parent=parent) def process_items(items: dict, parent=None): for title, value in items.items(): if isinstance(value, tuple): page_number, children = value bookmark = add_item(title, page_number, parent) if bookmark and children: process_items(children, bookmark) else: add_item(title, value, parent) process_items(structure) with open(output_pdf, "wb") as file: writer.write(file) print(f"Utworzono hierarchiczne zakładki w pliku: {output_pdf}") add_nested_bookmarks( "dokument.pdf", "dokument_hierarchiczny.pdf", { "Wprowadzenie": 1, "Część I": (5, { "Rozdział 1.1": 5, "Rozdział 1.2": (10, { "Podrozdział 1.2.1": 10, "Podrozdział 1.2.2": 12 }) }), "Część II": (20, { "Rozdział 2.1": 20, "Rozdział 2.2": 25 }), "Zakończenie": 30 } )
Hierarchiczne zakładki są szczególnie przydatne w dokumentacji, e-bookach, raportach rocznych i długich materiałach szkoleniowych.
Łączenie rozdziałów PDF z automatycznymi zakładkami
Python co tydzień — newsletter dla programistów
Otrzymuj codzienne ćwiczenia, ciekawostki z ekosystemu Pythona i wskazówki do rozmów rekrutacyjnych.
Częsty scenariusz: masz kilka osobnych plików PDF, np. wstep.pdf, rozdzial_1.pdf, rozdzial_2.pdf, zalaczniki.pdf, i chcesz połączyć je w jeden dokument. Przy okazji warto automatycznie dodać zakładki do początku każdego rozdziału.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49from pathlib import Path from pypdf import PdfReader, PdfWriter def merge_chapters_with_bookmarks(chapters: list[dict], output_pdf: str): """ Łączy rozdziały PDF i dodaje zakładki. chapters: [ {"file": "wstep.pdf", "title": "Wprowadzenie"}, {"file": "rozdzial_1.pdf", "title": "Rozdział 1"} ] """ writer = PdfWriter() current_page = 0 for chapter in chapters: file_path = chapter["file"] title = chapter["title"] if not Path(file_path).exists(): print(f"Pominięto brakujący plik: {file_path}") continue reader = PdfReader(file_path) writer.add_outline_item(title, current_page) for page in reader.pages: writer.add_page(page) print(f"Dodano: {title} ({len(reader.pages)} stron)") current_page += len(reader.pages) with open(output_pdf, "wb") as file: writer.write(file) print(f"Utworzono dokument: {output_pdf}") merge_chapters_with_bookmarks( [ {"file": "wstep.pdf", "title": "Wprowadzenie"}, {"file": "rozdzial_1.pdf", "title": "Rozdział 1: Podstawy"}, {"file": "rozdzial_2.pdf", "title": "Rozdział 2: Praktyka"}, {"file": "zalaczniki.pdf", "title": "Załączniki"} ], "kompletny_dokument.pdf" )
Ten wzorzec sprawdza się bardzo dobrze, gdy każdy rozdział jest generowany osobno, a dopiero na końcu tworzysz jeden finalny dokument.
Łączenie rozdziałów z podrozdziałami
Możesz też dodać podrozdziały wewnątrz każdego pliku. W takim przypadku page_in_file oznacza numer strony w konkretnym rozdziale, liczony od 1.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83from pathlib import Path from pypdf import PdfReader, PdfWriter def merge_with_toc(chapters: list[dict], output_pdf: str): """ Łączy rozdziały PDF z automatycznym outline. chapters: [ { "file": "rozdzial_1.pdf", "title": "Rozdział 1", "subchapters": [ {"title": "1.1 Wprowadzenie", "page_in_file": 1}, {"title": "1.2 Teoria", "page_in_file": 5} ] } ] """ writer = PdfWriter() current_page = 0 for chapter in chapters: file_path = chapter["file"] title = chapter["title"] if not Path(file_path).exists(): print(f"Pominięto brakujący plik: {file_path}") continue reader = PdfReader(file_path) chapter_start_page = current_page for page in reader.pages: writer.add_page(page) main_bookmark = writer.add_outline_item(title, chapter_start_page) for subchapter in chapter.get("subchapters", []): sub_page = chapter_start_page + subchapter["page_in_file"] - 1 if sub_page < chapter_start_page + len(reader.pages): writer.add_outline_item( subchapter["title"], sub_page, parent=main_bookmark ) else: print(f"Pominięto podrozdział: {subchapter['title']}") current_page += len(reader.pages) with open(output_pdf, "wb") as file: writer.write(file) print(f"Połączono rozdziały i dodano spis treści: {output_pdf}") merge_with_toc( [ { "file": "wstep.pdf", "title": "Wprowadzenie" }, { "file": "rozdzial_1.pdf", "title": "Rozdział 1: Podstawy", "subchapters": [ {"title": "1.1 Wprowadzenie", "page_in_file": 1}, {"title": "1.2 Teoria", "page_in_file": 5} ] }, { "file": "rozdzial_2.pdf", "title": "Rozdział 2: Zaawansowane", "subchapters": [ {"title": "2.1 Przykłady", "page_in_file": 2}, {"title": "2.2 Podsumowanie", "page_in_file": 8} ] } ], "ksiazka_z_outline.pdf" )
To praktyczne rozwiązanie do tworzenia dokumentów, w których struktura rozdziałów jest znana z góry.
Tworzenie strony ze spisem treści
Zakładki są wygodne w czytniku PDF, ale czasem potrzebujesz też klasycznej strony ze spisem treści. Możesz ją wygenerować za pomocą reportlab.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas def create_toc_page(toc_items: list[tuple[str, int, int]], output_pdf: str): """ Tworzy stronę PDF ze spisem treści. toc_items: [ ("Wprowadzenie", 1, 0), ("Rozdział 1", 5, 0), ("Podrozdział 1.1", 7, 1) ] Trzeci element oznacza poziom zagnieżdżenia. """ c = canvas.Canvas(output_pdf, pagesize=letter) width, height = letter c.setFont("Helvetica-Bold", 20) c.drawString(50, height - 50, "Spis treści") y = height - 100 c.setFont("Helvetica", 12) for title, page, level in toc_items: indent = 50 + level * 20 if y < 50: c.showPage() y = height - 50 c.setFont("Helvetica", 12) c.drawString(indent, y, title) title_width = c.stringWidth(title, "Helvetica", 12) dots_start = indent + title_width + 10 dots_end = width - 100 x = dots_start while x < dots_end: c.drawString(x, y, ".") x += 5 c.drawRightString(width - 50, y, str(page)) y -= 20 c.save() print(f"Utworzono stronę spisu treści: {output_pdf}") create_toc_page( [ ("Wprowadzenie", 1, 0), ("Część I", 5, 0), ("Rozdział 1.1", 5, 1), ("Rozdział 1.2", 10, 1), ("Podrozdział 1.2.1", 12, 2), ("Część II", 20, 0), ("Zakończenie", 30, 0) ], "toc.pdf" )
Taki spis treści jest statyczną stroną PDF. Możesz go wydrukować razem z dokumentem lub dodać na początku finalnego pliku.
Łączenie strony TOC z głównym dokumentem
Kurs Python dla początkujących — PyStart
Zacznij programować w Pythonie! Idealne dla osób bez doświadczenia. Praktyczne zadania, projekty i wsparcie społeczności.
- ✓24 lekcje wideo + 80 ćwiczeń
- ✓Realne bazy z e-commerce
- ✓Społeczność i code-review
Po utworzeniu strony spisu treści możesz dodać ją na początek dokumentu.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26from pypdf import PdfReader, PdfWriter def merge_toc_with_document(toc_pdf: str, main_pdf: str, output_pdf: str): """Dodaje stronę spisu treści na początek dokumentu.""" writer = PdfWriter() toc_reader = PdfReader(toc_pdf) main_reader = PdfReader(main_pdf) for page in toc_reader.pages: writer.add_page(page) for page in main_reader.pages: writer.add_page(page) with open(output_pdf, "wb") as file: writer.write(file) print(f"Połączono TOC z dokumentem: {output_pdf}") merge_toc_with_document( "toc.pdf", "kompletny_dokument.pdf", "dokument_z_toc.pdf" )
Ważna uwaga: jeśli dodasz stronę spisu treści na początku, numery stron w zakładkach i na stronie TOC mogą wymagać przesunięcia. Najlepiej uwzględnić to już na etapie generowania struktury dokumentu.
Łączenie rozdziałów, strony TOC i zakładek w jednym pipeline
Najlepszy efekt daje jeden proces, który:
- zna listę rozdziałów,
- oblicza strony startowe,
- tworzy stronę spisu treści,
- scala TOC i rozdziały,
- dodaje zakładki z przesunięciem stron.
Poniżej uproszczony przykład takiego procesu.
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67from pathlib import Path from pypdf import PdfReader, PdfWriter def build_pdf_book(chapters: list[dict], toc_pdf: str, output_pdf: str): """Buduje finalny PDF z TOC, rozdziałami i zakładkami.""" writer = PdfWriter() toc_reader = PdfReader(toc_pdf) toc_pages_count = len(toc_reader.pages) for page in toc_reader.pages: writer.add_page(page) current_page = toc_pages_count for chapter in chapters: file_path = chapter["file"] title = chapter["title"] if not Path(file_path).exists(): print(f"Pominięto brakujący plik: {file_path}") continue reader = PdfReader(file_path) chapter_start = current_page for page in reader.pages: writer.add_page(page) main_bookmark = writer.add_outline_item(title, chapter_start) for subchapter in chapter.get("subchapters", []): sub_page = chapter_start + subchapter["page_in_file"] - 1 writer.add_outline_item( subchapter["title"], sub_page, parent=main_bookmark ) current_page += len(reader.pages) with open(output_pdf, "wb") as file: writer.write(file) print(f"Utworzono finalny dokument: {output_pdf}") chapters = [ { "file": "wstep.pdf", "title": "Wprowadzenie" }, { "file": "rozdzial_1.pdf", "title": "Rozdział 1: Podstawy", "subchapters": [ {"title": "1.1 Start", "page_in_file": 1}, {"title": "1.2 Przykłady", "page_in_file": 4} ] }, { "file": "rozdzial_2.pdf", "title": "Rozdział 2: Praktyka" } ] build_pdf_book(chapters, "toc.pdf", "finalny_dokument.pdf")
To dobry fundament pod automatyczne składanie większych dokumentów z wielu plików źródłowych.
Dobre praktyki przy tworzeniu spisu treści PDF
Przy automatyzacji dużych dokumentów PDF warto trzymać się kilku zasad:
- używaj jednej numeracji stron w konfiguracji, najlepiej od 1,
- pamiętaj, że
pypdfużywa indeksów stron od 0, - przed dodaniem zakładki sprawdzaj, czy strona istnieje,
- przy dodaniu strony TOC uwzględnij przesunięcie numeracji,
- nazywaj pliki rozdziałów z numerami, np.
01_wstep.pdf, - dodawaj zakładki główne i podzakładki,
- testuj dokument w kilku czytnikach PDF,
- zapisuj finalny PDF jako nowy plik, nie nadpisuj źródeł,
- trzymaj strukturę rozdziałów w słowniku lub pliku JSON,
- przy dużych dokumentach loguj liczbę dodanych stron.
Dzięki temu finalny dokument będzie czytelny, łatwy w nawigacji i mniej podatny na błędy.
Podsumowanie
Tworzenie spisu treści i łączenie rozdziałów w PDF w Pythonie pozwala budować profesjonalne dokumenty z wygodną nawigacją. Dzięki pypdf możesz scalać rozdziały, dodawać proste i hierarchiczne zakładki oraz kontrolować strony startowe każdej sekcji. Dzięki reportlab możesz wygenerować klasyczną stronę ze spisem treści.
Najważniejsze jest dobre zaplanowanie struktury dokumentu: lista rozdziałów, numery stron, podrozdziały i ewentualne przesunięcie po dodaniu strony TOC. Gdy ta struktura jest opisana w kodzie, cały proces można powtarzać automatycznie dla kolejnych raportów, książek, ofert lub dokumentacji.
Jeśli chcesz zacząć praktycznie, przygotuj trzy pliki PDF jako rozdziały, połącz je w jeden dokument i dodaj zakładkę do początku każdego rozdziału. Potem rozbuduj skrypt o podrozdziały i osobną stronę spisu treści.
➡️ Następny artykuł
Teraz, gdy znasz różne techniki pracy z PDF, poznaj dostępne narzędzia i wybierz najlepsze dla swoich potrzeb:
Narzędzia do PDF w Pythonie: przegląd bibliotek — porównanie bibliotek pypdf, pdfminer.six, reportlab, WeasyPrint i PyMuPDF wraz z zaleceniami dotyczącymi wyboru narzędzia.



