node js 批量處理pdf,提取關鍵信息,並導出excel

batchPDF

a node programing that fetch key infomation from more than two thousand pdf documents,and output in exceljavascript

需求描述:處理同一目錄下的2000個pdf文件,提取每一個文件中的一些關鍵信息(單位名稱,統一社會信用代碼,行業類別),並整理數據導出爲excel表格。html


  最近在看node文件處理,剛好發現校友羣裏有個土木專業的同窗提出這麼一個問題,當時的第一想法就是我也許能夠作,而後就找到了那個同窗問清楚了明確需求,而且要了部分pdf文件,開始作......   個人第一想法就是,首先讀取目錄下的文件,而後對每一個文件內容,進行正則匹配,找出目的信息,而後再導出。事實上也是這麼回事,基本上分爲三步:前端

  1. 讀取文件
  2. 解析文件,匹配關鍵字。
  3. 導出excel

讀取文件

  node讀取pdf文件,引入了'pdf2json':java

npm install pdf2json --save
複製代碼

使用這個包,能夠將pdf解析爲json格式,從而獲得文件的內容node

const PDFParser = require('pdf2json');
const src = './pdf';

var pdfParser = new PDFParser(this, 1);
pdfParser.loadPDF(`${src}/${path}`);
pdfParser.on('pdfParser_dataError', errData =>reject( new Error(errData.parserError)));
pdfParser.on('pdfParser_dataReady', () => {
    let data = pdfParser.getRawTextContent();

});
複製代碼

使用正則表達式匹配出關鍵字

  目標是找出每一個文件中的「單位名稱」、」統一社會信用代碼「、「行業類別」,仔細分析上一過程當中輸出的結果:python

輸出結果

  由於要處理的文件內容格式都很是嚴謹,咱們所要獲取的信息都在第三頁,解析出的json數據中,目標文本分佈在page(1)和page(3)中,且目標文本格式都是key:value的格式,每個文本都換行,因此處理起來就方便多了,最終匹配的是以「單位名稱:」開頭的一個或者多個非空字符,因爲要匹配三個值,因此用(red|blue|green)這種方式來查找目標值。git

let result = data.match(/(統一社會信用代碼|單位名稱|行業類別):[\S]*/g);
複製代碼

match匹配最終獲得一個數組:github

result = ['統一社會信用代碼:xxx','單位名稱:xxx','行業類別:xxx']
複製代碼

導出爲excel表格

  網上有不少js代碼將table導出爲excel的代碼,這裏使用了'node-xlsx',安裝:web

npm install node-xlsx --save
複製代碼

使用這個是由於簡單,而且也符合需求,上手快。正則表達式

const xlsx = require('node-xlsx');
var buffer = xlsx.build([{name: 'company', data: list}]);
fs.writeFileSync('list.csv', buffer, 'binary');
複製代碼

  三行代碼就搞定了,就獲得了一個csv格式的excel,剩下的處理就是對list的處理了,傳入的list需爲一個二維數組,數組的第一項爲表頭,其餘項爲每一行對應得數據,也爲數組格式。整理的list以下:

[
  ['序號','統一社會信用代碼','單位名稱','行業類別'],
  ['xxx','xxx','xxx','xxxx']
]
複製代碼

  解析PDF的過程爲異步,因此在批量處理大量文件的狀況下,要考慮內存泄漏問題,每次只處理五個,處理完成以後再去處理剩餘的文件,直到所有完成處理,輸出爲excel。 當文件數量超過30個,報錯信息以下:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
複製代碼

報錯

