惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Martin Fowler
Martin Fowler
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
C
Check Point Blog
H
Help Net Security
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
P
Proofpoint News Feed
V
Visual Studio Blog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Vercel News
Vercel News
S
SegmentFault 最新的问题
L
LangChain Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Cloudflare Blog
Hugging Face - Blog
Hugging Face - Blog
有赞技术团队
有赞技术团队
博客园_首页
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
🛠️ pdf_to_markdown_chapters: Splits a long PDF into Markd...
Golden Alien · 2026-05-12 · via DEV Community

Golden Alien

PDF to Markdown Chapters

This tool converts a long PDF document into organized Markdown files, splitting the content into chapters based on heading structure. It is ideal for converting technical documents, books, or reports into a structured format suitable for documentation websites, wikis, or static site generators.

Features

  • Extracts text from PDF using layout-aware parsing
  • Identifies chapter headings using font size, style, and positional heuristics
  • Splits content into separate Markdown files per chapter
  • Preserves basic formatting such as bold, italic, lists, and code blocks
  • Creates a table of contents (_toc.md) for easy navigation
  • Lightweight and dependency-managed using standard Python libraries

Usage

Run the script from the command line:

python main.py input.pdf --output-dir chapters/

Enter fullscreen mode Exit fullscreen mode

This will create a directory (default: chapters/) containing individual .md files for each detected chapter and a _toc.md file.

Dependencies

  • Python 3.7+
  • pdfplumber for precise text and layout extraction
  • argparse for command-line interface

Install dependencies:

pip install pdfplumber

Enter fullscreen mode Exit fullscreen mode

Customization

You can adjust heading detection sensitivity by modifying the font-weight and size thresholds in the script. The tool assumes that chapter titles are larger and bold compared to body text.

Limitations

  • Works best with text-based PDFs (not scanned)
  • Heading detection is heuristic-based; may need tuning for specific documents
  • Complex layouts (multi-column, tables) may not convert perfectly

License

MIT

import argparse
import os
import re
import pdfplumber


def is_heading(obj, min_font_size=12, bold_keywords=['bold', 'Bold']):
    """Determine if a text object is a heading based on font characteristics."""
    font_name = obj.get('fontname', '')
    size = obj.get('size', 0)
    if size >= min_font_size:
        if any(keyword in font_name for keyword in bold_keywords):
            return True
    return False


def extract_headings_and_text(pdf_path):
    """Extract structured content: list of (heading, content) tuples."""
    chapters = []
    current_heading = 'Introduction'
    current_content = []

    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            text_objects = page.chars
            if not text_objects:
                continue

            # Group into lines by y-position
            lines = {}
            for obj in text_objects:
                y_key = round(obj['top'])
                lines.setdefault(y_key, []).append(obj)

            for y_key in sorted(lines.keys()):
                line_chars = lines[y_key]
                text = ''.join([c['text'] for c in line_chars])
                bbox = (min(c['x0'] for c in line_chars),
                        min(c['top'] for c in line_chars),
                        max(c['x1'] for c in line_chars),
                        max(c['bottom'] for c in line_chars))
                # Use first char to represent line style
                if is_heading(line_chars[0]):
                    if current_heading and current_content:
                        chapters.append((current_heading, '\n'.join(current_content)))
                    current_heading = text.strip()
                    current_content = []
                else:
                    current_content.append(text.strip())

    if current_heading and current_content:
        chapters.append((current_heading, '\n'.join(current_content)))
    return chapters


def save_chapters(chapters, output_dir):
    """Save each chapter as a markdown file and generate TOC."""
    os.makedirs(output_dir, exist_ok=True)
    toc_lines = ['# Table of Contents\n']

    for i, (heading, content) in enumerate(chapters):
        filename = f'{i+1:02d}_{re.sub(r"[^a-zA-Z0-9]", "_", heading.strip())[:50]}.md'
        filepath = os.path.join(output_dir, filename)
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(f'# {heading}\n\n{content}\n')
        toc_lines.append(f'{i+1}. [{heading}]({filename})')

    # Write TOC
    toc_path = os.path.join(output_dir, '_toc.md')
    with open(toc_path, 'w', encoding='utf-8') as f:
        f.write('\n'.join(toc_lines))


def main():
    parser = argparse.ArgumentParser(description='Split PDF into Markdown chapters.')
    parser.add_argument('pdf_path', help='Path to input PDF')
    parser.add_argument('--output-dir', '-o', default='chapters', help='Output directory')
    args = parser.parse_args()

    if not os.path.exists(args.pdf_path):
        print(f'PDF file not found: {args.pdf_path}')
        return

    chapters = extract_headings_and_text(args.pdf_path)
    save_chapters(chapters, args.output_dir)
    print(f'Saved {len(chapters)} chapters to {args.output_dir}/')

if __name__ == '__main__':
    main()

Enter fullscreen mode Exit fullscreen mode