| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- #!/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())
|