64

我曾想过尝试编写一个简单的爬虫,它可能会爬取并为我们的 NPO 的网站和内容生成其发现的列表。

有人对如何做到这一点有任何想法吗?您将爬虫指向哪里开始?它如何发回其发现并继续爬行?它怎么知道它发现了什么,等等。

4

10 回答 10

150

可以肯定的是,您将重新发明轮子。但这里是基础:

  • 未访问的 URL 列表 - 使用一个或多个起始页面作为种子
  • 访问过的 URL 列表 - 这样您就不会绕圈子
  • 一组您不感兴趣的 URL 的规则 - 这样您就不会索引整个 Internet

将它们放在持久存储中,这样您就可以停止和启动爬虫而不会丢失状态。

算法是:

while(list of unvisited URLs is not empty) {
    take URL from list
    remove it from the unvisited list and add it to the visited 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
        }
    }
}
于 2008-09-19T15:25:22.133 回答
30

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

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

  • 没有足够的 RAM 来处理巨大的索引

  • 多线程性能和并发性

  • 爬虫陷阱(通过更改 url、日历、会话 id ... 创建的无限循环)和重复的内容。

  • 从多台计算机上爬网

  • 格式错误的 HTML 代码

  • 来自服务器的持续 http 错误

  • 没有压缩的数据库,这使您对空间的需求增加了大约 8 倍。

  • 重新抓取例程和优先事项。

  • 使用压缩请求(Deflate/gzip)(适用于任何类型的爬虫)。

还有一些重要的事情

  • 尊重 robots.txt

  • 并且每个请求的爬虫延迟都不会使网络服务器窒息。

于 2011-12-19T13:45:21.893 回答
8

多线程网络爬虫

如果你想爬取大型网站,那么你应该编写一个多线程爬虫。在文件/数据库中连接、获取和写入爬取的信息 - 这些是爬取的三个步骤,但如果您使用单线程,那么您的 CPU 和网络利用率将会下降。

一个多线程的网络爬虫需要两个数据结构——linksVisited(这应该实现为 hashmap 或 trai)和 linksToBeVisited(这是一个队列)。

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

基本网络爬虫的算法:-

  1. 将一个或多个种子 URL 添加到 linksToBeVisited。向 linksToBeVisited 添加 url 的方法必须同步。
  2. 从 linksToBeVisited 中弹出一个元素并将其添加到 linksVisited。这个从 linksToBeVisited 中弹出 url 的弹出方法必须是同步的。
  3. 从互联网上获取页面。
  4. 解析文件并将页面中发现的任何到目前为止未访问的链接添加到 linksToBeVisited。如果需要,可以过滤 URL。用户可以给出一组规则来过滤要扫描的 url。
  5. 在页面上找到的必要信息保存在数据库或文件中。
  6. 重复步骤 2 到 5,直到队列为 linksToBeVisited 为空。

    这是有关如何同步线程的代码片段......

     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;
         }
      }
    

于 2012-12-12T15:40:05.093 回答
5

爬虫的概念很简单。

您通过 HTTP GET 获取根页面,对其进行解析以查找 URL 并将它们放在队列中,除非它们已经被解析(因此您需要已解析页面的全局记录)。

您可以使用 Content-type 标头找出内容的类型,并将您的爬虫限制为仅解析 HTML 类型。

您可以去掉 HTML 标签以获取纯文本,您可以对其进行文本分析(以获取标签等,页面的内容)。如果您掌握了高级功能,您甚至可以在图像的 alt/title 标签上执行此操作。

在后台,您可以有一个线程池从队列中获取 URL 并执行相同的操作。您当然想限制线程数。

于 2008-09-19T15:19:01.743 回答
5

如果您的 NPO 的网站相对较大或复杂(具有可有效创建“黑洞”的动态页面,例如带有“第二天”链接的日历),您最好使用真正的网络爬虫,如Heritrix。

如果站点总共有几页,您可以只使用 curl 或 wget 或您自己的。请记住,如果它们开始变大,或者您开始​​使脚本变得更复杂以使用真正的爬虫,或者至少查看其源代码以了解它们在做什么以及为什么。

一些问题(还有更多):

  • 黑洞(如所述)
  • 重试(如果你得到 500 怎么办?)
  • 重定向
  • 流量控制(否则您可能会成为网站的负担)
  • robots.txt 实现
于 2008-09-19T15:19:44.097 回答
4

维基百科有一篇关于网络爬虫的好文章,涵盖了许多算法和注意事项。

但是,我不会费心编写自己的爬虫。工作量很大,而且由于您只需要一个“简单的爬虫”,我认为您真正需要的只是一个现成的爬虫。有很多免费和开源的爬虫可能会做你需要的一切,而你的工作很少。

于 2008-09-19T15:19:25.283 回答
2

您可以制作一个单词列表,并为在 google 搜索的每个单词创建一个线程。
然后每个线程将为它在页面中找到的每个链接创建一个新线程。
每个线程都应该写入它在数据库中找到的内容。当每个线程完成读取页面时,它就会终止。
你的数据库中有一个非常大的链接数据库。

于 2008-09-19T15:17:56.690 回答
0

使用 wget,做一个递归的网络吸吮,它将所有文件转储到你的硬盘上,然后编写另一个脚本来检查所有下载的文件并分析它们。

编辑:或者可能是 curl 而不是 wget,但我不熟悉 curl,我不知道它是否像 wget 那样进行递归下载。

于 2008-09-19T15:13:08.950 回答
0

我在.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"));
于 2013-06-07T06:16:26.287 回答
0

I'm using Open search server for my company internal search, try this : http://open-search-server.com its also open soruce.

于 2012-06-29T12:42:27.937 回答