วิธีที่มีประสิทธิภาพในการขอขดนับพันครั้ง

ฉันใช้ CURL เพื่อสร้างคำขอหลายพันรายการ ในโค้ดของฉัน ฉันตั้งค่าคุกกี้เป็นค่าเฉพาะ จากนั้นจึงอ่านค่าบนหน้าเว็บ นี่คือโค้ด 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 ถึง 10,000 ฉันกำลังตั้งค่า cookienum123 เป็นค่านั้นและอ่านการตอบกลับทั้งหมดจากหน้าเว็บ จากนั้นฉันใช้ grep เพื่อแยก # ตอนนี้รหัสที่ฉันใช้งานได้ดี แต่ฉันสงสัยว่ามีวิธีที่เร็วกว่าหรือมีประสิทธิภาพมากกว่านี้ที่ฉันสามารถทำได้หรือไม่

โปรดทราบว่าสิ่งนี้ไม่จำเป็นต้องทำในรูปแบบสคริปต์ Perl (ฉันยังสามารถใช้ไฟล์แบตช์ Windows, เชลล์สคริปต์ Unix ฯลฯ )

แก้ไข 18 ม.ค. : เพิ่มเงินรางวัลพร้อมหมายเหตุ "คำตอบที่ต้องการควรรวมวิธีใน Perl เพื่อเรียกใช้คำขอ curl หลายพันคำขอพร้อมกัน แต่จะต้องรันเร็วกว่าอัตราที่กำลังทำงานอยู่ โดยจะต้องเขียนเอาต์พุตไปที่ สุดท้ายไฟล์เดียวแต่ลำดับไม่สำคัญ" ความคิดเห็นด้านล่างบางส่วนกล่าวถึง fork แต่ฉันไม่แน่ใจว่าจะนำไปใช้กับโค้ดของฉันได้อย่างไร ฉันยังใหม่มากกับ Perl เนื่องจากนี่เป็นโปรแกรมแรกของฉัน


person Bijan    schedule 16.01.2015    source แหล่งที่มา
comment
แล้วการฟอร์กให้กับลูกหลายคน เช่น อาจจะเริ่มครั้งละ 20 ลูก เพื่อให้คุณสามารถรันหลายรายการพร้อมกันได้?   -  person TheJester1977    schedule 16.01.2015
comment
Net::Curl::Multi ช่วยให้คุณสามารถส่งคำขอแบบขนานไปยังไลบรารี curl แน่นอนว่ายังมี Parallel::ForkManager ที่จะทำลายกระบวนการ curl หลายกระบวนการด้วย   -  person ikegami    schedule 16.01.2015
comment
@ TheJester1977 ฉันจะแยกลูกหลายคนได้อย่างไร   -  person Bijan    schedule 16.01.2015
comment
@ikegami จะใช้งานได้ไหมถ้าสิ่งเหล่านี้เขียนลงในไฟล์เดียวกัน   -  person Bijan    schedule 16.01.2015
comment
@Bijan ลองดูคำอธิบายของ fork ที่สร้างขึ้นอย่างแปลกประหลาดแต่ตรงประเด็น: [link]users.telenet.be/bartl/classicperl/fork/all.html   -  person TheJester1977    schedule 16.01.2015
comment
คุณจะสามารถสร้างตัวอย่างสิ่งนี้โดยใช้ตัวอย่างของฉันได้หรือไม่? ฉันยังไม่พบวิธีสร้างส้อมขดหลายอัน   -  person Bijan    schedule 16.01.2015
comment
@ikegami คุณพิมพ์ผิดที่น่าสนใจบ้างไหม? :)   -  person mpapec    schedule 16.01.2015
comment
@Bijan ไม่ ไม่ได้ใช้ curl เนื่องจากคุณจะต้องแน่ใจว่ามีการรัน curl ครั้งละหนึ่งรายการเท่านั้น หากคุณใช้ Net::Curl::Multi คุณจะต้องล็อคไฟล์เมื่อคุณเขียนมันเท่านั้นเพื่อที่จะสามารถทำงานได้ แต่ผลลัพธ์จะอยู่ในลำดับแบบสุ่ม ด้วยเหตุนี้ คุณจึงต้องการเก็บเอาต์พุตไว้ในหน่วยความจำ ในฐานข้อมูล หรือในไฟล์แยกกัน จากนั้นจึงประกอบเอาต์พุตอีกครั้งในตอนท้าย   -  person ikegami    schedule 16.01.2015
comment
เอาต์พุตแบบสุ่มนั้นใช้ได้เพราะมันเข้าสู่ Excel และฉันก็เรียงลำดับค่า   -  person Bijan    schedule 16.01.2015