出現問題的緣由:

  1. 網上有一種回答是:解析的是一個大的文件,轉換爲json後,至關於操做一個巨大的對象,因此會報錯,可是文件數量小的時候,解析是正常的,因此這種假設能夠排除。
  2. 內存溢出,程序執行所須要的內存超出了系統分配給內存大小

  解釋:因爲node是基於V8引擎,在node中經過javascript使用內存時只能使用部份內存,64位系統下約爲1.4GB,32位系統下約爲0.7GB,當執行的程序佔用系統資源太高,超出了V8對node默認的內存限制大小就會報上圖所示錯誤。

  若是是編譯項目,V8提供的默認內存大小不夠用,能夠去修改 --max-old-space-size,可是我目前的需求是處理2000多個pdf文件,解析爲json,因此使用的內存大小是不肯定的,不能採起這種方案。

  個人理解:node js 不少操做都是異步執行的,而異步操做的結果就是不能確保執行完成的時間,因此當多個異步操做同時進行的時候,不能確保執行完成的順序,可是執行的結果又是相互關聯的,因此在執行的時候,會一直佔用內存,而不被垃圾回收機制回收,形成內存泄漏。(也有一種多是隊列裏等待執行的任務太多了。。。)


錯誤的代碼

const PDFParser = require('pdf2json');
const fs = require('fs');
const src = './pdf';
const xlsx = require('node-xlsx');
let list = [['序號','統一社會信用代碼','單位名稱','行業類別']];
let index = 1;
let len = 0;

fs.readdir(src, (err, files) => {
    len = files.length;
    files.forEach(item => {
        var pdfParser = new PDFParser(this, 1);
        pdfParser.loadPDF(`${src}/${item}`);
        pdfParser.on('pdfParser_dataError', errData => console.error(errData.parserError)); pdfParser.on('pdfParser_dataReady', () => {
            let data = pdfParser.getRawTextContent();
            let result = data.match(/(統一社會信用代碼|單位名稱|行業類別):[\S]*/g);
            for (let i = 0 ;i < 3;++i){
                result[i] = result[i].split(':')[1];
            }
            list.push(result);
            ++index;
            if( index === len){
                var buffer = xlsx.build([{name: 'company', data: list}]); // Returns a buffer
                fs.writeFileSync('list.csv', buffer, 'binary');
            }
        });
    });
});
複製代碼

  可是究竟這個異步操做的併發量的上限是多少,不能肯定,有一個同窗嘗試過,讀取PDF文件的時候,上限是30,分析以上結果,進行改進,改進以後,每次執行五個異步操做,執行完成以後再繼續執行下一個五個異步函數。

  測試過,這種方式處理100個文件時沒有問題的,對比了兩種方式方法,以34個文件爲測試用例:

方法 | 文件數量 | 讀取時間(s) | CPU | 內存

  • | :-: |:-: -: | :-: | :-: 方法一 | 34| 26.817 | 暴漲(14%-42%) | 最大(1591MB) 方法二 | 34| 19.374 | (36%)平穩 | 最大(300MB)

改進後核心代碼

ConvertToJSON(path){
    return new Promise((resolve,reject) => {
        var pdfParser = new PDFParser(this, 1);
        pdfParser.loadPDF(`${src}/${path}`);
        pdfParser.on('pdfParser_dataError', errData =>reject( new Error(errData.parserError)));
        pdfParser.on('pdfParser_dataReady', () => {
            // 省略處理部分
            resolve(result);
        });
    }).catch(error => {
        console.log(error);
    });
}

seek(callback){
    let arr = this.files.splice(0,5);
    let all = [];
    arr.forEach(item => {
        all.push(this.ConvertToJSON(item));
    });
    let promise = Promise.all(all);
    promise.then(result => {
       // 省略處理部分
        return this.files.length === 0 ? callback(this.list) : this.seek(callback);
    });
}
複製代碼

源碼地址,歡迎指正。   可以幫助到別人同時本身又嘗試了新鮮事物,因此以爲很開心。

參考文檔:

  1. Node.js v8.9.3 文檔
  2. nodejs將PDF文件轉換成txt文本,並利用python處理轉換後的文本文件
  3. node-xlsx
  4. nodeJs內存泄漏問題詳解
  5. Node.js 中的 UnhandledPromiseRejectionWarning 問題
  6. 4種JavaScript的內存泄露及避免方法
  7. JavaScript 工做原理之二-如何在 V8 引擎中書寫最優代碼的 5 條小技巧(譯)

在此鳴謝大學好友邢旭磊。

個人我的博客:下雨天DY的前端成長記

相關文章
相關標籤/搜索