用golang解救PDF文件中的图片只要200行代码!

""PDF 里的图,难道只能看不能拿?""

—— 别急,今天教你用 Go 写个""图片提取器"",把 PDF 里的图统统打包带走!

你有没有遇到过这种情况:

看到一份超赞的 PDF 报告,里面的图表清晰又专业,想保存下来做参考?
老师发的课件里有张神图,但右键不能保存,复制还糊成马赛克?
想批量提取 PDF 中的所有插图,却只能一张张截图?
别慌!今天我们就用 Go 语言 + pdfcpu 库,手搓一个超实用的 PDF 图片提取工具,一键把 PDF 里的所有图片“扒”出来,整齐码好,命名清晰,绝不手软!

🛠️ 它是怎么工作的?
简单来说,这个工具干了三件事:

读 PDF:用 pdfcpu 库解析 PDF 文件结构。
找图片:遍历每一页,找出所有嵌入的图像对象(不管是 JPG、PNG 还是其他格式)。
存图片:把每张图按 名称-页码-对象ID.格式 的规则命名,存到你指定的文件夹里。
💡 小知识:PDF 里的图片其实是“嵌入对象”,每个都有唯一 ID 和所属页码。我们就是靠这些信息精准定位每张图!

😎 用起来有多爽?
假设你有个文件叫 report.pdf,只需一行命令:

extractimages report.pdf

它就会自动创建一个叫 report.pdf.images 的文件夹,里面是这样的:

chart-5-42.jpg
diagram-12-108.png
image-3-17.jpg
...

想换个目录?加个 -o 就行:

extractimages -o my_pics report.pdf

连帮助都贴心准备好了:

extractimages --help

🧙♂️ 技术小彩蛋
用 unsafe “偷看”私有字段:pdfcpu.Image 的内部字段是私有的,但我们用 unsafe.Pointer 强行转成自定义结构体 MyImage,就能拿到图片名、页码、对象ID等关键信息!(别怕,这在 Go 里是合法“黑科技”)
自动防重名:如果文件名冲突,自动加上时间戳,绝不覆盖!
智能默认输出目录:不指定 -o?没问题,自动用 PDF文件名.images 当文件夹!
🚀 快来试试吧!
只要你会装 Go,三步搞定:

安装依赖:

go mod init extractimages
go get github.com/pdfcpu/pdfcpu

把下面的代码保存为 main.go

编译运行:

go build -o extractimages main.go
./extractimages your_file.pdf

从此,PDF 再也不是图片的"监狱"——而是你的"图库仓库"!

📜 完整代码(直接复制就能用!)

package main

import (
    "flag"               // 用于处理命令行参数
    "fmt"               // 用于格式化输出
    "io"                // 提供基础的I/O接口
    "log"               // 提供日志功能
    "os"                // 提供操作系统功能接口
    "path/filepath"     // 用于处理文件路径
    "strings"           // 提供字符串处理函数
    "time"              // 用于计时和时间戳
    "unsafe"            // 用于不安全的内存操作

    "github.com/pdfcpu/pdfcpu/pkg/api"      // pdfcpu库的API接口
    "github.com/pdfcpu/pdfcpu/pkg/pdfcpu"   // pdfcpu库的核心功能
)

// MyImage 是对pdfcpu.Image的封装,用于访问图片的详细信息
type MyImage struct {
    io.Reader  // 嵌入的Reader接口,用于读取图片数据
    Name     string // 图片名称
    FileType string // 文件类型(如jpg、png等)
    PageNr   int    // 图片所在的PDF页码
    ObjNr    int    // 图片在PDF中的对象编号
}

// 全局变量,用于统计提取的图片数量
var extractedImagesCount int

