如果让你设计一个网络爬虫,你怎么避免陷入无限循环?
2017-05-15 18:27
246 查看
话说爬虫为什么会陷入循环呢?答案很简单,当我们重新去解析一个已经解析过的网页时, 就会陷入无限循环。这意味着我们会重新访问那个网页的所有链接, 然后不久后又会访问到这个网页。最简单的例子就是,网页A包含了网页B的链接, 而网页B又包含了网页A的链接,那它们之间就会形成一个闭环。
那么我们怎样防止访问已经访问过的页面呢,答案也很简单,设置一个标志即可。 整个互联网就是一个图结构,我们通常使用DFS(深度优先搜索)和BFS(广度优先搜索) 进行遍历。所以,像遍历一个简单的图一样,将访问过的结点标记一下即可。
爬虫的基本思路如下
1. 根据 Url 获取相应页面的 Html 代码
2. 利用正则匹配或者 Jsoup 等库解析 Html 代码,提取需要的内容
3. 将获取的内容持久化到数据库中
4. 处理好中文字符的编码问题,可以采用多线程提高效率
有几个概念,一个是发http请求,一个是正则匹配你感兴趣的链接,一个是多线程,另外还有两个队列。
来源于 该文章 的一张图
对 Web 服务器也是负担,可能击垮该站点,可能阻止正常用户访问该站点
即使没有性能影响,但获取大量重复页面也导致数据冗余
二叉树和散列表,快速判定某个 url 是否访问过
存在位图
就是 new int[length],然后把第几个数置为1,表示已经访问过了。可不可以再优化,int 是32位,32位可以表示32个数字。HashCode 会存在冲突的情况,两个 url 映射到同一个存在位上,冲突的后果是某个页面被忽略(这比死循环的恶作用小)
保存检查
一定要即使把已访问的 url 列表保存在硬盘上,防止爬虫崩溃,内存里的数据会丢失
集群 ,分而治之
多台机器一起爬虫,可以根据 url 计算 hashcode,然后根据 hashcode 映射到相应机器的 id (第0台、第1台、第2台等等)
该表格来自于 《HTTP 权威指南》
广度优先的爬行,避免深度优先陷入某个站点的环路中,无法访问其他站点。
限制访问次数,限定一段时间内机器人可以从一个 web 站点获取的页面数量
内容指纹,根据页面的内容计算出一个校验和,但是动态的内容(日期,评论数目)会阻碍重复检测
维护黑名单
人工监视,特殊情况发出邮件通知
动态变化,根据当前热点新闻等等
规划化 url,把一些转义字符、ip 与域名之类的统一
限制 url 大小,环路可能会使得 url 长度增加,比如/index.html, /folder/index,html, /folder/folder/index.html …
比如 文章 A 包含了 Java、学习、程序员
文章 B 包含了 Java 、Python、面试、招聘
如果搜索 Java,可以知道得到 文章 A 和文章 B,而不必对文章 A、B 全文扫描。
一个复杂的分布式爬虫系统由很多的模块组成,每个模块是一个独立的服务(SOA架构),所有的服务都注册到Zookeeper来统一管理和便于线上扩展。模块之间通过thrift(或是protobuf,或是soup,或是json,等)协议来交互和通讯。
Zookeeper负责管理系统中的所有服务,简单的配置信息的同步,同一服务的不同拷贝之间的负载均衡。它还有一个好处是可以实现服务模块的热插拔。
URLManager是爬虫系统的核心。负责URL的重要性排序,分发,调度,任务分配。单个的爬虫完成一批URL的爬取任务之后,会找 URLManager要一批新的URL。一般来说,一个爬取任务中包含几千到一万个URL,这些URL最好是来自不同的host,这样,不会给一个 host在很短一段时间内造成高峰值。
ContentAcceptor负责收集来自爬虫爬到的页面或是其它内容。爬虫一般将爬取的一批页面,比如,一百个页面,压缩打包成一个文件, 发送给ContentAcceptor。ContentAcceptor收到后,解压,存储到分布式文件系统或是分布式数据库,或是直接交给 ContentParser去分析。
CaptchaHandler负责处理爬虫传过来的captcha,通过自动的captcha识别器,或是之前识别过的captcha的缓存,或是通过人工打码服务,等等,识别出正确的码,回传给爬虫,爬虫按照定义好的爬取逻辑去爬取。
RobotsFileHandler负责处理和分析robots.txt文件,然后缓存下来,给ContentParser和 URLManager提供禁止爬取的信息。一个行为端正的爬虫,原则上是应该遵守robots协议。但是,现在大数据公司,为了得到更多的数据,基本上遵 守这个协议的不多。robots文件的爬取,也是通过URLManager作为一种爬取类型让分布式爬虫去爬取的。
ProxyManager负责管理系统用到的所有Proxy,说白了,负责管理可以用来爬取的IP。爬虫询问ProxyManager,得到一 批Proxy IP,然后每次访问的时候,会采用不同的IP。如果遇到IP被屏蔽,即时反馈给ProxyManager,ProxyManager会根据哪个host屏 蔽了哪个IP做实时的聪明的调度。
Administor负责管理整个分布式爬虫系统。管理者通过这个界面来配置系统,启动和停止某个服务,删除错误的结果,了解系统的运行情况,等等。
各种不同类型的爬取任务,比如,像给一个URL爬取一个页面( NormalCrawler),像需要用户名和密码注册然后才能爬取( SessionCrawler ),像爬取时先要输入验证码( CaptchaCrawler ),像需要模拟用户的行为来爬取( Simulator ),像移动页面和内容爬取( MobileCrawler ),和像App内内容的爬取( AppCrawler),需要不同类型的爬虫来爬取。当然,也可以开发一个通用的爬虫,然后根据不同的类型实施不同的策略,但这样一个程序内的代码复杂, 可扩展性和可维护性不强。
一个爬虫内部的爬取逻辑,通过解释从配置文件 CrawlLogic 来的命令来实现,而不是将爬取逻辑硬编码在爬虫程序里面。对于复杂的爬取逻辑,甚至可以通过用代码写的插件来实现。
ContentParser根据URLExtractionRules来抽取需要继续爬取的URL,因为focus的爬虫只需要爬取需要的数 据,不是网站上的每个URL都需要爬取。ContentParser还会根据FieldExtractionRules来抽取感兴趣的数据,然后将原始数 据结构化。由于动态生成的页面很多,很多数据是通过Javascript显示出来的,需要JavascriptEngine来帮助分析页面。这儿需要提及 下,有些页面大量使用AJAX来实时获取和展示数据,那么,需要一个能解释Javascript的爬虫类型来处理这些有AJAX的情形。
为了监控整个系统的运行情况和性能,需要 Monitor 系统。为了调试系统,保障系统安全有据可循,需要 Logger 系统。有了这些,系统才算比较完备。
所有的数据会存在分布式文件系统或是数据库中,这些数据包括URL( URLRepo),Page( PageRepo )和Field( FieldRepo ),至于选择什么样的存储系统,可以根据自己现有的基础设施和熟悉程度而定。
为了扩大爬虫系统的吞吐量,每个服务都可以横向扩展,包括横向复制,或是按URL来分片(sharding)。由于使用了Zookeeper,给某个服务增加一个copy,只用启动这个服务就可以了,剩下的Zookeeper会自动处理。
这里只是给出了复杂分布式爬虫系统的大框架,具体实现的时候,还有很多的细节需要处理,这时,之前做过爬虫系统,踩过坑的经验就很重要了。
那么我们怎样防止访问已经访问过的页面呢,答案也很简单,设置一个标志即可。 整个互联网就是一个图结构,我们通常使用DFS(深度优先搜索)和BFS(广度优先搜索) 进行遍历。所以,像遍历一个简单的图一样,将访问过的结点标记一下即可。
爬虫的基本思路如下
1. 根据 Url 获取相应页面的 Html 代码
2. 利用正则匹配或者 Jsoup 等库解析 Html 代码,提取需要的内容
3. 将获取的内容持久化到数据库中
4. 处理好中文字符的编码问题,可以采用多线程提高效率
爬虫基本原理
更宽泛意义上的爬虫侧重于如果在大量的 url 中寻找出高质量的资源,如何在有限的时间内访问更多页面等等。网络爬虫的基本工作流程如下:1.首先选取一部分精心挑选的种子URL; 2.将这些URL放入待抓取URL队列; 3.从待抓取URL队列中取出待抓取在URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。 4.分析已抓取URL队列中的URL,分析页面里包含的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。
有几个概念,一个是发http请求,一个是正则匹配你感兴趣的链接,一个是多线程,另外还有两个队列。
来源于 该文章 的一张图
爬虫难点1 环路
网络爬虫有时候会陷入循环或者环路中,比如从页面 A,A 链接到页面 B,B 链接 页面C,页面 C 又会链接到页面 A。这样就陷入到环路中。环路影响
消耗网络带宽,无法获取其他页面对 Web 服务器也是负担,可能击垮该站点,可能阻止正常用户访问该站点
即使没有性能影响,但获取大量重复页面也导致数据冗余
解决方案
1. 简单限定爬虫的最大循环次数,对于某 web 站点访问超过一定阈值就跳出,避免无限循环 2. 保存一个已访问 url 列表,记录页面是否被访问过的技术
二叉树和散列表,快速判定某个 url 是否访问过
存在位图
就是 new int[length],然后把第几个数置为1,表示已经访问过了。可不可以再优化,int 是32位,32位可以表示32个数字。HashCode 会存在冲突的情况,两个 url 映射到同一个存在位上,冲突的后果是某个页面被忽略(这比死循环的恶作用小)
保存检查
一定要即使把已访问的 url 列表保存在硬盘上,防止爬虫崩溃,内存里的数据会丢失
集群 ,分而治之
多台机器一起爬虫,可以根据 url 计算 hashcode,然后根据 hashcode 映射到相应机器的 id (第0台、第1台、第2台等等)
难点2 URL别名
有些 url 名称不一样,但是指向同一个资源。该表格来自于 《HTTP 权威指南》
URl 1 | URL 2 | 什么时候是别名 |
---|---|---|
www.foo.com/bar.html | www.foo.com:80/bar.html | 默认端口是80 |
www.foo.com/~fred | www.foo.com/%7Ffred | %7F与~相同 |
www.foo.com/x.html#top | www.foo.com/x.html#middle | %7F与~相同 |
https://www.baidu.com/ | https://www.BAIDU.com/ | 服务器是大小写无关 |
www.foo.com/index.html | www.foo.com | 默认页面为 index.html |
www.foo.com/index.html | 209.123.123/index.html | ip和域名相同 |
难点3 动态虚拟空间
比如日历程序,它会生成一个指向下一月的链接,真正的用户是不会不停地请求下个月的链接的。但是不了解这内容特性的爬虫蜘蛛可能会不断向这些资源发出无穷的请求。抓取策略
一般策略是深度优先或者广度优先。有些技术能使得爬虫蜘蛛有更好的表现广度优先的爬行,避免深度优先陷入某个站点的环路中,无法访问其他站点。
限制访问次数,限定一段时间内机器人可以从一个 web 站点获取的页面数量
内容指纹,根据页面的内容计算出一个校验和,但是动态的内容(日期,评论数目)会阻碍重复检测
维护黑名单
人工监视,特殊情况发出邮件通知
动态变化,根据当前热点新闻等等
规划化 url,把一些转义字符、ip 与域名之类的统一
限制 url 大小,环路可能会使得 url 长度增加,比如/index.html, /folder/index,html, /folder/folder/index.html …
全文索引
全文索引就是一个数据库,给它一个单词,它可以立刻提供包含那个单词的所有文字。创建了索引之后,就不必对文档自身进行扫描了。比如 文章 A 包含了 Java、学习、程序员
文章 B 包含了 Java 、Python、面试、招聘
如果搜索 Java,可以知道得到 文章 A 和文章 B,而不必对文章 A、B 全文扫描。
一个复杂的分布式爬虫系统由很多的模块组成,每个模块是一个独立的服务(SOA架构),所有的服务都注册到Zookeeper来统一管理和便于线上扩展。模块之间通过thrift(或是protobuf,或是soup,或是json,等)协议来交互和通讯。
Zookeeper负责管理系统中的所有服务,简单的配置信息的同步,同一服务的不同拷贝之间的负载均衡。它还有一个好处是可以实现服务模块的热插拔。
URLManager是爬虫系统的核心。负责URL的重要性排序,分发,调度,任务分配。单个的爬虫完成一批URL的爬取任务之后,会找 URLManager要一批新的URL。一般来说,一个爬取任务中包含几千到一万个URL,这些URL最好是来自不同的host,这样,不会给一个 host在很短一段时间内造成高峰值。
ContentAcceptor负责收集来自爬虫爬到的页面或是其它内容。爬虫一般将爬取的一批页面,比如,一百个页面,压缩打包成一个文件, 发送给ContentAcceptor。ContentAcceptor收到后,解压,存储到分布式文件系统或是分布式数据库,或是直接交给 ContentParser去分析。
CaptchaHandler负责处理爬虫传过来的captcha,通过自动的captcha识别器,或是之前识别过的captcha的缓存,或是通过人工打码服务,等等,识别出正确的码,回传给爬虫,爬虫按照定义好的爬取逻辑去爬取。
RobotsFileHandler负责处理和分析robots.txt文件,然后缓存下来,给ContentParser和 URLManager提供禁止爬取的信息。一个行为端正的爬虫,原则上是应该遵守robots协议。但是,现在大数据公司,为了得到更多的数据,基本上遵 守这个协议的不多。robots文件的爬取,也是通过URLManager作为一种爬取类型让分布式爬虫去爬取的。
ProxyManager负责管理系统用到的所有Proxy,说白了,负责管理可以用来爬取的IP。爬虫询问ProxyManager,得到一 批Proxy IP,然后每次访问的时候,会采用不同的IP。如果遇到IP被屏蔽,即时反馈给ProxyManager,ProxyManager会根据哪个host屏 蔽了哪个IP做实时的聪明的调度。
Administor负责管理整个分布式爬虫系统。管理者通过这个界面来配置系统,启动和停止某个服务,删除错误的结果,了解系统的运行情况,等等。
各种不同类型的爬取任务,比如,像给一个URL爬取一个页面( NormalCrawler),像需要用户名和密码注册然后才能爬取( SessionCrawler ),像爬取时先要输入验证码( CaptchaCrawler ),像需要模拟用户的行为来爬取( Simulator ),像移动页面和内容爬取( MobileCrawler ),和像App内内容的爬取( AppCrawler),需要不同类型的爬虫来爬取。当然,也可以开发一个通用的爬虫,然后根据不同的类型实施不同的策略,但这样一个程序内的代码复杂, 可扩展性和可维护性不强。
一个爬虫内部的爬取逻辑,通过解释从配置文件 CrawlLogic 来的命令来实现,而不是将爬取逻辑硬编码在爬虫程序里面。对于复杂的爬取逻辑,甚至可以通过用代码写的插件来实现。
ContentParser根据URLExtractionRules来抽取需要继续爬取的URL,因为focus的爬虫只需要爬取需要的数 据,不是网站上的每个URL都需要爬取。ContentParser还会根据FieldExtractionRules来抽取感兴趣的数据,然后将原始数 据结构化。由于动态生成的页面很多,很多数据是通过Javascript显示出来的,需要JavascriptEngine来帮助分析页面。这儿需要提及 下,有些页面大量使用AJAX来实时获取和展示数据,那么,需要一个能解释Javascript的爬虫类型来处理这些有AJAX的情形。
为了监控整个系统的运行情况和性能,需要 Monitor 系统。为了调试系统,保障系统安全有据可循,需要 Logger 系统。有了这些,系统才算比较完备。
所有的数据会存在分布式文件系统或是数据库中,这些数据包括URL( URLRepo),Page( PageRepo )和Field( FieldRepo ),至于选择什么样的存储系统,可以根据自己现有的基础设施和熟悉程度而定。
为了扩大爬虫系统的吞吐量,每个服务都可以横向扩展,包括横向复制,或是按URL来分片(sharding)。由于使用了Zookeeper,给某个服务增加一个copy,只用启动这个服务就可以了,剩下的Zookeeper会自动处理。
这里只是给出了复杂分布式爬虫系统的大框架,具体实现的时候,还有很多的细节需要处理,这时,之前做过爬虫系统,踩过坑的经验就很重要了。
相关文章推荐
- 如果让你设计一个网络爬虫,你怎么避免陷入无限循环?
- 如果要设计一个网络爬虫程序,该怎么避免陷入无限循环
- 程序员面试金典: 9.10 扩展性与存储限制 10.5如果要设计一个网络爬虫程序,该怎么样避免陷入无限循环。
- 【一个批量计算的调度系统的设计与实现】如果需要对成千上万的网络抓包数据文件在规定的时间内进行解析,应该怎么做?
- 4程序员小飞原计划三天完成某个任务,现在是第三天的下午,他马上就可以做完。但是在实现功能的过程中,他越来越意识到自己原来设计中的弱点,他应该采取另一个办法,才能避免后面集成阶段的额外工作。但是他如果现在就改弦更张,那势必要影响自己原来估计的准确性,并且会花费额外的时间,这样他的老板、同事也许会因此看不起他。如果他按部就班地按既定设计完成,还要花更多时间在后续集成上,但那就不是他个人的问题了,怎么办
- 如果一个节点的name每次都会变,但是有固定的格式,该怎么查找它?
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 如果在表格中想要显示一个页面,该怎么办?
- [8] 怎么设计用例证明一个三角形是等腰三角形
- poj 2948 比较好的dp,方向不是一个的最优方案dp,可以作为以后出题,如果改成4个方向的怎么解决?
- 给出10个整数,从中取走一个,从剩下的9个数里怎么找出哪个数被取走了。如果有1000万个数,取走一个数,怎么找出哪个数被取走
- 如果java中一个方法不知道要传递多少参数也不知道传递过来的是什么类型的数据你会怎么做?
- 刚发现了一个问题,关于vs2005 datagridview的,我发现在设计行标头的HeaderCell.Value的时候要是设置RowTemplate.Height 的值>= 17则行标头的那个黑三角就显示出来了,要是小于17就不能显示了,想问问大家,是怎么回事?
- 回复 “如果给你一个整数n 你不用for if等类似语句 怎么输出所有小于n的”
- 数据库设计中一个矛盾:数据库外键,用还是不用?你怎么看.?
- 数据库设计中一个矛盾:数据库外键,用还是不用?你怎么看.?
- 如果有一个这样的需求,您会如何设计?考考你的设计能力:)
- 设计模式---单件(C++版) 一个简单实例(避免用户忘记delete单件实例)
- 如果一个程序跑10000次只失败一次,你会怎么调试?
- 怎么才能做一个合格的设计项目经理?