2016-04-27 14 views
5

いくつかのURLを並行して取得し、何らかの作業を行うスクリプトを作成する必要があります。過去に私はいつもParallel::ForkManagerを使っていましたが、何か新しいことを学び、AnyEvent(とAnyEvent::HTTPまたは)という非同期プログラミングを試したかったのですが、AnyEventを理解し、行の後特定の例のperlでの非同期の理解

  • (すべての行が別々のURLである)ファイルを開く
  • (しかしFE 10の同時要求の制限と、並行して、今から)読み込み、ファイルの行(私はいけないロードしたいですメモリ全体のファイル - それは大きいかもしれません)
  • HTTPリクエストを作成するfまたはそのURL
  • は、応答を読ん
  • アップデートMySQLのレコードに応じ
  • (次のファイルの行)

私は、ブロッキングと非の違いを理解するために、私は多くのマニュアル、チュートリアルが、そのまだハードを読んだことがありますブロックコード。私は氏サボー基本を説明しhttp://perlmaven.com/fetching-several-web-pages-in-parallel-using-anyevent、で同様のスクリプトを発見したが、私はまだカントのようなものを実装する方法を理解する:

... 
open my $fh, "<", $file; 
while (my $line = <$fh>) 
{ 
# http request, read response, update MySQL 
} 
close $fh 
... 

を...この場合、同時実行の制限を追加します。

私は助けのために非常に感謝される;)私はNet::Curl::Multi試した池上のアドバイスに従い

UPDATE

。私は結果に非常に満足しています。同時に何千ものURLを取得するのにParallel::ForkManagerを使用した後、Net::Curl::Multiはすばらしいようです。 ここに私のコードはwhile loop with filehandleです。このようなことを書いたのは初めてのことだと思うが、経験豊富なPerlユーザーに見てみようと思って、潜在的なバグや欠けているものがあるかどうか教えてほしい。 また、もし私が尋ねることができます:Net::Curl::Multiの同時実行性の仕組みを完全に理解していないので、MySQL UPDATEコマンド(DBI経由)を入れて何か問題が起きるかどうか教えてください。RESPONSEループ約50台の同時実行スクリプトN::C::M、多分もっと)。

#!/usr/bin/perl 

use Net::Curl::Easy qw(:constants); 
use Net::Curl::Multi qw(); 

sub make_request { 
    my ($url) = @_; 
    my $easy = Net::Curl::Easy->new(); 
    $easy->{url} = $url; 
    $easy->setopt(CURLOPT_URL,  $url); 
    $easy->setopt(CURLOPT_HEADERDATA, \$easy->{head}); 
    $easy->setopt(CURLOPT_FILE,  \$easy->{body}); 
    return $easy; 
} 

my $maxWorkers = 10; 

my $multi = Net::Curl::Multi->new(); 
my $workers = 0; 

my $i = 1; 
open my $fh, "<", "urls.txt"; 
LINE: while (my $url = <$fh>) 
{ 
    chomp($url); 
    $url .= "?$i"; 
    print "($i) $url\n"; 
    my $easy = make_request($url); 
    $multi->add_handle($easy); 
    $workers++; 

    my $running = 0; 
    do { 
     my ($r, $w, $e) = $multi->fdset(); 
     my $timeout = $multi->timeout(); 
     select $r, $w, $e, $timeout/1000 
     if $timeout > 0; 

     $running = $multi->perform(); 
     RESPONSE: while (my ($msg, $easy, $result) = $multi->info_read()) { 
      $multi->remove_handle($easy); 
      $workers--; 
      printf("%s getting %s\n", $easy->getinfo(CURLINFO_RESPONSE_CODE), $easy->{url}); 
     } 

     # dont max CPU while waiting 
     select(undef, undef, undef, 0.01); 
    } while ($workers == $maxWorkers || (eof && $running)); 
    $i++; 
} 
close $fh; 

答えて

5

