PDF与图片互转WEB应用开发教程

PDF与图片互转WEB应用开发教程

目录


项目概述

功能需求

本项目是一个基于 Streamlit 框架的 Web 应用程序,实现 PDF 文件与图片文件之间的相互转换功能。

功能模块 描述
文件上传模块 支持用户上传 PDF 文件和常见格式的图片文件(JPG、PNG、BMP、GIF、TIFF)
PDF转图片功能 将上传的 PDF 文件逐页转换为图片,支持选择输出图片格式和质量设置
图片转PDF功能 将多张上传的图片按顺序合并转换为单个 PDF 文件,支持调整图片排序和页面方向设置
文件下载模块 提供转换后文件的下载功能,支持批量下载
错误处理 文件格式验证、文件大小限制(单文件不超过200MB)、转换失败提示

项目结构

复制代码
pdf和图片互转/
├── venv/                    # Python虚拟环境
├── app.py                   # 主应用程序
├── requirements.txt         # 依赖清单
├── 启动应用.bat             # Windows启动脚本
└── 开发教程.md              # 开发文档

技术架构

技术栈

技术 用途 版本要求
Python 开发语言 >= 3.8
Streamlit Web框架 >= 1.28.0
pdf2image PDF转图片 >= 1.16.0
Pillow 图片处理 >= 10.0.0
img2pdf 图片转PDF >= 0.5.0
Poppler PDF渲染引擎 外部依赖

架构图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    Streamlit Web 界面                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────┐    ┌─────────────────┐                 │
│  │  PDF转图片模块   │    │  图片转PDF模块   │                 │
│  └────────┬────────┘    └────────┬────────┘                 │
│           │                      │                          │
│           ▼                      ▼                          │
│  ┌─────────────────┐    ┌─────────────────┐                 │
│  │   pdf2image     │    │    img2pdf      │                 │
│  │   (包装器)       │    │   (纯Python)    │                 │
│  └────────┬────────┘    └─────────────────┘                 │
│           │                                                  │
│           ▼                                                  │
│  ┌─────────────────┐                                        │
│  │    Poppler      │  ← 需要外部安装                         │
│  │  (PDF渲染引擎)   │                                        │
│  └─────────────────┘                                        │
└─────────────────────────────────────────────────────────────┘

环境搭建

步骤1:创建虚拟环境

powershell 复制代码
# 进入项目目录
cd d:\原创\自研软件\pdf和图片互转

# 创建虚拟环境
python -m venv venv

步骤2:激活虚拟环境

Windows PowerShell:

powershell 复制代码
.\venv\Scripts\Activate

Windows CMD:

cmd 复制代码
.\venv\Scripts\activate.bat

Linux/Mac:

bash 复制代码
source venv/bin/activate

步骤3:安装依赖

powershell 复制代码
# 安装核心依赖
pip install streamlit pdf2image Pillow img2pdf

# 或使用 requirements.txt
pip install -r requirements.txt

requirements.txt 内容:

复制代码
streamlit>=1.28.0
pdf2image>=1.16.0
Pillow>=10.0.0
img2pdf>=0.5.0

步骤4:安装 Poppler(PDF转图片必需)

方法1:使用 Conda(推荐)
powershell 复制代码
# 如果已安装 Anaconda 或 Miniconda
conda install -c conda-forge poppler
方法2:手动安装(Windows)
  1. poppler-windows releases 下载最新版本
  2. 解压到指定目录,如 C:\Program Files\poppler
  3. bin 目录添加到系统 PATH 环境变量:
    • 右键"此电脑" → 属性 → 高级系统设置 → 环境变量
    • 在"系统变量"中找到 Path,点击编辑
    • 添加路径:C:\Program Files\poppler\Library\bin
方法3:使用 Chocolatey(Windows包管理器)
powershell 复制代码
choco install poppler

步骤5:验证安装

powershell 复制代码
# 验证 pdftoppm 命令
pdftoppm -v

# 预期输出:
# pdftoppm version 24.xx.x
# Copyright 2005-2024 The Poppler Developers

步骤6:运行应用

方式1:命令行启动

powershell 复制代码
streamlit run app.py

方式2:使用启动脚本(Windows)

双击运行 启动应用.bat 文件,脚本内容如下:

batch 复制代码
@echo off
chcp 65001 >nul 2>&1
title PDF Image Converter

echo ========================================
echo    PDF Image Converter Starting...
echo ========================================
echo.

cd /d "%~dp0"

if not exist "venv\Scripts\python.exe" (
    echo [ERROR] Virtual environment not found!
    echo Please run: python -m venv venv
    echo.
    pause
    exit /b 1
)