// main 程序入口函数
func main() {
    // 设置日志格式
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.Println("PDF图片提取工具启动")

    // 解析命令行参数
    var help bool
    var outputDir string
    flag.BoolVar(&help, "h", false, "显示帮助信息")
    flag.BoolVar(&help, "help", false, "显示帮助信息")
    flag.StringVar(&outputDir, "o", "", "指定输出图片的目录")
    flag.StringVar(&outputDir, "output", "", "指定输出图片的目录")
    flag.Parse()

    // 获取剩余的非选项参数(即PDF文件路径)
    args := flag.Args()
    if help || len(args) < 1 {
        showHelp()
        return
    }

    // 获取PDF文件的绝对路径
    pdfFilename, err := filepath.Abs(args[0])
    if err != nil {
        log.Fatalf("获取文件绝对路径失败: %v", err)
    }

    // 检查文件是否存在
    if _, err := os.Stat(pdfFilename); os.IsNotExist(err) {
        log.Fatalf("PDF文件不存在: %s", pdfFilename)
    }

    // 检查文件扩展名是否为pdf
    if !strings.HasSuffix(strings.ToLower(pdfFilename), ".pdf") {
        log.Printf("警告: 文件扩展名不是.pdf,可能不是有效的PDF文件: %s\n", pdfFilename)
    }

    // 确定输出目录
    if outputDir == "" {
        // 如果未指定输出目录,使用默认目录(PDF文件名.images)
        pdfName := strings.TrimSuffix(filepath.Base(pdfFilename), ".pdf")
        outputDir = pdfName + ".images"
    }
    
    // 获取输出目录的绝对路径
    outputDir, err = filepath.Abs(outputDir)
    if err != nil {
        log.Fatalf("获取输出目录绝对路径失败: %v", err)
    }

    // 创建输出目录(如果不存在)
    if err := os.MkdirAll(outputDir, 0755); err != nil {
        log.Fatalf("创建输出目录失败: %v", err)
    }

    log.Printf("开始处理PDF文件: %s", pdfFilename)
    log.Printf("图片将保存到目录: %s", outputDir)
    startTime := time.Now()

    // 打开 PDF 文件
    pdfFile, err := os.Open(pdfFilename)
    if err != nil {
        log.Fatalf("打开PDF文件失败: %v", err)
    }
    // 确保程序结束时关闭文件
    defer pdfFile.Close()

    // 解析 PDF,将提取的图片写入指定目录
    // 使用saveImageToDir函数作为回调函数处理每个提取到的图片
    if err := api.ExtractImages(pdfFile, nil, saveImageToDir(outputDir), nil); err != nil {
        log.Fatalf("提取图片过程中出错: %v", err)
    }

    elapsedTime := time.Since(startTime)
    log.Printf("图片提取完成,共提取 %d 张图片,耗时: %v", extractedImagesCount, elapsedTime)
    log.Printf("所有图片已保存到目录: %s", outputDir)
}

// showHelp 显示帮助信息
func showHelp() {
    fmt.Println("PDF图片提取工具 - 从PDF文件中提取所有图片并保存到指定目录")
    fmt.Println("\n用法:")
    fmt.Println("  extractimages [选项] <PDF文件路径>")
    fmt.Println("\n选项:")
    fmt.Println("  -h, --help              显示此帮助信息")
    fmt.Println("  -o, --output <目录路径>  指定输出图片的目录(默认: PDF文件名.images)")
    fmt.Println("\n示例:")
    fmt.Println("  extractimages document.pdf")
    fmt.Println("  extractimages -o images_folder document.pdf")
    fmt.Println("\n说明:")
    fmt.Println("  1. 程序会从指定的PDF文件中提取所有图片")
    fmt.Println("  2. 提取的图片将保存到指定目录或默认目录中")
    fmt.Println("  3. 图片文件名格式: 图片名称-页码-对象编号.文件类型")
    fmt.Println("  4. 如果指定的目录不存在,将自动创建")
}

// saveImageToDir 创建一个回调函数,用于将提取的图片保存到指定目录
// 参数 outputDir 是保存图片的目标目录路径
// 返回值是一个函数,该函数将在pdfcpu库提取到图片时被调用
func saveImageToDir(outputDir string) func(pdfcpu.Image, bool, int) error {
    return func(img pdfcpu.Image, singleImgPerPage bool, maxPageDigits int) error {
        // 使用unsafe包将pdfcpu.Image类型强制转换为MyImage类型,以便访问私有字段
        myImg := (*MyImage)(unsafe.Pointer(&img))
        
        // 处理空文件名的情况
        name := myImg.Name
        if name == "" {
            name = "image"
        }
        
        // 处理未知文件类型的情况
        fileType := myImg.FileType
        if fileType == "" {
            fileType = "jpg" // 默认使用jpg格式
        }
        
        // 生成图片文件名,格式为:图片名称-页码-对象编号.文件类型
        filename := fmt.Sprintf("%s-%d-%d.%s", name, myImg.PageNr, myImg.ObjNr, fileType)
        
        // 生成完整的文件路径
        filePath := filepath.Join(outputDir, filename)
        
        // 检查文件是否已存在,如果存在则添加时间戳
        if _, err := os.Stat(filePath); err == nil {
            timestamp := time.Now().Format("20060102_150405")
            filename = fmt.Sprintf("%s-%d-%d_%s.%s", name, myImg.PageNr, myImg.ObjNr, timestamp, fileType)
            filePath = filepath.Join(outputDir, filename)
            log.Printf("图片文件已存在,将使用新文件名: %s", filename)
        }
        
        // 创建目标文件
        outFile, err := os.Create(filePath)
        if err != nil {
            log.Printf("创建图片文件失败: %v", err)
            return err
        }
        
        // 确保文件关闭
        defer outFile.Close()
        
        // 将图片数据写入文件
        n, err := io.Copy(outFile, img.Reader)
        if err != nil {
            log.Printf("写入图片数据失败: %v", err)
            return err
        }
        
        // 增加统计计数
        extractedImagesCount++
        
        // 输出成功处理的图片文件名和大小
        log.Printf("成功提取图片 #%d: %s (大小: %d 字节)", extractedImagesCount, filename, n)
        return nil
    }
}

🎉 从此 PDF 里的图,都是你的!

快去试试吧,说不定下一个被你提取到的,就是价值千金的设计图呢~

往期部分文章列表

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容