Day 15 – A Simple Web Spider With Promises
承诺,承诺 去年夏天,我申请了一项编程工作,面试官要求我编写一个程序来抓取给定的域,只在该域中的链接之后,找到它引用的所有页面。我被允许以任何语言编写程序,但我选择使用Go语言执行任务,因为这是该公司使用的主要语言。这对于并发编程来说是一个理想的任务,并且Go具有非常好的现代化功能,即使有些低级别的并发支持。网络蜘蛛中的主要工作是执行与在域中发现的唯一锚链接相同的次数,即在每个页面上执行HTTP GET并解析页面文本以获取新链接。这个任务可以并行安全地完成,因为没有可能(除非你做得很糟糕),任何调用爬取代码都会干扰其他任何调用。
Go和Raku的创造者受到安东尼霍尔爵士1978年的开创性工作“沟通顺序过程”的启发,但值得注意的是,Raku代码更加简洁,因此更容易隐藏到博客文章中。事实上,Go设计者总是将他们的结构称为“并发原语”。 Go为我的作业应用程序编写的并发spider代码大约有200行,而在Raku中大小不到这个大小的一半。
下面我们来看看如何在Raku中实现一个简单的Web爬虫。内置的Promise类允许您启动,调度和检查异步计算的结果。所有你需要做的就是给Promise.start方法一个代码引用,然后调用await方法,这会阻塞,直到promise完成执行。然后您可以测试结果方法以确定承诺是否已被保留或中断。
您可以通过将其保存到本地文件中来运行本文中的代码,例如网络spider.p6。如果您希望抓取https网站,请使用zef安装HTML :: Parser :: XML和HTTP :: UserAgent以及IO :: Socket :: SSL。我会提醒你,SSL支持目前看起来有点狼狈,所以最好坚持http站点。 Raku程序中的MAIN子程序存在时表示一个独立程序,这就是执行开始的地方。 MAIN的参数表示命令行参数。我编写了这个程序,以便默认情况下它会抓取Perlmonks站点,但是您可以覆盖它,如下所示:
$ raku web-spider.p6 [–domain=http://example.com]
简单的Raku域蜘蛛
use HTML::Parser::XML;
use XML::Document;
use HTTP::UserAgent;
sub MAIN(:$domain="http://www.perlmonks.org") {
my $ua = HTTP::UserAgent.new;
my %url_seen;
my @urls=($domain);
loop {
my @promises;
while ( @urls ) {
my $url = @urls.shift;
my $p = Promise.start({crawl($ua, $domain, $url)});
@promises.push($p);
}
await Promise.allof(@promises);
for @promises.kv -> $index, $p {
if $p.status ~~ Kept {
my @results = $p.result;
for @results {
unless %url_seen{$_} {
@urls.push($_);
%url_seen{$_}++;
}
}
}
}
# Terminate if no more URLs to crawl
if @urls.elems == 0 {
last;
}
}
say %url_seen.keys;
}
# Get page and identify urls linked to in it. Return urls.
sub crawl($ua, $domain, $url) {
my $page = $ua.get($url);
my $p = HTML::Parser::XML.new;
my XML::Document $doc = $p.parse($page.content);
# URLs to crawl
my %todo;
my @anchors = $doc.elements(:TAG<a>, :RECURSE);
for @anchors -> $anchor {
next unless $anchor.defined;
my $href = $anchor.attribs<href>;
# Convert relative to absolute urls
if $href.starts-with('/') or $href.starts-with('?') {
$href = $domain ~ $href;
}
# Get unique urls from page
if $href.starts-with($domain) {
%todo{$href}++;
}
}
my @urls = %todo.keys;
return @urls;
}
结论是
并发编程总是会有很多陷阱,从竞争状态到资源匮乏和死锁,但我认为很显然,Raku 已经使得这种编程形式更容易被大家接受。