""PDF 里的圖,難道只能看不能拿?""
—— 別急,今天教你用 Go 寫個(gè)""圖片提取器"",把 PDF 里的圖統(tǒng)統(tǒng)打包帶走!
你有沒有遇到過這種情況:
看到一份超贊的 PDF 報(bào)告,里面的圖表清晰又專業(yè),想保存下來做參考?
老師發(fā)的課件里有張神圖,但右鍵不能保存,復(fù)制還糊成馬賽克?
想批量提取 PDF 中的所有插圖,卻只能一張張截圖?
別慌!今天我們就用 Go 語言 + pdfcpu 庫,手搓一個(gè)超實(shí)用的 PDF 圖片提取工具,一鍵把 PDF 里的所有圖片“扒”出來,整齊碼好,命名清晰,絕不手軟!
??? 它是怎么工作的?
簡單來說,這個(gè)工具干了三件事:
讀 PDF:用 pdfcpu 庫解析 PDF 文件結(jié)構(gòu)。
找圖片:遍歷每一頁,找出所有嵌入的圖像對象(不管是 JPG、PNG 還是其他格式)。
存圖片:把每張圖按 名稱-頁碼-對象ID.格式 的規(guī)則命名,存到你指定的文件夾里。
?? 小知識:PDF 里的圖片其實(shí)是“嵌入對象”,每個(gè)都有唯一 ID 和所屬頁碼。我們就是靠這些信息精準(zhǔn)定位每張圖!
?? 用起來有多爽?
假設(shè)你有個(gè)文件叫 report.pdf,只需一行命令:
extractimages report.pdf
它就會自動創(chuàng)建一個(gè)叫 report.pdf.images 的文件夾,里面是這樣的:
chart-5-42.jpg
diagram-12-108.png
image-3-17.jpg
...
想換個(gè)目錄?加個(gè) -o 就行:
extractimages -o my_pics report.pdf
連幫助都貼心準(zhǔn)備好了:
extractimages --help
??♂? 技術(shù)小彩蛋
用 unsafe “偷看”私有字段:pdfcpu.Image 的內(nèi)部字段是私有的,但我們用 unsafe.Pointer 強(qiáng)行轉(zhuǎn)成自定義結(jié)構(gòu)體 MyImage,就能拿到圖片名、頁碼、對象ID等關(guān)鍵信息?。▌e怕,這在 Go 里是合法“黑科技”)
自動防重名:如果文件名沖突,自動加上時(shí)間戳,絕不覆蓋!
智能默認(rèn)輸出目錄:不指定 -o?沒問題,自動用 PDF文件名.images 當(dāng)文件夾!
?? 快來試試吧!
只要你會裝 Go,三步搞定:
安裝依賴:
go mod init extractimages
go get github.com/pdfcpu/pdfcpu
把下面的代碼保存為 main.go
編譯運(yùn)行:
go build -o extractimages main.go
./extractimages your_file.pdf
從此,PDF 再也不是圖片的"監(jiān)獄"——而是你的"圖庫倉庫"!
?? 完整代碼(直接復(fù)制就能用?。?/p>
package main
import (
"flag" // 用于處理命令行參數(shù)
"fmt" // 用于格式化輸出
"io" // 提供基礎(chǔ)的I/O接口
"log" // 提供日志功能
"os" // 提供操作系統(tǒng)功能接口
"path/filepath" // 用于處理文件路徑
"strings" // 提供字符串處理函數(shù)
"time" // 用于計(jì)時(shí)和時(shí)間戳
"unsafe" // 用于不安全的內(nèi)存操作
"github.com/pdfcpu/pdfcpu/pkg/api" // pdfcpu庫的API接口
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu" // pdfcpu庫的核心功能
)
// MyImage 是對pdfcpu.Image的封裝,用于訪問圖片的詳細(xì)信息
type MyImage struct {
io.Reader // 嵌入的Reader接口,用于讀取圖片數(shù)據(jù)
Name string // 圖片名稱
FileType string // 文件類型(如jpg、png等)
PageNr int // 圖片所在的PDF頁碼
ObjNr int // 圖片在PDF中的對象編號
}
// 全局變量,用于統(tǒng)計(jì)提取的圖片數(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()
// 獲取剩余的非選項(xiàng)參數(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)
}
// 檢查文件擴(kuò)展名是否為pdf
if !strings.HasSuffix(strings.ToLower(pdfFilename), ".pdf") {
log.Printf("警告: 文件擴(kuò)展名不是.pdf,可能不是有效的PDF文件: %s\n", pdfFilename)
}
// 確定輸出目錄
if outputDir == "" {
// 如果未指定輸出目錄,使用默認(rèn)目錄(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é)束時(shí)關(guān)閉文件
defer pdfFile.Close()
// 解析 PDF,將提取的圖片寫入指定目錄
// 使用saveImageToDir函數(shù)作為回調(diào)函數(shù)處理每個(gè)提取到的圖片
if err := api.ExtractImages(pdfFile, nil, saveImageToDir(outputDir), nil); err != nil {
log.Fatalf("提取圖片過程中出錯: %v", err)
}
elapsedTime := time.Since(startTime)
log.Printf("圖片提取完成,共提取 %d 張圖片,耗時(shí): %v", extractedImagesCount, elapsedTime)
log.Printf("所有圖片已保存到目錄: %s", outputDir)
}
// showHelp 顯示幫助信息
func showHelp() {
fmt.Println("PDF圖片提取工具 - 從PDF文件中提取所有圖片并保存到指定目錄")
fmt.Println("\n用法:")
fmt.Println(" extractimages [選項(xiàng)] <PDF文件路徑>")
fmt.Println("\n選項(xiàng):")
fmt.Println(" -h, --help 顯示此幫助信息")
fmt.Println(" -o, --output <目錄路徑> 指定輸出圖片的目錄(默認(rèn): 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. 提取的圖片將保存到指定目錄或默認(rèn)目錄中")
fmt.Println(" 3. 圖片文件名格式: 圖片名稱-頁碼-對象編號.文件類型")
fmt.Println(" 4. 如果指定的目錄不存在,將自動創(chuàng)建")
}
// saveImageToDir 創(chuàng)建一個(gè)回調(diào)函數(shù),用于將提取的圖片保存到指定目錄
// 參數(shù) outputDir 是保存圖片的目標(biāo)目錄路徑
// 返回值是一個(gè)函數(shù),該函數(shù)將在pdfcpu庫提取到圖片時(shí)被調(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類型強(qiáng)制轉(zhuǎn)換為MyImage類型,以便訪問私有字段
myImg := (*MyImage)(unsafe.Pointer(&img))
// 處理空文件名的情況
name := myImg.Name
if name == "" {
name = "image"
}
// 處理未知文件類型的情況
fileType := myImg.FileType
if fileType == "" {
fileType = "jpg" // 默認(rèn)使用jpg格式
}
// 生成圖片文件名,格式為:圖片名稱-頁碼-對象編號.文件類型
filename := fmt.Sprintf("%s-%d-%d.%s", name, myImg.PageNr, myImg.ObjNr, fileType)
// 生成完整的文件路徑
filePath := filepath.Join(outputDir, filename)
// 檢查文件是否已存在,如果存在則添加時(shí)間戳
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)建目標(biāo)文件
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)計(jì)計(jì)數(shù)
extractedImagesCount++
// 輸出成功處理的圖片文件名和大小
log.Printf("成功提取圖片 #%d: %s (大小: %d 字節(jié))", extractedImagesCount, filename, n)
return nil
}
}
?? 從此 PDF 里的圖,都是你的!
快去試試吧,說不定下一個(gè)被你提取到的,就是價(jià)值千金的設(shè)計(jì)圖呢~
往期部分文章列表
- 200KB 的煩惱,Go 語言 20 分鐘搞定!—— 一個(gè)程序員的圖片壓縮自救指南
- 從“CPU 燒開水“到優(yōu)雅暫停:Go 里 sync.Cond 的正確打開方式
- 時(shí)移世易,篡改天機(jī):吾以 Go 語令 Windows 文件“返老還童“記
- golang圓陣列圖記:天靈靈地靈靈圖標(biāo)排圓形
- golang解圖記
- 從 4.8 秒到 0.25 秒:我是如何把 Go 正則匹配提速 19 倍的?
- 用 Go 手搓一個(gè)內(nèi)網(wǎng) DNS 服務(wù)器:從此告別 IP 地址,用域名暢游家庭網(wǎng)絡(luò)!
- 我用Go寫了個(gè)華容道游戲,曹操終于不用再求關(guān)羽了!
- 用 Go 接口把 Excel 變成數(shù)據(jù)庫:一個(gè)瘋狂但可行的想法
- 穿墻術(shù)大揭秘:用 Go 手搓一個(gè)"內(nèi)網(wǎng)穿透"神器!
- 布隆過濾器(go):一個(gè)可能犯錯但從不撒謊的內(nèi)存大師
- 自由通訊的魔法:Go從零實(shí)現(xiàn)UDP/P2P 聊天工具
- Go語言實(shí)現(xiàn)的簡易遠(yuǎn)程傳屏工具:讓你的屏幕「飛」起來
- 當(dāng)你的程序?qū)W會了"詐尸":Go 實(shí)現(xiàn) Windows 進(jìn)程守護(hù)術(shù)
- 驗(yàn)證碼識別API:告別收費(fèi)接口,迎接免費(fèi)午餐
- 用 Go 給 Windows 裝個(gè)"順風(fēng)耳":兩分鐘寫個(gè)錄音小工具
- 無奈!我用go寫了個(gè)MySQL服務(wù)
- 使用 Go + govcl 實(shí)現(xiàn) Windows 資源管理器快捷方式管理器
- 用 Go 手搓一個(gè) NTP 服務(wù):從"時(shí)間混亂"到"精準(zhǔn)同步"的奇幻之旅
- 用 Go 手搓一個(gè) Java 構(gòu)建工具:當(dāng) IDE 不在身邊時(shí)的自救指南
- 深入理解 Windows 全局鍵盤鉤子(Hook):攔截 Win 鍵的 Go 實(shí)現(xiàn)
- 用 Go 語言實(shí)現(xiàn)《周易》大衍筮法起卦程序
- Go 語言400行代碼實(shí)現(xiàn) INI 配置文件解析器:支持注釋、轉(zhuǎn)義與類型推斷
- 高性能 Go 語言帶 TTL 的內(nèi)存緩存實(shí)現(xiàn):精確過期、自動刷新、并發(fā)安全