from pathlib import Pathimport reimport fitz # PyMuPDFdef redact_numbers_with_overlay(input_path, output_path): doc = fitz.open(input_path) # 列表元素为元组(正则表达式, 保留的前部长度, 保留的后缀长度) re_rules = [ (re.compile(r"\d{17}[\dXx]"), 10, 4), # 身份证号(18位):保留前10,保留后4,中间8位覆盖 (re.compile(r"1\d{10}"), 3, 4), # 手机号(11位):保留前3,保留后4,中间4位覆盖 #(re.compile(r"\d{10,11}"), 3, 4), # 固定电话,保留前3,保留后4,中间覆盖 ] for page in doc: # 获取页面所有文本的详细信息(包含每个字符的位置) text_instances = page.get_text("words") for inst in text_instances: x0, y0, x1, y1, word, block_no, line_no, span_no = inst # 检查当前单词是否符合脱敏规则 for pattern, keep_pre, keep_suf in re_rules: if pattern.fullmatch(word): total_len = len(word) # 计算需要覆盖的星号数量和位置 if total_len <= keep_pre + keep_suf: continue # 长度过短,无需脱敏 # 计算中间部分的宽度(基于原文字符宽度均分) char_width = (x1 - x0) / total_len # 覆盖区域的起始X:前导文字的宽度 cover_x0 = x0 + keep_pre * char_width # 覆盖区域的结束X:总宽度 - 后缀文字的宽度 cover_x1 = x1 - keep_suf * char_width # 绘制与原字符同高的黑色矩形,背景设为白色(匹配纸张),边框设为无,完全无缝融合 rect = fitz.Rect(cover_x0, y0 - 1, cover_x1, y1 + 1) page.draw_rect(rect, color=(1, 1, 1), fill=(1, 1, 1), width=0) # 精准写入星号:按原字符宽度均分,字距与原有格式一致 star_count = total_len - keep_pre - keep_suf star_text = "*" * star_count # 星号的起始位置(与原中间位置对齐) star_x = cover_x0 + 1 # 微调1像素防止溢出 star_y = y1 - 2 # 与原数字基线对齐 # 使用小号字体确保星号紧凑,匹配表格字距 page.insert_text( point=(star_x, star_y), text=star_text, fontsize=6, # 字号 fontname="china-s", # 字体 color=(0, 0, 0), # 黑色 ) break # 匹配一个规则后立即退出,避免重复处理 # 保存,文件体积不变 doc.save(output_path, garbage=4, deflate=True, clean=True) doc.close()def batch_process_pdf(input_folder, output_folder): input_folder = Path(input_folder) output_folder = Path(output_folder) if not input_folder.exists(): print(f"输入文件夹不存在:{input_folder}") return if not output_folder.exists(): output_folder.mkdir(parents=True,exist_ok=True) print(f"创建输出文件夹:{output_folder}") pdf_files = [f for f in input_folder.glob("*.pdf")] if not pdf_files: print("未找到PDF文件") return for filename in pdf_files: name_new = f"{filename.stem}_脱敏.pdf" file_new = output_folder.joinpath(name_new) try: redact_numbers_with_overlay(filename, file_new) print(f"处理完成:{filename}") except Exception as e: print(f"处理失败:{filename}:{str(e)}")if __name__ == "__main__": input_folder = r"D:\pdf" # PDF文件夹路径 output_folder = r"D:\pdf_output" # 输出文件夹路径 batch_process_pdf(input_folder, output_folder)