echo [INFO] Activating virtual environment...
call venv\Scripts\activate.bat

echo [INFO] Starting Streamlit app...
echo [INFO] Browser will open http://localhost:8501
echo.
echo [TIP] Press Ctrl+C to stop
echo ========================================
echo.

venv\Scripts\python.exe -m streamlit run app.py

pause

注意事项

  • 脚本使用英文提示,避免中文乱码问题
  • 直接使用 venv\Scripts\python.exe 运行,确保使用虚拟环境的Python
  • 不要使用 streamlit run app.py,因为激活虚拟环境后可能仍使用系统Python

应用将在浏览器中打开:http://localhost:8501


核心功能实现

1. 文件验证模块

python 复制代码
MAX_FILE_SIZE = 200 * 1024 * 1024  # 200MB
ALLOWED_IMAGE_TYPES = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff']

def validate_file_size(file_size: int, file_name: str) -> Tuple[bool, str]:
    """验证文件大小"""
    if file_size > MAX_FILE_SIZE:
        return False, f"文件 '{file_name}' 大小超过200MB限制"
    return True, ""

def validate_file_type(file_name: str, allowed_types: List[str]) -> Tuple[bool, str]:
    """验证文件类型"""
    file_ext = file_name.lower().split('.')[-1] if '.' in file_name else ''
    if file_ext not in allowed_types:
        return False, f"文件格式不支持。支持的格式: {', '.join(allowed_types)}"
    return True, ""

2. PDF转图片功能

python 复制代码
from pdf2image import convert_from_bytes
from PIL import Image
import io

@st.cache_data
def convert_pdf_to_images(pdf_bytes: bytes, output_format: str, quality: int, dpi: int):
    """
    将PDF转换为图片列表
    
    参数:
        pdf_bytes: PDF文件的字节数据
        output_format: 输出格式 ('png' 或 'jpg')
        quality: 图片质量 (10-100)
        dpi: 分辨率 (72-300)
    """
    images = convert_from_bytes(
        pdf_bytes,
        dpi=dpi,
        fmt=output_format.lower(),
        thread_count=4  # 多线程加速
    )
    return images

def image_to_bytes(image: Image.Image, format: str, quality: int) -> bytes:
    """将PIL Image对象转换为字节数据"""
    img_byte_arr = io.BytesIO()
    save_format = 'JPEG' if format.lower() in ['jpg', 'jpeg'] else format.upper()
    if save_format == 'JPEG':
        image = image.convert('RGB')  # JPEG不支持透明通道
    image.save(img_byte_arr, format=save_format, quality=quality, optimize=True)
    return img_byte_arr.getvalue()

3. 图片转PDF功能

python 复制代码
import img2pdf
from PIL import Image
import io

def images_to_pdf(image_files: list, page_orientation: str) -> bytes:
    """
    将多张图片合并为PDF
    
    参数:
        image_files: 图片文件列表
        page_orientation: 页面方向 ('自动', '纵向', '横向')
    """
    img_bytes_list = []
    
    for file in image_files:
        img = Image.open(file)
        # 转换为RGB模式(PDF不支持RGBA)
        if img.mode in ('RGBA', 'P'):
            img = img.convert('RGB')
        
        # 转换为字节
        img_byte_arr = io.BytesIO()
        img.save(img_byte_arr, format='JPEG', quality=95)
        img_bytes_list.append(img_byte_arr.getvalue())
    
    # 设置页面布局(注意:get_layout_fun 在某些版本可能不可用)
    layout_fun = None
    try:
        if page_orientation == '纵向':
            layout_fun = img2pdf.get_layout_fun(
                (img2pdf.mm_to_pt(210), img2pdf.mm_to_pt(297))  # A4尺寸
            )
        elif page_orientation == '横向':
            layout_fun = img2pdf.get_layout_fun(
                (img2pdf.mm_to_pt(297), img2pdf.mm_to_pt(210))
            )
    except AttributeError:
        pass  # 如果方法不存在,使用默认布局
    
    # 生成PDF
    if layout_fun is not None:
        pdf_bytes = img2pdf.convert(img_bytes_list, layout_fun=layout_fun)
    else:
        pdf_bytes = img2pdf.convert(img_bytes_list)
    
    return pdf_bytes

4. 批量下载功能

python 复制代码
import zipfile

def create_zip_file(files: List[Tuple[str, bytes]]) -> bytes:
    """
    将多个文件打包为ZIP
    
    参数:
        files: 文件列表,每个元素为 (文件名, 文件字节) 元组
    """
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
        for file_name, file_bytes in files:
            zip_file.writestr(file_name, file_bytes)
    return zip_buffer.getvalue()

