0

数日前、PERL を使用してファイルから HTML を削除することについて質問しました。私は n00b で、質問への回答をサイトで検索しました...しかし、残念ながら何も見つかりませんでした...これはおそらく、私が n00b であり、回答が表示されなかったことが原因です。それを見ています。

それで、これが状況です。約 20 GB のテキスト ファイルを含むディレクトリがあります。各ファイルから HTML を取り除き、各ファイルを一意のテキスト ファイルに出力したいと考えています。以下のプログラムを作成しました。これは、ディレクトリ内の最初の 12 個のテキスト ファイル (合計で約 12,000 個のテキスト ファイルがあります) のトリックを行うようです...しかし、いくつかの問題に遭遇します。最初の問題は、12 番目のテキスト ファイルが解析された後、深い再帰に関する警告が表示され始めたことです...そして、この直後に、メモリが不足したためにプログラムが終了します。私のプログラミングは非常に非効率的だと思います。ですから、以下のコードで明らかなエラーが発生し、メモリが不足する可能性がある人がいるかどうか疑問に思っています。…私が物事を理解したら、うまくいけば私は貢献できるでしょう。

#!/usr/bin/perl -w
#use strict;
use Benchmark;
#get the HTML-Format package from the package manager.
use HTML::Formatter;
#get the HTML-TREE from the package manager
use HTML::TreeBuilder;
use HTML::FormatText;
$startTime = new Benchmark;
my $direct="C:\\Directory";
my $slash='\\';

opendir(DIR1,"$direct")||die "Can't open directory";
my @New1=readdir(DIR1);

foreach $file(@New1)
{

if ($file=~/^\./){next;}
#Initialize the variable names.
my $HTML=0;
my $tree="Empty";
my $data="";
#Open the file and put the file in variable called $data

{
    local $/;
    open (SLURP, "$direct$slash"."$file") or die "can't open $file: $!"; 
    #read the contents into data
    $data = <SLURP>; 

    #close the filehandle called SLURP
    close SLURP or die "cannot close $file: $!";
    if($data=~m/<HTML>/i){$HTML=1;}
    if($HTML==1)
        {
            #the following steps strip out any HTML tags, etc.
            $tree=HTML::TreeBuilder->new->parse($data);
            $formatter=HTML::FormatText->new(leftmargin=> 0, rightmargin=>60);
            $Alldata=$formatter->format($tree); 
        }
}
#print
my $outfile = "out_".$file;
open (FOUT, "> $direct\\$outfile");
print FOUT "file: $file\nHTML: $HTML\n$Alldata\n","*" x 40, "\n" ;
close(FOUT);

}


$endTime = new Benchmark;
$runTime = timediff($endTime, $startTime);
print ("Processing files took ", timestr($runTime));
4

3 に答える 3

2

のファイルのリストで大量のスペースを使用しています@New1

さらに、古いバージョンの を使用している場合HTML::TreeBuilder、このクラスのオブジェクトは自動 Perl ガベージ コレクションの影響を受けていなかったため、明示的に削除する必要がある場合があります。

次のプログラムは、ディレクトリをインクリメンタルに読み取り、作成したオブジェクトHTML::FormatText->format_stringを暗黙的に削除するテキストをフォーマットするために使用することにより、これらの問題の両方を回避します。HTML::TreeBuilder

さらに、File::Spec絶対ファイル パスを作成する作業を整理します。これはコア モジュールであるため、システムにインストールする必要はありません。

use strict;
use warnings;

use File::Spec;
use HTML::FormatText;

my $direct = 'C:\Directory';

opendir my $dh, $direct or die "Can't open directory";

while ( readdir $dh ) {

  next if /^\./;

  my $file = File::Spec->catfile($direct, $_);
  my $outfile = File::Spec->catfile($direct, "out_$_");
  next unless -f $file;

  my $html = do {
    open my $fh, '<', $file or die qq(Unable to open "$file" for reading: $!);
    local $/;
    <$fh>;
  };

  next unless $html =~ /<html/i;

  my $formatted = HTML::FormatText->format_string(
      $html, leftmargin => 0, rightmargin => 60);

  open my $fh, '>', $outfile or die qq(Unable to open "$outfile" for writing: $!);

  print $fh "File: $file\n\n";
  print $fh "$formatted\n";
  print $fh "*" x 40, "\n" ;

  close $fh or die qq(Unable to close "$outfile" after writing: $!);
}
于 2012-08-05T23:50:11.160 に答える
1

