PuppeteerでExcelファイルを読み取り入力させる
一般の業務では、非常にExcelファイルを扱うシーンが多いです。管理帳票として使っているケースが多いですが、そこから更に次の業務が待ってるのが常です。しかし、せっかく管理帳票でデータを入れているのに、ウェブアプリで再度入力は無駄な作業以外の何物でもありません。
Excelシートからデータを読み取り、まとめてウェブアプリに繰り返し登録作業を行えたら非常に便利ですね。そこで今回は手元のExcelシートからとあるサービスに連続で登録するPuppeteerを作ってみようと思います。
目次 [隠す]
今回使用するモジュール等
- puppeteer-core – npm
- xlsxモジュール – xlsxファイルを読み書きするためのモジュール
- prompts – npm
今回は、以前もElectronで利用したxlsxモジュールを用いて読み書きを行います。デスクトップに用意したtellist.xlsxファイルに基づいてサイトに情報を連続登録を試みます。
また、今回は利用にあたって問題点と解消方法があるのでその点を踏まえてコーディングをする必要があります。
今回の入力上の問題点
今回はこれまでのようなダウンロードやアップロードではなく、サイトに情報を登録するタイプのPuppeteerです。その際の要件は以下の通りです。
- Excelはきちんと整形されたデータである必要がある(セルの結合やらわけのわからないレイアウトであってはNG)
- Excel側には完了した場合用のフラグ列を用意しておく
- Puppeteer側は、Excelファイルの読み書きが必要である。
- Puppeteer側は2.のフラグが無いデータを登録対象として処理を行う。
- すでに過去に登録済み(社員IDとメアドで判定されてる)で、削除をした場合には新規登録で同じデータはエラーになるので注意が必要なサービスです。
図:入力自体はそれほど難しくなさそうなUI
入力用のExcelファイルについて
今回使用するサービス用のExcelシートは、登録リストと部署コードの2つのシートで構成されています。入力欄はそれぞれウェブサービスの入力欄に対応しています。また、最後の列は登録済みかどうかのフラグになっています。このフラグが空のレコードだけが今回登録する対象になるデータになります。
また、今回のサービスは、部署選択のセレクトボックスが用意されているのですが、各selectboxのoptionにはvalue値がランダムに設定されており、自分の所属部署名(例:Z_111)に対応するselectboxのvalue値をChrome Developer Toolで調べて於いて、部署コードシートに入れてあります。所属部署コードがこのselectboxのvalue値になります。
所属部署は入力規則のドロップダウンになっており、選択するとvlookup関数で対応するselectboxの値が呼ばれるようになっています。この値がPuppeteerにてselectboxを選択する時に、await page.select('対象のelementのID', 選択するvalue)にて、選択させる事が可能になります(部署名のテキストだと具合が悪い為今回はこういった仕組みを使っています)。
図:入力用のExcelシート
図:xlsxモジュールで読み取り配列にした様子
ソースコード
冒頭部分
- 今回はログインページでログイン後にユーザ追加ページへ直接移動してユーザ追加を行いますので、2つのURLを変数に入れてあります。
- xlsxファイルの読み書き用モジュールを追加しています。
- グローバル変数でデスクトップのパスを取得しておきます。
- つづけて、getprompt()を実行してユーザの入力を受付待ちします。
- chromeはいつもの「C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe」ではなく、「C:\\Users\\ユーザー名\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe」となるため、ユーザ毎のパスを取得して、chromepathに格納する
プロンプト入力受付部分
- ユーザIDはメールアドレス、パスワードは文字列型で引き受けます。
- 受付後にmain関数に引き渡して、puppeteerを実行
Puppeteer部分
- page.onのイベント追加によって、登録時に出てくるalertのOKボタンをdialog.acceptで自動で押しています。
- xlsxモジュールで登録リストシートにあるデータをロードし、まずは二次元配列に変換します。
- 二次元配列化したxlsxのデータを元にループで順番に登録作業を行います。
- 登録後、都度登録リストのK列に✔を加えたデータを上書きでxlsx.writeFileしています。
- フォームに入力時にテキスト型に数値を入れるとエラーが出る箇所が二箇所あるので、Stringで型変換をしておきます。
- 最後に完了メッセージを出す前に、page.onイベントで追加したalertのOKボタンを押すイベントをremoveAllListenersにてイベントを削除しないとメッセージが出ずに閉じてしまいます。
問題点
今回のスクリプトを使うにあたって、現在2つの問題点があります。
- puppeteer 3.2.0は、page.onで追加したイベントリスナーを削除する為にpage.removeListnerをしてもイベント削除ができずまた、page.removeAllListenersをしてもis not a functionとエラーが出てしまう。これは、Puppeteer3.2.0のバグで現在対応中のようですので、puppeteer-coreは3.1.0を利用しましょう(npm i puppeteer-core@3.1.0でインストールする)
- xlsxモジュールは読み書きが出来るのですが、書き込み時にセルの書式等が全てクリアされた状態で書き込まれてしまいます。フォーマットを維持した状態で書き込みたい場合にはxlsx-populateというモジュールを使うと、レイアウトを崩さずに書き込みが可能です。
xlsx-populate-wrapperを使ってみる
xlsxモジュールは、書式を無視して書き込みをしてしまうのと、少々扱いが面倒臭いという事もあって、xlsx-populateおよびそれを更に楽にしたxlsx-populate-wrapperモジュールを使って読み書きをする事で既存のレイアウトや書式を壊さずに書き込みができるのと、座標の読み書きが楽になるので、こちらを使っての方法を試してみました。
今回は読み書きに特化して一番楽だと思われるxlsx-populate-wrapperを使ってのコードに置き換えてみようと思います。使う場合には、npm i xlsx-populate-wrapperでインストールが必要です。
- デフォルトでUsedRangeでシートデータを取得してくれるので余計な作業が必要ありません。
- データはJSON形式で取得してくれる。
- JSONのキー(タイトル行)を元にデータを取り出して、順次フォームへ登録していきます。配列と違って1行目データの処理等などは不要です。
- workbook.init()でデータの読み込みとデータの書き出しのそれぞれをやらせます。workbook.initの中ではpromiseで処理されているが故に、その中でpuppeteerのコードを動かそうとするとエラーになるため、切り離してあります。
- jsonデータを一気に書き込みしますが、Range指定なども必要ありません。
- 書き込み時だけはreturnにてworkbook.commit()しないと確定しませんので注意が必要です。
図:こんな感じのJSONで取得してくれるのでとても楽
図:無事にレイアウト崩さずに✔を入れられました。
関連リンク
- 【Node】エクセルを読み込む(ループで回して読み込み)
- Node.js でエクセルファイルを更新する
- ドロップダウン選択からオプションを選択する方法
- puppeteerでダイヤログを扱う方法
- EventEmitterでイベントリスナーを削除する
- TypeError: this.global.page.addListener is not a function with puppeteer@3.2.0 #353
- TypeError: this.global.page.addListener is not a function #5944
- Node.jsでイベントを使用する
- SheetJS/sheetjs
- Node.jsでExcelファイルのread/write
- xlsx-populatenode.jsで簡易データベースとしてExcelファイルを使う
- xlsx-populateで取得した値がややこしいことになってた時に文字列だけを取り出す方法