"""PDF文件读取和文本提取工具。 使用pypdf库实现PDF文件的读取、基本信息展示和文本内容提取。 支持通过命令行参数指定PDF文件路径和可选的页码范围。 """ import argparse import sys from typing import Optional import pypdf from pypdf import PdfReader class PDFOpener: """PDF文件打开和内容提取类。 封装了PDF读取、信息提取和文本提取的全部功能。 Attributes: file_path: PDF文件的路径。 start_page: 提取文本的起始页码(从0开始,包含)。 end_page: 提取文本的结束页码(从0开始,包含),None表示最后一页。 """ def __init__( self, file_path: str, start_page: int = 0, end_page: Optional[int] = None, ) -> None: """初始化PDFOpener实例。 Args: file_path: PDF文件的路径。 start_page: 提取文本的起始页码(从0开始),默认为0。 end_page: 提取文本的结束页码(从0开始),默认为None(最后一页)。 """ self._file_path: str = file_path self._start_page: int = start_page self._end_page: Optional[int] = end_page def open(self) -> PdfReader: """打开PDF文件并返回PdfReader对象。 处理文件不存在、格式错误、权限不足等常见异常, 遇到异常时输出友好错误信息并退出程序。 Returns: pypdf.PdfReader对象。 Raises: SystemExit: 当文件不存在、格式错误或权限不足时退出程序。 """ try: reader = PdfReader(self._file_path) return reader except FileNotFoundError: print( f"错误:文件未找到,请检查路径是否正确:'{self._file_path}'", file=sys.stderr, ) sys.exit(1) except pypdf.errors.PdfReadError as e: print( f"错误:无法读取PDF文件,文件可能已损坏或格式不正确:{e}", file=sys.stderr, ) sys.exit(1) except PermissionError: print( f"错误:没有权限读取文件:'{self._file_path}'", file=sys.stderr, ) sys.exit(1) def get_info(self, reader: PdfReader) -> dict: """获取PDF文件的基本信息。 Args: reader: pypdf.PdfReader对象。 Returns: 包含PDF基本信息的字典,包括总页数和元数据。 """ num_pages: int = len(reader.pages) metadata: Optional[pypdf.DocumentInformation] = reader.metadata info: dict = { "num_pages": num_pages, "title": metadata.title if metadata and metadata.title else "未知", "author": metadata.author if metadata and metadata.author else "未知", "subject": metadata.subject if metadata and metadata.subject else "未知", "creator": metadata.creator if metadata and metadata.creator else "未知", "producer": metadata.producer if metadata and metadata.producer else "未知", "creation_date": ( metadata.creation_date if metadata and metadata.creation_date else "未知" ), } return info def display_info(self, info: dict) -> None: """将PDF基本信息格式化输出到控制台。 Args: info: 包含PDF基本信息的字典。 """ separator: str = "=" * 50 print(separator) print("PDF 文件基本信息") print(separator) print(f" 总页数 : {info.get('num_pages', '未知')}") print(f" 标题 : {info.get('title', '未知')}") print(f" 作者 : {info.get('author', '未知')}") print(f" 主题 : {info.get('subject', '未知')}") print(f" 创建工具 : {info.get('creator', '未知')}") print(f" 生成工具 : {info.get('producer', '未知')}") print(f" 创建日期 : {info.get('creation_date', '未知')}") print(separator) print() def extract_text(self, reader: PdfReader) -> list[str]: """按页码范围逐页提取PDF文本内容。 Args: reader: pypdf.PdfReader对象。 Returns: 每页文本内容组成的列表,列表索引对应页码偏移。 """ num_pages: int = len(reader.pages) # 确定实际的起始和结束页码(基于0的索引) actual_start: int = max(0, self._start_page) actual_end: int = ( num_pages - 1 if self._end_page is None else min(self._end_page, num_pages - 1) ) if actual_start > actual_end: print( f"警告:起始页码 ({actual_start + 1}) 大于结束页码 ({actual_end + 1})," f"将不提取任何文本。", file=sys.stderr, ) return [] texts: list[str] = [] for page_index in range(actual_start, actual_end + 1): page = reader.pages[page_index] page_text: str = page.extract_text() or "" texts.append(page_text) return texts def display_text(self, texts: list[str]) -> None: """将提取的文本内容格式化输出到控制台。 Args: texts: 每页文本内容组成的列表。 """ if not texts: print("未提取到任何文本内容。") return separator: str = "-" * 50 actual_start_display: int = self._start_page + 1 # 转换为1-based显示 for i, text in enumerate(texts): page_number: int = actual_start_display + i print(f"【第 {page_number} 页】") print(separator) if text.strip(): print(text) else: print("(本页无可提取的文本内容)") print(separator) print() def run(self) -> None: """主流程入口,依次执行PDF读取、信息展示和文本提取。 按顺序调用 open、get_info、display_info、extract_text、display_text。 """ reader: PdfReader = self.open() info: dict = self.get_info(reader) self.display_info(info) texts: list[str] = self.extract_text(reader) self.display_text(texts) def parse_args() -> argparse.Namespace: """解析命令行参数。 Returns: 包含解析后参数的Namespace对象: - file_path: PDF文件路径(必填) - start_page: 起始页码,1-based(可选,默认为1) - end_page: 结束页码,1-based(可选,默认为None表示最后一页) """ parser = argparse.ArgumentParser( description="PDF文件读取和文本提取工具", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "示例用法:\n" " python main.py document.pdf\n" " python main.py document.pdf --start-page 2 --end-page 5\n" " python main.py document.pdf --start-page 3\n" ), ) parser.add_argument( "file_path", type=str, help="PDF文件的路径(必填)", ) parser.add_argument( "--start-page", type=int, default=1, dest="start_page", metavar="N", help="提取文本的起始页码(从1开始,默认为1)", ) parser.add_argument( "--end-page", type=int, default=None, dest="end_page", metavar="N", help="提取文本的结束页码(从1开始,默认为最后一页)", ) args: argparse.Namespace = parser.parse_args() # 验证页码参数合法性 if args.start_page < 1: parser.error("--start-page 必须大于等于1") if args.end_page is not None and args.end_page < 1: parser.error("--end-page 必须大于等于1") if args.end_page is not None and args.start_page > args.end_page: parser.error("--start-page 不能大于 --end-page") return args def main() -> None: """程序主入口函数。 解析命令行参数,创建PDFOpener实例并执行主流程。 """ args: argparse.Namespace = parse_args() # 将1-based的用户输入页码转换为0-based的内部索引 start_page_index: int = args.start_page - 1 end_page_index: Optional[int] = ( args.end_page - 1 if args.end_page is not None else None ) pdf_opener = PDFOpener( file_path=args.file_path, start_page=start_page_index, end_page=end_page_index, ) pdf_opener.run() if __name__ == "__main__": main()