from PyPDF2 import PdfReader, PdfWriter import re # 导入正则表达式库 import os # 用于文件和文件夹操作 def clean_page_content(text, common_header): """ 清理页面内容,通过删除公共抬头和页码。 """ if common_header: for header_line in common_header.split('\n'): if header_line.strip(): # 替换首次出现的完整行 text = re.sub(r'^' + re.escape(header_line.strip()) + r'\n?', '', text, count=1) # 删除页码,支持多种格式 text = re.sub(r'^\s*\d+\s*(?=\D)', '', text) # 删除开头的页码,仅当紧跟非数字字符时 text = re.sub(r'\s+\d+\s*$', '', text) # 删除结尾的页码 text = re.sub(r'\s*\/\s*\d+\s*', '', text) # 删除形如 /129 的页码 text = re.sub(r'\s*[—-]\s*\d+\s*[—-]\s*', '', text) # 删除形如 '—2—' 或 '-2-' 的页码 return text def extract_common_header(pdf_path): """ 从PDF的前几页提取公共抬头。 """ pdf_document = PdfReader(pdf_path) headers = [] total_pages = len(pdf_document.pages) # 确定要读取的页数和起始页 pages_to_read = 2 if total_pages == 2 else 3 start_page = 0 if total_pages == 2 else max(0, (total_pages // 2) - 1) for i in range(start_page, min(start_page + pages_to_read, total_pages)): page = pdf_document.pages[i] text = page.extract_text() or "" if text: # 只取每页的前三行 first_lines = text.strip().split('\n')[:3] headers.append(first_lines) if len(headers) < 2: return "" # 如果没有足够的页来比较,返回空字符串 # 寻找每一行中的公共部分,按顺序保留 common_headers = [] for lines in zip(*headers): first_words = lines[0].split() common_line = [word for word in first_words if all(word in line.split() for line in lines[1:])] if common_line: common_headers.append(' '.join(common_line)) return '\n'.join(common_headers) def is_pdf_or_doc(filename): """ 判断文件是否为PDF或Word文档。 """ return filename.lower().endswith(('.pdf', '.doc', '.docx')) # def convert_to_pdf(file_path): # """ # 如果是Word文档,则转换为PDF。 # """ # if file_path.lower().endswith(('.doc', '.docx')): # return docx2pdf(file_path) # return file_path def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix): """ 将提取的页面保存为新的PDF文件,统一命名为 "bid_format.pdf" 并存放在独立的子文件夹中。 """ try: output_pdf_path = os.path.join(output_folder, "bid_format.pdf") # 检查 start_page 和 end_page 是否为 None if start_page is None or end_page is None: print(f"起始页或结束页为 None: start_page={start_page}, end_page={end_page} 文件: {pdf_path}") return "" if start_page < 0 or end_page >= len(pdf_document.pages) or start_page > end_page: print(f"无效的页面范围: {start_page} 到 {end_page} 文件: {pdf_path}") return "" output_doc = PdfWriter() for page_num in range(start_page, end_page + 1): output_doc.add_page(pdf_document.pages[page_num]) with open(output_pdf_path, 'wb') as f: output_doc.write(f) print(f"{output_suffix} 已截取并保存页面从 {start_page} 到 {end_page} 为 {output_pdf_path}") return output_pdf_path except Exception as e: print(f"Error in save_extracted_pages for file {pdf_path}: {e}") return "" # 返回空字符串 def extract_pages_generic(pdf_document, begin_pattern, begin_page, common_header, total_pages): """ 通用函数,根据开始模式提取起始页,并将结束页设为文件末尾。 参数: - pdf_document: PdfReader 对象。 - begin_pattern: 用于匹配起始页的正则模式。 - begin_page: 起始页的页面索引下限。 - common_header: 公共页头,用于清理页面内容。 - total_pages: PDF文档的总页数(索引从0开始)。 返回: - start_page: 匹配到的起始页的页面索引。 - end_page: 结束页的页面索引(设为文件末尾)。 """ start_page = None end_page = total_pages # 结束页设为文件末尾 for i in range(total_pages, -1, -1): page = pdf_document.pages[i] text = page.extract_text() or "" cleaned_text = clean_page_content(text, common_header) # print(cleaned_text) # 可根据需要取消注释以调试 if re.search(begin_pattern, cleaned_text) and i > begin_page: start_page = i print(f"找到起始页: 页码 {i + 1}") # 页码通常从1开始 break # 找到起始页后退出循环 return start_page, end_page def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header): """ 二次提取PDF页面,仅根据开始模式从后往前提取。 参数: - pdf_path: PDF文件的路径。 - output_folder: 提取后页面的输出文件夹。 - output_suffix: 输出文件的后缀名。 - common_header: 公共页头,用于清理页面内容。 返回: - 提取的页面保存结果,或空字符串表示失败。 """ try: pdf_document = PdfReader(pdf_path) total_pages = len(pdf_document.pages) - 1 # 获取总页数(索引从0开始) begin_page = 10 # 起始页的页面索引下限 start_page = None end_page = total_pages # 结束页设为文件末尾 # 定义开始模式列表 begin_patterns = [ re.compile(r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:响应|投标).*?格式.*', re.MULTILINE), re.compile(r"\s*(投标文件格式|响应文件格式|响应性文件格式)\s*") ] # 定义排除模式 exclusion_pattern = re.compile(r'文件的构成|文件的组成|目录|评标办法|技术标准|其他材料|合同') # 遍历每个开始模式 for begin_pattern in begin_patterns: # 从后往前遍历页面 for i in range(total_pages, -1, -1): page = pdf_document.pages[i] text = page.extract_text() or "" cleaned_text = clean_page_content(text, common_header) # 如果页面内容匹配排除模式,则跳过 if exclusion_pattern and re.search(exclusion_pattern, cleaned_text): continue # 检查是否匹配开始模式,并且页面索引大于 begin_page if re.search(begin_pattern, cleaned_text) and i > begin_page: start_page = i print(f"二次提取找到起始页: 页码 {i + 1}") # 页码通常从1开始 break # 找到起始页后退出循环 if start_page is not None: break # 如果找到起始页,退出外层循环 if start_page is None: print(f"{output_suffix}: 未找到起始页,二次提取失败! 文件: {pdf_path}") return "" # end_page 已经设为文件末尾 return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix) except Exception as e: print(f"Error processing {pdf_path} in extract_pages_twice: {e}") return "" def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, output_suffix): """ 根据开始模式从后往前提取PDF页面。 参数: - pdf_path: PDF文件的路径。 - output_folder: 提取后页面的输出文件夹。 - begin_pattern: 用于匹配起始页的正则模式。 - begin_page: 起始页的页面索引下限。 - output_suffix: 输出文件的后缀名。 """ try: common_header = extract_common_header(pdf_path) pdf_document = PdfReader(pdf_path) total_pages = len(pdf_document.pages) - 1 # 获取总页数(索引从0开始) # 提取起始页,结束页直接设为文件末尾 start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, begin_page, common_header, total_pages) if output_suffix == "format": if start_page is None: print(f"{output_suffix}: 未找到起始页,提取失败! 文件: {pdf_path} 尝试二次提取!") return extract_pages_twice(pdf_path, output_folder, output_suffix, common_header) # end_page 已经设为文件末尾 return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix) # 如果 output_suffix 不是 "format",同样保存提取的页面 return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix) except Exception as e: print(f"Error processing {pdf_path}: {e}") return "" def process_files(file_path, output_folder, begin_pattern, begin_page, output_suffix): """ 处理单个文件:转换为PDF(如果需要)并提取页面。 """ # pdf_path = convert_to_pdf(file_path) pdf_path=file_path result = extract_pages(pdf_path, output_folder, begin_pattern, begin_page, output_suffix) return result or "" def process_input(input_path, output_folder, begin_pattern, begin_page,output_suffix): """ 处理输入路径,可以是文件或目录。 """ if not os.path.exists(output_folder): os.makedirs(output_folder) generated_files = [] if os.path.isdir(input_path): for file_name in os.listdir(input_path): file_path = os.path.join(input_path, file_name) if is_pdf_or_doc(file_path): result = process_files(file_path, output_folder, begin_pattern, begin_page, output_suffix) if isinstance(result, tuple): generated_files.extend([f if f else "" for f in result]) # 保留空字符串 else: generated_files.append(result) # 直接添加result,可能是空字符串 elif os.path.isfile(input_path) and is_pdf_or_doc(input_path): result = process_files(input_path, output_folder, begin_pattern, begin_page, output_suffix) if isinstance(result, tuple): generated_files.extend([f if f else "" for f in result]) # 保留空字符串 else: generated_files.append(result) # 直接添加result,可能是空字符串 else: print("提供的路径既不是文件夹也不是PDF文件。") return generated_files def truncate_pdf_main(input_path, output_folder, selection,begin_page=10): """ 主函数,根据选择截取PDF内容。 """ try: if selection == 1: # 投标文件格式 begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:响应|投标).*?格式.*' ) # end_pattern = re.compile( # r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE # ) local_output_suffix = "format" else: print("无效的选择:请选择1") return None # 调用相应的处理函数 return process_input(input_path, output_folder, begin_pattern, begin_page, local_output_suffix) or "" except Exception as e: print(f"Error in truncate_pdf_main: {e}") return "" # 返回空字符串 if __name__ == "__main__": # 定义输入和输出路径 input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles" # input_path="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest13.pdf" output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\新建文件夹" selection = 1 # 1 - 投标文件格式 # 执行截取 generated_files = truncate_pdf_main(input_path, output_folder, selection) print("生成的文件:", generated_files)