如何编写爬网程序?

我曾经想过要写一个简单的爬虫程序来抓取并为我们NPO的网站和内容制作一份调查结果清单。

有没有人有任何想法如何做到这一点? 你在哪里指定爬虫开始? 它如何发callback查结果并继续爬行? 它如何知道它发现了什么,等等。

当然,你会重新发明轮子。 但是这里的基础知识:

  • 未访问的URL列表 – 将其与一个或多个起始页面一起进行播种
  • 访问的url列表 – 所以你不要绕圈子
  • 一套你不感兴趣的URL规则 – 所以你不要索引整个互联网

把它们放在永久存储器中,这样你就可以停止并启动爬虫而不会丢失状态。

algorithm是:

while(list of unvisited URLs is not empty) { take URL from list fetch content record whatever it is you want to about the content if content is HTML { parse out URLs from links foreach URL { if it matches your rules and it's not already in either the visited or unvisited list add it to the unvisited list } } } 

抓取工具的复杂部分是如果你想扩展到大量的网站/请求。 在这种情况下,你将不得不处理一些问题,如:

  • 不可能将信息全部保存在一个数据库中。

  • 没有足够的内存来处理庞大的索引

  • multithreading性能和并发性

  • 抓取工具陷阱(通过更改url,日历,会话ID创build的无限循环…)和重复的内容。

  • 从多台计算机上爬网

  • 格式不正确的HTML代码

  • 来自服务器的常量http错误

  • 数据库没有压缩,这使得你需要大约8倍的空间。

  • 重新抓取例程和优先级。

  • 使用压缩请求(Deflate / gzip)(适用于任何types的抓取工具)。

还有一些重要的事情

  • 尊重robots.txt

  • 每个请求的爬行器延迟不要窒息Web服务器。

multithreading的networking爬虫

如果你想抓取大型网站,那么你应该写一个multithreading的爬虫。 在文件/数据库中连接,抓取和写入爬行的信息 – 这些是抓取的三个步骤,但是如果使用单个线程而不是CPU,networking利用率将会下降。

一个multithreading的networking爬虫需要两个数据结构linksVisited(这应该作为一个hashmap或trai)和linksToBeVisited(这是一个队列)。

networking爬虫使用BFS遍历万维网。

基本networking爬虫algorithm:

  1. 将一个或多个种子url添加到linksToBeVisited。 将url添加到linksToBeVisited的方法必须是同步的。
  2. 从linksToBeVisited中popup一个元素,并将其添加到linksVisited。 这个从linksToBeVisitedpopupurl的pop方法必须被同步。
  3. 从互联网上获取该页面。
  4. parsing文件并添加任何到现在未访问的页面中find的链接到linksToBeVisited。 如果需要,可以过滤URL。 用户可以提供一组规则来过滤要扫描的url。
  5. 在页面上find的必要信息保存在数据库或文件中。
  6. 重复步骤2到5,直到queueToBeVisited为空。

    这里是如何同步线程的代码片段….

      public void add(String site) { synchronized (this) { if (!linksVisited.contains(site)) { linksToBeVisited.add(site); } } } public String next() { if (linksToBeVisited.size() == 0) { return null; } synchronized (this) { // Need to check again if size has changed if (linksToBeVisited.size() > 0) { String s = linksToBeVisited.get(0); linksToBeVisited.remove(0); linksVisited.add(s); return s; } return null; } } 

如果您的NPO网站相对较大或较为复杂(有dynamic页面会像“日历”链接那样有效地创build“黑洞”),您最好使用真正的networking爬虫,比如Heritrix。

如果网站总共只有几个页面,你可以使用curl或wget或你自己的。 只要记住,如果他们开始变大,或者开始让脚本变得更加复杂,只使用一个真正的爬虫,或者至less看看它的来源,看看他们在做什么,为什么。

有些问题(还有更多):

  • 黑洞(如上所述)
  • 重试(如果你得到500呢?)
  • redirect
  • stream量控制(否则你可能成为网站的负担)
  • robots.txt的实现

爬行者的概念很简单。

您通过HTTP GET获取根页面,parsing它以查找URL并将其放在队列中,除非已经被parsing(因此您需要已经parsing的页面的全局logging)。

您可以使用Content-type头来找出内容的types,并将抓取工具限制为仅parsingHTMLtypes。

您可以去除HTML标签以获取纯文本,您可以对其进行文本分析(以获取标签等,页面的肉)。 你甚至可以在图像的alt / title标签上做到这一点,如果你有先进的。

在后台,你可以有一个线程池,从队列中去掉URL并且做同样的事情。 你当然想限制线程的数量。

维基百科有一个关于networking爬虫的好文章,涵盖了许多algorithm和考虑因素。

但是,我不打扰写我自己的爬虫。 这是很多的工作,因为你只需要一个“简单的爬虫”,我想你真正需要的是一个现成的爬行器 。 有很多免费和开源的抓取工具,可能会做你需要的一切,只需很less的工作。

你可以制作一个单词列表,并为每个在谷歌search单词做一个线程。
然后,每个线程将为它在页面中find的每个链接创build一个新的线程。
每个线程都应该写入在数据库中find的内容。 当每个线程完成阅读页面,它终止。
在那里你有一个非常大的数据库链接的数据库。

我正在使用公开的search服务器为我公司内部search,试试这个: http : //open-search-server.com它也打开soruce。

使用wget,做一个recursion的web吸盘,将所有的文件转储到你的硬盘上,然后编写另一个脚本去浏览所有下载的文件并分析它们。

编辑:或者也许curl,而不是wget,但我不熟悉curl,我不知道它是否recursion下载像wget。

我做了一个简单的networking爬虫在.net中使用被动扩展。

https://github.com/Misterhex/WebCrawler

 public class Crawler { class ReceivingCrawledUri : ObservableBase<Uri> { public int _numberOfLinksLeft = 0; private ReplaySubject<Uri> _subject = new ReplaySubject<Uri>(); private Uri _rootUri; private IEnumerable<IUriFilter> _filters; public ReceivingCrawledUri(Uri uri) : this(uri, Enumerable.Empty<IUriFilter>().ToArray()) { } public ReceivingCrawledUri(Uri uri, params IUriFilter[] filters) { _filters = filters; CrawlAsync(uri).Start(); } protected override IDisposable SubscribeCore(IObserver<Uri> observer) { return _subject.Subscribe(observer); } private async Task CrawlAsync(Uri uri) { using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMinutes(1) }) { IEnumerable<Uri> result = new List<Uri>(); try { string html = await client.GetStringAsync(uri); result = CQ.Create(html)["a"].Select(i => i.Attributes["href"]).SafeSelect(i => new Uri(i)); result = Filter(result, _filters.ToArray()); result.ToList().ForEach(async i => { Interlocked.Increment(ref _numberOfLinksLeft); _subject.OnNext(i); await CrawlAsync(i); }); } catch { } if (Interlocked.Decrement(ref _numberOfLinksLeft) == 0) _subject.OnCompleted(); } } private static List<Uri> Filter(IEnumerable<Uri> uris, params IUriFilter[] filters) { var filtered = uris.ToList(); foreach (var filter in filters.ToList()) { filtered = filter.Filter(filtered); } return filtered; } } public IObservable<Uri> Crawl(Uri uri) { return new ReceivingCrawledUri(uri, new ExcludeRootUriFilter(uri), new ExternalUriFilter(uri), new AlreadyVisitedUriFilter()); } public IObservable<Uri> Crawl(Uri uri, params IUriFilter[] filters) { return new ReceivingCrawledUri(uri, filters); } } 

你可以使用它如下:

 Crawler crawler = new Crawler(); IObservable observable = crawler.Crawl(new Uri("http://www.codinghorror.com/")); observable.Subscribe(onNext: Console.WriteLine, onCompleted: () => Console.WriteLine("Crawling completed"));