如何编写爬虫?

时间:2008-09-19 15:09:24

标签: web-crawler

我曾想过尝试编写一个简单的抓取工具,可以抓取并为我们的NPO网站和内容生成一份调查结果列表。

有人对如何做到这一点有任何想法吗?你在哪里指出爬虫开始?它如何发送回调查结果仍然继续爬行?它是如何知道它发现的等等。

9 个答案:

答案 0 :(得分:146)

你肯定会重新发明轮子。但这是基础知识:

  • 未访问的网址列表 - 使用一个或多个起始网页
  • 播种
  • 访问过的网址列表 - 因此您不会绕圈子
  • 一组您不感兴趣的网址规则 - 因此您不会将整个互联网编入索引

将它们放在持久存储中,这样您就可以在不丢失状态的情况下停止并启动爬虫。

算法是:

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

答案 1 :(得分:8)

多线程网络抓取工具

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

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

Web爬网程序使用BFS遍历万维网。

基本网络爬虫的算法: -

  1. 将一个或多个种子网址添加到linksToBeVisited。必须同步将URL添加到linksToBeVisited的方法。
  2. 从linksToBeVisited弹出一个元素并将其添加到linksVisited。必须同步这个来自linksToBeVisited的弹出网址的pop方法。
  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;
         }
      }
    

答案 2 :(得分:5)

爬行者的概念很简单。

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

您可以使用Content-type标头查找内容类型,并将抓取工具限制为仅解析HTML类型。

您可以删除HTML标记以获取纯文本,您可以对其进行文本分析(获取标记等,页面的内容)。如果你有高级的话,你甚至可以在图像的alt / title标签上这样做。

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

答案 3 :(得分:5)

如果您的NPO网站相对较大或较为复杂(动态网页有效地创建'黑洞',就像带有'次日'链接的日历一样),您最好使用真正的网络抓取工具,例如{ {3}}

如果这些网站总共只有几页,那么只需使用curl或wget或您自己的网页即可。只要记住,如果他们开始变大,或者你开始使你的脚本更复杂,只使用真正的爬虫或者至少查看它的来源,看看他们在做什么以及为什么。

一些问题(还有更多):

  • 黑洞(如上所述)
  • 重试(如果你得到一个500怎么办?)
  • 重定向
  • 流量控制(否则你可能成为网站的负担)
  • robots.txt implementation

答案 4 :(得分:4)

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

但是,我不打算写自己的抓取工具。这是很多工作,因为你只需要一个“简单的爬虫”,我认为你真正需要的只是一个off-the-shelf crawler。有很多免费和开源的爬虫可能会做你需要的一切,而你的工作却很少。

答案 5 :(得分:2)

您可以为Google搜索的每个单词创建一个单词列表并创建一个主题。
然后每个主题将为它在页面中找到的每个链接创建一个新主题。
每个主题都应该写它在数据库中找到了什么。当每个线程完成阅读页面时,它终止。
在那里,你的数据库中有一个非常大的链接数据库。

答案 6 :(得分:1)

我正在使用开放搜索服务器进行公司内部搜索,请尝试以下操作:http://open-search-server.com它也是开放式的。

答案 7 :(得分:0)

使用wget,执行递归web suck,将所有文件转储到硬盘上,然后编写另一个脚本来浏览所有下载的文件并进行分析。

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

答案 8 :(得分: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"));