Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/hibernate/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Perl,如何并行地从URL获取数据?_Perl_Parallel Processing_Fetch - Fatal编程技术网

Perl,如何并行地从URL获取数据?

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) {

我需要从许多不公开任何服务的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) {
 $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';
};