【问题标题】:Efficient way to make thousands of curl requests发出数千个 curl 请求的有效方法
【发布时间】:2015-01-16 00:29:40
【问题描述】:

我正在使用 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 日:添加赏金注释“所需的答案应包括在 Perl 中同时运行数千个 curl 请求的方法,但它的运行速度需要比当前运行的速度更快。它必须写最后输出到单个文件,但顺序无关紧要。”下面的一些 cmets 提到了 fork,但我不确定如何将它应用到我的代码中。我对 Perl 很陌生,因为这是我在其中的第一个程序。

【问题讨论】:

  • 如何分叉给多个孩子,比如一次可能有 20 个,这样你就可以一次运行多个?
  • Net::Curl::Multi 允许您向 curl 库发出并行请求。当然,还有 Parallel::ForkManager 破坏多个curl 进程
  • @TheJester1977 我如何分叉给多个孩子?
  • @ikegami 你有什么有趣的错字吗? :)
  • @Bijan,不,不使用curl,因为您要确保一次只运行一个curl。如果您使用 Net::Curl::Multi,则只需在写入文件时锁定文件,这样就可以工作,但输出将是随机顺序的。因此,您希望将输出保存在内存、数据库或单独的文件中,然后在最后重新组合输出。

标签: perl curl cookies


【解决方案1】:

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

在 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 执行类似的操作,但对于面向数据的任务,我个人更喜欢线程。

您可以使用“整理”子程序对任何数据进行后处理,例如对其进行排序、计数等。

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

【讨论】:

  • 一件小事,你只输出 $result 到文件。你将如何输出 $target 在同一行?
  • 将您想要从源线程打印的任何内容排入队列。以$collate_queue -> enqueue ( "Target: $target Output $output\n" ); 为例。
  • 还有一件事,是否可以使用Term::ProgressBar 来跟踪已完成的进度?我之前的示例已经这样做了,但我想它是不同的,因为我使用的是线程
  • $fetch_q->pending() 将为您提供尚未处理的项目计数。您可以在单独的线程或整理线程中使用它。
  • 一般经验认为 20 通常是一个不错的数字。每个线程都会增加内存使用量。太多人也将开始相互竞争。对于面向 CPU 的任务,我会说每个内核一个线程。对于面向网络 IO,它在很大程度上取决于您的基础设施和请求大小。以这种方式使网络管道饱和并不难。
【解决方案2】:

我很确定以下内容会满足您的要求,但是猛烈抨击具有 10000 个同时请求的服务器并不是很有礼貌。事实上,通过遍历给定 url 的 id 来获取站点数据听起来也不是很友好。我还没有测试过以下内容,但它应该能让你完成 99% 的工作(可能是某个地方的语法/使用错误)。

查看更多信息:

祝你好运!

#!/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;

【讨论】:

    猜你喜欢
    • 2021-03-21
    • 2018-08-10
    • 2018-07-12
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-18
    相关资源
    最近更新 更多