5. Streamlit 界面实现

python 复制代码
import streamlit as st

st.set_page_config(
    page_title="PDF与图片互转工具",
    page_icon="📄",
    layout="wide"
)

# 文件上传(注意:必须指定唯一的 key)
uploaded_file = st.file_uploader(
    "上传PDF文件",
    type=['pdf'],
    help="支持PDF格式,单文件最大200MB",
    key="pdf_uploader"  # 唯一标识
)

# 转换设置(每个控件都需要唯一的 key)
output_format = st.selectbox(
    "输出格式", 
    options=['PNG', 'JPG'],
    key="output_format_select"
)
quality = st.slider(
    "图片质量", 
    min_value=10, 
    max_value=100, 
    value=85,
    key="quality_slider"
)
dpi = st.select_slider(
    "分辨率", 
    options=[72, 96, 150, 200, 300], 
    value=150,
    key="dpi_slider"
)

# 转换按钮(注意:按钮也需要唯一的 key)
if st.button("开始转换", type="primary", key="pdf_to_image_btn"):
    with st.spinner("正在转换中..."):
        progress_bar = st.progress(0)  # 注意:progress 不支持 key 参数
        # 执行转换逻辑
        progress_bar.progress(100)
        st.success("转换成功!")

# 下载按钮(每个下载按钮也需要唯一的 key)
st.download_button(
    label="下载文件",
    data=file_bytes,
    file_name="converted.pdf",
    mime="application/pdf",
    key="download_pdf_result"  # 唯一标识
)

关键技术解析

为什么 PDF转图片需要 Poppler?

pdf2image 的工作原理

pdf2image 库本身并不包含 PDF 渲染引擎,它只是一个包装器(wrapper)

复制代码
Python代码 → pdf2image库 → 调用poppler命令行工具 → 渲染PDF → 返回图片
Poppler 是什么?

Poppler 是一个开源的 PDF 渲染库,基于 Xpdf 项目开发。它提供了:

工具 功能
pdftoppm 将PDF页面转换为PPM图片
pdftocairo 使用Cairo库渲染PDF(支持更多格式)
pdftotext 提取PDF文本
pdfinfo 显示PDF信息
为什么不内置到 pdf2image?
原因 说明
跨平台兼容 Poppler 是 C/C++ 编写的原生库,不同操作系统需要不同编译版本
体积问题 Poppler 完整包约 50-100MB,不适合打包进 Python 库
许可证 Poppler 使用 GPL 许可证,与某些商业场景有冲突
维护分离 PDF渲染是复杂任务,由专业团队维护更合适

为什么图片转PDF不需要额外依赖?

img2pdf 库不需要外部工具,因为:

  • 它只是将图片嵌入PDF容器中
  • 不需要渲染复杂的PDF内容
  • Python 的 Pillow 库就能处理图片读取

Streamlit 元素 ID 机制

Streamlit 会根据元素类型和参数自动生成内部 ID。如果同一页面存在多个相同类型和参数的元素,会导致 ID 冲突。

解决方案 :为每个交互元素指定唯一的 key 参数:

python 复制代码
# ❌ 错误:两个标签页中有相同的按钮,会导致 ID 冲突
st.button("开始转换")  # 在 tab1 中
st.button("开始转换")  # 在 tab2 中 - 冲突!

# ✅ 正确:使用唯一的 key 区分
st.button("开始转换", key="tab1_convert_btn")
st.button("开始转换", key="tab2_convert_btn")

注意事项

  • st.progress() 不支持 key 参数
  • st.spinner() 不支持 key 参数
  • st.empty() 不支持 key 参数

性能优化技巧

1. 使用缓存
python 复制代码
@st.cache_data
def convert_pdf_to_images(pdf_bytes, output_format, quality, dpi):
    # 缓存转换结果,避免重复转换
    return convert_from_bytes(pdf_bytes, dpi=dpi, fmt=output_format)
2. 多线程处理
python 复制代码
# pdf2image 支持多线程
images = convert_from_bytes(pdf_bytes, thread_count=4)
3. 分辨率选择建议
DPI 适用场景 文件大小 转换速度
72 网页预览 最小 最快
96 屏幕显示
150 一般文档 中等 中等
200 打印预览 较大 较慢
300 高质量打印 最大 最慢

常见问题与解决方案

问题1:PDF转图片报错 "Unable to get page count"

原因:未安装 Poppler 或 PATH 配置不正确

解决方案

powershell 复制代码
# 验证安装
pdftoppm -v

# 如果命令不存在,重新安装 Poppler
conda install -c conda-forge poppler

