Perl,如何并行地从URL获取数据?
我需要从许多不公开任何服务的web数据提供商那里获取一些数据,因此我必须编写类似的内容,例如使用WWW::Mechanize:Perl,如何并行地从URL获取数据?,perl,parallel-processing,fetch,Perl,Parallel Processing,Fetch,我需要从许多不公开任何服务的web数据提供商那里获取一些数据,因此我必须编写类似的内容,例如使用WWW::Mechanize: use WWW::Mechanize; @urls = ('http://www.first.data.provider.com', 'http://www.second.data.provider.com', 'http://www.third.data.provider.com'); %results = {}; foreach my $url (@urls) {
use WWW::Mechanize;
@urls = ('http://www.first.data.provider.com', 'http://www.second.data.provider.com', 'http://www.third.data.provider.com');
%results = {};
foreach my $url (@urls) {
$mech = WWW::Mechanize->new();
$mech->get($url);
$mech->form_number(1);
$mech->set_fields('user' => 'myuser', pass => 'mypass');
$resp = $mech->submit();
$results{$url} = parse($resp->content());
}
consume(%results);
是否有一些(可能很简单;-)方法可以同时从所有提供程序并行地将数据提取到一个公共的%results变量?好的,您可以创建线程来实现这一点——具体请参见
perldoc perlthrtut
和。所以,它可能看起来像这样
use WWW::Mechanize;
use threads;
use threads::shared;
use Thread::Queue;
my @urls=(#whatever
);
my %results :shared;
my $queue=Thread::Queue->new();
foreach(@urls)
{
$queue->enqueue($_);
}
my @threads=();
my $num_threads=16; #Or whatever...a pre-specified number of threads.
foreach(1..$num_threads)
{
push @threads,threads->create(\&mechanize);
}
foreach(@threads)
{
$queue->enqueue(undef);
}
foreach(@threads)
{
$_->join();
}
consume(\%results);
sub mechanize
{
while(my $url=$queue->dequeue)
{
my $mech=WWW::Mechanize->new();
$mech->get($url);
$mech->form_number(1);
$mech->set_fields('user' => 'myuser', pass => 'mypass');
$resp = $mech->submit();
$results{$url} = parse($resp->content());
}
}
请注意,由于您将结果存储在散列中(而不是将内容写入文件),因此不需要任何类型的锁定,除非存在覆盖值的危险。在这种情况下,您需要通过替换
$results{$url}=parse($resp->content())代码>
与
看起来这就是您要查找的内容。Try——上周看到了一个非常好的演示,其中一个示例是URL的并行检索——pod示例中也介绍了这一点。它比手动使用线程更简单(尽管它在下面使用fork)
据我所知,您仍然可以使用WWW::Mechanize
,但避免在线程之间共享内存。对于这个任务来说,它是一个更高层次的模型,可能更简单一些,保留@jackmaney的mechanize例程的主要逻辑完好无损 在Perl中要避免线程<代码>使用线程
主要用于
在Windows上模拟UNIX风格的fork;除此之外,这是毫无意义的
(如果您介意的话,这个实现会让这一事实变得非常清楚,
解释器是一个PerlInterpreter
对象
作品是通过制作一堆线程,然后创建一个全新的
每个线程中的PerlInterpreter对象。线程绝对共享
什么都没有,甚至比子进程做的还要少;fork
可以让您
写时复制,但是使用线程
,所有复制都是在Perl中完成的
空间!慢!)
如果您想在同一个过程中同时做很多事情
在Perl中实现这一点的方法是使用事件循环,如
,
或
,或使用Coro。(你可以
还可以使用AnyEvent
API编写代码,这将使
你可以使用任何事件循环。这是我喜欢的。)区别
两者之间是如何编写代码
(和EV,事件,
POE等)强制您以面向回调的方式编写代码
风格控件不是从上到下流动的,而是处于
延续传球风格。函数不返回值,而是调用
其他函数及其结果。这允许您运行多个IO
并行操作——当给定的IO操作产生
结果,将调用处理这些结果的函数。什么时候
另一个IO操作完成后,将调用该函数。及
等等
这种方法的缺点是您必须重写
代码。因此,有一个名为Coro
的模块,它为Perl提供了真正的
(用户空间)允许您自上而下编写代码的线程,
但仍然是非阻塞的。(这样做的缺点是
大量修改了Perl的内部结构,但它似乎工作得很好。)
既然我们不想重写
今晚,我们将使用Coro。Coro附带了一个名为
那会让你
所有的电话都是
非阻塞。它将在Coro中阻止当前线程(“coroutine”)
行话),但它不会阻止任何其他线程。这意味着你可以
一次处理大量请求,并在结果变为
可用。Coro将比您的网络连接扩展得更好;
每个协同程序只使用几k的内存,所以很容易有十个
周围有成千上万的人
记住这一点,让我们看看一些代码。这是一个开始的程序
并行三个HTTP请求,并打印每个请求的长度
答复。这与你正在做的事情相似,减去实际的
处理;但是你可以把你的代码放在我们计算
长度和工作原理相同
我们将从通常的Perl脚本样板开始:
#!/usr/bin/env perl
use strict;
use warnings;
然后我们将加载Coro特定模块:
use Coro;
use Coro::LWP;
use EV;
Coro在幕后使用事件循环;它会为你挑选一个,如果
您需要,但我们将明确指定EV。这是最好的活动
循环
然后,我们将加载工作所需的模块,即:
use WWW::Mechanize;
现在我们准备好编写程序了。首先,我们需要一个URL列表:
my @urls = (
'http://www.google.com/',
'http://www.jrock.us/',
'http://stackoverflow.com/',
);
然后我们需要一个函数来生成一个线程并完成我们的工作。作出决定
在Coro上的新线程中,您调用的async
类似于
线程;转到此处}。这将创建一个线程,启动它,然后
继续本程序的其余部分
sub start_thread($) {
my $url = shift;
return async {
say "Starting $url";
my $mech = WWW::Mechanize->new;
$mech->get($url);
printf "Done with $url, %d bytes\n", length $mech->content;
};
}
这是我们节目的重点。我们只是把我们正常的LWP程序
在async内部,它将神奇地实现非阻塞<代码>获取块,
但其他协同程序将在等待它获取数据时运行
来自网络
现在我们只需要启动线程:
start_thread $_ for @urls;
最后,我们要开始处理事件:
EV::loop;
就这样。运行此操作时,您将看到一些输出,如:
Starting http://www.google.com/
Starting http://www.jrock.us/
Starting http://stackoverflow.com/
Done with http://www.jrock.us/, 5456 bytes
Done with http://www.google.com/, 9802 bytes
Done with http://stackoverflow.com/, 194555 bytes
如您所见,请求是并行的,您没有
使用线程
更新
您在原始帖子中提到要限制并行运行的HTTP请求的数量。一种方法是使用信号灯,
在科罗
信号灯就像一个计数器。当您想要使用信号量保护的资源时,您可以“关闭”信号量。这将使计数器递减,并继续运行程序。但是,如果在尝试关闭信号量时计数器为零,线程/协同程序将进入睡眠状态,直到它不为零。当计数再次上升时,您的线程将被唤醒,放下信号量,然后继续。最后,当您使用完信号量保护的资源后,您将“启动”信号量
Starting http://www.google.com/
Starting http://www.jrock.us/
Starting http://stackoverflow.com/
Done with http://www.jrock.us/, 5456 bytes
Done with http://www.google.com/, 9802 bytes
Done with http://stackoverflow.com/, 194555 bytes
my $sem = Coro::Semaphore->new(5);
async {
say "Waiting for semaphore";
my $guard = $sem->guard;
say "Starting";
...;
return result;
}
async { EV::loop };
# start all threads
my @running = map { start_thread $_ } @urls;
# wait for each one to return
my @results = map { $_->join } @running;
for my $result (@results) {
say $result->[0], ': ', $result->[1];
}
sub start_thread($) {
return async {
...;
return [$url, length $mech->content];
}
}
my %results;
async {
...;
$results{$url} = 'whatever';
};