如何编写爬虫?

Posted

技术标签:

【中文标题】如何编写爬虫?【英文标题】:How to write a crawler? 【发布时间】:2010-09-11 06:50:26 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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

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

算法是:

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
        
    

【讨论】:

很好的答案,但是当您说重新发明***时,免费的开源网络爬虫框架到底在哪里?可能适用于 java,但我没有找到任何适用于 .net 的。 呃,回车太快了。该链接有很多,其中没有一个是.Net。但是,我真的不明白您为什么选择将自己限制在 .Net 上。 嗨,我遇到了这个答案,我想你可以为我提供一些关于开发网络爬虫的见解。假设我已完成上述步骤,当我访问所有 URL 时会发生什么?我要跳出while循环并结束脚本吗?还是将其作为守护程序运行或简单的 while 循环以再次检索未访问的 URL? ahhh,在while 循环中您可能想要做的第一件事是将URL 添加到already listed list... 否则如果有两个页面引用,您可能会陷入无限循环彼此... @CpILL 你是对的 - 任何人都需要 9 年的时间才能注意到。现已修复。【参考方案2】:

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

无法将所有信息保存在一个数据库中。

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

多线程性能和并发性

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

从多台计算机上抓取

HTML 代码格式错误

来自服务器的持续 http 错误

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

重新抓取例程和优先级。

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

还有一些重要的事情

尊重 robots.txt

每个请求都会有爬虫延迟,以免让 Web 服务器窒息。

【讨论】:

很好的答案!您可以使用布隆过滤器来处理 RAM 问题。 我认为前 1-3 和 5 的答案是亚马逊的 AWS。哈希可以解决“重复内容”。像 Beautiful Soup 这样的抓取库可以处理 6 个。 7- 检查您的 http 标头。 8 - 使用带压缩的数据库。等【参考方案3】:

多线程网络爬虫

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

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

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

基本网络爬虫的算法:-

    将一个或多个种子 URL 添加到 linksToBeVisited。向 linksToBeVisited 添加 url 的方法必须同步。 从 linksToBeVisited 中弹出一个元素并将其添加到 linksVisited。这个从 linksToBeVisited 中弹出 url 的弹出方法必须同步。 从 Internet 获取页面。 解析文件并将页面中发现的任何迄今为止未访问的链接添加到linksToBeVisited。如果需要,可以过滤 URL。用户可以给出一组规则来过滤要扫描的 url。 页面上找到的必要信息保存在数据库或文件中。

    重复步骤 2 到 5,直到队列为 linksToBeVisited 为空。

    这是一个关于如何同步线程的代码 sn-p....

     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;
         
      
    

【讨论】:

或者你可以简单地异步使用node.js。 这里说的是大型爬虫,这样的爬虫不能用javascript。最佳实践是 c 或 c++ ,java 也很好用。 为什么说js不可扩展?有什么证据可以给我看看吗? 来吧,javascript 是一种解释型的动态语言,它完全运行在网络浏览器上,因此性能和可扩展性取决于浏览器的能力。如果您创建许多线程,浏览器将冻结。 Javascript 适用于 Web 应用程序(以及一些玩具程序),但不适用于大型应用程序。如果您想编写玩具爬虫,那很好,但在处理现实世界的多线程应用程序时(这里你必须处理 TB 和 PB),那么 javascript 甚至无法接近编译语言。 我想你甚至没有听说过 node.js:google.pt/search?q=node.js+linkedin【参考方案4】:

爬虫的概念很简单。

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

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

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

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

【讨论】:

【参考方案5】:

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

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

一些问题(还有更多):

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

【讨论】:

您能否就处理您提到的问题提供一些见解?特别是黑洞? 摆脱黑洞的常用方法是为每个匹配 URL 的域或正则表达式编程一个可配置的限制(即,如果 URL 匹配这个或域是那个,则在 1000 个检索到的匹配页面后继续前进)。流量控制通常以每个域每秒的页面数实现(通常它们会让您等待超过一秒钟以避免成为负担)。【参考方案6】:

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

但是,我不会费心编写自己的爬虫。工作量很大,因为你只需要一个“简单的爬虫”,我想你真正需要的只是一个off-the-shelf crawler。有很多免费和开源的爬虫可以满足您的所有需求,而您只需做很少的工作。

【讨论】:

【参考方案7】:

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

【讨论】:

搞笑加1【参考方案8】:

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

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

【讨论】:

【参考方案9】:

我正在使用开放搜索服务器进行公司内部搜索,试试这个:http://open-search-server.com 它也是开放源。

【讨论】:

【参考方案10】:

我在 .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"));

【讨论】:

以上是关于如何编写爬虫?的主要内容,如果未能解决你的问题,请参考以下文章

如何用php 编写网络爬虫?

python轻量级爬虫的编写

爬虫进阶之分布式爬虫编写

Python 爬虫的入门教程都有哪些值得推荐的?

独家分享| 爬虫框架编写-python篇

Python爬虫,利用scrapy来编写一个爬虫