🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

Q&A

解決済

2回答

1008閲覧

perlのLWP、get($url)部分でOut of memory!というエラーが出ます。解決策は?

h5x

総合スコア11

Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

0グッド

0クリップ

投稿2019/10/11 19:59

下記のPerlスクリプトはpdfファイルか否か確認してpdfならダウンロード、それ以外なら簡易にファイル種類を表示させています。
しかし、このスクリプトには問題があり「my $res = $ua->get( $url );」で$urlのファイルの容量が大きいとOut of memory!と表示されスクリプトが停止してしまいます。
どのようにすれば解決しますでしょうか?
宜しくお願い致します。

perl

1$| = 1; 2use strict; 3use LWP::UserAgent; 4 5my @url = ('https://XXXXXXXXXXXXXXXXXXX/a.pdf', 6 'https://XXXXXXXXXXXXXXXXXXX/b.pdf' 7 ); 8my $count = 0; 9for my $url (@url){ 10 $count++; 11 my $ua = LWP::UserAgent->new; 12 $ua->timeout(10); 13 my $res = $ua->get( $url ); #★ここが問題点★ PDFファイルが大きすぎるとOut of memory!がでて止まる 14 my $source = $res->content; 15 if(substr($source,0,10) =~ m/pdf/i){ 16 print "PDF file-$url\n"; 17 open(F,">${count}.pdf"); 18 binmode F; 19 print F $source; 20 close F; 21 next; 22 } 23 if($source =~ m/<html/i){ 24 print "HTML-$url\n"; 25 next; 26 } 27 print "OtherFile-$url\n"; 28 29}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2019/10/11 20:08 編集

perl out of memory で検索するとそれなりに解決策が出てきますが、それでは足りませんでしたか?
h5x

2019/10/11 20:36

コメントいただきありがとうございます。 ファイルとして保存する方法までは見つかったのですが、データの一部をちょっとだけ確認してその後に保存したりしなかったりということができず困っている感じになります。質問がわかりにくくすみません。
m.ts10806

2019/10/11 21:28

どこまで把握しているかは質問内容に書いてあることが全てですので、調べたこと試したことを追記してください。
tatsu99

2019/10/12 02:33

仮想メモリを増やしてから実行してみてはいかがでしょうか。 仮想メモリを増やせばなんとかなるかもしれません。
h5x

2019/10/12 03:56

m.ts10806 さま 数日掛けて大量に調べていますのでどこまで書いて良いものか・・・ 一応、このプログラムに直接関係して可能性があるものとしては、このようにして一時ファイルを作成。 これを解析して削除またはリネームで保存という方法がおそらく英語のサイトだったと思いますがとりあえず止まらずに動く方法としては見つかりました。その後ファイル判定して削除するものです。 $count++; my $ua = LWP::UserAgent->new; $ua->timeout(10); my $req = HTTP::Request->new( GET => $url); my $res = $ua->request( $req, "temp${count}"); ただ、すべてのファイルをHDDに保存する必要があれば最適ですが、今回は必ずしもその必要性がなくできればもう少し良い方法がないか気になっています。 他にはHDDではなくすべてメモリにダウンロードしてそこでファイル解析してHDDへの保存可否を決める感じです。いろいろ調べているとpythonという言語では img_bin = BytesIO() img.save(img_bin , "PNG") #PNG圧縮されたバイト列としてメモリに保持 この様にHDDに保存せず一度メモリ上に保存できそれを解析することもできるそうです。 perlに同じ機能があれば便利ですが良い方法が見つかりませんでした。 他にも例えば、全てをダウンロードしなくても先頭の辺りだけ少し読んで中身をチェックしてその結果で判定してダウンロードなどあれば良いのですがこれも良い方法が見つからずという感じです。 回答1で教えていただいた方法はかなり良いと思っていますが、サーバの返してくるヘッダー情報はかならずしも正確ではないと思います(以前アップロードするものを作った時にヘッダーを作成したのですが間違えて書くことも可能でしたので)のでもう少し良い方法があればと思っています。 tatsu99 さま Windows 10を使っているのですが、メモリ使用率を見る限りまだ十分に余裕がありますのでこれでは解決しませんでした。
m.ts10806

2019/10/12 20:34

代表的なもので結構ですので、おおよその方向性がわかれば良いです。 ただ、こちらのコメント欄ではデフォルト非表示で埋もれますので本文編集して追記願います
h5x

2019/10/13 17:29

色々な実験していますので方向性はない感じです。強いて言えば数年以内にスパイダーみたいなものができればいいと思っていますが先はかなり長いです。また、上記のように色々やっていますので全て書くと流石に論点がぼやけますので、Out of memory!の部分に可能な限りフォーカスして質問するのが良いかと思いシンプルに書いたつもりです。今のところ、サンプロプログラムをコピーして、一つずつエラーを潰していくようなスタイルで勉強しています。 なお、こちらは公開されていないということであればちょっと気になるのが、 https://www.meti.go.jp/report/whitepaper/mono/2019/honbun_pdf/pdf/honbun_01_04_.pdf にアクセスするとOut of memroy!になりました。 実際には膨大なPDFと想定できるユニークなドメインのファイルリンク集を使っています。 ただ、再現性無し、こちらはデータ容量も小さいはずですが様々なPDFやHTMLファイルにアクセスしていると my $res = $ua->get( $url ); でOut of memroy!となっていました。また、エラーメッセージが表示されないため場所を特定するのに、 その上下にはprint __LINE__ . "\n";を記載して突き止めた感じです。 また、 $ua -> max_size(2000000); というものを入れても Out of memroy! となり my $res = $ua->get( $url ); で止まっていました。なお、URLは上記とは異なりました。 はじめのコメントのperl out of memoryについては一応見ており、当初は容量オーバーかと思い質問したのですが、容量オーバーというわけではないのかもしれません。また、今、perl out of memoryで検索すると私の質問が上位でヒットしました。こちらに質問すればこれへの対処法が容易にわかるかと思ったのですが、結構レアなエラーなのでしょうかね? とりあえず、原因まで不明となりよく分からなくなっています。 evalでエラーをキャッチして飛ばす方法も見つけたのですが、out of memoryについてはそれも使えずデバッグ難しいですね。
m.ts10806

2019/10/13 20:47

こちらのコメント欄ではデフォルト非表示で埋もれますので本文編集して追記願います
KojiDoi

2019/10/14 04:51

そもそも、たかがpdfひとつダウンロードしただけでout of memoryというのがとても不自然な状況に思えます。perlを使わずにwgetなどで取得することは問題なくできるのでしょうか。その場合、そのpdfファイルのファイルサイズはどれぐらいですか。 なお上で提示されているURLはリンク切れしています。状況が再現できる正しいURLを提示していただければ助かります。
h5x

2019/10/17 18:09

m.ts10806 さま 大量にコメントしており文章の下手な私が下手に編集すると意味がわからなくなりそうですので、今の質問説明文のほうが良いかと思っています。当方、こちらを利用するのはほぼ初めてで使い方をよくわかってないところがあるのですが、他の方の質問を見てもかなりシンプルであとからの利用がしやすく、できれば私の質問もあとから外国人を含め他の方が見てもすぐに分かるような質問になればいいと思っています。 KojiDoi さま 説明不足ですみません。リンク先が消失しているものも含めてアクセスしています。そのため、上記URLで正しいものとなります。 ただ、out of memoryとなりファイル内容を取得することができず内容がつかめずデバッグが進まない感じです。また、直後に同じURLにアクセスしても再現性がなく何が起こっているのか推測するしかない感じです。一応、こちらが正しいURLになります。 https://www.meti.go.jp/report/whitepaper/mono/2019/honbun_pdf/pdf/honbun_01_01.pdf なお、PCの異常も考えたのですが、YahooAPIなどでは何日使っても一度も落ちたことがないため雑多なURLを大量にアクセスしていると落ちる事があるようです。
guest

回答2

0

ベストアンサー

少しずつ読んで先頭にPDFが含まれているか調べるならこんな感じでしょう。
:content_cb:read_size_hintについてはドキュメントを。

https://perldoc.jp/docs/modules/libwww-perl-5.813/LWP/UserAgent.pod

perl

1use strict; 2use warnings; 3use LWP::UserAgent; 4 5{ 6 my $fw; 7 my $f = 0; 8 my $uri = 'http://example.com/foo.pdf'; 9 10 my $res = LWP::UserAgent->new->get( 11 $uri, 12 ':content_cb' => sub { 13 my ($chunk, $res, $proto) = @_; 14 if($f == 0){ 15 if(substr($chunk, 0, 10) =~ m/pdf/i){ 16 open $fw, '>', 'out.pdf' or die; 17 binmode $fw; 18 print "OK.\n"; 19 $f = 1; 20 } else { 21 print "NG.\n"; 22 die; 23 } 24 } 25 print $fw $chunk; 26 }, 27 ':read_size_hint' => 65536, 28 ); 29 30 if($f == 1){ 31 close $fw; 32 } 33}

投稿2019/10/16 21:02

harrek

総合スコア123

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

h5x

2019/10/17 18:41 編集

ありがとうございます。 こちらを実行してみたところ、 my $uri = 'https://www.meti.go.jp/report/whitepaper/mono/2019/honbun_pdf/pdf/honbun_01_01.pdf'; # PDFファイル であれば正常に動作しました。 しかし、 my $uri = 'https://www.meti.go.jp/report/whitepaper/mono/2019/honbun_pdf/pdf/honbun_01_001.pdf'; # PDFファイルではない(あえて間違えたURL) にしますとNGが表示されませんでした。 また、念の為どのようなデータ処理されているのか見てみようと以下のようにコードを書き換えたところ、正常なURLでもout.pdfもtext.txtも表示されなくなりました。ドキュメントを見たのですが、以下の文章が正直良くわからず前に進まない感じです。 ":" で始まるフィールド名は特殊です。 これらはリクエストのヘッダの初期化はせず、レスポンスオブジェクトが どのように扱われるかを決定します。 以下の特殊フィールド名を認識します: ```perl use strict; use warnings; use LWP::UserAgent; my $fh; #追加 my $fw; my $f = 0; my $uri = 'https://www.meti.go.jp/report/whitepaper/mono/2019/honbun_pdf/pdf/honbun_01_01.pdf'; my $res = LWP::UserAgent->new->get( $uri,':content_cb' => sub { my ($chunk, $res, $proto) = @_; if($f == 0){ opne($fh,">>text.txt"); #追加 print $fh substr($chunk, 0, 10); #追加 close $fh; #追加 exit; #追加 if(substr($chunk, 0, 10) =~ m/pdf/i){ open $fw, '>', 'out.pdf' or die; binmode $fw; print "OK.\n"; $f = 1; } else { print "NG.\n"; die; } } print $fw $chunk; }, ':read_size_hint' => 65536, ); if($f == 1){ close $fw; } ```
harrek

2019/10/17 19:50

URL先にそもそもファイルがない場合の処理は入っていないので必要なら末尾に: ``` if(!$res->is_success){ print "Fail.\n"; } ``` などとするとよいでしょう。 追加コードで動作しなくなるのは"open"を"opne"とタイプミスしているのと(これ警告出ないんですね、知りませんでした)、exitが不要です。
harrek

2019/10/17 20:08

ドキュメントの該当部分の意味としては、本来getメソッドのURL指定の後はリクエストヘッダ指定なので、例えば「'read_size_hint' => 65536」と指定するとリクエストヘッダに「read_size_hint: 65536」を追加してリクエストを発行することになるのですが、「':read_size_hint' => 65536」のようにフィールド名の先頭が":"だとこのような処理は行われず、代わりに特殊処理が行われる、ということです。
h5x

2019/10/19 17:05

タイポ気づきませんでした。また、詳細な説明ありがとうございます。とても助かりました!
guest

0

ファイルのタイプをチェックしたいだけなら、ファイル全体を丸呑みする必要はありません。ヘッダだけ読むようにすればメモリ不足にもならないんじゃないでしょうか。

$r = $ua->head($url); print $r->header('content-type');

投稿2019/10/11 20:34

KojiDoi

総合スコア13692

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

h5x

2019/10/11 20:43

とても効率的な方法ですね。ありがとうございます! ただ、サーバが返してくるcontent-typeは間違えている場合もあるかと思っています。この場合には対応できないためできれば全文読むほうが寄り良いのではないかと思っています。PDFですと確実に先頭にPDFが含まれますので。 また、その後に保存する場合もあるので一度のリクエストで済めばリクエスト回数が減るのでこれも良いのではないかと思っています。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問