前説
Node.jsでPDFの操作をやった事がなかったので勉強がてら、手を出したらヒドイ目にあった。(ぇ
自前でPDFを、こねくり回すのはシンドイのでライブラリを探したけど、日本では、あまり「やってみた系」がありませんでした。
npmのライブラリを漁った所、hummusが良さそうだったので、このライブラリの検証がてらLambdaで実装してみました。
出力先のS3バケット名をLambdaの環境変数に指定して、S3インプットトリガーで起動するだけで、PDFファイルのページ毎分割ができるLambdaを実装しました。
このLambdaを利用する場合は、S3のputトリガーを仕込むS3バケットと、分割したPDFファイルを保存するようのS3バケット、必ず2つ用意してください。
間違っても、putトリガーを仕込んだS3バケットに分割したPDFファイルを保存して無限ループさせる事がないようにしてください。
hummusのライブラリがサイズ(約12MB)が大きくて、S3に一度アップしてからじゃないとLambdaに登録できないので注意してください。
hummusは、内部でコンパイルしてるので、Windows環境で
「npm install hummus」しても動作しません。
開発プロジェクトから作成したい方は、AmazonLinuxのEC2を一時的に起動して開発プロジェクトを作る必要があります。
githubで公開してる物を落とした場合はAWS Lambdaで動作します。
githubにて公開
S3プットトリガー + Lambda + S3 のサンプルソースです。
ダウンロードは下記より
https://github.com/SyoAwsBlog/ShoLambdaSample07
- S3putトリガーを受け取ってS3オブジェクトをLambdaの/tmp配下に保存する
- PDFファイルをhummusに喰わせて構造解析する
- 1ページ毎のデータを構築して環境変数で指定したS3バケットに保存する
- /tmp配下の保存したPDFファイルを削除する
- S3putトリガーで受け取った原本のPDFファイルを削除する
などの仕様を盛り込んでいます。
利用可能な環境変数
変数名 | 変数値 |
---|---|
LogLevel | ログの出力レベルを(0~4)までの間で設定する |
LogLevelForWorker | ログの出力レベルを(0~4)までの間で設定する(ワーカー処理(疑似スレッド処理)) |
ExecutorsThreadsNum | 並行処理の多重度を数字で指定 |
ExecutorsThreadsWait | ワーカー処理(疑似スレッド処理)毎に Sleepを入れられる(ミリ秒指定) |
OutputS3BucketName | ページ分割したPDFファイルを保存するS3バケット名 |
概要というか実装時の四方山話
S3から取得したオブジェクトをメモリ展開したまま、ページ分割データをS3バケットにアップできたら良いなぁ~と思って、試行削除したのですが、、、
「TypeError: Unable to start parsing PDF file」
ライブラリの出力してくる、このエラーが潰せなかった。
エラーとしては、不正な形式ファイルを読み込んだ時に発生するエラーっぽいのだけど、一旦、/tmpにおいて取り込むと処理できるという。。。
(誰か、この謎を解いてください(ぇ
hummusにBufferから取り込む為のクラスも用意はされてるですが、上手く動かない。issueとかを見てると、デカいファイルの時はローカルにファイルを保存してしまった方が格段に処理が早いとのコメントもあったので、/tmpに置く方式で実装しています。
主要な基底処理(制御側)
まずは制御側のS3putトリガーからデータ取得してハンドリングする制御処理から説明します。
AbstractS3PutTriggerCommon 565行目
AbstractS3PutTriggerCommon.prototype.AbstractBaseCommon.getTasks = function (
event,
context
) {
var base = AbstractS3PutTriggerCommon.prototype.AbstractBaseCommon;
try {
base.writeLogTrace("AbstractS3PutTriggerCommon# getTasks :start");
return [
this.initS3PutTriggerParameter,
this.waitExistsS3Object,
this.beforeMainExecute,
this.pdfParse,
this.businessMainExecute,
this.afterTmpDeleteExecute,
this.afterMainExecute,
];
} catch (err) {
base.printStackTrace(err);
throw err;
} finally {
base.writeLogTrace("AbstractS3PutTriggerCommon# getTasks :end");
}
};
- initS3PutTriggerParameter ・・・ putトリガーからS3取得のパラメータを返却
- waitExistsS3Object ・・・ S3のオブジェクト存在確認
- beforeMainExecute ・・・S3オブジェクトデータ(PDF)を/tmpに保存
- pdfParse ・・・PDF構造解析データとページ分割制御データの作成
- businessMainExecute ・・・非同期多重実行の制御
- afterTmpDeleteExecute ・・・/tmpに保存したデータ削除
- afterMainExecute ・・・ putトリガー元となったS3オブジェクト削除
この処理では1ページ毎にファイル分割をしていますが、複数ページでファイル分割した場合は、「pdfParse」で作成しているワーカ処理用のデータを改造すると出来ると思います。
主要な基底処理(ワーカー側)
AbstractWorkerS3UploaderCommon・・・226行目
AbstractWorkerS3UploaderCommon.prototype.AbstractBaseCommon.getTasks = function (
event,
context
) {
var base = AbstractWorkerS3UploaderCommon.prototype.AbstractBaseCommon;
try {
base.writeLogTrace("AbstractWorkerS3UploaderCommon# getTasks :start");
return [this.beforeMainExecute, this.businessMainExecute];
} catch (err) {
base.printStackTrace(err);
throw err;
} finally {
base.writeLogTrace("AbstractWorkerS3UploaderCommon# getTasks :end");
}
};
- beforeMainExecute ・・・ 制御側から受け取ったデータを返却
- businessMainExecute ・・・ 1ページ分のPDFデータを作ってS3へアップロード
と非常にシンプルな構成になっています。
「ExecutorsThreadsNum=5」にして、5多重とかにしても、一応、ちゃんと動いてるっポイです。
あとがき
軽い気持ちで手を出したら、イロイロ上手くいかない事や調査しないといけない事があって勉強になりました。
英語のコミュニティを漁るのは、しばらく、お腹イッパイです。