#!/usr/bin/env python3 """ PDF页面提取工具 从PDF文件中提取指定页面并保存为新PDF文件。 使用方法: python pdf_extractor.py input.pdf --pages "1-5,7,9-12" --output output.pdf python pdf_extractor.py input.pdf --pages "1-" --output output.pdf # 提取第1页到最后 python pdf_extractor.py input.pdf --pages "-10" --output output.pdf # 提取前10页 """ import argparse import sys from pathlib import Path from typing import List import io try: import pypdfium2 as pdfium PDFIUM_AVAILABLE = True except ImportError: PDFIUM_AVAILABLE = False pdfium = None from loguru import logger from core.pdf_utils import PDFUtils def extract_pdf_pages( input_pdf_path: Path, page_indices: List[int], output_pdf_path: Path ) -> bool: """ 从PDF中提取指定页面并保存为新PDF Args: input_pdf_path: 输入PDF文件路径 page_indices: 要提取的页面索引列表(0-based) output_pdf_path: 输出PDF文件路径 Returns: 是否成功 """ if not PDFIUM_AVAILABLE: logger.error("❌ pypdfium2 未安装,请先安装: pip install pypdfium2") return False if not input_pdf_path.exists(): logger.error(f"❌ 输入文件不存在: {input_pdf_path}") return False if not input_pdf_path.suffix.lower() == '.pdf': logger.error(f"❌ 输入文件不是PDF格式: {input_pdf_path}") return False try: # 读取PDF文件 with open(input_pdf_path, 'rb') as f: pdf_bytes = f.read() # 加载PDF文档 pdf = pdfium.PdfDocument(pdf_bytes) total_pages = len(pdf) if total_pages == 0: logger.error("❌ PDF文件为空") pdf.close() return False # 验证页面索引 valid_indices = [] for idx in sorted(set(page_indices)): # 去重并排序 if 0 <= idx < total_pages: valid_indices.append(idx) else: logger.warning(f"⚠️ 页面索引 {idx + 1} 超出范围(总页数: {total_pages}),已跳过") if not valid_indices: logger.error("❌ 没有有效的页面可提取") pdf.close() return False # 创建新PDF文档 output_pdf = pdfium.PdfDocument.new() # 导入指定页面 success_count = 0 for page_idx in valid_indices: try: output_pdf.import_pages(pdf, pages=[page_idx]) success_count += 1 except Exception as e: logger.warning(f"⚠️ 导入第 {page_idx + 1} 页失败: {e},已跳过") continue if success_count == 0: logger.error("❌ 没有成功导入任何页面") pdf.close() output_pdf.close() return False # 保存到文件 output_pdf_path.parent.mkdir(parents=True, exist_ok=True) # 保存到内存缓冲区 output_buffer = io.BytesIO() output_pdf.save(output_buffer) output_bytes = output_buffer.getvalue() # 写入文件 with open(output_pdf_path, 'wb') as f: f.write(output_bytes) # 清理资源 pdf.close() output_pdf.close() logger.info(f"✅ 成功提取 {success_count} 页到: {output_pdf_path}") logger.info(f" 提取的页面: {', '.join([str(idx + 1) for idx in valid_indices])}") return True except Exception as e: logger.error(f"❌ 提取PDF页面时出错: {e}") import traceback logger.debug(traceback.format_exc()) return False def main(): """命令行入口""" parser = argparse.ArgumentParser( description='从PDF文件中提取指定页面并保存为新PDF文件', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 示例: # 提取第1-5页和第7页 python pdf_extractor.py --input input.pdf --pages "1-5,7" --output output.pdf # 提取第1页到最后 python pdf_extractor.py --input input.pdf --pages "1-" --output output.pdf # 提取前10页 python pdf_extractor.py --input input.pdf --pages "-10" --output output.pdf # 提取单页 python pdf_extractor.py --input input.pdf --pages "3" --output output.pdf """ ) parser.add_argument( '--input', '-i', type=str, help='输入PDF文件路径' ) parser.add_argument( '--pages', '-p', type=str, required=True, help='要提取的页面范围,支持格式: "1-5,7,9-12", "1-", "-10", "3"' ) parser.add_argument( '--output', '-o', type=str, required=True, help='输出PDF文件路径' ) args = parser.parse_args() # 解析输入路径 input_path = Path(args.input).resolve() # 解析输出路径 output_path = Path(args.output).resolve() # 如果输出路径是目录,自动生成文件名 if output_path.is_dir() or not output_path.suffix.lower() == '.pdf': output_path = output_path / f"{input_path.stem}_extracted.pdf" # 检查 pypdfium2 是否可用 if not PDFIUM_AVAILABLE: logger.error("❌ pypdfium2 未安装,请先安装: pip install pypdfium2") sys.exit(1) # 先加载PDF获取总页数(用于验证页面范围) try: with open(input_path, 'rb') as f: pdf_bytes = f.read() pdf = pdfium.PdfDocument(pdf_bytes) total_pages = len(pdf) pdf.close() except Exception as e: logger.error(f"❌ 无法读取PDF文件: {e}") sys.exit(1) # 解析页面范围 page_set = PDFUtils.parse_page_range(args.pages, total_pages) page_indices = sorted(list(page_set)) if not page_indices: logger.error(f"❌ 页面范围 '{args.pages}' 没有匹配到任何有效页面(总页数: {total_pages})") sys.exit(1) logger.info(f"📋 PDF总页数: {total_pages}") logger.info(f"📋 要提取的页面: {args.pages} → {len(page_indices)} 页") # 执行提取 success = extract_pdf_pages(input_path, page_indices, output_path) if success: logger.info(f"✅ 提取完成!输出文件: {output_path}") sys.exit(0) else: logger.error("❌ 提取失败") sys.exit(1) if __name__ == '__main__': if len(sys.argv) == 1: # 没有命令行参数时,使用默认配置运行 print("ℹ️ 未提供命令行参数,使用默认配置运行...") # 默认配置 default_config = { "input": "/Users/zhch158/workspace/data/流水分析/施博深.pdf", "output": "/Users/zhch158/workspace/data/流水分析/施博深_extracted.pdf", # 页面范围(可选) "pages": "1-20", # 只处理前20页 # "pages": "1-3,5,7-10", # 处理指定页面 } # 构造参数 sys.argv = [sys.argv[0]] for key, value in default_config.items(): if isinstance(value, bool): if value: sys.argv.append(f"--{key}") else: sys.argv.extend([f"--{key}", str(value)]) sys.exit(main())