最近はエクセルをWebサーバーに置いておいて、
それを直接読むCGIをおいて似非データベースみたいにして使ってます。
仕事だと、データはエクセルで作ってもらわざるを得なかったり、
会計処理ソフトなどはデータの書き出しがエクセルのみだったりとかで、
データはエクセルでしか存在することが多く、
それをいちいちCSVに変換したり、
データベースに突っ込んだりしていてはデータを最新に保ち続けるのは
難しいわけです。
なので、エクセルデータを直接置いて直接読むという蛮行をやっているわけです。
エクセルデータはPerlのモジュール「Spreadsheet::ParseExcel」で
読み込むことができます。
http://search.cpan.org/dist/Spreadsheet-ParseExcel/lib/Spreadsheet/ParseExcel.pm
小さなエクセルファイルならば問題ないのですが、
少し大きくなると、途端にCGIの動作が遅くなります。
そこがなんとかなんないかなと思っていたら、
よさそうな方法があったので書いておきます。
以下は、ParseExcelモジュールドキュメントの「Reducing the memory usage of Spreadsheet::ParseExcel」という一節の翻訳です。
===========
Spreadsheet::ParseExcel を使ったプログラムは、大きなエクセルファイルを処理するときは大量のメモリーを消費してしまうかもしれず、その結果処理が完了せずに終わってしまうかもしれない。下に、それがなぜ起こるのかと対処法を記す。
Spreadsheet::ParseExcelはエクセルファイルを2段階で処理します。第一段階ではエクセルのバイナリストリームをOLE::Storage_Liteを使ってOLEコンテナファイルから取り出します。第二段階ではworkbook・worksheet・cellのデータを読むためにバイナリストリームをパースし、メモリに蓄えます。cellのデータを蓄えるために大量のメモリーが使用されます。
エクセルファイルがパースされ、それぞれのcellがcell操作関数に読み込まれると、比較的大きくて入り組んだcellオブジェクトが生成されます。その中にはcellの値とcellの書式に関するすべての情報が含まれるために、大量のメモリが消費されるのです。大きなエクセルファイル(PCのメモリが256MBの場合のサイズが10MBのエクセルファイル)の場合、この過剰消費がシステムを停止させてしまいます。
しかし、多くの場合、必要とされる情報はcellの値だけです。そういう場合には、cell操作関数に独自の関数を指定し、Spreadsheet::ParseExcelにcellのデータを保持しないように指示すれば、メモリの過剰消費をかなり避けられます。そうするためには、パースオブジェクトを作る際に、new()にcell操作関数を渡せば済みます。以下がその例です。
ユーザーの独自のcell操作関数はコードリファレンスとしてnew()に渡され、NotSetCellも一緒に渡されることで、 Spreadsheet::ParseExcel にパースしたcellを保持しないよう伝達します。その際に、行と列を反復する必要がないことに注意してください。cellを読み込むための行と列の反復はパースの一部として自動的に行われます。
cell操作関数には5個の引数が渡されます。1番目($workbook)は、Spreadsheet::ParseExcel::Workbookのオブジェクトへのリファレンスで、パースしたワークブックを表現します。このオブジェクトはSpreadsheet::ParseExcel::Workbookのメソッドを使ってアクセスすることができます。"Workbook"の部分を参照して下さい。2番目($sheet_index)は、パースされたworksheetの番号です(0はじまり)。3番目と四番目( $row 、$col)は、(0から始まる)cellの行と列の番号です。5番目($cell)は Spreadsheet::ParseExcel::Cellオブジェクトのリファレンスです。これはcellからデータを引き出すのに使います。詳しくは"Cell"を見て下さい。
この独自cell操作関数を使う方法は、データベースフィルターにエクセルを書き込む場合に便利です。(?)DBの呼び出しをcell操作関数の中に書いて置けるからです。
スプレッドシートのデータの全てが必要なわけではない場合は、cell操作関数に制御コマンドを加えます。例えば、先に出した例を拡張して、いくつかif文を加えるだけで、はじめの2つのワークシートの最初の10行だけを表示するように変更できます。
しかし、このままだと結局はワークブック全体を処理することになります。さらに処理時間を短縮したい時は、「workbook ParseAbortメソッド」を使って、必要なデータを読み込んだ後に、パースを(強制)終了します。
それを直接読むCGIをおいて似非データベースみたいにして使ってます。
仕事だと、データはエクセルで作ってもらわざるを得なかったり、
会計処理ソフトなどはデータの書き出しがエクセルのみだったりとかで、
データはエクセルでしか存在することが多く、
それをいちいちCSVに変換したり、
データベースに突っ込んだりしていてはデータを最新に保ち続けるのは
難しいわけです。
なので、エクセルデータを直接置いて直接読むという蛮行をやっているわけです。
エクセルデータはPerlのモジュール「Spreadsheet::ParseExcel」で
読み込むことができます。
http://search.cpan.org/dist/Spreadsheet-ParseExcel/lib/Spreadsheet/ParseExcel.pm
小さなエクセルファイルならば問題ないのですが、
少し大きくなると、途端にCGIの動作が遅くなります。
そこがなんとかなんないかなと思っていたら、
よさそうな方法があったので書いておきます。
以下は、ParseExcelモジュールドキュメントの「Reducing the memory usage of Spreadsheet::ParseExcel」という一節の翻訳です。
===========
Spreadsheet::ParseExcel を使ったプログラムは、大きなエクセルファイルを処理するときは大量のメモリーを消費してしまうかもしれず、その結果処理が完了せずに終わってしまうかもしれない。下に、それがなぜ起こるのかと対処法を記す。
Spreadsheet::ParseExcelはエクセルファイルを2段階で処理します。第一段階ではエクセルのバイナリストリームをOLE::Storage_Liteを使ってOLEコンテナファイルから取り出します。第二段階ではworkbook・worksheet・cellのデータを読むためにバイナリストリームをパースし、メモリに蓄えます。cellのデータを蓄えるために大量のメモリーが使用されます。
エクセルファイルがパースされ、それぞれのcellがcell操作関数に読み込まれると、比較的大きくて入り組んだcellオブジェクトが生成されます。その中にはcellの値とcellの書式に関するすべての情報が含まれるために、大量のメモリが消費されるのです。大きなエクセルファイル(PCのメモリが256MBの場合のサイズが10MBのエクセルファイル)の場合、この過剰消費がシステムを停止させてしまいます。
しかし、多くの場合、必要とされる情報はcellの値だけです。そういう場合には、cell操作関数に独自の関数を指定し、Spreadsheet::ParseExcelにcellのデータを保持しないように指示すれば、メモリの過剰消費をかなり避けられます。そうするためには、パースオブジェクトを作る際に、new()にcell操作関数を渡せば済みます。以下がその例です。
#!/usr/bin/perl -w use strict; use Spreadsheet::ParseExcel; my $parser = Spreadsheet::ParseExcel->new( CellHandler => \&cell_handler, NotSetCell => 1 ); my $workbook = $parser->parse('file.xls'); sub cell_handler { my $workbook = $_[0]; my $sheet_index = $_[1]; my $row = $_[2]; my $col = $_[3]; my $cell = $_[4]; # Do something useful with the formatted cell value print $cell->value(), "\n"; }
ユーザーの独自のcell操作関数はコードリファレンスとしてnew()に渡され、NotSetCellも一緒に渡されることで、 Spreadsheet::ParseExcel にパースしたcellを保持しないよう伝達します。その際に、行と列を反復する必要がないことに注意してください。cellを読み込むための行と列の反復はパースの一部として自動的に行われます。
cell操作関数には5個の引数が渡されます。1番目($workbook)は、Spreadsheet::ParseExcel::Workbookのオブジェクトへのリファレンスで、パースしたワークブックを表現します。このオブジェクトはSpreadsheet::ParseExcel::Workbookのメソッドを使ってアクセスすることができます。"Workbook"の部分を参照して下さい。2番目($sheet_index)は、パースされたworksheetの番号です(0はじまり)。3番目と四番目( $row 、$col)は、(0から始まる)cellの行と列の番号です。5番目($cell)は Spreadsheet::ParseExcel::Cellオブジェクトのリファレンスです。これはcellからデータを引き出すのに使います。詳しくは"Cell"を見て下さい。
この独自cell操作関数を使う方法は、データベースフィルターにエクセルを書き込む場合に便利です。(?)DBの呼び出しをcell操作関数の中に書いて置けるからです。
スプレッドシートのデータの全てが必要なわけではない場合は、cell操作関数に制御コマンドを加えます。例えば、先に出した例を拡張して、いくつかif文を加えるだけで、はじめの2つのワークシートの最初の10行だけを表示するように変更できます。
#!/usr/bin/perl -w use strict; use Spreadsheet::ParseExcel; my $parser = Spreadsheet::ParseExcel->new( CellHandler => \&cell_handler, NotSetCell => 1 ); my $workbook = $parser->parse('file.xls'); sub cell_handler { my $workbook = $_[0]; my $sheet_index = $_[1]; my $row = $_[2]; my $col = $_[3]; my $cell = $_[4]; # Skip some worksheets and rows (inefficiently). return if $sheet_index >= 3; return if $row >= 10; # Do something with the formatted cell value print $cell->value(), "\n"; }
しかし、このままだと結局はワークブック全体を処理することになります。さらに処理時間を短縮したい時は、「workbook ParseAbortメソッド」を使って、必要なデータを読み込んだ後に、パースを(強制)終了します。
#!/usr/bin/perl -w use strict; use Spreadsheet::ParseExcel; my $parser = Spreadsheet::ParseExcel->new( CellHandler => \&cell_handler, NotSetCell => 1 ); my $workbook = $parser->parse('file.xls'); sub cell_handler { my $workbook = $_[0]; my $sheet_index = $_[1]; my $row = $_[2]; my $col = $_[3]; my $cell = $_[4]; # Skip some worksheets and rows (more efficiently). if ( $sheet_index >= 1 and $row >= 10 ) { $workbook->ParseAbort(1); return; } # Do something with the formatted cell value print $cell->value(), "\n"; }