Net :: Curlは非常に高速なライブラリです。さらに、並列要求も処理できます。 AnyEventの代わりにこれを使用することをお勧めします。

use Net::Curl::Easy qw(:constants); 
use Net::Curl::Multi qw(); 

sub make_request { 
    my ($url) = @_; 
    my $easy = Net::Curl::Easy->new(); 
    $easy->{url} = $url; 
    $easy->setopt(CURLOPT_URL,  $url); 
    $easy->setopt(CURLOPT_HEADERDATA, \$easy->{head}); 
    $easy->setopt(CURLOPT_FILE,  \$easy->{body}); 
    return $easy; 
} 

my $max_running = 10; 
my @urls = ('http://www.google.com/'); 

my $multi = Net::Curl::Multi->new(); 
my $running = 0; 
while (1) { 
    while (@urls && $running < $max_running) { 
     my $easy = make_request(shift(@urls)); 
     $multi->add_handle($easy); 
     ++$running; 
    } 

    last if !$running; 

    my ($r, $w, $e) = $multi->fdset(); 
    my $timeout = $multi->timeout(); 
    select($r, $w, $e, $timeout/1000) 
     if $timeout > 0; 

    $running = $multi->perform(); 
    while (my ($msg, $easy, $result) = $multi->info_read()) { 
     $multi->remove_handle($easy); 
     printf("%s getting %s\n", $easy->getinfo(CURLINFO_RESPONSE_CODE), $easy->{url}); 
    } 
} 
+0

私は、「コールバック関数の多くが設定されていないそうです" URLホストにドメインがある場合に表示されるようです。私がIPを使っているなら、このエラーは表示されません。また、私がf.e。 'print 'はそれを得ました!"; '#process $ easy'があるところでは、ページの内容が自動的に出力されます。 – alan

+0

コンテンツを印刷するのではなく$ easyに保存するように修正しました。私はあなたが得るコールバックエラーを取得しないでください?[それを試してみてください。関連するかもしれません] – ikegami

+0

助けてくれてありがとう。 Unfortunatelly私はまだ "コールバック関数が設定されていません"を取得します。実際には4回、あなたの 'printf'。私はそれがどこから来るか知りません。 – alan

2

これは、非同期の方法で、正確に何をしたいし、それが安全な方法でNet::Curlを包むことであることを行います。

#!/usr/bin/env perl 

package MyDownloader; 
use strict; 
use warnings qw(all); 

use Moo; 

extends 'YADA::Worker'; 

has '+use_stats'=> (default => sub { 1 }); 
has '+retry' => (default => sub { 10 }); 

after init => sub { 
    my ($self) = @_; 

    $self->setopt(
     encoding   => '', 
     verbose    => 1, 
    ); 
}; 

after finish => sub { 
    my ($self, $result) = @_; 

    if ($self->has_error) { 
     print "ERROR: $result\n"; 
    } else { 
     # do the interesting stuff here 
     printf "Finished downloading %s: %d bytes\n", $self->final_url, length ${$self->data}; 
    } 
}; 

around has_error => sub { 
    my $orig = shift; 
    my $self = shift; 

    return 1 if $self->$orig(@_); 
    return 1 if $self->getinfo('response_code') =~ m{^5[0-9]{2}$}x; 
}; 

1; 

package main; 
use strict; 
use warnings qw(all); 

use Carp; 

use YADA; 

my $q = YADA->new(
    max  => 8, 
    timeout => 30, 
); 

open(my $fh, '<', 'file_with_urls_per_line.txt') 
    or croak "can't open queue: $!"; 
while (my $url = <$fh>) { 
    chomp $url; 

    $q->append(sub { 
     MyDownloader->new($url) 
    }); 
} 
close $fh; 
$q->wait; 
+1

あなたのアイデアはすばらしく、すべての要件を満たしていますが、池上のソリューションは私にとってははるかに理解しやすく読みやすいです。 – alan

関連する問題