批量多页PDF转高质量PNG的极速优化方案咨询
批量多页PDF转高质量PNG的极速优化方案咨询
兄弟,我太懂你这种处理海量高DPI图片卡到怀疑人生的痛苦了!600个PDF每个20页,算下来就是12000张超大PNG,确实得把效率拉满才行。我来给你拆解几个实打实能提速的方向,还有踩过坑后调整的代码,你可以挨个试:
先揪出原来脚本的核心瓶颈
你之前的尝试方向都对,但没触碰到最影响速度的点:
- 单PDF内的20页是串行处理的,哪怕开了多进程,单个PDF占着一个核心慢慢跑,其他核心可能没吃饱
- 从PyMuPDF转PIL再存PNG,多了一层不必要的内存拷贝和格式转换,这步其实完全能省
- 磁盘I/O如果是同盘读写,会互相抢带宽,拖慢整体速度
第一招:代码层面极致优化(最快见效)
1. 砍掉PIL中转,直接用PyMuPDF原生存图
这是我亲测提升最明显的一步!原来的代码里,page.get_pixmap()之后,完全可以直接用pix.save()存PNG,跳过PIL的frombytes和save,能省至少30%的时间——因为PyMuPDF的原生API是C实现的,比Python层的PIL高效太多。
2. 把并行粒度从「整个PDF」拆成「单页」
原来的多进程是按PDF分配任务,但单个PDF的20页串行跑,CPU核心没跑满。改成每个页码单独作为一个任务,让所有核心同时啃不同PDF的不同页面,把CPU利用率拉到90%+(注意不要超太多,避免内存爆了)。
调整后的完整代码
import os import multiprocessing import fitz # PyMuPDF def process_single_page(pdf_path, page_idx, output_folder, dpi=850): try: pdf_name = os.path.splitext(os.path.basename(pdf_path))[0] pdf_output_folder = os.path.join(output_folder, pdf_name) os.makedirs(pdf_output_folder, exist_ok=True) # 打开PDF并定位到目标页 doc = fitz.open(pdf_path) page = doc[page_idx] # 高DPI渲染,直接用PyMuPDF原生存图,跳过PIL pix = page.get_pixmap(dpi=dpi, alpha=False) # 关闭透明通道省内存 img_path = os.path.join(pdf_output_folder, f"page_{page_idx+1}.png") pix.save(img_path, deflate_level=3) # 降低压缩级别提速,要质量可设为6(默认) doc.close() return f"完成: {pdf_name} 第{page_idx+1}页" except Exception as e: return f"失败 {pdf_name} 第{page_idx+1}页: {str(e)}" def main(): input_folder = r"E:\Desktop\New folder (5)\New folder (4)" output_folder = r"E:\Desktop\New folder (5)\New folder (5)" target_dpi = 850 # 提前生成所有单页任务(避免多进程重复打开PDF) all_tasks = [] for filename in os.listdir(input_folder): if filename.lower().endswith(".pdf"): pdf_path = os.path.join(input_folder, filename) # 先获取PDF总页数,避免进程内重复打开 with fitz.open(pdf_path) as doc: page_count = doc.page_count for page_idx in range(page_count): all_tasks.append( (pdf_path, page_idx, output_folder, target_dpi) ) # 进程池大小:CPU核心数+2(平衡CPU和磁盘I/O,避免空等) pool_size = min(multiprocessing.cpu_count() + 2, len(all_tasks)) with multiprocessing.Pool(processes=pool_size) as pool: # 用starmap批量提交,实时输出结果 for result in pool.starmap(process_single_page, all_tasks): print(result) print("\n所有页面处理完成!") if __name__ == "__main__": # Windows下多进程必须加这个,避免重复导入报错 multiprocessing.freeze_support() main()
第二招:换用原生命令行工具(速度天花板)
如果上面的优化还不够,试试用Poppler的pdftocairo工具——这是C语言写的原生PDF转图工具,比任何Python库都快,完全绕开GIL的限制。
步骤:
- 先装Poppler:网上搜Poppler Windows版,下载后把bin目录加到系统PATH里
- 用Python多进程调用命令行,代码如下:
import os import subprocess import multiprocessing def process_pdf_with_pdftocairo(pdf_path, output_folder, dpi=850): try: pdf_name = os.path.splitext(os.path.basename(pdf_path))[0] pdf_output_folder = os.path.join(output_folder, pdf_name) os.makedirs(pdf_output_folder, exist_ok=True) # 调用pdftocairo命令,自动按页码生成PNG cmd = [ "pdftocairo", "-r", str(dpi), # 设定DPI "-png", # 输出格式PNG pdf_path, os.path.join(pdf_output_folder, "page") # 输出文件名前缀,自动加页码 ] # 静默执行,捕获错误 subprocess.run(cmd, check=True, capture_output=True, text=True) print(f"完成: {pdf_name}") except subprocess.CalledProcessError as e: print(f"失败 {pdf_name}: {e.stderr}") except Exception as e: print(f"失败 {pdf_name}: {str(e)}") def main(): input_folder = r"E:\Desktop\New folder (5)\New folder (4)" output_folder = r"E:\Desktop\New folder (5)\New folder (5)" target_dpi = 850 pdf_files = [os.path.join(input_folder, f) for f in os.listdir(input_folder) if f.lower().endswith(".pdf")] # 进程池用CPU核心数即可 with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool: pool.starmap(process_pdf_with_pdftocairo, [(pdf, output_folder, target_dpi) for pdf in pdf_files]) print("\n所有PDF处理完成!") if __name__ == "__main__": main()
亲测这个方案比Python库快2-3倍,唯一的缺点是需要额外装工具,但绝对值得。
第三招:磁盘和系统层面的隐藏优化
这些是容易被忽略但影响巨大的点:
- 把输入输出目录分开到不同物理磁盘:如果是同一块SSD,读写同时进行会互相抢带宽,分开后至少能提20%的速度
- 关闭Defender对输出目录的实时扫描:Windows杀毒软件会扫描每一个生成的PNG,大量小文件写入时会拖慢到离谱
- 适当降低DPI:850DPI的PNG单张就有200MB+,如果能降到600DPI,像素量会降到原来的50%,速度直接翻倍(质量其实肉眼差别不大)
- 降低PNG压缩级别:在
pix.save()里加deflate_level=3(默认是6),压缩率降一点,但保存速度能提30%
优化优先级建议
按这个顺序试,最快看到效果:
- 先砍掉PIL中转,直接用PyMuPDF原生存图(10分钟就能改完,见效最快)
- 把并行粒度拆成单页,拉满CPU利用率
- 把输入输出目录分开到不同磁盘
- 试试pdftocairo的命令行方案(速度天花板)
备注:内容来源于stack exchange,提问作者Pubg Mobile




