【问题标题】:How to extract text from PDF in JavaSript如何在 JavaScript 中从 PDF 中提取文本
【发布时间】:2010-12-05 23:20:36
【问题描述】:

我想知道是否可以仅使用 Javascript 来获取 PDF 文件中的文本? 如果是的话,谁能告诉我怎么做?

我知道有一些服务器端 java、c# 等库,但我不想使用服务器。 谢谢

【问题讨论】:

    标签: javascript pdf text


    【解决方案1】:

    这是一个古老的问题,但由于pdf.js多年来一直在发展,我想给出一个新的答案。也就是说,它可以在本地完成,而不涉及任何服务器或外部服务。新的 pdf.js 有一个函数:page.getTextContent()。您可以从中获取文本内容。我已经使用以下代码成功完成了它。

    1. 您在每一步中得到的都是一个承诺。您需要这样编码:.then( function(){...}) 才能继续下一步。

      1) PDFJS.getDocument( data ).then( function(pdf) {

      2) pdf.getPage(i).then( function(page){

      3)page.getTextContent().then( function(textContent){

    2. 你最终得到的是一个字符串数组textContent.bidiTexts[]。您将它们连接起来以获取 1 页的文本。文本块的坐标用于判断是否需要插入换行符或空格。 (这可能并不完全健壮,但从我的测试看来还可以。)

    3. 输入参数data 需要是 URL 或 ArrayBuffer 类型的数据。我使用FileReader API 中的 ReadAsArrayBuffer(file) 函数来获取数据。

    希望这会有所帮助。

    注意:根据其他一些用户的说法,该库已更新并导致代码中断。根据下面 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's 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
    

    【讨论】:

    • Ancient question 但很好的答案。您知道如何让 textLayer 不在单个 div 中呈现字符而是将它们呈现为整个单词?尝试使用与绝对定位的 div 重叠的文本层对我的性能造成了很大的影响,因为它们太多了。如果您希望将此作为单独的实际 StackOverflow 问题,我会提出一个。
    • @gm2008 我一直在尝试使用您的功能从 PDF 中提取文本。但是,我无法提取文本。 full_text 最后返回一个空字符串。你能帮忙吗?
    • 我也无法让它工作(API 已更改)。在下面添加了我自己的示例。
    • 用 textContent.items 替换 textContent.bidiTexts
    【解决方案2】:

    我无法让 gm2008 的示例工作(pdf.js 上的内部数据结构明显改变了),所以我编写了自己的完全基于 Promise 的解决方案,不使用任何 DOM 元素、查询选择器或画布,使用mozilla 示例中更新的 pdf.js

    因为我将它与 node-webkit 一起使用,所以它吃掉了上传的文件路径。 您需要确保已下载 cmaps 并指向某处,并且需要 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 代码,可以使用来自 http://hublog.hubmed.org/archives/001948.html 的 Pdf.js 执行您想要的操作:

    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 的回答是一个很好的起点(请阅读它及其 cmets 了解更多信息),但需要一些更新 (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's 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's 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】:

    2021 年 2 月更新

    <script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
        <script>
        
    function Pdf2TextClass(){
        var self = this;
        this.complete = 0;
    
        this.pdfToText = function(data, callbackPageDone, callbackAllDone){
        console.assert( data  instanceof ArrayBuffer  || typeof data == 'string' );
        var loadingTask = pdfjsLib.getDocument(data);
        loadingTask.promise.then(function(pdf) {
    
    
        var total = pdf._pdfInfo.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){
           
           //console.log(textContent.items[0]);0
             if( null != textContent.items ){
               var page_text = "";
               var last_block = null;
               for( var k = 0; k < textContent.items.length; k++ ){
                   var block = textContent.items[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] ;
                 console.log(full_text);
               }, 1000);              
             }
           }); // end  of page.getTextContent().then
         }); // end of page.then
       } // of for
     });
    }; // end of pdfToText()
    }; // end of class
    var pdff = new Pdf2TextClass();
    pdff.pdfToText('PDF_URL');
        </script>
    

    【讨论】:

      【解决方案6】:

      在这里留下一个完整的工作示例。

      <html>
          <head>
              <script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
          </head>
          <body>
              <input id="pdffile" name="pdffile" type="file" />
              <button id="btn" onclick="convert()">Process</button>
              <div id="result"></div>
          </body>
      </html>
      
      <script>
      
          function convert() {
              var fr=new FileReader();
              var pdff = new Pdf2TextClass();
              fr.onload=function(){
                  pdff.pdfToText(fr.result, null, (text) => { document.getElementById('result').innerText += text; });
              }
              fr.readAsDataURL(document.getElementById('pdffile').files[0])
              
          }
      
          function Pdf2TextClass() {
              var self = this;
              this.complete = 0;
      
              this.pdfToText = function (data, callbackPageDone, callbackAllDone) {
                  console.assert(data instanceof ArrayBuffer || typeof data == 'string');
                  var loadingTask = pdfjsLib.getDocument(data);
                  loadingTask.promise.then(function (pdf) {
      
      
                      var total = pdf._pdfInfo.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) {
      
                                  //console.log(textContent.items[0]);0
                                  if (null != textContent.items) {
                                      var page_text = "";
                                      var last_block = null;
                                      for (var k = 0; k < textContent.items.length; k++) {
                                          var block = textContent.items[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
      
      </script>

      【讨论】:

        【解决方案7】:

        对于所有真正想在节点服务器上使用它的人:

        /**
         * 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();
        });
        

        【讨论】:

        • "dest.on 不是函数"
        • @BartusZak foo.bar 也不是函数 ;)
        【解决方案8】:

        有可能,但是:

        • 无论如何,您都必须使用服务器,如果不将文件传输到服务器并返回,您将无法在用户计算机上获取文件的内容
        • 我觉得还没有人写过这样的库

        所以如果你有空闲时间你可以学习pdf格式并自己写一个这样的库,或者你当然可以使用服务器端库。

        【讨论】:

          猜你喜欢
          • 2019-12-05
          • 2023-04-02
          • 2016-04-22
          • 2010-11-11
          • 1970-01-01
          • 2015-08-17
          • 1970-01-01
          相关资源
          最近更新 更多