使用golang实现PDF图片提取
作者:九江Mgx
““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 里的图,都是你的!
快去试试吧,说不定下一个被你提取到的,就是价值千金的设计图呢~
到此这篇关于使用golang实现PDF图片提取的文章就介绍到这了,更多相关go提取PDF图片内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