问题2:PNG图片转PDF后颜色异常

原因:PNG支持透明通道,但PDF/JPEG不支持

解决方案

python 复制代码
from PIL import Image

img = Image.open(file)
if img.mode in ('RGBA', 'P'):
    img = img.convert('RGB')  # 转换为RGB模式

问题3:StreamlitDuplicateElementId 错误

错误信息

复制代码
StreamlitDuplicateElementId: There are multiple button elements with the same auto-generated ID.

原因 :多个交互元素具有相同的标签但没有唯一的 key

解决方案

python 复制代码
# 为每个元素添加唯一的 key
st.button("开始转换", key="pdf_to_image_btn")
st.button("开始转换", key="image_to_pdf_btn")

st.selectbox("输出格式", options=['PNG', 'JPG'], key="output_format_select")
st.selectbox("页面大小", options=['A4', 'A3'], key="page_size_select")

问题4:TypeError: ProgressMixin.progress() got an unexpected keyword argument 'key'

原因st.progress() 不支持 key 参数

解决方案

python 复制代码
# ❌ 错误
progress_bar = st.progress(0, key="my_progress")

# ✅ 正确
progress_bar = st.progress(0)

问题5:img2pdf 转换失败 "'NoneType' object is not callable"

原因img2pdf.get_layout_fun() 在某些版本中可能不可用或返回 None

解决方案

python 复制代码
layout_fun = None
try:
    if page_orientation == '纵向':
        layout_fun = img2pdf.get_layout_fun((img2pdf.mm_to_pt(210), img2pdf.mm_to_pt(297)))
except AttributeError:
    pass  # 方法不存在时使用默认布局

# 安全调用
if layout_fun is not None:
    pdf_bytes = img2pdf.convert(img_bytes_list, layout_fun=layout_fun)
else:
    pdf_bytes = img2pdf.convert(img_bytes_list)

问题6:大文件转换超时

原因:文件过大或DPI设置过高

解决方案

  • 降低DPI设置
  • 分批处理大文件
  • 增加Streamlit超时配置

问题7:中文文件名乱码

原因:编码问题

解决方案

python 复制代码
from urllib.parse import quote

# 下载时处理文件名
st.download_button(
    label="下载文件",
    data=file_bytes,
    file_name=quote(filename),  # URL编码
    mime="application/pdf"
)

问题8:验证 Poppler 安装

方法1:命令行验证

powershell 复制代码
pdftoppm -v

方法2:Python验证

python 复制代码
from pdf2image import convert_from_path
import tempfile
import os

# 创建测试PDF
import fitz
doc = fitz.open()
page = doc.new_page()
page.insert_text((100, 100), "Test")
pdf_bytes = doc.tobytes()

# 测试转换
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f:
    f.write(pdf_bytes)
    temp_path = f.name

try:
    images = convert_from_path(temp_path)
    print(f"✅ 成功!转换了 {len(images)} 页")
except Exception as e:
    print(f"❌ 失败: {e}")
finally:
    os.unlink(temp_path)

问题9:启动脚本中文乱码

错误信息

复制代码
'PDF涓庡浘鐗囦簰杞伐鍏?鍚姩涓?..' 不是内部或外部命令

原因:批处理文件编码问题,CMD默认使用GBK编码,UTF-8中文会乱码

解决方案

batch 复制代码
# 使用英文提示,避免编码问题
echo [INFO] Starting Streamlit app...

# 或者确保文件保存为 ANSI/GBK 编码

问题10:ModuleNotFoundError: No module named 'img2pdf'

错误信息

复制代码
ModuleNotFoundError: No module named 'img2pdf'

原因:启动脚本未正确使用虚拟环境,使用了系统Python

解决方案

batch 复制代码
# ❌ 错误:可能使用系统Python
call venv\Scripts\activate.bat
streamlit run app.py

# ✅ 正确:直接指定虚拟环境的Python
venv\Scripts\python.exe -m streamlit run app.py

项目扩展建议

1. 替代方案:使用 PyMuPDF

如果不想安装 Poppler,可以使用 PyMuPDF(fitz)替代:

powershell 复制代码
pip install PyMuPDF
python 复制代码
import fitz  # PyMuPDF

