JavaScript提取PDF和Word文檔內(nèi)圖片
最近接了個(gè)需求,要求就是基于文檔的 AI 問答,文檔里面最常見的就是 PDF 和 Word 文檔了,里面的內(nèi)容無非就是文本和圖片了,目前是沒有直接接收這種文檔的模型的,那么我們需要經(jīng)過一些處理來進(jìn)行。
首先我們要先把圖片和文本來進(jìn)行處理,我這邊的處理方式就是圖片調(diào)用圖片的模型來識(shí)別圖片信息,將返回的信息和文檔的文本作為后面的問答的前置 prompt,至于這些 prompt 就可以根據(jù)不同的需求來做不同處理了,這里不多解釋。
在接下來,我們將使用 NextJs 項(xiàng)目作為例子進(jìn)行講解,后面的內(nèi)容跟框架依性不是很大,vue,astro 等項(xiàng)目都可以直接拿來使用。
提取 PDF 中的圖片
PDF.js
是一個(gè)開源的 JavaScript 庫,用于在網(wǎng)頁上直接顯示和渲染 PDF 文件。它將 PDF 文件解析為 HTML5 元素,使得瀏覽器可以無插件地加載和查看 PDF 文檔。PDF.js 支持多種功能,如文本選擇、搜索、頁面導(dǎo)航等,提供了良好的瀏覽體驗(yàn)。通過它,開發(fā)者可以輕松集成 PDF 查看功能到網(wǎng)站或應(yīng)用中。
為了避免 PDF 解析過程阻塞主線程,PDF.js 使用 Web Worker ,因?yàn)?PDF 解析是一個(gè) CPU 密集型的操作,涉及大量計(jì)算和內(nèi)存處理。
首先我們需要在項(xiàng)目中安裝相關(guān)依賴包:
pnpm add pdfjs-dist
安裝完成之后,我們需要設(shè)置 WebWorker 的路徑,我們不使用 cdn 的文件,我們聰明人用聰明的方法:
我們可以將 node_modules 中 pdfjs-dist 目錄下的 build 目錄下 pdf.worker.mjs
文件放到 public/js
目錄下,但是要把 mjs 后綴改成 js。
最后在項(xiàng)目中引入即可:
import * as pdfjsLib from "pdfjs-dist"; pdfjsLib.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.js";
處理文件上傳
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return; try { const arrayBuffer = await file.arrayBuffer(); // 讀取文件為 ArrayBuffer const typedarray = new Uint8Array(arrayBuffer); // 轉(zhuǎn)換為 Uint8Array const loadingTask = pdfjsLib.getDocument(typedarray); // 使用 pdf.js 加載文檔 await loadPDFFile(loadingTask); // 加載 PDF 文件 } catch (err) { setError(err instanceof Error ? err.message : "Failed to read file"); console.error("File reading error:", err); } };
當(dāng)用戶選擇文件時(shí),將文件轉(zhuǎn)化為 ArrayBuffer
,然后再轉(zhuǎn)為 Uint8Array
,最終使用 pdfjsLib.getDocument
加載 PDF 文件并調(diào)用 loadPDFFile
。
加載 PDF 文件并提取圖像和文本
const loadPDFFile = async (loadingTask: pdfjsLib.PDFDocumentLoadingTask) => { try { setIsLoading(true); setError(null); const pdf = await loadingTask.promise; // 獲取 PDF 實(shí)例 const numPages = pdf.numPages; // 獲取 PDF 總頁數(shù) const allImages: PDFImage[] = []; const allTexts: PDFText[] = []; // 循環(huán)加載每一頁 for (let pageNumber = 1; pageNumber <= numPages; pageNumber++) { const page = await pdf.getPage(pageNumber); // 獲取單個(gè)頁面 // 提取圖像 const pageImages = await extractImagesFromPage(page, pageNumber); allImages.push(...pageImages); // 提取文本 const pageTexts = await extractTextFromPage(page, pageNumber); allTexts.push(...pageTexts); } setImages(allImages); // 更新圖像數(shù)據(jù) setTexts(allTexts); // 更新文本數(shù)據(jù) } catch (err) { setError(err instanceof Error ? err.message : "Failed to process PDF"); console.error("PDF processing error:", err); } finally { setIsLoading(false); } };
loadPDFFile
函數(shù)加載 PDF 文件并提取所有頁面的圖像和文本。通過 getPage
獲取每一頁,調(diào)用 extractImagesFromPage
和 extractTextFromPage
提取數(shù)據(jù)。
提取圖像
const extractImagesFromPage = async ( page: pdfjsLib.PDFPageProxy, pageNumber: number ): Promise<PDFImage[]> => { const extractedImages: PDFImage[] = []; const ops = await page.getOperatorList(); // 獲取頁面的操作列表 const imageNames = ops.fnArray.reduce<string[]>((acc, curr, i) => { if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintXObject].includes(curr)) { acc.push(ops.argsArray[i][0]); // 過濾出圖像對(duì)象名稱 } return acc; }, []); // 提取圖像 for (const imageName of imageNames) { try { const image = await new Promise<PDFImageObject>((resolve) => page.objs.get(imageName, resolve) // 獲取圖像對(duì)象 ); if (!image || !image.bitmap) continue; const bmp = image.bitmap; const resizeScale = 1 / 4; // 縮放比例 const width = Math.floor(bmp.width * resizeScale); // 計(jì)算縮放后的寬度 const height = Math.floor(bmp.height * resizeScale); // 計(jì)算縮放后的高度 const canvas = new OffscreenCanvas(width, height); // 創(chuàng)建離屏 canvas const ctx = canvas.getContext("bitmaprenderer"); if (!ctx) continue; ctx.transferFromImageBitmap(bmp); // 將圖片渲染到 canvas 上 const blob = await canvas.convertToBlob(); // 轉(zhuǎn)換為 Blob 對(duì)象 const imgURL = URL.createObjectURL(blob); // 生成圖像 URL extractedImages.push({ url: imgURL, pageNumber, }); } catch (err) { console.error(`Error processing image ${imageName}:`, err); } } return extractedImages; };
extractImagesFromPage
從頁面的操作列表中提取圖像對(duì)象,并將圖像渲染到離屏 OffscreenCanvas
上,最后轉(zhuǎn)換為 Blob 并生成 URL。返回一個(gè)包含所有圖像的數(shù)組。
提取文本
const extractTextFromPage = async ( page: pdfjsLib.PDFPageProxy, pageNumber: number ): Promise<PDFText[]> => { const extractedTexts: PDFText[] = []; try { const textContent = (await page.getTextContent()) as TextContent; // 獲取文本內(nèi)容 const text = textContent.items.map((item) => item.str).join(" "); // 拼接所有文本 extractedTexts.push({ text, pageNumber, }); } catch (err) { console.error(`Error processing text on page ${pageNumber}:`, err); } return extractedTexts; };
extractTextFromPage
使用 getTextContent
方法提取頁面的文本內(nèi)容,并將所有文本拼接成一個(gè)字符串,返回提取的文本。
完整代碼
完整代碼如下所示:
"use client"; import { useState } from "react"; import * as pdfjsLib from "pdfjs-dist"; pdfjsLib.GlobalWorkerOptions.workerSrc = "/js/pdf.worker.js"; interface PDFImage { url: string; pageNumber: number; } interface PDFText { text: string; pageNumber: number; } interface PDFImageObject { bitmap: ImageBitmap; } interface TextItem { str: string; dir: string; width: number; height: number; transform: number[]; fontName: string; } interface TextStyle { fontFamily: string; ascent: number; descent: number; vertical: boolean; } interface TextContent { items: TextItem[]; styles: Record<string, TextStyle>; } export default function Home() { const [images, setImages] = useState<PDFImage[]>([]); const [texts, setTexts] = useState<PDFText[]>([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState<string | null>(null); // 提取圖像 const extractImagesFromPage = async ( page: pdfjsLib.PDFPageProxy, pageNumber: number ): Promise<PDFImage[]> => { const extractedImages: PDFImage[] = []; const ops = await page.getOperatorList(); const imageNames = ops.fnArray.reduce<string[]>((acc, curr, i) => { if ( [pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintXObject].includes( curr ) ) { acc.push(ops.argsArray[i][0]); } return acc; }, []); for (const imageName of imageNames) { try { const image = await new Promise<PDFImageObject>((resolve) => page.objs.get(imageName, resolve) ); if (!image || !image.bitmap) continue; const bmp = image.bitmap; const resizeScale = 1 / 4; const width = Math.floor(bmp.width * resizeScale); const height = Math.floor(bmp.height * resizeScale); const canvas = new OffscreenCanvas(width, height); const ctx = canvas.getContext("bitmaprenderer"); if (!ctx) continue; ctx.transferFromImageBitmap(bmp); const blob = await canvas.convertToBlob(); const imgURL = URL.createObjectURL(blob); extractedImages.push({ url: imgURL, pageNumber, }); } catch (err) { console.error(`Error processing image ${imageName}:`, err); } } return extractedImages; }; // 提取文本 const extractTextFromPage = async ( page: pdfjsLib.PDFPageProxy, pageNumber: number ): Promise<PDFText[]> => { const extractedTexts: PDFText[] = []; try { const textContent = (await page.getTextContent()) as TextContent; const text = textContent.items.map((item) => item.str).join(" "); extractedTexts.push({ text, pageNumber, }); } catch (err) { console.error(`Error processing text on page ${pageNumber}:`, err); } return extractedTexts; }; // 加載 PDF 文件 const loadPDFFile = async (loadingTask: pdfjsLib.PDFDocumentLoadingTask) => { try { setIsLoading(true); setError(null); const pdf = await loadingTask.promise; const numPages = pdf.numPages; const allImages: PDFImage[] = []; const allTexts: PDFText[] = []; for (let pageNumber = 1; pageNumber <= numPages; pageNumber++) { const page = await pdf.getPage(pageNumber); // 提取圖像 const pageImages = await extractImagesFromPage(page, pageNumber); allImages.push(...pageImages); // 提取文本 const pageTexts = await extractTextFromPage(page, pageNumber); allTexts.push(...pageTexts); } setImages(allImages); setTexts(allTexts); // 設(shè)置提取的文本 } catch (err) { setError(err instanceof Error ? err.message : "Failed to process PDF"); console.error("PDF processing error:", err); } finally { setIsLoading(false); } }; // 處理文件上傳 const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return; try { const arrayBuffer = await file.arrayBuffer(); const typedarray = new Uint8Array(arrayBuffer); const loadingTask = pdfjsLib.getDocument(typedarray); await loadPDFFile(loadingTask); } catch (err) { setError(err instanceof Error ? err.message : "Failed to read file"); console.error("File reading error:", err); } }; const handleImageLoad = (imgURL: string) => { // Clean up object URL when image is loaded URL.revokeObjectURL(imgURL); }; return ( <div className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">PDF 圖像提取器</h1> <div className="mb-6"> <label className="block text-sm font-medium text-gray-700 mb-2"> 選擇 PDF 文件 <input type="file" accept="application/pdf" onChange={handleFileChange} className="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" /> </label> </div> {isLoading && ( <div className="text-center py-4"> <p className="text-blue-600">正在處理 PDF 文件,請(qǐng)稍候...</p> </div> )} {error && ( <div className="bg-red-50 border-l-4 border-red-500 p-4 mb-4"> <p className="text-red-700">{error}</p> </div> )} {!isLoading && images.length > 0 && ( <div> <h2 className="text-xl font-semibold mb-4">提取的圖像:</h2> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {images.map((image, index) => ( <div key={index} className="border rounded-lg p-4"> <img src={image.url} alt={`Extracted Image ${index + 1} from page ${ image.pageNumber }`} className="max-w-full h-auto" onLoad={() => handleImageLoad(image.url)} /> <p className="text-sm text-gray-600 mt-2"> 來自第 {image.pageNumber} 頁 </p> </div> ))} </div> </div> )} {!isLoading && !error && images.length === 0 && ( <p className="text-gray-600 text-center py-8"> 上傳 PDF 文件以提取其中的圖像 </p> )} {/* 顯示提取的文本 */} {!isLoading && texts.length > 0 && ( <div> <h2 className="text-xl font-semibold mb-4">提取的文本:</h2> <div className="space-y-4"> {texts.map((text, index) => ( <div key={index} className="border rounded-lg p-4"> <p className="text-sm text-gray-600">{text.text}</p> <p className="text-sm text-gray-600 mt-2"> 來自第 {text.pageNumber} 頁 </p> </div> ))} </div> </div> )} {!isLoading && !error && texts.length === 0 && ( <p className="text-gray-600 text-center py-8"> 上傳 PDF 文件以提取其中的文本 </p> )} </div> ); }
最終輸出結(jié)果如下圖所示:
這個(gè)原文檔是這樣的:
完美輸出
提取 Word 文檔
Mammoth 是一個(gè) JavaScript 庫,專門用于將 .docx
格式的文件轉(zhuǎn)換為 HTML 或其他格式。它的目標(biāo)是提供一個(gè)高質(zhì)量的 Word 文檔轉(zhuǎn)換工具,特別適用于將 Word 文檔內(nèi)容轉(zhuǎn)化為干凈、結(jié)構(gòu)化的 HTML,而不包含多余的樣式和復(fù)雜的 HTML 標(biāo)簽。與其他文檔轉(zhuǎn)換工具不同,Mammoth 強(qiáng)調(diào)簡潔和可讀性,幫助開發(fā)者輕松將 Word 文檔的內(nèi)容嵌入到網(wǎng)頁中。它特別適合處理簡單的文本內(nèi)容和基本的格式化。
接下來我們就用這個(gè)包來出來這個(gè)類型的文檔:
pnpm add mammoth
這里就不做前面講解得那么詳細(xì)了:
"use client"; import React, { useState } from "react"; import mammoth from "mammoth"; const FileUpload = () => { const [images, setImages] = useState<string[]>([]); // 存儲(chǔ)圖片 const [text, setText] = useState<string>(""); // 存儲(chǔ)提取的文本 const [error, setError] = useState<string | null>(null); // 存儲(chǔ)錯(cuò)誤信息 const [isLoading, setIsLoading] = useState(false); // 加載狀態(tài) // 處理文件上傳 const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0]; if (!file) return; setIsLoading(true); setError(null); setImages([]); setText(""); try { const arrayBuffer = await file.arrayBuffer(); await processDocxFile(arrayBuffer); } catch (err) { setError(err instanceof Error ? err.message : "Failed to read file"); } finally { setIsLoading(false); } }; // 使用 mammoth 提取文本和圖片 const processDocxFile = async (arrayBuffer: ArrayBuffer) => { try { const extractedImages: string[] = []; const options = { convertImage: mammoth.images.imgElement((image) => { return image.read("base64").then((imageBuffer) => { const imageType = image.contentType || "image/png"; const base64Image = `data:${imageType};base64,${imageBuffer}`; extractedImages.push(base64Image); return { src: base64Image, alt: "Extracted image", }; }); }), }; const result = await mammoth.convertToHtml({ arrayBuffer }, options); // 去除重復(fù)的文本 const uniqueText = removeDuplicateText(result.value); setText(uniqueText); setImages(extractedImages); if (result.messages.length > 0) { console.log("Conversion messages:", result.messages); } } catch (err) { setError(err instanceof Error ? err.message : "Failed to process file"); console.error("Error processing DOCX:", err); } }; // 去除文本中的重復(fù)內(nèi)容 const removeDuplicateText = (htmlText: string): string => { const cleanText = htmlText.replace(/<img[^>]*>/g, ""); // 移除 img 標(biāo)簽 const paragraphs = cleanText.split("<p>").filter((p) => p.trim()); const uniqueParagraphs = Array.from(new Set(paragraphs)); return uniqueParagraphs.map((p) => `<p>${p}`).join("\n"); }; return ( <div className="p-4"> <h1 className="text-2xl font-bold mb-4">Upload DOCX File</h1> <input type="file" accept=".docx" onChange={handleFileChange} className="mb-4 p-2 border rounded" /> {isLoading && <p className="text-blue-500">Processing...</p>} {error && <p className="text-red-500">{error}</p>} {/* 顯示提取的文本 */} {text && ( <div className="mt-4"> <h3 className="text-xl font-semibold">Extracted Text:</h3> <div dangerouslySetInnerHTML={{ __html: text }} className="mt-2 p-4 border rounded" /> </div> )} {/* 顯示提取的圖片 */} {images.length > 0 && ( <div className="mt-4"> <h3 className="text-xl font-semibold">Extracted Images:</h3> <div className="grid grid-cols-3 gap-4 mt-2"> {images.map((img, index) => ( <img key={index} src={img} alt={`Extracted Image ${index + 1}`} className="max-w-full h-auto border rounded" /> ))} </div> </div> )} </div> ); }; export default FileUpload;
這段代碼實(shí)現(xiàn)了一個(gè) DOCX 文件上傳并提取文本和圖片的功能。用戶通過文件上傳控件選擇一個(gè) .docx
文件,觸發(fā) handleFileChange
方法來讀取文件并將其轉(zhuǎn)換為 arrayBuffer
。然后,processDocxFile
函數(shù)使用 mammoth
庫對(duì)文件進(jìn)行處理,提取其中的文本和圖片。
在提取過程中,mammoth
通過 convertToHtml
方法將 DOCX 文件轉(zhuǎn)換為 HTML,同時(shí)通過 convertImage
選項(xiàng)將圖片提取為 Base64 編碼的格式。提取的文本會(huì)經(jīng)過去除重復(fù)內(nèi)容的處理,最終顯示在頁面上。圖片以 Base64 格式顯示,用戶可以查看提取的圖像。
isLoading
狀態(tài)用于顯示文件正在處理中,error
狀態(tài)用來捕獲并顯示錯(cuò)誤信息。提取的文本和圖片被存儲(chǔ)在 text
和 images
狀態(tài)變量中,并在界面上相應(yīng)地展示。
最終輸出結(jié)果如下圖所示;
參考
以上就是JavaScript提取PDF和Word文檔內(nèi)圖片的詳細(xì)內(nèi)容,更多關(guān)于JavaScript提取PDF和Word圖片的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript中querySelectorAll的基本用法及詳細(xì)解析
querySelectorAll是一個(gè)用于獲取文檔中所有匹配指定選擇器的元素的方法,這篇文章主要介紹了JavaScript中querySelectorAll的基本用法及詳細(xì)解析,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-04-04javascript中閉包c(diǎn)losure的深入講解
這篇文章主要給大家介紹了關(guān)于javascript中閉包c(diǎn)losure的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03在Z-Blog中運(yùn)行代碼[html][/html](純JS版)
在Z-Blog中運(yùn)行代碼[html][/html](純JS版)...2007-03-03javascript點(diǎn)擊按鈕實(shí)現(xiàn)隱藏顯示切換效果
這篇文章主要介紹了javascript點(diǎn)擊按鈕實(shí)現(xiàn)隱藏顯示切換效果,以一個(gè)完整的實(shí)例為大家分析了js點(diǎn)擊按鈕實(shí)現(xiàn)隱藏顯示切換的功能,感興趣的小伙伴們可以參考一下2016-02-02