Подтвердить что ты не робот

Извлечь текст из pdf в Javascript

Интересно, можно ли получить текст внутри PDF файла, используя только Javascript? Если да, может ли кто-нибудь показать мне, как?

Я знаю, что есть некоторые библиотеки java, С#, etc на стороне сервера, но я бы предпочел не использовать сервер. спасибо

4b9b3361

Ответ 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-формат и написать такую ​​библиотеку самостоятельно, или вы можете просто использовать серверную библиотеку, конечно.