一段 perl 代码展示如何 all in one 优雅地编写一个异步爬虫 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dfgddgf
V2EX    Perl

一段 perl 代码展示如何 all in one 优雅地编写一个异步爬虫

  •  
  •   dfgddgf 2022-09-15 18:28:30 +08:00 2491 次点击
    这是一个创建于 1184 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #cpan https://cpan.metacpan.org/authors/id/S/SR/SRI/Mojolicious-7.31.tar.gz #cpanm -n [email protected] use feature ':5.10'; use strict; use warnings; use utf8; use Mojo; use Encode qw(decode encode); ########################################################################## $ENV{MOJO_REACTOR} = 'Mojo::Reactor::EV'; #使用 EV 具有更好的性能 my $ua = Mojo::UserAgent->new; $ua->inactivity_timeout(60); $ua->connect_timeout(60); $ua->request_timeout(60); #适当延长超时的时间,阻止过早的 http 请求失败,会有更好的性能 $ua->max_connections(1000); #最大连接数 1000 $ua->max_redirects(0); #阻止 http3xx 重定向 $ua->transactor->name('Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0'); #使用正常浏览器的 user agent $ua->cookie_jar->ignore( sub { 1 } ); #禁用 Mojo::UserAgent 自动处理 cookie $ua->proxy->http('http://127.0.0.1:8080')->https('http://127.0.0.1:8080'); #使用代理服务器 ########################################################################## my @list = (); #原始队列 my @urllist = (); #下载队列 my $n = 0; #下载数量 my $m = 0; #出错数量 my $produce_num = 0; #生产者数量 my $consumer_num = 0; #消费者数量 my $cookie_num = 0; #cookie 数量 my @cookielist = (); #使用的 cookie 队列 my %cookieinvalid = (); #失效的 cookie 散列 ########################################################################## open FILEIN, '<', "./url.txt" or die "$!"; while (<FILEIN>) { my $cOntent= $_; chomp($content); $cOntent=~ s/\r//; push( @list, $content ); } close FILEIN; #导入下载列表 ########################################################################## sub append_txt_to_file { my $file_name = $_[0]; my $txt = $_[1]; local *FH; open FH, '>>', $file_name; print FH $txt; close FH; } sub write_txt_to_file { my $file_name = $_[0]; my $txt = $_[1]; local *FH; open FH, '>', $file_name; print FH $txt; close FH; } my %safe_character = ( '<' => '<', '>' => '>', ':' => ':', '"' => '"', '/' => '/', '\\' => '\', '|' => '|', '?' => '?', '*' => '*', ); sub repace_safe { my $per_char = $_[0]; my $one_txt = $_[1]; my $output_char; if ( exists $safe_character{$per_char} ) { $output_char = $safe_character{$per_char}; } else { $output_char = $per_char; } return $output_char; } sub find { my $html_bin = $_[0]; my $id = $_[1]; if ( $html_bin =~ m/<\/html>/ ) { return '####'; } else { return '@@@'; } } ########################################################################## sub get_multiplex { my $id = $_[0]; my $delay = Mojo::IOLoop->delay( sub { get_multiplex($id) } ); #get_multiplex 递归迭代的开始标记 #$id 是每一个线程(端口的序号) my $end = $delay->begin; Mojo::IOLoop->timer( 0.1 => $delay->begin ); #每个 http 请求前暂停 0.1s if ( scalar @urllist == 0 ) { if ( $produce_num == $consumer_num ) { Mojo::IOLoop->stop; #异步循环结束 #当队列数量为 0 ,且所有的线程数据都处理完毕的时候,终止事件循环 #return 存在一个递归返回链,这里可以更快地结束 } return; #这里返回后异步任务数量为 0 时,系统会自动结束异步循环,不过速度较慢 #return 返回闭包函数的开始,并结束闭包函数,下面不开启递归自身 } else { my $object = shift @urllist; $produce_num++; my $url = $object; my $filename = $object; $filename =~ s/^http:\/\/www\.bing\.com\/w\///m; $filename =~ s/(.)/repace_safe($1)/eg; $filename = "./www.bing.com/" . $filename . ".html"; if ( -e $filename ) { syswrite STDERR, encode( 'utf8', $n . "\t" . $object . "\t 跳过\n" ); #STDOUT 编码已改,输送到 STDOUT 会出现错误 $consumer_num++; $end->(); } else { my $build_tx = $ua->build_tx( GET => $url ); $build_tx->req->headers->remove('Accept-Encoding'); #阻止网页压缩,保证更好的性能 $build_tx->req->headers->add( 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' ); $build_tx->req->headers->add( 'Accept-Language' => 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2' ); $ua->transactor->name( 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:' . int( rand(900) ) . ') Gecko/' . int( rand(40000001) ) . ' Firefox/' . int( rand(900) ) . '.0' ); #使用 2 万个 cookie $ua->start( $build_tx => sub { my ( $ua, $tx ) = @_; if ( !$tx->is_finished ) { push( @urllist, $object ); syswrite STDERR, "http 传输未完成" . "\n"; syswrite STDERR, encode( 'utf8', $url . "\t" . $tx->error->{message} . "\n" ); } else { my $code = ''; $code = $tx->res->code if defined $tx->res->code; if ( $code =~ /\A2/ ) { my $size = $tx->res->content->asset->size; my $content_length = $tx->res->headers->to_hash->{'Content-Length'}; if ( ( $size == $content_length ) || !( defined $content_length ) ) { my $outnum = find( $tx->res->body, $id ); if ( $outnum ne '@@@' ) { append_txt_to_file( "url.txt", $object . "\t" . $outnum . "\n" ); write_txt_to_file( $filename, $tx->res->body ); $n++; syswrite STDERR, encode( 'utf8', $n . "\t" . $object . "\n" ); } else { $n++; syswrite STDERR, encode( 'utf8', $m . "\t" . $object . "\t 网页下载完整但未提取到数据\n" ); } } else { $m++; push( @urllist, $object ); syswrite STDERR, encode( 'utf8', $m . "\t" . $object . "\t 网页未下载完整\n" ); } } elsif ( $code =~ /\A4/ ) { syswrite STDERR, encode( 'utf8', $m . "\t" . $object . "\thttp 4xx\n" ); push( @urllist, $object ); Mojo::IOLoop->timer( 0.5 => $delay->begin ); #http 404 } else { $m++; push( @urllist, $object ); syswrite STDERR, encode( 'utf8', $m . "\t" . $object . "\t 未发现 http code, http 3xx, http 5xx\n" ); #标记失效的从 cookie http 3xx Mojo::IOLoop->timer( 0.5 => $delay->begin ); #服务器返回 5xx ,暂停 0.5s #未发现 http code, http 302, http 503 } } $consumer_num++; $end->(); #get_multiplex 递归迭代的结束标记 #从这里跳转到下一个 get_multiplex } ); } } } ########################################################################## $produce_num = 0; $consumer_num = 0; @urllist = @list; #异步下载前的变量准备 foreach my $id ( 1 .. 50 ) { get_multiplex($id) } #使用 50 个线程(端口)下载 #如果线程数是 100 ,限制最大 cookie 数无法生效,并且 EV 会出现错误 Mojo::IOLoop->start; #异步循环启动 ########################################################################## 
    17 条回复    2023-12-19 00:25:13 +08:00
    wxf666
        1
    wxf666  
       2022-09-15 18:47:48 +08:00
    每秒大概能爬多少个页面?
    dfgddgf
        2
    dfgddgf  
    OP
       2022-09-15 18:49:26 +08:00
    @wxf666 300M 宽带跑满,必应和百度都能坚挺,扛得住
    wxf666
        3
    wxf666  
       2022-09-15 18:55:39 +08:00
    @dfgddgf 你本地测试,平均每秒能爬下来并解析多少页面呢

    想看看这 perl 的效率如何
    dfgddgf
        4
    dfgddgf  
    OP
       2022-09-15 18:56:51 +08:00
    @wxf666 300M 带宽 每秒 37.5-40MB/s 下载速度,按照一个网页 0.7MB 计算,每秒可以下载 50 个。

    如果网页比较小,每秒下载几百个网页轻轻松松。

    别把人家服务器搞崩溃了。

    爬虫学的好,牢饭吃得饱。
    wxf666
        5
    wxf666  
       2022-09-15 19:05:42 +08:00
    @dfgddgf 你在上一个帖子的意思,不是『如何用最少的人工,写出速度最快的爬虫』嘛

    感觉你有几亿数十亿页面要爬取解析来着。。

    所以想看看你最后,是如何用最优雅的姿势,写出最能压榨机器性能的爬虫的
    dfgddgf
        6
    dfgddgf  
    OP
       2022-09-15 19:27:56 +08:00
    @wxf666 VirtualBox 虚拟机 linux mint 安装 apache2 ,使用 84KB 的网页文件作为主页,使用上面的代码稍作修改

    在 cygwin 环境执行上面的 perl 代码,重复下载本地的 84KB 的网页文件( http://192.168.1.5/index.html) 10 万次数
    耗时

    real 3m25.076s
    user 2m5.890s
    sys 0m31.780s


    算下来,连同网页正则匹配,平均请求速率是 100000/205s=487.8 个 /每秒

    perl 做异步爬虫够不够强大

    那些说 perl 没落、过时、已死的网友,其实是不了解 perl 语言及其生态的。
    wxf666
        7
    wxf666  
       2022-09-15 19:41:24 +08:00
    @dfgddgf 感觉脚本语言的网络库、正则库、网页解析库等,底层应该都是 C/C++ 实现的吧

    Python 、Perl 、Ruby 速度应该差不多的

    perl 好像是文本处理较为优势,听说搞生物的常用?
    renmu
        8
    renmu  
       2022-09-15 19:54:26 +08:00 via Android
    爬虫主要瓶颈都在网络了,性能什么反倒没什么要紧的
    iwh718
        9
    iwh718  
       2022-09-15 19:58:51 +08:00 via Android
    一直觉得 perl 很厉害,学正则的时候,了解的。
    dbow
        10
    dbow  
       2022-09-15 21:48:27 +08:00
    perl 早就不更新了吧,老语言不如放弃。
    dfgddgf
        11
    dfgddgf  
    OP
       2022-09-16 04:52:19 +08:00
    @dbow
    perl 5.36.0 is now available

    Date: May 28, 2022 00:33
    zsj1029
        12
    zsj1029  
       2022-09-16 09:25:58 +08:00
    @dbow 后面的版本 Raku 改名了
    runningman
        13
    runningman  
       2022-09-16 10:21:21 +08:00
    还好,08 年那会就用 perl 了。一直到 12 ,13 年还在用,由于 team 的人都不会,最后切换到 python 了
    zzzkkk
        14
    zzzkkk  
       2022-09-16 10:22:26 +08:00
    你们自己去 shadowsocks python 和 go 版本 分别用一下 速度差多少
    这 还只是代理本机十几个 几十个请求
    zzzkkk
        15
    zzzkkk  
       2022-09-16 10:23:05 +08:00
    @runningman
    perl 倒闭不是没原因的 它的写法属于倒闭活该 增加码农大脑负担
    runningman
        16
    runningman  
       2022-09-16 10:29:26 +08:00
    @zzzkkk 你不用,不代表人家倒闭,很多运维人员还是在用,没必要评价这个。想用就用,不用拉倒
    louisxxx
        17
    louisxxx  
       2023-12-19 00:25:13 +08:00
    my $n = 0; 这语法兼职逆天。什么叫 my 。估计作者发明时随便搞的
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2709 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 07:47 PVG 15:47 LAX 23:47 JFK 02:47
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86