JavaScript提取PDF和Word文檔內(nèi)圖片
最近接了個需求,要求就是基于文檔的 AI 問答,文檔里面最常見的就是 PDF 和 Word 文檔了,里面的內(nèi)容無非就是文本和圖片了,目前是沒有直接接收這種文檔的模型的,那么我們需要經(jīng)過一些處理來進行。
首先我們要先把圖片和文本來進行處理,我這邊的處理方式就是圖片調(diào)用圖片的模型來識別圖片信息,將返回的信息和文檔的文本作為后面的問答的前置 prompt,至于這些 prompt 就可以根據(jù)不同的需求來做不同處理了,這里不多解釋。
在接下來,我們將使用 NextJs 項目作為例子進行講解,后面的內(nèi)容跟框架依性不是很大,vue,astro 等項目都可以直接拿來使用。
提取 PDF 中的圖片
PDF.js 是一個開源的 JavaScript 庫,用于在網(wǎng)頁上直接顯示和渲染 PDF 文件。它將 PDF 文件解析為 HTML5 元素,使得瀏覽器可以無插件地加載和查看 PDF 文檔。PDF.js 支持多種功能,如文本選擇、搜索、頁面導航等,提供了良好的瀏覽體驗。通過它,開發(fā)者可以輕松集成 PDF 查看功能到網(wǎng)站或應用中。
為了避免 PDF 解析過程阻塞主線程,PDF.js 使用 Web Worker ,因為 PDF 解析是一個 CPU 密集型的操作,涉及大量計算和內(nèi)存處理。
首先我們需要在項目中安裝相關(guān)依賴包:
pnpm add pdfjs-dist
安裝完成之后,我們需要設置 WebWorker 的路徑,我們不使用 cdn 的文件,我們聰明人用聰明的方法:

我們可以將 node_modules 中 pdfjs-dist 目錄下的 build 目錄下 pdf.worker.mjs 文件放到 public/js 目錄下,但是要把 mjs 后綴改成 js。
最后在項目中引入即可:
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);
}
};
當用戶選擇文件時,將文件轉(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 實例
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); // 獲取單個頁面
// 提取圖像
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]); // 過濾出圖像對象名稱
}
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); // 創(chuàng)建離屏 canvas
const ctx = canvas.getContext("bitmaprenderer");
if (!ctx) continue;
ctx.transferFromImageBitmap(bmp); // 將圖片渲染到 canvas 上
const blob = await canvas.convertToBlob(); // 轉(zhuǎn)換為 Blob 對象
const imgURL = URL.createObjectURL(blob); // 生成圖像 URL
extractedImages.push({
url: imgURL,
pageNumber,
});
} catch (err) {
console.error(`Error processing image ${imageName}:`, err);
}
}
return extractedImages;
};
extractImagesFromPage 從頁面的操作列表中提取圖像對象,并將圖像渲染到離屏 OffscreenCanvas 上,最后轉(zhuǎn)換為 Blob 并生成 URL。返回一個包含所有圖像的數(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)容,并將所有文本拼接成一個字符串,返回提取的文本。
完整代碼
完整代碼如下所示:
"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); // 設置提取的文本
} 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 文件,請稍候...</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é)果如下圖所示:

這個原文檔是這樣的:

完美輸出
提取 Word 文檔
Mammoth 是一個 JavaScript 庫,專門用于將 .docx 格式的文件轉(zhuǎn)換為 HTML 或其他格式。它的目標是提供一個高質(zhì)量的 Word 文檔轉(zhuǎn)換工具,特別適用于將 Word 文檔內(nèi)容轉(zhuǎn)化為干凈、結(jié)構(gòu)化的 HTML,而不包含多余的樣式和復雜的 HTML 標簽。與其他文檔轉(zhuǎn)換工具不同,Mammoth 強調(diào)簡潔和可讀性,幫助開發(fā)者輕松將 Word 文檔的內(nèi)容嵌入到網(wǎng)頁中。它特別適合處理簡單的文本內(nèi)容和基本的格式化。
接下來我們就用這個包來出來這個類型的文檔:
pnpm add mammoth
這里就不做前面講解得那么詳細了:
"use client";
import React, { useState } from "react";
import mammoth from "mammoth";
const FileUpload = () => {
const [images, setImages] = useState<string[]>([]); // 存儲圖片
const [text, setText] = useState<string>(""); // 存儲提取的文本
const [error, setError] = useState<string | null>(null); // 存儲錯誤信息
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);
// 去除重復的文本
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);
}
};
// 去除文本中的重復內(nèi)容
const removeDuplicateText = (htmlText: string): string => {
const cleanText = htmlText.replace(/<img[^>]*>/g, ""); // 移除 img 標簽
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;
這段代碼實現(xiàn)了一個 DOCX 文件上傳并提取文本和圖片的功能。用戶通過文件上傳控件選擇一個 .docx 文件,觸發(fā) handleFileChange 方法來讀取文件并將其轉(zhuǎn)換為 arrayBuffer。然后,processDocxFile 函數(shù)使用 mammoth 庫對文件進行處理,提取其中的文本和圖片。
在提取過程中,mammoth 通過 convertToHtml 方法將 DOCX 文件轉(zhuǎn)換為 HTML,同時通過 convertImage 選項將圖片提取為 Base64 編碼的格式。提取的文本會經(jīng)過去除重復內(nèi)容的處理,最終顯示在頁面上。圖片以 Base64 格式顯示,用戶可以查看提取的圖像。
isLoading 狀態(tài)用于顯示文件正在處理中,error 狀態(tài)用來捕獲并顯示錯誤信息。提取的文本和圖片被存儲在 text 和 images 狀態(tài)變量中,并在界面上相應地展示。
最終輸出結(jié)果如下圖所示;

參考
以上就是JavaScript提取PDF和Word文檔內(nèi)圖片的詳細內(nèi)容,更多關(guān)于JavaScript提取PDF和Word圖片的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript中querySelectorAll的基本用法及詳細解析
querySelectorAll是一個用于獲取文檔中所有匹配指定選擇器的元素的方法,這篇文章主要介紹了JavaScript中querySelectorAll的基本用法及詳細解析,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2025-04-04
在Z-Blog中運行代碼[html][/html](純JS版)
在Z-Blog中運行代碼[html][/html](純JS版)...2007-03-03
javascript點擊按鈕實現(xiàn)隱藏顯示切換效果
這篇文章主要介紹了javascript點擊按鈕實現(xiàn)隱藏顯示切換效果,以一個完整的實例為大家分析了js點擊按鈕實現(xiàn)隱藏顯示切換的功能,感興趣的小伙伴們可以參考一下2016-02-02

