Files
2026-07-13 12:35:23 +08:00

270 lines
7.6 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PDF Parser - Extract text and sections from PDF files.
Features:
- Extract text from PDF
- Parse IMRaD sections (Introduction, Methods, Results, Discussion)
- Extract metadata (title, authors, abstract)
- Summarize content
Requirements:
pip install pymupdf
Usage:
python pdf_parser.py paper.pdf --output text.txt
python pdf_parser.py paper.pdf --sections --json output.json
python pdf_parser.py paper.pdf --summarize
"""
import argparse
import json
import re
import sys
from pathlib import Path
try:
import fitz # PyMuPDF
except ImportError:
print("Error: PyMuPDF required. Install with: pip install pymupdf")
sys.exit(1)
# Section patterns for academic papers
SECTION_PATTERNS = {
"abstract": [
r"^abstract\s*$",
r"^摘要\s*$",
r"^abstract[.:]",
],
"introduction": [
r"^1\.?\s*introduction\s*$",
r"^introduction\s*$",
r"^一、引言\s*$",
r"^1\s+引言\s*$",
],
"methods": [
r"^2\.?\s*methods?\s*$",
r"^methodology\s*$",
r"^materials\s+and\s+methods\s*$",
r"^二、方法\s*$",
r"^2\s+方法\s*$",
],
"results": [
r"^3\.?\s*results?\s*$",
r"^findings\s*$",
r"^三、结果\s*$",
r"^3\s+结果\s*$",
],
"discussion": [
r"^4\.?\s*discussion\s*$",
r"^conclusions?\s*$",
r"^四、讨论\s*$",
r"^4\s+讨论\s*$",
r"^五、结论\s*$",
],
"references": [
r"^references?\s*$",
r"^bibliography\s*$",
r"^参考文献\s*$",
],
}
def extract_text(pdf_path: str) -> str:
"""Extract all text from PDF."""
doc = fitz.open(pdf_path)
text_parts = []
for page in doc:
text_parts.append(page.get_text())
doc.close()
return "\n\n".join(text_parts)
def extract_metadata(pdf_path: str) -> dict:
"""Extract metadata from PDF."""
doc = fitz.open(pdf_path)
meta = doc.metadata
metadata = {
"title": meta.get("title", ""),
"author": meta.get("author", ""),
"subject": meta.get("subject", ""),
"keywords": meta.get("keywords", ""),
"pages": len(doc),
"creator": meta.get("creator", ""),
"producer": meta.get("producer", ""),
}
doc.close()
return metadata
def extract_abstract(text: str) -> str:
"""Extract abstract from paper text."""
lines = text.split("\n")
abstract_start = None
abstract_end = None
for i, line in enumerate(lines):
line_lower = line.strip().lower()
# Find abstract start
if abstract_start is None:
for pattern in SECTION_PATTERNS["abstract"]:
if re.match(pattern, line_lower, re.IGNORECASE):
abstract_start = i + 1
break
# Find abstract end (next section)
elif abstract_end is None:
for section in ["introduction", "methods"]:
for pattern in SECTION_PATTERNS.get(section, []):
if re.match(pattern, line_lower, re.IGNORECASE):
abstract_end = i
break
if abstract_end:
break
if abstract_start is not None:
if abstract_end is None:
abstract_end = min(abstract_start + 50, len(lines))
abstract = "\n".join(lines[abstract_start:abstract_end])
# Clean up
abstract = re.sub(r"\s+", " ", abstract).strip()
return abstract[:2000] # Limit length
return ""
def parse_sections(text: str) -> dict:
"""Parse paper into IMRaD sections."""
lines = text.split("\n")
sections = {}
current_section = "other"
current_content = []
for line in lines:
line_stripped = line.strip()
line_lower = line_stripped.lower()
# Check if this line is a section header
found_section = None
for section_name, patterns in SECTION_PATTERNS.items():
for pattern in patterns:
if re.match(pattern, line_lower, re.IGNORECASE):
found_section = section_name
break
if found_section:
break
if found_section:
# Save previous section
if current_content:
sections[current_section] = "\n".join(current_content).strip()
current_section = found_section
current_content = []
else:
current_content.append(line)
# Save last section
if current_content:
sections[current_section] = "\n".join(current_content).strip()
return sections
def summarize_text(text: str, max_length: int = 500) -> str:
"""Simple extractive summarization."""
# Split into sentences
sentences = re.split(r'[。.!?]\s*', text)
if len(sentences) <= 5:
return text[:max_length]
# Take first 3 and last 2 sentences
selected = sentences[:3] + sentences[-2:]
summary = ". ".join(selected)
return summary[:max_length]
def main():
parser = argparse.ArgumentParser(
description="Extract text and sections from PDF files",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Extract all text
python pdf_parser.py paper.pdf --output text.txt
# Extract sections as JSON
python pdf_parser.py paper.pdf --sections --json output.json
# Extract metadata and abstract
python pdf_parser.py paper.pdf --metadata --abstract
# Summarize content
python pdf_parser.py paper.pdf --summarize
"""
)
parser.add_argument("pdf_path", help="Path to PDF file")
parser.add_argument("--output", "-o", help="Output file path")
parser.add_argument("--json", help="Output as JSON to file")
parser.add_argument("--sections", action="store_true", help="Parse IMRaD sections")
parser.add_argument("--metadata", action="store_true", help="Extract metadata")
parser.add_argument("--abstract", action="store_true", help="Extract abstract")
parser.add_argument("--summarize", action="store_true", help="Summarize content")
parser.add_argument("--all", action="store_true", help="Extract everything")
args = parser.parse_args()
if not Path(args.pdf_path).exists():
print(f"Error: File not found: {args.pdf_path}", file=sys.stderr)
sys.exit(1)
# Default to all extraction if no specific options
if not any([args.sections, args.metadata, args.abstract, args.summarize, args.all]):
args.all = True
result = {}
# Extract text
print(f"Processing: {args.pdf_path}", file=sys.stderr)
text = extract_text(args.pdf_path)
if args.all or args.metadata:
result["metadata"] = extract_metadata(args.pdf_path)
if args.all or args.abstract:
result["abstract"] = extract_abstract(text)
if args.all or args.sections:
result["sections"] = parse_sections(text)
if args.all or args.summarize:
result["summary"] = summarize_text(text)
# Always include full text
result["text"] = text[:10000] # Limit for practical use
result["text_length"] = len(text)
# Output
if args.json:
with open(args.json, "w", encoding="utf-8") as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"Saved to {args.json}", file=sys.stderr)
elif args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(text)
print(f"Saved to {args.output}", file=sys.stderr)
else:
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()