I have written a basic web crawler in perl. How can I make it more sophisticated by having it do
the retrieval in a 'breadth first' manner, like wget does?
This is from the wget docs:
Recursive retrieval of HTTP and HTML/CSS content is breadth-first. This means that Wget first downloads the requested document, then the documents linked from that document, then the documents linked by them, and so on. In other words, Wget first downloads the documents at depth 1, then those at depth 2, and so on until the specified maximum depth.
Any comments about my code would also be appreciated.
use feature 'say';
use WWW::Mechanize;
use List::MoreUtils 'any';
##############
# parameters #
##############
my $url = "https://www.crawler-test.com/"; # starting url
my $depth_level = 2; # depth level for crawling (level 1 will only look at links on the starting page)
my $filter = ".pdf"; # for multiple types use this format: ".pdf|.docx|.doc|.rtf"
my $wait = 2; # wait this number of seconds between http requests (be kind to the server)
my $domain = ""; # only crawl links with host ending in this string, leave blank if not required. For multiple domains, use this format: "domain1|domain2"
my $nocrawlagain = 1; # setting this to 1 will mean that the same link will not be crawled again, set to 0 to turn this off
##############
$domain = quotemeta($domain);
$domain =~ s/\\\|/|/g;
my @linkscrawled;
open LOG, ">mecherrors.log";
LOG->autoflush;
my $mech = WWW::Mechanize->new(stack_depth => 0, onerror => \&mecherror);
sub crawl {
my $url = shift;
my $filter = shift;
my $depth = shift || 1;
return if $depth > $depth_level;
say "Crawling $url";
$mech->get($url);
sleep $wait;
return unless ($mech->success and $mech->is_html);
my @linkstocrawl;
for $link ($mech->find_all_links(url_abs_regex => qr/^http/)) # only get http links (excludes things like mailto:)
{
next if $link->url =~ /#/; # excludes URLs that are referring to an anchor
# if the link matches the filter then download it
if ($link->url =~ /($filter)$/)
{
my $urlfilename = ($link->URI->path_segments)[-1];
next if -e $urlfilename;
$mech->get($url); # go to base page
sleep $wait;
$mech->get($link->url);
sleep $wait;
my $filename = $mech->response->filename;
next if -e $filename;
$mech->save_content($filename);
say "Saved $filename";
} else {
push @linkstocrawl, $link;
}
}
for $link (@linkstocrawl)
{
next unless $link->url_abs->host =~ /($domain)$/;
if ($nocrawlagain)
{
# skip if already crawled this link
next if any { $_ eq $link->url_abs->abs } @alreadycrawled;
push @alreadycrawled, $link->url_abs->abs;
}
crawl($link->url_abs->abs, $filter, $depth + 1);
}
}
crawl($url, $filter);
sub mecherror {
print LOG "[", $mech->uri, "] ", $mech->response->message, "\n";
}