我想检查一个站点的链接,然后递归检查这些站点的链接。但我不想两次获取同一页面。我遇到了逻辑问题。这是Perl代码:
my %urls_to_check = ();
my %checked_urls = ();
&fetch_and_parse($starting_url);
use Data::Dumper; die Dumper(\%checked_urls, \%urls_to_check);
sub fetch_and_parse {
my ($url) = @_;
if ($checked_urls{$url} > 1) { return 0; }
warn "Fetching 'me' links from $url";
my $p = HTML::TreeBuilder->new;
my $req = HTTP::Request->new(GET => $url);
my $res = $ua->request($req, sub { $p->parse($_[0])});
$p->eof();
my $base = $res->base;
my @tags = $p->look_down(
"_tag", "a",
);
foreach my $e (@tags) {
my $full = url($e->attr('href'), $base)->abs;
$urls_to_check{$full} = 1 if (!defined($checked_urls{$full}));
}
foreach my $url (keys %urls_to_check) {
delete $urls_to_check{$url};
$checked_urls{$url}++;
&fetch_and_parse($url);
}
}
但这似乎并没有真正做到我想要的。
帮助?!
编辑:我想要从$starting_url
获取网址,然后从结果提取中获取所有网址。但是,如果其中一个网址链接回$starting_url
,我不想再次抓取它。
答案 0 :(得分:9)
最简单的做法是不重新发明轮子并使用the CPAN。
答案 1 :(得分:2)
我猜这个问题是
foreach my $url (keys %urls_to_check) {...}
不会以您认为的方式重复出现。对于您恢复的每个URL,您必须再次递归调用您的函数,这是非常低效的内存。
虽然您正在编写一个“递归”抓取网页的程序,但在您的代码中,您需要使用迭代,而不是递归:
sub fetch_and_parse {
my ($url) = @_;
$urls_to_check{$url} = 1;
while(%urls_to_check) {
// Grab a URL and process it, putting any new URLs you find into urls_to_check
}
}
当然,正如其他海报所述,还有其他工具可以为您自动化。
答案 2 :(得分:2)
如果您有要检查的链接队列并且想要跳过重复项,请使用哈希来记录您已访问过的哈希值。跳过该哈希中的链接:
my @need_to_check = ( ... ); # however you make that list my %already_checked = (); while( my $link = shift @need_to_check ) { next if exists $already_checked{$link}; ...; $already_checked{$link}++; }
情况稍微复杂一些,网址看起来略有不同但最终位于同一资源,例如http://example.com,http://www.example.com,http://www.example.com/等。如果我关心这些,我会通过为每个创建一个URI对象来添加一个规范化步骤,然后再将该URL作为字符串拉出。如果这是一个更大的问题,我还会查看响应标头声称我获得的URL(例如,通过重定向等),并标记我也看到了这些。
答案 3 :(得分:0)
如果您想从页面中提取所有链接,我建议使用Gisle Aas的LinkExtor,快速的CPAN搜索会向您显示。然后,您可以通过将它们推送到列表上来递归遍历找到的链接,然后将它们弹出,在遍历它们之前首先检查它们(如果您已经访问过它们),使用哈希就像您一样。
答案 4 :(得分:0)
也许这可以帮到你:blog.0x53a.de/where-do-my-links-go/ 它从给定的网站开始进行广度优先搜索。使用的模块HTML :: LinkExtractor也可能对您有用。
此致 曼努埃尔