Извлечь текст из pdf в Javascript
Интересно, можно ли получить текст внутри PDF файла, используя только Javascript?
Если да, может ли кто-нибудь показать мне, как?
Я знаю, что есть некоторые библиотеки java, С#, etc на стороне сервера, но я бы предпочел не использовать сервер.
спасибо
Ответы
Ответ 1
Это древний вопрос, но поскольку pdf.js развивается на протяжении многих лет, я хотел бы дать новый ответ. То есть, это можно сделать локально без привлечения какого-либо сервера или внешней службы. Новый pdf.js имеет функцию: page.getTextContent(). Вы можете получить текстовое содержимое. Я сделал это успешно со следующим кодом.
-
То, что вы получаете на каждом шаге, - это обещание. Вам нужно закодировать следующим образом: .then( function(){...})
, чтобы перейти к следующему шагу.
1) PDFJS.getDocument( data ).then( function(pdf) {
2) pdf.getPage(i).then( function(page){
3) page.getTextContent().then( function(textContent){
-
То, что вы получили, это массив строк textContent.bidiTexts[]
. Вы объединяете их, чтобы получить текст 1 страницы. Координаты текстовых блоков используются для определения необходимости вставки новой строки или пространства. (Это может быть не совсем здорово, но из моего теста это выглядит нормально.)
-
Входной параметр data
должен быть либо типом URL, либо типом массива ArrayBuffer. Я использовал функцию ReadAsArrayBuffer (файл) в FileReader
API для получения данных.
Надеюсь, что это поможет.
Примечание.. По словам другого пользователя, библиотека обновилась и вызвала разрыв кода. Согласно комментарию от async5 ниже, вам нужно заменить textContent.bidiTexts
на textContent.items
.
function Pdf2TextClass(){
var self = this;
this.complete = 0;
/**
*
* @param data ArrayBuffer of the pdf file content
* @param callbackPageDone To inform the progress each time
* when a page is finished. The callback function input parameters are:
* 1) number of pages done;
* 2) total number of pages in file.
* @param callbackAllDone The input parameter of callback function is
* the result of extracted text from pdf file.
*
*/
this.pdfToText = function(data, callbackPageDone, callbackAllDone){
console.assert( data instanceof ArrayBuffer || typeof data == 'string' );
PDFJS.getDocument( data ).then( function(pdf) {
var div = document.getElementById('viewer');
var total = pdf.numPages;
callbackPageDone( 0, total );
var layers = {};
for (i = 1; i <= total; i++){
pdf.getPage(i).then( function(page){
var n = page.pageNumber;
page.getTextContent().then( function(textContent){
if( null != textContent.bidiTexts ){
var page_text = "";
var last_block = null;
for( var k = 0; k < textContent.bidiTexts.length; k++ ){
var block = textContent.bidiTexts[k];
if( last_block != null && last_block.str[last_block.str.length-1] != ' '){
if( block.x < last_block.x )
page_text += "\r\n";
else if ( last_block.y != block.y && ( last_block.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null ))
page_text += ' ';
}
page_text += block.str;
last_block = block;
}
textContent != null && console.log("page " + n + " finished."); //" content: \n" + page_text);
layers[n] = page_text + "\n\n";
}
++ self.complete;
callbackPageDone( self.complete, total );
if (self.complete == total){
window.setTimeout(function(){
var full_text = "";
var num_pages = Object.keys(layers).length;
for( var j = 1; j <= num_pages; j++)
full_text += layers[j] ;
callbackAllDone(full_text);
}, 1000);
}
}); // end of page.getTextContent().then
}); // end of page.then
} // of for
});
}; // end of pdfToText()
}; // end of class
Ответ 2
Я не мог заставить пример gm2008 работать (внутренняя структура данных на pdf.js изменилась, по-видимому), поэтому я написал собственное полностью обещанное решение, которое не использует никаких элементов DOM, queryselectors или canvas, используя обновленный pdf.js из примера в mozilla
Он ест путь к файлу для загрузки, так как я использую его с node -webkit.
Вам нужно убедиться, что вы загрузили и указали какие-то пиксели, а вы - pdf.js и pdf.worker.js, чтобы получить эту работу.
/**
* Extract text from PDFs with PDF.js
* Uses the demo pdf.js from https://mozilla.github.io/pdf.js/getting_started/
*/
this.pdfToText = function(data) {
PDFJS.workerSrc = 'js/vendor/pdf.worker.js';
PDFJS.cMapUrl = 'js/vendor/pdfjs/cmaps/';
PDFJS.cMapPacked = true;
return PDFJS.getDocument(data).then(function(pdf) {
var pages = [];
for (var i = 0; i < pdf.numPages; i++) {
pages.push(i);
}
return Promise.all(pages.map(function(pageNumber) {
return pdf.getPage(pageNumber + 1).then(function(page) {
return page.getTextContent().then(function(textContent) {
return textContent.items.map(function(item) {
return item.str;
}).join(' ');
});
});
})).then(function(pages) {
return pages.join("\r\n");
});
});
}
использование:
self.pdfToText(files[0].path).then(function(result) {
console.log("PDF done!", result);
})
Ответ 3
Вот код JavaScript, который делает то, что вы хотите, используя Pdf.js из http://hublog.hubmed.org/archives/001948.html:
var input = document.getElementById("input");
var processor = document.getElementById("processor");
var output = document.getElementById("output");
// listen for messages from the processor
window.addEventListener("message", function(event){
if (event.source != processor.contentWindow) return;
switch (event.data){
// "ready" = the processor is ready, so fetch the PDF file
case "ready":
var xhr = new XMLHttpRequest;
xhr.open('GET', input.getAttribute("src"), true);
xhr.responseType = "arraybuffer";
xhr.onload = function(event) {
processor.contentWindow.postMessage(this.response, "*");
};
xhr.send();
break;
// anything else = the processor has returned the text of the PDF
default:
output.textContent = event.data.replace(/\s+/g, " ");
break;
}
}, true);
... и вот пример:
http://git.macropus.org/2011/11/pdftotext/example/
Ответ 4
Примечание. В этом коде предполагается, что вы используете nodejs. Это означает, что вы анализируете локальный файл вместо файла с веб-страницы, поскольку в исходном вопросе явно не задается вопрос о разборе pdf на веб-странице.
Ответ @gm2008 был отличной отправной точкой (пожалуйста, прочитайте его и его комментарии для получения дополнительной информации), но потребовались некоторые обновления (08/19) и некоторый неиспользованный код. Мне также нравятся примеры, которые являются более полными. Существует больше возможностей рефакторинга и настройки (например, с помощью await
), но на данный момент он настолько близок к первоначальному ответу, насколько это возможно.
Как и прежде, здесь используется библиотека Mozilla PDFjs. Пакет npmjs находится в https://www.npmjs.com/package/pdfjs-dist.
По моему опыту, это не помогает найти место для пробелов, но это проблема для другого времени.
[Редактировать: я полагаю, что обновление для использования .transform
восстановило пробелы в том виде, в котором оно было изначально.]
// This file is called myPDFfileToText.js and is in the root folder
let PDFJS = require('pdfjs-dist');
let pathToPDF = 'path/to/myPDFfileToText.pdf';
let toText = Pdf2TextObj();
let onPageDone = function() {}; // don't want to do anything between pages
let onFinish = function(fullText) { console.log(fullText) };
toText.pdfToText(pathToPDF, onPageDone, onFinish);
function Pdf2TextObj() {
let self = this;
this.complete = 0;
/**
*
* @param path Path to the pdf file.
* @param callbackPageDone To inform the progress each time
* when a page is finished. The callback function input parameters are:
* 1) number of pages done.
* 2) total number of pages in file.
* 3) the 'page' object itself or null.
* @param callbackAllDone Called after all text has been collected. Input parameters:
* 1) full text of parsed pdf.
*
*/
this.pdfToText = function(path, callbackPageDone, callbackAllDone) {
// console.assert(typeof path == 'string');
PDFJS.getDocument(path).promise.then(function(pdf) {
let total = pdf.numPages;
callbackPageDone(0, total, null);
let pages = {};
// For some (pdf?) reason these don't all come in consecutive
// order. That why they're stored as an object and then
// processed one final time at the end.
for (let pagei = 1; pagei <= total; pagei++) {
pdf.getPage(pagei).then(function(page) {
let pageNumber = page.pageNumber;
page.getTextContent().then(function(textContent) {
if (null != textContent.items) {
let page_text = "";
let last_item = null;
for (let itemsi = 0; itemsi < textContent.items.length; itemsi++) {
let item = textContent.items[itemsi];
// I think to add whitespace properly would be more complex and
// would require two loops.
if (last_item != null && last_item.str[last_item.str.length - 1] != ' ') {
let itemX = item.transform[5]
let lastItemX = last_item.transform[5]
let itemY = item.transform[4]
let lastItemY = last_item.transform[4]
if (itemX < lastItemX)
page_text += "\r\n";
else if (itemY != lastItemY && (last_item.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null))
page_text += ' ';
} // ends if may need to add whitespace
page_text += item.str;
last_item = item;
} // ends for every item of text
textContent != null && console.log("page " + pageNumber + " finished.") // " content: \n" + page_text);
pages[pageNumber] = page_text + "\n\n";
} // ends if has items
++self.complete;
callbackPageDone(self.complete, total, page);
// If all done, put pages in order and combine all
// text, then pass that to the callback
if (self.complete == total) {
// Using 'setTimeout()' isn't a stable way of making sure
// the process has finished. Watch out for missed pages.
// A future version might do this with promises.
setTimeout(function() {
let full_text = "";
let num_pages = Object.keys(pages).length;
for (let pageNum = 1; pageNum <= num_pages; pageNum++)
full_text += pages[pageNum];
callbackAllDone(full_text);
}, 1000);
}
}); // ends page.getTextContent().then
}); // ends page.then
} // ends for every page
});
}; // Ends pdfToText()
return self;
}; // Ends object factory
Запустите в терминале:
node myPDFfileToText.js
Ответ 5
Для всех людей, которые действительно хотят использовать его на сервере node:
/**
* Created by velten on 25.04.16.
*/
"use strict";
let pdfUrl = "http://example.com/example.pdf";
let request = require('request');
var pdfParser = require('pdf2json');
let pdfPipe = request({url: pdfUrl, encoding:null}).pipe(pdfParser);
pdfPipe.on("pdfParser_dataError", err => console.error(err) );
pdfPipe.on("pdfParser_dataReady", pdf => {
//optionally:
//let pdf = pdfParser.getMergedTextBlocksIfNeeded();
let count1 = 0;
//get text on a particular page
for (let page of pdf.formImage.Pages) {
count1 += page.Texts.length;
}
console.log(count1);
pdfParser.destroy();
});
Ответ 6
Возможно, но:
- вам придется использовать сервер в любом случае, вы не сможете получить содержимое файла на компьютере пользователя, не передавая его на сервер и обратно
- Я не знаю, чтобы кто-нибудь написал такую библиотеку еще
Итак, если у вас есть свободное время, вы можете изучить pdf-формат и написать такую библиотеку самостоятельно, или вы можете просто использовать серверную библиотеку, конечно.