使用golang實現(xiàn)PDF圖片提取
““PDF 里的圖,難道只能看不能拿?””
—— 別急,今天教你用 Go 寫個"“圖片提取器”",把 PDF 里的圖統(tǒng)統(tǒng)打包帶走!
你有沒有遇到過這種情況:
- 看到一份超贊的 PDF 報告,里面的圖表清晰又專業(yè),想保存下來做參考?
- 老師發(fā)的課件里有張神圖,但右鍵不能保存,復制還糊成馬賽克?
- 想批量提取 PDF 中的所有插圖,卻只能一張張截圖?
別慌!今天我們就用 Go 語言 + pdfcpu 庫,手搓一個超實用的 PDF 圖片提取工具,一鍵把 PDF 里的所有圖片“扒”出來,整齊碼好,命名清晰,絕不手軟!
它是怎么工作的
簡單來說,這個工具干了三件事:
- 讀 PDF:用 pdfcpu 庫解析 PDF 文件結(jié)構(gòu)。
- 找圖片:遍歷每一頁,找出所有嵌入的圖像對象(不管是 JPG、PNG 還是其他格式)。
- 存圖片:把每張圖按 名稱-頁碼-對象ID.格式 的規(guī)則命名,存到你指定的文件夾里。
小知識:PDF 里的圖片其實是“嵌入對象”,每個都有唯一 ID 和所屬頁碼。我們就是靠這些信息精準定位每張圖!
用起來有多爽
假設(shè)你有個文件叫 report.pdf,只需一行命令:
extractimages report.pdf
它就會自動創(chuàng)建一個叫 report.pdf.images 的文件夾,里面是這樣的:
chart-5-42.jpg
diagram-12-108.png
image-3-17.jpg
...
想換個目錄?加個 -o 就行:
extractimages -o my_pics report.pdf
連幫助都貼心準備好了:
extractimages --help
技術(shù)小彩蛋
用 unsafe “偷看”私有字段:pdfcpu.Image 的內(nèi)部字段是私有的,但我們用 unsafe.Pointer 強行轉(zhuǎn)成自定義結(jié)構(gòu)體 MyImage,就能拿到圖片名、頁碼、對象
ID等關(guān)鍵信息?。▌e怕,這在 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 再也不是圖片的"監(jiān) 獄"——而是你的"圖庫倉庫"!
完整代碼(直接復制就能用?。?/h2>
package main
import (
"flag" // 用于處理命令行參數(shù)
"fmt" // 用于格式化輸出
"io" // 提供基礎(chǔ)的I/O接口
"log" // 提供日志功能
"os" // 提供操作系統(tǒng)功能接口
"path/filepath" // 用于處理文件路徑
"strings" // 提供字符串處理函數(shù)
"time" // 用于計時和時間戳
"unsafe" // 用于不安全的內(nèi)存操作
"github.com/pdfcpu/pdfcpu/pkg/api" // pdfcpu庫的API接口
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu" // pdfcpu庫的核心功能
)
// MyImage 是對pdfcpu.Image的封裝,用于訪問圖片的詳細信息
type MyImage struct {
io.Reader // 嵌入的Reader接口,用于讀取圖片數(shù)據(jù)
Name string // 圖片名稱
FileType string // 文件類型(如jpg、png等)
PageNr int // 圖片所在的PDF頁碼
ObjNr int // 圖片在PDF中的對象編號
}
// 全局變量,用于統(tǒng)計提取的圖片數(shù)量
var extractedImagesCount int
// main 程序入口函數(shù)
func main() {
// 設(shè)置日志格式
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("PDF圖片提取工具啟動")
// 解析命令行參數(shù)
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()
// 獲取剩余的非選項參數(shù)(即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)
}
// 創(chuàng)建輸出目錄(如果不存在)
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Fatalf("創(chuàng)建輸出目錄失敗: %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)
}
// 確保程序結(jié)束時關(guān)閉文件
defer pdfFile.Close()
// 解析 PDF,將提取的圖片寫入指定目錄
// 使用saveImageToDir函數(shù)作為回調(diào)函數(shù)處理每個提取到的圖片
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. 如果指定的目錄不存在,將自動創(chuàng)建")
}
// saveImageToDir 創(chuàng)建一個回調(diào)函數(shù),用于將提取的圖片保存到指定目錄
// 參數(shù) outputDir 是保存圖片的目標目錄路徑
// 返回值是一個函數(shù),該函數(shù)將在pdfcpu庫提取到圖片時被調(diào)用
func saveImageToDir(outputDir string) func(pdfcpu.Image, bool, int) error {
return func(img pdfcpu.Image, singleImgPerPage bool, maxPageDigits int) error {
// 使用unsafe包將pdfcpu.Image類型強制轉(zhuǎn)換為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)
}
// 創(chuàng)建目標文件
outFile, err := os.Create(filePath)
if err != nil {
log.Printf("創(chuàng)建圖片文件失敗: %v", err)
return err
}
// 確保文件關(guān)閉
defer outFile.Close()
// 將圖片數(shù)據(jù)寫入文件
n, err := io.Copy(outFile, img.Reader)
if err != nil {
log.Printf("寫入圖片數(shù)據(jù)失敗: %v", err)
return err
}
// 增加統(tǒng)計計數(shù)
extractedImagesCount++
// 輸出成功處理的圖片文件名和大小
log.Printf("成功提取圖片 #%d: %s (大小: %d 字節(jié))", extractedImagesCount, filename, n)
return nil
}
}
package main
import (
"flag" // 用于處理命令行參數(shù)
"fmt" // 用于格式化輸出
"io" // 提供基礎(chǔ)的I/O接口
"log" // 提供日志功能
"os" // 提供操作系統(tǒng)功能接口
"path/filepath" // 用于處理文件路徑
"strings" // 提供字符串處理函數(shù)
"time" // 用于計時和時間戳
"unsafe" // 用于不安全的內(nèi)存操作
"github.com/pdfcpu/pdfcpu/pkg/api" // pdfcpu庫的API接口
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu" // pdfcpu庫的核心功能
)
// MyImage 是對pdfcpu.Image的封裝,用于訪問圖片的詳細信息
type MyImage struct {
io.Reader // 嵌入的Reader接口,用于讀取圖片數(shù)據(jù)
Name string // 圖片名稱
FileType string // 文件類型(如jpg、png等)
PageNr int // 圖片所在的PDF頁碼
ObjNr int // 圖片在PDF中的對象編號
}
// 全局變量,用于統(tǒng)計提取的圖片數(shù)量
var extractedImagesCount int
// main 程序入口函數(shù)
func main() {
// 設(shè)置日志格式
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Println("PDF圖片提取工具啟動")
// 解析命令行參數(shù)
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()
// 獲取剩余的非選項參數(shù)(即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)
}
// 創(chuàng)建輸出目錄(如果不存在)
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Fatalf("創(chuàng)建輸出目錄失敗: %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)
}
// 確保程序結(jié)束時關(guān)閉文件
defer pdfFile.Close()
// 解析 PDF,將提取的圖片寫入指定目錄
// 使用saveImageToDir函數(shù)作為回調(diào)函數(shù)處理每個提取到的圖片
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. 如果指定的目錄不存在,將自動創(chuàng)建")
}
// saveImageToDir 創(chuàng)建一個回調(diào)函數(shù),用于將提取的圖片保存到指定目錄
// 參數(shù) outputDir 是保存圖片的目標目錄路徑
// 返回值是一個函數(shù),該函數(shù)將在pdfcpu庫提取到圖片時被調(diào)用
func saveImageToDir(outputDir string) func(pdfcpu.Image, bool, int) error {
return func(img pdfcpu.Image, singleImgPerPage bool, maxPageDigits int) error {
// 使用unsafe包將pdfcpu.Image類型強制轉(zhuǎn)換為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)
}
// 創(chuàng)建目標文件
outFile, err := os.Create(filePath)
if err != nil {
log.Printf("創(chuàng)建圖片文件失敗: %v", err)
return err
}
// 確保文件關(guān)閉
defer outFile.Close()
// 將圖片數(shù)據(jù)寫入文件
n, err := io.Copy(outFile, img.Reader)
if err != nil {
log.Printf("寫入圖片數(shù)據(jù)失敗: %v", err)
return err
}
// 增加統(tǒng)計計數(shù)
extractedImagesCount++
// 輸出成功處理的圖片文件名和大小
log.Printf("成功提取圖片 #%d: %s (大小: %d 字節(jié))", extractedImagesCount, filename, n)
return nil
}
}
從此 PDF 里的圖,都是你的!
快去試試吧,說不定下一個被你提取到的,就是價值千金的設(shè)計圖呢~
到此這篇關(guān)于使用golang實現(xiàn)PDF圖片提取的文章就介紹到這了,更多相關(guān)go提取PDF圖片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang結(jié)構(gòu)體與json格式串實例代碼
本文通過實例代碼給大家介紹了golang結(jié)構(gòu)體與json格式串的相關(guān)知識,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-10-10
Go中Gzip與json搭配實現(xiàn)數(shù)據(jù)壓縮demo
這篇文章主要為大家介紹了Go中Gzip與json搭配使用壓縮數(shù)據(jù)的實現(xiàn)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05
golang 如何用反射reflect操作結(jié)構(gòu)體
這篇文章主要介紹了golang 用反射reflect操作結(jié)構(gòu)體的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04
Go 循環(huán)結(jié)構(gòu)for循環(huán)使用教程全面講解
這篇文章主要為大家介紹了Go 循環(huán)結(jié)構(gòu)for循環(huán)使用全面講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10