คำตอบ (2)


สิ่งที่คุณมีนี่คือปัญหาคู่ขนานที่น่าอับอาย สิ่งเหล่านี้เหมาะอย่างยิ่งสำหรับการขนาน เนื่องจากไม่จำเป็นต้องมีการพึ่งพาระหว่างเธรดหรือการสื่อสาร

มีสองวิธีหลักในการดำเนินการนี้ใน 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 ได้ แต่สำหรับงานที่เน้นข้อมูล โดยส่วนตัวแล้วฉันชอบทำเธรดมากกว่า

คุณสามารถใช้ส่วนย่อย 'เปรียบเทียบ' เพื่อประมวลผลข้อมูลภายหลัง เช่น การเรียงลำดับ การนับ อะไรก็ตาม

ฉันจะชี้ให้เห็นด้วย - การใช้ curl และ grep เนื่องจากการเรียกของระบบไม่เหมาะ - ฉันทิ้งมันไว้ตามที่เป็นอยู่ แต่จะแนะนำให้ดูที่ LWP และอนุญาตให้ Perl จัดการการประมวลผลข้อความเพราะมันค่อนข้างดี

person Sobrique    schedule 19.01.2015
comment
สิ่งเล็กๆ น้อยๆ ก็คือ คุณจะส่งออก $result ไปยังไฟล์เท่านั้น คุณจะส่งออก $target ให้อยู่ในบรรทัดเดียวกันได้อย่างไร - person Bijan; 19.01.2015
comment
จัดคิวสิ่งที่คุณต้องการพิมพ์จากเธรดต้นทาง $collate_queue -> enqueue ( "Target: $target Output $output\n" ); เช่น - person Sobrique; 19.01.2015
comment
อีกประการหนึ่ง เป็นไปได้ไหมที่จะใช้ Term::ProgressBar เพื่อติดตามความคืบหน้าที่เสร็จสมบูรณ์ไปมากน้อยเพียงใด ฉันเคยทำสิ่งนี้มาก่อนด้วยตัวอย่างก่อนหน้านี้ แต่ฉันคิดว่ามันแตกต่างออกไปเนื่องจากฉันใช้เธรด - person Bijan; 20.01.2015
comment
$fetch_q->pending() จะแสดงจำนวนรายการที่ยังไม่ได้ดำเนินการให้กับคุณ คุณอาจใช้สิ่งนั้นได้ทั้งในเธรดแยกหรือเธรดตัวเทียบเคียง - person Sobrique; 20.01.2015
comment
สมบูรณ์แบบ ใช้งานได้แล้ว คุณจะแนะนำอะไรเกี่ยวกับ # ของกระทู้? ฉันพบว่าเธรดที่น้อยเกินไปถือว่าช้า แต่การมีเธรดมากเกินไป (~100) ก็ช้าเช่นกัน มีเหตุผลที่คุณเลือก 20 หรือไม่? - person Bijan; 21.01.2015
comment
ประสบการณ์ทั่วไปที่ 20 มักจะเป็นตัวเลขที่ดี แต่ละเธรดจะเพิ่มการใช้หน่วยความจำ มากเกินไปก็จะเริ่มแย่งชิงกัน สำหรับงานเน้น CPU ฉันจะบอกว่าหนึ่งเธรดต่อคอร์ สำหรับเครือข่ายที่มุ่งเน้น IO นั้นจะขึ้นอยู่กับโครงสร้างพื้นฐานและขนาดคำขอของคุณเป็นอย่างมาก การทำให้ท่อเครือข่ายอิ่มตัวด้วยวิธีนี้ไม่ใช่เรื่องยากเกินไป - person Sobrique; 21.01.2015

ฉันค่อนข้างแน่ใจว่าสิ่งต่อไปนี้จะทำสิ่งที่คุณต้องการ แต่การกระแทกเซิร์ฟเวอร์ด้วยคำขอพร้อมกัน 10,000 รายการนั้นไม่สุภาพมากนัก ในความเป็นจริง การรวบรวมข้อมูลของไซต์โดยการเดิน ID ของ URL ที่ระบุนั้นฟังดูไม่เป็นมิตรนักเช่นกัน ฉันยังไม่ได้ทดสอบสิ่งต่อไปนี้ แต่ควรช่วยให้คุณได้ผล 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;
person FunkyShu    schedule 19.01.2015