天天看点

crawler

<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-&gt;new;   </code><code>#UserAgent用来发送网页访问请求</code>

<code>$tmp_ua</code><code>-&gt;timeout(15);               </code><code>##连接超时时间设为15秒</code>

<code>$tmp_ua</code><code>-&gt;protocols_allowed( [</code><code>'http'</code><code>,</code><code>'https'</code> <code>] );</code><code>##只允许http和https协议</code>

<code>$tmp_ua</code><code>-&gt;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>-&gt;cookie_jar(HTTP::Cookies-&gt;new(</code><code>'file'</code><code>=&gt;</code><code>"$ENV{'HOME'}/$cookie_jar"</code><code>,</code><code>'autosave'</code><code>=&gt;1));</code>

<code># 设置cookie,在运行过程中必须执行两个方法,extract_cookies($request) 和 add_cookie_header($response)。在运行的过程中实际用到了HTTP::Cookies模块。如:</code>

<code># $ua-&gt;cookie_jar({ file =&gt; "$ENV{HOME}/.cookies.txt" });</code>

<code># 等价于</code>

<code># require HTTP::Cookies;</code>

<code># $ua-&gt;cookie_jar(HTTP::Cookies-&gt;new(file =&gt; "$ENV{HOME}/.cookies.txt"));</code>

<code>push</code> <code>@{</code><code>$tmp_ua</code><code>-&gt;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-&gt;new(</code><code>$base_url</code><code>)-&gt;host;</code>

<code>print</code> <code>"Host Name: $host.\n"</code><code>;</code>

<code>my</code> <code>$queue</code> <code>= Thread::Queue-&gt;new( );      </code><code>#线程队列,每个线程负责去处理一个url</code>

<code>my</code> <code>$semaphore</code> <code>= Thread::Semaphore-&gt;new(</code><code>$max_threads</code> <code>);</code>

<code>my</code> <code>$mutex</code> <code>= Thread::Semaphore-&gt;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,"&gt;&gt;$logfile") or die("can't open logfile:$!\n");</code>

<code># Bloom::Filter使用更少的内存采用一种基于概率的算法来进行存在性测试。</code>

<code>my</code> <code>$filter</code> <code>= shared_clone( Bloom::Filter-&gt;new(</code><code>capacity</code> <code>=&gt; 1000000,</code><code>error_rate</code> <code>=&gt; 0.001) );</code>

<code>$queue</code><code>-&gt;enqueue(</code><code>$base_url</code> <code>);       </code><code>#放入线程队列的URL就要被线程所处理</code>

<code>$filter</code><code>-&gt;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-&gt;list(threads::joinable) )</code>

<code>    </code><code>{</code>

<code>        </code><code>#$joined ++;</code>

<code>        </code><code>$_</code><code>-&gt;</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>-&gt;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-&gt;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>-&gt;down;</code>

<code>    </code><code>#print "[MAIN]Create thread.n";</code>

<code>    </code><code>threads-&gt;create( \</code><code>&amp;ProcessUrl</code> <code>);</code>

<code>}</code>

<code># join all threads which can be joined</code>

<code>foreach</code> <code>( threads-&gt;list() )</code>

<code>    </code><code>$_</code><code>-&gt;</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>=&gt;</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>-&gt;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-&gt;new(</code><code>$url</code><code>)-&gt;as_string,</code><code>"\t\$fid=$fid\n"</code><code>;</code>

<code>            </code><code>LWP::Simple::getstore(URI-&gt;new(</code><code>$url</code><code>)-&gt;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>-&gt;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>-&gt;scrape( URI-&gt;new(</code><code>$url</code><code>) )-&gt;{</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-&gt;list(threads::running))." threads, ", $queue-&gt;pending(), " urls need process.n";</code>

<code>        </code><code>foreach</code><code>( @{</code><code>$res</code><code>} )</code>

<code>            </code><code># $_ =&gt; URI-&gt;new("http://example.com/")     所以要调用sa_string来获取"http://example.com/"</code>

<code>            </code><code>$link</code> <code>=</code><code>$_</code><code>-&gt;as_string;</code>

<code>            </code><code>$link</code> <code>= URI::URL-&gt;new(</code><code>$link</code><code>,</code><code>$url</code><code>);</code>

<code>            </code><code>#$u1 = URI::URL-&gt;new($str, $base);</code>

<code>            </code><code>#$u2 = $u1-&gt;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>-&gt;scheme ne</code><code>'http'</code> <code>&amp;&amp;</code><code>$link</code><code>-&gt;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>#&lt;scheme&gt;:&lt;scheme-specific-part&gt;#&lt;fragment&gt;</code>

<code>            </code><code>#&lt;scheme&gt;://&lt;authority&gt;&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt;</code>

<code>            </code><code>#&lt;path&gt;?&lt;query&gt;#&lt;fragment&gt;</code>

<code>            </code><code>#可以通过URL::Split把名个部分分离出来</code>

<code>            </code><code># another domain?</code>

<code>            </code><code># next if( $link-&gt;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>-&gt;host =~ /</code><code>$host</code><code>/));</code>

<code>            </code><code>$link</code> <code>=</code><code>$link</code><code>-&gt;</code><code>abs</code><code>-&gt;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>&amp;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>-&gt;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>-&gt;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>&amp;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>-&gt;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>-&gt;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-&gt;key_count(), " ", $link, "\n";</code>

<code>                    </code><code>#print $filter-&gt;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>-&gt;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>-&gt;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&amp;name=orisun-zhang#ref=www-0-C被处理为http://category.dangdang.com/?ref=&amp;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-&gt;new(</code><code>$urlold</code><code>);</code>

<code>    </code><code>my</code> <code>@tmp_array</code> <code>=</code><code>$u</code><code>-&gt;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>&lt;</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>"=&amp;"</code><code>;</code>

<code>    </code><code>if</code><code>(</code><code>@tmp_array</code> <code>!= 0)</code>

<code>        </code><code>$tmp</code> <code>=~ s/&amp;$//;</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>&amp;EscapeUrl</code><code>(</code><code>$urlold</code><code>);</code>

<code>    </code><code>my</code> <code>$urlnewx</code> <code>=</code><code>&amp;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) &amp;&amp; (</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>