<code>#!/usr/bin/perl</code>
<code>use</code> <code>strict;</code>
<code>use</code> <code>warnings;</code>
<code>use</code> <code>threads;</code>
<code>use</code> <code>threads::shared;</code>
<code>use</code> <code>Thread::Queue;</code>
<code>use</code> <code>Thread::Semaphore;</code>
<code>use</code> <code>Bloom::Filter;</code>
<code>use</code> <code>URI;</code>
<code>use</code> <code>URI::URL;</code>
<code>use</code> <code>Web::Scraper;</code>
<code>use</code> <code>LWP::Simple;</code>
<code>use</code> <code>LWP::UserAgent;</code>
<code>use</code> <code>HTTP::Cookies;</code>
<code>#use HTTP::Cookies::Guess;</code>
<code>use</code> <code>String::Diff;</code>
<code>use</code> <code>String::Diff</code><code>qw(diff_fully diff diff_merge diff_regexp)</code><code>;</code>
<code>use</code> <code>URI::Split</code><code>qw(uri_split uri_join)</code><code>;</code>
<code>my</code> <code>$fid</code> <code>: shared;</code><code>#下载的页面以递增的数字命名</code>
<code>share(</code><code>$fid</code><code>); </code><code>#多线程共享该变量</code>
<code>$fid</code><code>=0;</code>
<code>#crawling with signed cookie</code>
<code>my</code> <code>$cookie_jar</code> <code>=</code><code>'.mozilla/firefox/bg146ia6.default/cookies.sqlite'</code><code>;</code>
<code>my</code> <code>$tmp_ua</code> <code>= LWP::UserAgent->new; </code><code>#UserAgent用来发送网页访问请求</code>
<code>$tmp_ua</code><code>->timeout(15); </code><code>##连接超时时间设为15秒</code>
<code>$tmp_ua</code><code>->protocols_allowed( [</code><code>'http'</code><code>,</code><code>'https'</code> <code>] );</code><code>##只允许http和https协议</code>
<code>$tmp_ua</code><code>->agent(</code>
<code>"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727;.NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)"</code>
<code> </code><code>)</code>
<code> </code><code>;</code><code>##用来在header中告诉服务器你用的是什么"浏览器",设置文件头的User-Agent</code>
<code>$tmp_ua</code><code>->cookie_jar(HTTP::Cookies->new(</code><code>'file'</code><code>=></code><code>"$ENV{'HOME'}/$cookie_jar"</code><code>,</code><code>'autosave'</code><code>=>1));</code>
<code># 设置cookie,在运行过程中必须执行两个方法,extract_cookies($request) 和 add_cookie_header($response)。在运行的过程中实际用到了HTTP::Cookies模块。如:</code>
<code># $ua->cookie_jar({ file => "$ENV{HOME}/.cookies.txt" });</code>
<code># 等价于</code>
<code># require HTTP::Cookies;</code>
<code># $ua->cookie_jar(HTTP::Cookies->new(file => "$ENV{HOME}/.cookies.txt"));</code>
<code>push</code> <code>@{</code><code>$tmp_ua</code><code>->requests_redirectable},</code><code>'POST'</code><code>;</code><code>#告诉LWP在POST请求发送后如果发生重新定向就自动跟随</code>
<code>my</code> <code>$max_threads</code> <code>= 5;</code>
<code>my</code> <code>$base_url</code> <code>=</code><code>$ARGV</code><code>[0] ||</code><code>'http://www.cnblogs.com/zhangchaoyang/'</code><code>;</code>
<code>my</code> <code>$host</code> <code>= URI::URL->new(</code><code>$base_url</code><code>)->host;</code>
<code>print</code> <code>"Host Name: $host.\n"</code><code>;</code>
<code>my</code> <code>$queue</code> <code>= Thread::Queue->new( ); </code><code>#线程队列,每个线程负责去处理一个url</code>
<code>my</code> <code>$semaphore</code> <code>= Thread::Semaphore->new(</code><code>$max_threads</code> <code>);</code>
<code>my</code> <code>$mutex</code> <code>= Thread::Semaphore->new( 1 );</code>
<code>#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;</code>
<code>#my $logfile = "crawler".($year+1900).($mon+1).$mday.$hour.$min.$sec.".log";</code>
<code>#open(BANLOG,">>$logfile") or die("can't open logfile:$!\n");</code>
<code># Bloom::Filter使用更少的内存采用一种基于概率的算法来进行存在性测试。</code>
<code>my</code> <code>$filter</code> <code>= shared_clone( Bloom::Filter->new(</code><code>capacity</code> <code>=> 1000000,</code><code>error_rate</code> <code>=> 0.001) );</code>
<code>$queue</code><code>->enqueue(</code><code>$base_url</code> <code>); </code><code>#放入线程队列的URL就要被线程所处理</code>
<code>$filter</code><code>->add(</code><code>$base_url</code> <code>); </code><code>#放入filter中好判断该URL是否已经存在</code>
<code>my</code> <code>@tmp_url</code> <code>= (); </code><code>#@tmp_url存在处理过的url</code>
<code>push</code><code>(</code><code>@tmp_url</code><code>,</code><code>$base_url</code><code>);</code>
<code>while</code><code>( 1 )</code>
<code>{</code>
<code> </code><code># join所有可以被join的线程</code>
<code> </code><code>#my $joined = 0;</code>
<code> </code><code>foreach</code> <code>( threads->list(threads::joinable) )</code>
<code> </code><code>{</code>
<code> </code><code>#$joined ++;</code>
<code> </code><code>$_</code><code>-></code><code>join</code><code>( );</code>
<code> </code><code>}</code>
<code> </code><code>#print $joined, " joinedn";</code>
<code> </code><code># if there are no url need process.</code>
<code> </code><code>my</code> <code>$item</code> <code>=</code><code>$queue</code><code>->pending();</code><code>#返回队列中url的个数</code>
<code> </code><code># 线程队列为空</code>
<code> </code><code>if</code><code>(</code><code>$item</code> <code>== 0 )</code>
<code> </code><code>my</code> <code>$active</code> <code>= threads->list(threads::running);</code>
<code> </code><code># 已经没有active线程了,结束所有的工作</code>
<code> </code><code>if</code><code>(</code><code>$active</code> <code>== 0 )</code>
<code> </code><code>{</code>
<code> </code><code>print</code> <code>"All done!\n"</code><code>;</code>
<code> </code><code>last</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code># 如果还有活动线程,那么主线程sleep,等待处理URL的子线程结束</code>
<code> </code><code>else</code>
<code> </code><code>#print "[MAIN] 0 URL, but $active active threadn";</code>
<code> </code><code>sleep</code> <code>1;</code>
<code> </code><code>next</code><code>;</code>
<code> </code><code># 线程队列不为空,信号量减1,占用一个线程来处理url</code>
<code> </code><code>#print "[MAIN] $item URLn";</code>
<code> </code><code>$semaphore</code><code>->down;</code>
<code> </code><code>#print "[MAIN]Create thread.n";</code>
<code> </code><code>threads->create( \</code><code>&ProcessUrl</code> <code>);</code>
<code>}</code>
<code># join all threads which can be joined</code>
<code>foreach</code> <code>( threads->list() )</code>
<code> </code><code>$_</code><code>-></code><code>join</code><code>( );</code>
<code>sub</code> <code>ProcessUrl</code>
<code> </code><code>my</code> <code>$scraper</code> <code>= scraper</code>
<code> </code><code>process</code><code>'//a'</code><code>,</code><code>'links[]'</code> <code>=></code><code>'@href'</code><code>;</code><code>#根据XPath表达式寻找所有的标签a,把href属性存到散列的value中</code>
<code> </code><code>};</code>
<code> </code><code>my</code> <code>$res</code><code>;</code>
<code> </code><code>my</code> <code>$link</code><code>;</code>
<code> </code><code>while</code><code>(</code><code>my</code> <code>$url</code> <code>=</code><code>$queue</code><code>->dequeue_nb() )</code>
<code> </code><code>eval</code><code>#eval BLOCK,BLOCK只会被解析一次,并且在编译时进行代码语法检查。</code>
<code> </code><code>{</code>
<code> </code><code>print</code> <code>"开始下载"</code><code>,URI->new(</code><code>$url</code><code>)->as_string,</code><code>"\t\$fid=$fid\n"</code><code>;</code>
<code> </code><code>LWP::Simple::getstore(URI->new(</code><code>$url</code><code>)->as_string,</code><code>"$ENV{'HOME'}/master/cnblog/cn$fid"</code><code>) or</code><code>print</code> <code>"Can't download the web page."</code><code>;</code>
<code> </code><code>$fid</code><code>+=1;</code>
<code> </code><code>$scraper</code><code>->user_agent(</code><code>$tmp_ua</code><code>);</code><code>#设置$scraper的user_agent</code>
<code> </code><code>$res</code> <code>=</code><code>$scraper</code><code>->scrape( URI->new(</code><code>$url</code><code>) )->{</code><code>'links'</code><code>};</code><code>#把URI传给scrape函数。scrape函数返回一个数组引用,因为links是数组</code>
<code> </code><code>};</code>
<code> </code><code>if</code><code>( $@ )</code><code># 当BLOCK中有语法错误、运行时错误遇到 die 语句, eval 将返回 undef 。错误码被保存在 $@ 中。</code>
<code> </code><code>warn</code> <code>"$@\n"</code><code>;</code>
<code> </code><code>next</code> <code>if</code> <code>(!</code><code>defined</code> <code>$res</code> <code>);</code><code>#如果HTML文档中没有发现a标签</code>
<code> </code><code>#print "there are ".scalar(threads->list(threads::running))." threads, ", $queue->pending(), " urls need process.n";</code>
<code> </code><code>foreach</code><code>( @{</code><code>$res</code><code>} )</code>
<code> </code><code># $_ => URI->new("http://example.com/") 所以要调用sa_string来获取"http://example.com/"</code>
<code> </code><code>$link</code> <code>=</code><code>$_</code><code>->as_string;</code>
<code> </code><code>$link</code> <code>= URI::URL->new(</code><code>$link</code><code>,</code><code>$url</code><code>);</code>
<code> </code><code>#$u1 = URI::URL->new($str, $base);</code>
<code> </code><code>#$u2 = $u1->abs;</code>
<code> </code><code># not http and not https?</code>
<code> </code><code>next</code> <code>if</code><code>(</code><code>$link</code><code>->scheme ne</code><code>'http'</code> <code>&&</code><code>$link</code><code>->scheme ne</code><code>'https'</code> <code>);</code>
<code> </code><code>#The three forms of URI reference syntax are summarized as follows:</code>
<code> </code><code>#<scheme>:<scheme-specific-part>#<fragment></code>
<code> </code><code>#<scheme>://<authority><path>?<query>#<fragment></code>
<code> </code><code>#<path>?<query>#<fragment></code>
<code> </code><code>#可以通过URL::Split把名个部分分离出来</code>
<code> </code><code># another domain?</code>
<code> </code><code># next if( $link->host ne $host );</code>
<code> </code><code>#search for the sub domain</code>
<code> </code><code>next</code> <code>if</code><code>(!(</code><code>$link</code><code>->host =~ /</code><code>$host</code><code>/));</code>
<code> </code><code>$link</code> <code>=</code><code>$link</code><code>-></code><code>abs</code><code>->as_string;</code><code>#获得绝对路径</code>
<code> </code><code>if</code><code>(</code><code>$link</code> <code>=~ /(.*?)</code><code>#(.*)/ )#去除书签锚点,即#以后的内容</code>
<code> </code><code>{</code>
<code> </code><code>$link</code> <code>= $1;</code>
<code> </code><code>}</code>
<code> </code><code>next</code> <code>if</code><code>(</code><code>$link</code> <code>=~ /rss|.(jpg|png|bmp|mp3|wma|wmv|gz|zip|rar|iso|pdf)$/i );</code><code>#这些文件格式我们不抓取</code>
<code> </code><code>#print "test:$link\n";</code>
<code> </code><code>#EscapeUrl,skip query form values</code>
<code> </code><code>my</code> <code>$tmp_link</code> <code>=</code><code>&EscapeUrl</code><code>(</code><code>$link</code><code>);</code><code>#$tmp_link中已经把查询参数的值去掉了</code>
<code> </code><code>#print "Escape:".$tmp_link."\n";</code>
<code> </code><code>$mutex</code><code>->down();</code><code>#互质体减1,进入线程临界资源区</code>
<code> </code><code>my</code> <code>$tmp_mark</code> <code>= 0;</code>
<code> </code><code>#print "test start:$link\n";</code>
<code> </code><code>if</code><code>( !</code><code>$filter</code><code>->check(</code><code>$tmp_link</code><code>) ) </code><code>#如果$tmp_link不在$filter中</code>
<code> </code><code>#print "Test filter ok:$tmp_link\n";</code>
<code> </code><code>#DiffUrl,diff $link from queue with number</code>
<code> </code><code>foreach</code><code>(</code><code>@tmp_url</code><code>)</code>
<code> </code><code>{</code>
<code> </code><code>#print "Test Queue:".$tmpurl."\n";</code>
<code> </code><code>#print "test-1:$_\ntest-2:$tmp_link\n";</code>
<code> </code><code>if</code><code>(</code><code>&DiffUrl</code><code>(</code><code>$_</code><code>,</code><code>$link</code><code>))</code><code>#如果发现@tmp_url中的url和当前页面中的一个链接url仅是在某些数字上不同(很可能是查询参数值不同),则跳过该链接,即跳到else里面去。</code>
<code> </code><code>{</code>
<code> </code><code>$tmp_mark</code> <code>= 2;</code>
<code> </code><code>last</code><code>;</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>if</code><code>(</code><code>$tmp_mark</code> <code>!= 2 )</code>
<code> </code><code>$queue</code><code>->enqueue(</code><code>$link</code><code>); </code><code>#把页面上的链接$link交给线程进行处理</code>
<code> </code><code>#print "add queue:$link\n";</code>
<code> </code><code>$filter</code><code>->add(</code><code>$tmp_link</code><code>);</code><code>#$tmp_link放入$filter</code>
<code> </code><code>#print "add filter:$tmp_link\n";</code>
<code> </code><code>#print BANLOG $filter->key_count(), " ", $link, "\n";</code>
<code> </code><code>#print $filter->key_count(), " ", $link, "\n";</code>
<code> </code><code>push</code><code>(</code><code>@tmp_url</code><code>,</code><code>$link</code><code>);</code><code>#把$link放入已处理的url数组@tmp_url</code>
<code> </code><code>else</code>
<code> </code><code>#print "pass:$link\n";#$link被忽略</code>
<code> </code><code>#print "pass:$link\n";</code>
<code> </code><code>$mutex</code><code>->up();</code><code>#互斥信号量加1</code>
<code> </code><code>undef</code> <code>$link</code><code>;</code>
<code> </code><code>undef</code> <code>$res</code><code>;</code><code>#清除创建的一些object,否则在while循环中这些object越积越多</code>
<code> </code><code>undef</code> <code>$scraper</code><code>;</code>
<code> </code><code>$semaphore</code><code>->up( );</code><code>##普通信号量加1</code>
<code>#close(BANLOG);</code>
<code>print</code> <code>"ALL DONE.\n"</code><code>;</code>
<code>#把URL尾部的request参数置为空</code>
<code>#比如http://category.dangdang.com/?ref=www-0-C&name=orisun-zhang#ref=www-0-C被处理为http://category.dangdang.com/?ref=&name=</code>
<code>sub</code> <code>EscapeUrl</code>
<code> </code><code>my</code> <code>$urlold</code> <code>=</code><code>shift</code><code>;</code>
<code> </code><code>my</code> <code>(</code><code>$scheme</code><code>,</code><code>$auth</code><code>,</code><code>$path</code><code>,</code><code>$query</code><code>,</code><code>$frag</code><code>) = uri_split(</code><code>$urlold</code><code>);</code><code>#把一个url的各部分分离出来</code>
<code> </code><code>my</code> <code>$urlnew</code> <code>= uri_join(</code><code>$scheme</code><code>,</code><code>$auth</code><code>,</code><code>$path</code><code>);</code>
<code> </code><code>my</code> <code>$u</code> <code>= URI->new(</code><code>$urlold</code><code>);</code>
<code> </code><code>my</code> <code>@tmp_array</code> <code>=</code><code>$u</code><code>->query_form();</code>
<code> </code><code>my</code> <code>$tmp</code> <code>=</code><code>''</code><code>;</code>
<code> </code><code>my</code> <code>$i</code> <code>= 0;</code>
<code> </code><code>for</code><code>(</code><code>$i</code><code>=0;</code><code>$i</code><code><</code><code>@tmp_array</code><code>;</code><code>$i</code><code>+=2)</code><code>#把request参数的值去掉</code>
<code> </code><code>$tmp</code> <code>.=</code><code>$tmp_array</code><code>[</code><code>$i</code><code>].</code><code>"=&"</code><code>;</code>
<code> </code><code>if</code><code>(</code><code>@tmp_array</code> <code>!= 0)</code>
<code> </code><code>$tmp</code> <code>=~ s/&$//;</code>
<code> </code><code>$urlnew</code> <code>.=</code><code>"?"</code><code>.</code><code>$tmp</code><code>;</code>
<code> </code><code>undef</code> <code>$u</code><code>;</code><code>#清除子例程中创建的object</code>
<code> </code><code>#print $urlnew."\n";</code>
<code> </code><code>return</code> <code>$urlnew</code><code>;</code>
<code>sub</code> <code>DiffUrl</code>
<code> </code><code>my</code> <code>$urlnew</code> <code>=</code><code>shift</code><code>;</code>
<code> </code><code>my</code> <code>$urloldx</code> <code>=</code><code>&EscapeUrl</code><code>(</code><code>$urlold</code><code>);</code>
<code> </code><code>my</code> <code>$urlnewx</code> <code>=</code><code>&EscapeUrl</code><code>(</code><code>$urlnew</code><code>);</code>
<code> </code><code>my</code><code>(</code><code>$old</code><code>,</code><code>$new</code><code>) = String::Diff::diff(</code><code>$urloldx</code><code>,</code><code>$urlnewx</code><code>);</code>
<code> </code><code>#my($old,$new) = String::Diff::diff($urlold,$urlnew);</code>
<code> </code><code>if</code> <code>((</code><code>$old</code> <code>=~ m/(\[\d+\])/i) && (</code><code>$new</code> <code>=~ m/{\d+}/i)) </code><code>#如果两个url仅是在某些数字上不同</code>
<code> </code><code>#if ($new =~ m/{\d+}/i)</code>
<code> </code><code>#print "test num success.\n";</code>
<code> </code><code>return</code> <code>1;</code>
<code> </code><code>else</code>
<code> </code><code>#print "test num failed.\n";</code>
<code> </code><code>return</code> <code>0;</code>