の質問への回答のどこが間違っていましたか?

戻りコードをチェックせずに書き込み用に開いているファイル。本当に成功しますか?また、ファイルはどのディレクトリに作成されますか?

より良いアプローチは次のとおりです。

  • ファイルを 1 つずつ読み取る
  • HTMLを取り除く
  • 正しいディレクトリに新しいファイルを書き出し、リターン コードをチェックする

何かのようなもの:

while ( my $file = readdir DIR ) {

    ....process file

    open my $newfile, '>', "$direct/out_$outfile" or die "cannot open $outfile: $!\n";

   ... etc
}
于 2012-08-05T19:29:22.337 に答える
0

このアプリケーションのメモリ フットプリントを削減する方法:

$tree = $tree->deleteループの最後に追加しても問題は解決しませんか?

perl ガベージ コレクタは循環参照を解決できません。メモリ不足にならないように、ツリーを手動で破棄する必要があります。

( http://metacpan.org/pod/HTML::TreeBuilderのモジュール ドキュメントの最初の例を参照してください)

readdirループの内側に配置する必要があります。あなたがコーディングした方法では、最初にこの巨大なファイルのリストを読み込みました。あなたが言う時

my $file;
while (defined($file = readdir DIR1)) {..}

一度に実際に読み取られるエントリは 1 つだけです。余分なメモリを節約する必要があります。

スタイルに関するその他のコメント:

デフォルト値

$treeのデフォルト値を指定します"Empty"。それはまったく不要です。変数がどのように未定義であるかを表示したい場合undefは、デフォルトで設定されている に設定します。Perl はこの初期化を保証します。

バックスラッシュ

ディレクトリ区切り文字としてバックスラッシュを使用していますか? 心配するのはやめて、通常のスラッシュを使用してください。DOS を使用していない限り、通常のスラッシュも使用できます。Windows はそれほど愚かではありません。

ステートメント修飾子

この行

if ($file=~/^\./){next;}

次のようにはるかに読みやすく書くことができます

next if $file =~ /^\./;

結果としての括弧の使用

関数の引数リストに括弧を使用しても意味がありません。あいまいさがない限り、すべての組み込み関数の括弧を省略できます。私はそれらを避けることを好みますが、他の人にとっては読みやすいかもしれません。でもスタイルは守ってください!

より良い正規表現

の存在をテストし/<HTML>/iます。htmlタグに属性を持たせることができると言ったらどうしますか? のテストを検討する必要があります/<html/i

簡素化 (別のバグを削除)

あなたのテスト

if($data=~m/<HTML>/i){$HTML=1;}
if($HTML==1) {...}

次のように書くことができます

$HTML = $data =~ /<html/i;
if ($HTML == 1) {...}

次のように書くことができます

$HTML = $data =~ /<html/i
if ($HTML) {...}

に折りたたむことができます

if ($data =~ /<html/i) {...}

実装した方法では、変数がfalse$HTMLにリセットされることはありませんでした。したがって、ファイルに html が含まれると、後続のすべてのファイルも同様に html として扱われます。最も内側の適切なスコープで変数を定義することで、このような問題に対処できます。

HTML::FormatText を使用し、@pavel に敬意を表します

使用するモジュールを最大限に活用してください。の例で見つけたものを見てくださいHTML::FormatText

my $string = HTML::FormatText->format_file(
           'test.html',
           leftmargin => 0, rightmargin => 50
           );

手動でツリーを構築することを回避するために、これを簡単に適応させることができます。@pavel が他の投稿で教えてくれたように、なぜこのアプローチを試さなかったのですか? メモリの問題を解決できたでしょうか...

厳密に使用する

なぜコメントアウトしたのuse strictですか?言語を学ぶときは、致命的な警告をできるだけ多く受け取ることが重要です。またはしっかりしたコードを書くとき。$fileこれにより、すべての変数を賢明に宣言する必要があります。そして、少し時代遅れuse warningsのスイッ​​チではなく。-w

素晴らしい

しかし、の戻り値をチェックする上で非常に大きな「よくやった」close;-) それは非常に不自然です!

于 2012-08-05T17:06:21.657 に答える