发出数千个 curl 请求的有效方法

Efficient way to make thousands of curl requests

我正在使用 CURL 发出数千个请求。在我的代码中,我将 cookie 设置为特定值,然后在页面上读入该值。这是我的 Perl 代码:

#!/usr/bin/perl
my $site = "http://SITENAME/?id=";
my $cookie_name = "cookienum123";
print $fh "#\t\tValue\n";
for my $i ('1'..'10000') {
    my $output = `curl -s -H "Cookie: $cookie_name=$i" -L $site$i | grep -Eo "[0-9]+"`;
    print "$i\t\t$output\n";
}

因此,从 1 到 10000,我将 cookienum123 设置为该值并读取页面的整个响应。然后我使用 grep 来提取#。我现在拥有的代码可以正常工作,但我想知道是否有更快或更有效的方法可以做到这一点。

请注意,这不必作为 Perl 脚本来完成(我也可以使用 Windows 批处理文件、Unix shell 脚本等)。

1 月 18 日编辑:添加注释 "The desired answer should include a way in Perl to run through several thousand curl requests simultaneously but it needs to be run faster than the rate it is currently running at. It has to write the output to a single file in the end but the order does not matter." 的赏金 下面的一些评论提到 fork 但我不确定如何将它应用到我的代码中。我对 Perl 很陌生,因为这是我的第一个程序。

你这里遇到的是一个令人尴尬的并行问题。这些非常适合并行化,因为不需要线程间依赖或通信。

在 perl 中有两种主要的方法可以做到这一点——线程或分叉。我会通常 建议对您正在做的事情使用基于线程的并行处理。这是一个选择问题,但我认为它更适合整理信息。

#!/usr/bin/perl

use strict;
use warnings;

use threads;
use Thread::Queue;

my $numthreads = 20;

my $site        = "http://SITENAME/?id=";
my $cookie_name = "cookienum123";

my $fetch_q   = Thread::Queue->new();
my $collate_q = Thread::Queue->new();


#fetch sub sits in a loop, takes items off 'fetch_q' and runs curl. 
sub fetch {
    while ( my $target = $fetch_q->dequeue() ) {
        my $output =
            `curl -s -H "Cookie: $cookie_name=$target" -L $site$target | grep -Eo "[0-9]+"`;
        $collate_q->enqueue($output);
    }
}

#one instance of collate, which exists to serialise the output from fetch. 
#writing files concurrently can get very messy and build in race conditions. 
sub collate {
    open( my $output_fh, ">", "results.txt" ) or die $!;
    print {$output_fh} "#\t\tValue\n";

    while ( my $result = $collate_q->dequeue() ) {
        print {$output_fh} $result;
    }
    close($output_fh);
}


## main bit:

#start worker threads
my @workers = map { threads->create( \&fetch ) } 1 .. $numthreads;

#collates results. 
my $collater = threads->create( \&collate );

$fetch_q->enqueue( '1' .. '10000' );
$fetch_q->end();

foreach my $thr (@workers) {
    $thr->join();
}

#end collate_q here, because we know all the fetchers are 
#joined - so no more results will be generated. 
#queue will then generate 'undef' when it's empty, and the thread will exit. 
$collate_q->end;

#join will block until thread has exited, e.g. all results in the queue
#have been 'processed'. 
$collater->join;

这将生成 20 个工作线程,它们将 运行 并行,并在结果退出到文件时收集结果。作为替代方案,您可以使用 Parallel::ForkManager 执行类似的操作,但对于面向数据的任务,我个人更喜欢线程。

您可以使用 'collate' sub 对任何数据进行后处理,例如排序、计数等等。

我还要指出 - 使用 curlgrep 作为系统调用并不理想 - 我将它们保持原样,但建议查看 LWP 和允许 perl 处理文本处理,因为它非常擅长。

我很确定以下会做你想做的,但是用 10000 个并发请求来攻击服务器是不太礼貌的。事实上,通过遍历给定 url 的 ID 来收集站点数据听起来也不是很友好。我没有测试以下内容,但它应该可以让你完成 99% 的事情(可能是某个地方的 syntax/usage 错误)。

查看更多信息:

祝你好运!

#!/usr/bin/perl

use warnings;
use strict;

use Mojo::UserAgent;
use Mojo::IOLoop;

my $site = 'http://SITENAME/?id=';
my $cookie_name = 'cookienum123';

#open filehandle and write file header
open my $output_fh, q{>}, 'results.txt'
    or die $!;
print {$output_fh} "#\t\tValue\n";


# Use Mojo::UserAgent for concurrent non-blocking requests
my $ua = Mojo::UserAgent->new;


#create your requests
for my $i (1..10000) {

    #build transaction
    my $tx = $ua->build_tx(GET => "$site$i");

    #add cookie header
    $tx->req->cookies({name => $cookie_name, value => $i});

    #start "GET" with callback to write to file
    $tx = $ua->start( $tx => sub {
      my ($ua, $mojo) = @_;
      print {$output_fh} $i . "\t\t" . $mojo->res->dom->to_string;
    });
}

# Start event loop if necessary
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;


#close filehandle
close $output_fh;