么无力再爱谁 發表於 2025-11-23 11:20:38

使用golang实现PDF图片提取

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">它是怎么工作的</a></li><li><a href="#_label1">用起来有多爽</a></li><li><a href="#_label2">技术小彩蛋</a></li><li><a href="#_label3">快来试试吧</a></li><li><a href="#_label4">完整代码(直接复制就能用!)</a></li></ul></div><p>&ldquo;&ldquo;PDF 里的图,难道只能看不能拿?&rdquo;&rdquo;</p>
<p>&mdash;&mdash; 别急,今天教你用 Go 写个&quot;&ldquo;图片提取器&rdquo;&quot;,把 PDF 里的图统统打包带走!</p>
<p>你有没有遇到过这种情况:</p>
<ul><li>看到一份超赞的 PDF 报告,里面的图表清晰又专业,想保存下来做参考?</li><li>老师发的课件里有张神图,但右键不能保存,复制还糊成马赛克?</li><li>想批量提取 PDF 中的所有插图,却只能一张张截图?</li></ul>
<p>别慌!今天我们就用 Go 语言 + pdfcpu 库,手搓一个超实用的 PDF 图片提取工具,一键把 PDF 里的所有图片&ldquo;扒&rdquo;出来,整齐码好,命名清晰,绝不手软!</p>
<p class="maodian"><a name="_label0"></a></p><h2>它是怎么工作的</h2>
<p>简单来说,这个工具干了三件事:</p>
<ul><li>读 PDF:用 pdfcpu 库解析 PDF 文件结构。</li><li>找图片:遍历每一页,找出所有嵌入的图像对象(不管是 JPG、PNG 还是其他格式)。</li><li>存图片:把每张图按 名称-页码-对象ID.格式 的规则命名,存到你指定的文件夹里。</li></ul>
<p>小知识:PDF 里的图片其实是&ldquo;嵌入对象&rdquo;,每个都有唯一 ID 和所属页码。我们就是靠这些信息精准定位每张图!</p>
<p class="maodian"><a name="_label1"></a></p><h2>用起来有多爽</h2>
<p>假设你有个文件叫 report.pdf,只需一行命令:</p>
<div class="jb51code"><pre class="brush:bash;">extractimages report.pdf
</pre></div>
<p>它就会自动创建一个叫 report.pdf.images 的文件夹,里面是这样的:</p>
<blockquote><p>chart-5-42.jpg<br />diagram-12-108.png<br />image-3-17.jpg<br />...</p></blockquote>
<p>想换个目录?加个 -o 就行:</p>
<div class="jb51code"><pre class="brush:bash;">extractimages -o my_pics report.pdf
</pre></div>
<p>连帮助都贴心准备好了:</p>
<div class="jb51code"><pre class="brush:bash;">extractimages --help
</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>技术小彩蛋</h2>
<p>用 unsafe &ldquo;偷看&rdquo;私有字段:pdfcpu.Image 的内部字段是私有的,但我们用 unsafe.Pointer 强行转成自定义结构体 MyImage,就能拿到图片名、页码、对象</p>
<p>ID等关键信息!(别怕,这在 Go 里是合法&ldquo;黑科技&rdquo;)</p>
<p>自动防重名:如果文件名冲突,自动加上时间戳,绝不覆盖!</p>
<p>智能默认输出目录:不指定 -o?没问题,自动用 PDF文件名.images 当文件夹!</p>
<p class="maodian"><a name="_label3"></a></p><h2>快来试试吧</h2>
<p>只要你会装 Go,三步搞定:</p>
<p>安装依赖:</p>
<div class="jb51code"><pre class="brush:bash;">go mod init extractimages
go get github.com/pdfcpu/pdfcpu
</pre></div>
<p>把下面的代码保存为 main.go</p>
<p>编译运行:</p>
<div class="jb51code"><pre class="brush:bash;">go build -o extractimages main.go
./extractimages your_file.pdf
</pre></div>
<p>从此,PDF 再也不是图片的&quot;监 狱&quot;&mdash;&mdash;而是你的&quot;图库仓库&quot;!</p>
<p class="maodian"><a name="_label4"></a></p><h2>完整代码(直接复制就能用!)</h2>
<div class="jb51code"><pre class="brush:go;">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(&amp;help, "h", false, "显示帮助信息")
        flag.BoolVar(&amp;help, "help", false, "显示帮助信息")
        flag.StringVar(&amp;outputDir, "o", "", "指定输出图片的目录")
        flag.StringVar(&amp;outputDir, "output", "", "指定输出图片的目录")
        flag.Parse()

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

        // 获取PDF文件的绝对路径
        pdfFilename, err := filepath.Abs(args)
        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 [选项] &lt;PDF文件路径&gt;")
        fmt.Println("\n选项:")
        fmt.Println("-h, --help            显示此帮助信息")
        fmt.Println("-o, --output &lt;目录路径&gt;指定输出图片的目录(默认: 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(&amp;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
        }
}
</pre></div>
<p>从此 PDF 里的图,都是你的!</p>
<p>快去试试吧,说不定下一个被你提取到的,就是价值千金的设计图呢~</p>
頁: [1]
查看完整版本: 使用golang实现PDF图片提取