使用方法
python pdf_compressor.py -i ".\11" -o ".\12"
或者下载exe版本pdf_compressor.exe -i ".\11" -o ".\12"
其中11这个文件夹是原PDF所在目录,12是压缩后PDF所在目录,文件夹你们也可以用别的,意思对了就行
EXE版本在这里下载
pdf_compressor.py内容如下
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PDF压缩工具
功能:将PDF文件压缩至900KB以内
"""
import os
import sys
import argparse
from tqdm import tqdm
from PIL import Image
import io
import tempfile
def compress_pdf(input_path, output_path, target_size_kb=900):
"""
压缩PDF文件至目标大小以下
Args:
input_path (str): 输入PDF文件路径
output_path (str): 输出PDF文件路径
target_size_kb (int): 目标文件大小(KB)
Returns:
bool: 压缩是否成功
"""
try:
# 检查输入文件是否存在
if not os.path.exists(input_path):
print(f"错误:找不到输入文件 {input_path}")
return False
# 检查文件类型是否为PDF
if not input_path.lower().endswith('.pdf'):
print(f"错误:文件 {input_path} 不是PDF文件")
return False
# 获取原始文件大小
original_size = os.path.getsize(input_path) / 1024 # KB
print(f"原始文件大小: {original_size:.2f} KB")
# 如果原始文件已经小于目标大小,直接复制
if original_size <= target_size_kb:
print(f"文件已经小于目标大小,无需压缩")
import shutil
shutil.copy2(input_path, output_path)
return True
# 压缩策略1: 使用PyPDF2进行基础压缩
try:
from PyPDF2 import PdfReader, PdfWriter
reader = PdfReader(input_path)
writer = PdfWriter()
# 添加所有页面并压缩内容流
for page in tqdm(reader.pages, desc="基础压缩处理"):
page.compress_content_streams() # 压缩页面内容
writer.add_page(page)
# 写入临时文件
temp_output = output_path + ".temp"
with open(temp_output, "wb") as f:
writer.write(f)
# 检查压缩后大小
compressed_size = os.path.getsize(temp_output) / 1024 # KB
if compressed_size <= target_size_kb:
os.replace(temp_output, output_path)
print(f"PyPDF2压缩成功!压缩后大小: {compressed_size:.2f} KB")
return True
else:
print(f"PyPDF2压缩后大小: {compressed_size:.2f} KB,继续尝试更高级压缩")
os.remove(temp_output)
except Exception as e:
print(f"PyPDF2压缩失败: {e}")
# 压缩策略2: 使用pikepdf进行更高级压缩
try:
import pikepdf
from pikepdf import Pdf
# 尝试不同级别的压缩
compression_strategies = [
# 策略1: 基本压缩
{'desc': '基本压缩', 'compress_streams': True},
# 策略2: 中等压缩
{'desc': '中等压缩', 'compress_streams': True, 'normalize_content': True},
# 策略3: 高级压缩
{'desc': '高级压缩', 'compress_streams': True, 'object_stream_mode': 'generate'},
]
for strategy in compression_strategies:
print(f"尝试{pikepdf.__name__} {strategy['desc']}")
pdf = pikepdf.open(input_path)
# 简化pikepdf参数,避免不支持的参数
pdf.save(output_path, compress_streams=True) # 仅使用基础压缩参数
compressed_size = os.path.getsize(output_path) / 1024 # KB
if compressed_size <= target_size_kb:
print(f"压缩成功!压缩后大小: {compressed_size:.2f} KB")
return True
else:
print(f"当前压缩后大小: {compressed_size:.2f} KB,继续尝试...")
except Exception as e:
print(f"pikepdf压缩失败: {e}")
# 压缩策略3: 使用ghostscript进行强力压缩(如果可用)
try:
import subprocess
import shutil
# 检查ghostscript是否安装
gs_path = shutil.which('gswin64c') or shutil.which('gswin32c') or shutil.which('gs')
if gs_path:
print("尝试使用Ghostscript进行强力压缩")
# 定义不同的压缩级别
gs_levels = [
{'desc': '屏幕质量', 'settings': ['-dPDFSETTINGS=/screen', '-dDownsampleColorImages=true', '-dColorImageResolution=72']},
{'desc': ' ebook质量', 'settings': ['-dPDFSETTINGS=/ebook', '-dDownsampleColorImages=true', '-dColorImageResolution=150']},
{'desc': '打印机质量', 'settings': ['-dPDFSETTINGS=/printer', '-dDownsampleColorImages=true', '-dColorImageResolution=300']},
{'desc': '最低质量', 'settings': ['-dPDFSETTINGS=/screen', '-dDownsampleColorImages=true', '-dColorImageResolution=30', '-dCompressFonts=true']},
]
for level in gs_levels:
print(f"尝试Ghostscript {level['desc']}压缩")
# 构建命令
cmd = [
gs_path,
'-sDEVICE=pdfwrite',
'-dNOPAUSE',
'-dBATCH',
'-dQUIET',
'-dCompatibilityLevel=1.4',
'-dEmbedAllFonts=true',
'-dSubsetFonts=true',
'-sOutputFile=' + output_path,
] + level['settings'] + [input_path]
# 执行命令
try:
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
compressed_size = os.path.getsize(output_path) / 1024 # KB
if compressed_size <= target_size_kb:
print(f"Ghostscript压缩成功!压缩后大小: {compressed_size:.2f} KB")
return True
else:
print(f"Ghostscript当前压缩后大小: {compressed_size:.2f} KB,继续尝试...")
except subprocess.CalledProcessError as e:
print(f"Ghostscript命令执行失败: {e}")
else:
print("未找到Ghostscript,跳过此压缩方法")
print("提示:安装Ghostscript可获得更好的压缩效果: https://www.ghostscript.com/")
except Exception as e:
print(f"Ghostscript压缩过程出错: {e}")
# 图像压缩函数
def compress_image(img_data, quality=85):
"""压缩图像数据,降低质量和分辨率"""
try:
# 打开图像
img = Image.open(io.BytesIO(img_data))
# 根据质量级别调整不同的压缩策略
if quality <= 30: # 低质量
max_size = 768 # 最大边长
elif quality <= 50: # 中等质量
max_size = 896 # 最大边长
else: # 高质量
max_size = 1024 # 最大边长
# 如果图像很大,降低分辨率
if max(img.size) > max_size:
ratio = max_size / max(img.size)
new_size = tuple(int(dim * ratio) for dim in img.size)
img = img.resize(new_size, Image.LANCZOS)
# 保存为压缩后的图像,使用更高效的压缩选项
output = io.BytesIO()
img_format = img.format if img.format else 'JPEG'
# 针对不同质量级别使用不同的压缩参数
if quality <= 20: # 最低质量,使用最高压缩率
img.save(output, format=img_format, quality=quality, optimize=True, progressive=True)
else: # 其他质量级别
img.save(output, format=img_format, quality=quality, optimize=True)
# 获取压缩后的数据
compressed_data = output.getvalue()
# 如果是彩色图像,尝试转换为RGB模式以减少颜色空间
if img.mode == 'RGBA' and quality <= 50:
# 转换为RGB(忽略透明度)
img_rgb = img.convert('RGB')
output_rgb = io.BytesIO()
img_rgb.save(output_rgb, format='JPEG', quality=quality, optimize=True)
rgb_data = output_rgb.getvalue()
# 如果转换后更小,使用RGB版本
if len(rgb_data) < len(compressed_data):
compressed_data = rgb_data
return compressed_data
except Exception:
# 如果压缩失败,返回原始数据
return img_data
# 压缩策略4: 作为最后手段,尝试非常激进的压缩(使用PIL深度压缩图像)
try:
print("尝试最终激进压缩方案...")
# 这个方法会尝试提取并压缩所有图像,然后重建PDF
with open(input_path, 'rb') as file:
from PyPDF2 import PdfReader, PdfWriter
reader = PdfReader(file)
writer = PdfWriter()
# 压缩级别递减的质量参数
quality_levels = [70, 50, 30]
success = False
for quality in quality_levels:
writer = PdfWriter() # 重置writer
# 复制所有页面并尝试压缩图像
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
# 不再移除可能包含重要功能的属性
# 保留页面的交互功能和脚本
# 尝试处理页面中的图像
resources = page.get('/Resources', {})
if '/XObject' in resources:
xobjects = resources['/XObject'].get_object()
if xobjects:
for obj_name in list(xobjects.keys()):
obj = xobjects[obj_name]
if obj.get('/Subtype') == '/Image':
try:
# 尝试提取和压缩图像数据
if '/Filter' in obj:
# 获取图像数据
img_data = obj._data if hasattr(obj, '_data') else b''
# 压缩图像
compressed_data = compress_image(img_data, quality=quality)
# 替换图像数据
if hasattr(obj, '_data'):
obj._data = compressed_data
except Exception:
# 如果处理失败,跳过此图像
continue
writer.add_page(page)
# 保存当前质量级别的压缩结果
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
temp_path = temp_file.name
writer.write(temp_file)
# 检查当前压缩结果大小
current_size = os.path.getsize(temp_path) / 1024
print(f"尝试质量级别 {quality}%: {current_size:.2f} KB")
if current_size <= target_size_kb:
# 如果达到目标大小,复制到最终输出
import shutil
shutil.copy2(temp_path, output_path)
os.unlink(temp_path) # 删除临时文件
success = True
break
else:
# 否则继续尝试更低的质量级别
os.unlink(temp_path) # 删除当前临时文件
# 如果所有质量级别都尝试过但还是太大,使用最低质量级别再次尝试
if not success:
print("所有质量级别都尝试过,使用最低质量级别(20%)进行最终压缩")
writer = PdfWriter()
for page_num in range(len(reader.pages)):
page = reader.pages[page_num]
# 尝试处理页面中的图像
resources = page.get('/Resources', {})
if '/XObject' in resources:
xobjects = resources['/XObject'].get_object()
if xobjects:
for obj_name in list(xobjects.keys()):
obj = xobjects[obj_name]
if obj.get('/Subtype') == '/Image':
try:
# 尝试提取和压缩图像数据
if '/Filter' in obj:
# 获取图像数据
img_data = obj._data if hasattr(obj, '_data') else b''
# 使用最低质量进行压缩
compressed_data = compress_image(img_data, quality=20)
# 替换图像数据
if hasattr(obj, '_data'):
obj._data = compressed_data
except Exception:
# 如果处理失败,跳过此图像
continue
writer.add_page(page)
# 保存最终压缩结果
with open(output_path, 'wb') as output_file:
writer.write(output_file)
else:
# 如果已成功,直接使用成功的临时文件
pass
# 检查最终大小
final_size = os.path.getsize(output_path) / 1024 # KB
print(f"最终压缩后大小: {final_size:.2f} KB")
if final_size <= target_size_kb:
print("激进压缩成功!")
return True
else:
print(f"警告:已尝试所有压缩方法,但文件仍大于目标大小")
print(f"建议:1. 安装Ghostscript以获得更好压缩\n2. 考虑将PDF拆分为多个文件\n3. 检查PDF是否包含大量高分辨率图片")
return True # 返回True因为文件已压缩,只是未达到理想大小
except Exception as e:
print(f"激进压缩失败: {e}")
# 回退到原始的激进压缩方法
try:
# 尝试使用pikepdf压缩图像资源
try:
import pikepdf
from pikepdf import Pdf
# 创建一个新的PDF,尝试降低图像质量
pdf = pikepdf.open(input_path)
# 尝试压缩所有图像资源
for i, page in enumerate(pdf.pages):
for name, obj in page.images.items():
try:
# 获取图像对象
img_obj = obj.obj
if img_obj.Subtype == pikepdf.Name('/Image'):
# 移除可能的高质量解码参数
if hasattr(img_obj, 'DecodeParms'):
img_obj.DecodeParms = pikepdf.Dictionary()
except Exception:
# 跳过无法处理的图像
continue
# 保存压缩后的PDF,使用压缩流
pdf.save(output_path, compress_streams=True)
except Exception:
# 如果pikepdf图像压缩失败,回退到PyPDF2方法
pass
# 检查压缩后大小,如果仍然太大,使用PyPDF2进行更激进的压缩
current_size = os.path.getsize(output_path) / 1024 if os.path.exists(output_path) else float('inf')
if current_size > target_size_kb:
# 使用PyPDF2进行保守压缩,保留所有重要内容
from PyPDF2 import PdfReader, PdfWriter
reader = PdfReader(input_path)
writer = PdfWriter()
# 添加所有页面内容,保留所有重要属性
for page in reader.pages:
# 压缩内容流但保留所有页面属性和功能
page.compress_content_streams()
writer.add_page(page)
# 写入最终文件
with open(output_path, "wb") as f:
writer.write(f)
# 检查最终大小
final_size = os.path.getsize(output_path) / 1024 # KB
print(f"回退方法压缩后大小: {final_size:.2f} KB")
if final_size <= target_size_kb:
print("回退方法压缩成功!")
return True
else:
print(f"警告:已尝试所有压缩方法,但文件仍大于目标大小")
print(f"建议:1. 安装Ghostscript以获得更好压缩\n2. 考虑将PDF拆分为多个文件\n3. 检查PDF是否包含大量高分辨率图片")
return True # 返回True因为文件已压缩,只是未达到理想大小
except Exception as inner_e:
print(f"回退压缩也失败: {inner_e}")
return False
# 如果所有方法都失败
print("所有压缩方法均失败")
return False
except Exception as e:
print(f"压缩过程中出错: {e}")
return False
def batch_compress_pdfs(input_folder, output_folder, target_size_kb=900):
"""
批量压缩文件夹中的所有PDF文件
Args:
input_folder (str): 输入文件夹路径
output_folder (str): 输出文件夹路径
target_size_kb (int): 目标文件大小(KB)
"""
# 创建输出文件夹
os.makedirs(output_folder, exist_ok=True)
# 获取所有PDF文件
pdf_files = [f for f in os.listdir(input_folder) if f.lower().endswith('.pdf')]
if not pdf_files:
print(f"错误:在文件夹 {input_folder} 中未找到PDF文件")
return
print(f"找到 {len(pdf_files)} 个PDF文件待处理")
print(f"目标文件夹: {output_folder}")
print("="*50)
# 初始化统计信息
success_count = 0
failed_count = 0
total_original_size = 0
total_compressed_size = 0
# 处理每个文件
with tqdm(total=len(pdf_files), desc="总体进度", unit="文件") as pbar:
for i, pdf_file in enumerate(pdf_files):
input_path = os.path.join(input_folder, pdf_file)
output_path = os.path.join(output_folder, pdf_file)
# 获取原始文件大小
original_size = os.path.getsize(input_path) / 1024 # KB
total_original_size += original_size
# 显示当前正在处理的文件信息
tqdm.write(f"\n[{i+1}/{len(pdf_files)}] 正在处理: {pdf_file}")
tqdm.write(f"原始大小: {original_size:.2f} KB")
# 执行压缩
if compress_pdf(input_path, output_path, target_size_kb):
success_count += 1
# 获取压缩后文件大小
if os.path.exists(output_path):
compressed_size = os.path.getsize(output_path) / 1024 # KB
total_compressed_size += compressed_size
# 计算压缩率
compression_ratio = (1 - compressed_size / original_size) * 100
tqdm.write(f"压缩状态: 成功 ✅ | 压缩率: {compression_ratio:.1f}%")
else:
tqdm.write(f"压缩状态: 成功 ✅ | 但输出文件不存在")
else:
failed_count += 1
tqdm.write(f"压缩状态: 失败 ❌")
# 更新进度条
pbar.update(1)
# 计算总体统计信息
print("\n" + "="*50)
print("批量压缩完成!")
print(f"成功: {success_count} 个文件")
print(f"失败: {failed_count} 个文件")
if total_original_size > 0:
total_compression_ratio = (1 - total_compressed_size / total_original_size) * 100
print(f"原始总大小: {total_original_size:.2f} KB")
print(f"压缩总大小: {total_compressed_size:.2f} KB")
print(f"总体压缩率: {total_compression_ratio:.1f}%")
print(f"节省空间: {total_original_size - total_compressed_size:.2f} KB")
print("="*50)
def main():
"""
主函数,处理命令行参数
"""
parser = argparse.ArgumentParser(description='PDF压缩工具 - 将PDF文件压缩至900KB以内')
parser.add_argument('-i', '--input', required=True, help='输入PDF文件或文件夹路径')
parser.add_argument('-o', '--output', help='输出路径(文件或文件夹)')
parser.add_argument('-s', '--size', type=int, default=900, help='目标文件大小(KB),默认900KB')
parser.add_argument('-f', '--force', action='store_true', help='强制使用所有可用的压缩方法')
args = parser.parse_args()
# 检查输入路径
if not os.path.exists(args.input):
print(f"错误:找不到输入路径 {args.input}")
sys.exit(1)
# 确定输出路径
if not args.output:
if os.path.isfile(args.input):
# 如果输入是文件,输出文件名添加_compressed后缀
base_name, ext = os.path.splitext(args.input)
args.output = f"{base_name}_compressed{ext}"
else:
# 如果输入是文件夹,输出到compressed_pdfs子文件夹
args.output = os.path.join(args.input, "compressed_pdfs")
# 显示压缩配置
print(f"压缩配置:")
print(f" 输入: {args.input}")
print(f" 输出: {args.output}")
print(f" 目标大小: {args.size} KB")
print()
# 执行压缩
if os.path.isfile(args.input):
# 单个文件压缩
compress_pdf(args.input, args.output, args.size)
else:
# 批量压缩
batch_compress_pdfs(args.input, args.output, args.size)
if __name__ == "__main__":
print("=== PDF压缩工具 ===")
print("功能:将PDF文件压缩至指定大小以下")
print("注意:请确保已安装必要的库: pip install PyPDF2 pikepdf tqdm")
'''
可选参数说明:
- -o/--output :指定输出路径(文件或文件夹)
- 如果不指定,对于单文件压缩,将在原文件名后添加_compressed后缀
- 对于批量压缩,将在输入文件夹下创建compressed_pdfs子文件夹
- -s/--size :指定目标文件大小(KB),默认900KB
- -f/--force :强制使用所有可用的压缩方法
'''
print()
main()