def pdf_to_images_with_fitz(pdf_bytes, output_format='png', dpi=150):
    """使用PyMuPDF转换PDF为图片"""
    doc = fitz.open(stream=pdf_bytes, filetype='pdf')
    images = []
    
    for page_num in range(len(doc)):
        page = doc[page_num]
        # 设置缩放比例
        zoom = dpi / 72
        mat = fitz.Matrix(zoom, zoom)
        
        # 渲染页面为图片
        pix = page.get_pixmap(matrix=mat)
        
        # 转换为PIL Image
        if output_format.lower() == 'png':
            img_data = pix.tobytes('png')
        else:
            img_data = pix.tobytes('jpeg')
        
        img = Image.open(io.BytesIO(img_data))
        images.append(img)
    
    doc.close()
    return images

2. 添加OCR功能

powershell 复制代码
pip install pytesseract
python 复制代码
import pytesseract

def extract_text_from_image(image):
    """从图片中提取文字"""
    return pytesseract.image_to_string(image, lang='chi_sim+eng')

3. 添加PDF合并/拆分功能

python 复制代码
import fitz

def merge_pdfs(pdf_files):
    """合并多个PDF"""
    merged = fitz.open()
    for pdf_file in pdf_files:
        doc = fitz.open(stream=pdf_file, filetype='pdf')
        merged.insert_pdf(doc)
        doc.close()
    return merged.tobytes()

def split_pdf(pdf_bytes, page_ranges):
    """拆分PDF"""
    doc = fitz.open(stream=pdf_bytes, filetype='pdf')
    new_doc = fitz.open()
    for page_num in page_ranges:
        new_doc.insert_pdf(doc, from_page=page_num, to_page=page_num)
    return new_doc.tobytes()

4. 添加水印功能

python 复制代码
import fitz

def add_watermark(pdf_bytes, watermark_text):
    """添加水印"""
    doc = fitz.open(stream=pdf_bytes, filetype='pdf')
    for page in doc:
        # 创建水印
        rect = page.rect
        text = page.insert_text(
            (rect.width/2, rect.height/2),
            watermark_text,
            fontsize=50,
            color=(0.8, 0.8, 0.8),
            rotate=45
        )
    return doc.tobytes()

5. 部署到云端

Streamlit Cloud 部署:

  1. 创建 packages.txt 文件(用于安装系统依赖):

    poppler-utils

  2. 推送到 GitHub

  3. Streamlit Cloud 部署


开发注意事项总结

Streamlit 开发要点

要点 说明
唯一 key 所有交互元素(button、selectbox、slider等)必须指定唯一的 key 参数
progress 无 key st.progress() 不支持 key 参数,不要添加
session_state 使用 st.session_state 在页面刷新间保存数据
缓存装饰器 使用 @st.cache_data 缓存耗时操作结果

img2pdf 使用要点

要点 说明
布局函数 get_layout_fun() 可能不可用,需要 try-except 处理
RGB模式 PNG等透明图片需转换为RGB模式
字节输入 img2pdf.convert() 接受字节列表作为输入

pdf2image 使用要点

要点 说明
Poppler依赖 必须安装 Poppler 并配置 PATH
多线程 使用 thread_count 参数加速转换
内存管理 大文件转换可能占用大量内存,注意监控

总结

本教程详细介绍了 PDF 与图片互转工具的完整开发过程,包括:

  • ✅ 环境搭建与依赖安装
  • ✅ PDF转图片功能实现
  • ✅ 图片转PDF功能实现
  • ✅ 文件验证与错误处理
  • ✅ 批量下载功能
  • ✅ Poppler 依赖原理详解
  • ✅ Streamlit 元素 ID 机制与解决方案
  • ✅ 常见问题解决方案
  • ✅ 项目扩展建议

通过本教程,你可以快速搭建一个功能完善的 PDF 与图片互转 Web 应用,并根据实际需求进行扩展。

相关推荐
云原生指北2 小时前
记忆不上云:mem9 + TiDB 打造 OpenClaw 私有记忆中枢
前端
IT_陈寒2 小时前
Vite vs Webpack终极对决:5个关键指标告诉你谁更快?
前端·人工智能·后端
Moment2 小时前
2026 年前端 Agent 框架选型:Mastra 与 LangChain 该怎么选
前端·后端·面试
云浪2 小时前
5 分钟入门 fetch
前端·javascript
晓得迷路了2 小时前
栗子前端技术周刊第 120 期 - Vite 8.0、Solid v2.0.0 Beta、TypeScript 6.0 RC...
前端·javascript·vite
学习3人组2 小时前
PowerShell 执行策略限制导致的 `npm` 命令无法运行的安全错误
前端·安全·npm
optimistic_chen2 小时前
【Vue入门】组件及组件化
前端·javascript·vue.js·html·组件
北寻北爱2 小时前
面试题-css篇
前端
Hello_Embed2 小时前
LVGL 入门(八):标签控件 lv_label
前端·笔记·stm32·单片机·嵌入式