我必须编写一个脚本,以并行获取一些URL并进行一些工作。过去,我总是使用Parallel::ForkManager
进行此类操作,但现在我想学习一些新知识,并尝试使用AnyEvent
(以及AnyEvent::HTTP
或AnyEvent::Curl::Multi
)进行异步编程...但是我在理解AnyEvent并编写应满足以下条件的脚本时遇到了问题:
我已经阅读了许多手册和教程,但是对于我来说,仍然很难理解阻塞代码和非阻塞代码之间的区别。我在http://perlmaven.com/fetching-several-web-pages-in-parallel-using-anyevent上找到了类似的脚本,Szabo先生在其中解释了基础知识,但我仍然不明白如何实现类似以下内容的脚本:
...
open my $fh, "<", $file;
while ( my $line = <$fh> )
{
# http request, read response, update MySQL
}
close $fh
...
...并在这种情况下添加并发限制。
我将非常感谢您的帮助;)
更新
按照池上的建议,我尝试了
Net::Curl::Multi
。我对结果感到非常满意。经过多年使用Parallel::ForkManager
并发抓取成千上万个URL之后,Net::Curl::Multi
似乎很棒。这是我的文件句柄上带有
while
循环的代码。它似乎应该可以工作,但是考虑到这是我第一次编写类似的东西,我想请经验丰富的Perl用户看一下,并告诉我是否存在一些潜在的错误,我错过的东西等。另外,如果我可能要问:由于我不完全了解
Net::Curl::Multi
的并发性如何工作,请告诉我是否应该在将MySQL UPDATE命令(通过DBI
)放入RESPONSE
循环中时遇到任何问题(显然服务器负载会更高-我预计最终脚本将使用约50个并发的N::C::M
worker运行,也许更多)。#!/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;
最佳答案
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} );
}
}