之前写的是获取单个网页的内容,但是在实际项目中是需要遍历整个网络的相关网页。图论中有深度优先遍历和宽度优先遍历,深度优先可能会因为过”深“或者进入黑洞;同时,也不能完全按照宽度优先进行遍历,需要进行优先级排序。


1.图的宽度优先遍历

先回顾一下图论中的有向图的BFS宽度优先遍历算法。
例题:如图,根据BFS写出各个节点的遍历顺序

首先任选一点A作为开始节点(种子节点)。

操作 队列中的元素
初始
A入队 A
A出队
BCDEF入队 BCDEF
B出队 CDEF
C出队 DEF
D出队 EF
E出队 F
H入队 FH
F出队 H
G入队 HG
H出队 G
I入队 GI
G出队 I
I出队

所以图的优先遍历顺序为ABCDEFHGI
算法总结:
1.任选顶点V入队
2.当队列非空时继续执行否则停止算法
3.队列头部元素M出队列,访问并且标记M已经被访问过
4.查找M的邻接顶点X
5.若X已经被访问则继续寻找邻接顶点,若没有,则X入队
6.循环第五步,直到M的所有邻接顶点均已入栈,若M的所有邻接顶点均已被访问过(即没有一个X入栈)则跳转步骤2

2.宽度优先遍历互联网

在网页中所有的节点都是html网页,对于非HTML文档可以看成是终端节点。并且网络的宽度优先遍历不是从单个的链接开始的,而是从一系列链接开始的,把这些网页中的“子节点”就是超链接提取出来,放入TODO队列重依次进行抓取。被处理过的链接需要放入一张Visited表中,每次处理一个链接之前,需要判断这个链接是否已经在Visited表中,若是,则已经处理过,则跳过这个链接,若不是,则继续处理。
上面拿到例题,用TODO表和Visited表来表示就是:

TODO表 Visited表
A
BCDEF A
CDEF AB
DEF ABC
EF ABCD
FH ABCDE
HG ABCDEF
GI ABCDEFH
I ABCDEFHG
ABCDEFHGI

为什么使用宽度优先遍历的爬虫策略

他是爬虫中使用最广泛的一种策略
1.重要的网页往往离最初选择的种子节点比较近,随着宽度的深入,网页的重要性就会降低
2.万维网的实际深度最多能够达到17层,但是到达某一个具体的网页总是存在一条很短路径,宽度优先遍历总是能以最快的速度到达这个网页
3.宽度有限有利于多爬虫的合作抓取,多爬虫通常先抓取站内的链接,抓取的封闭性很强

3.宽度优先遍历的Java实现

实现一个存储URL的队列

public class Queue {//使用链表实现队列private LinkedList queue=new LinkedList();//入队列public void enQueue(Object t){queue.addLast(t);}//出队列public Object deQueue(){return queue.removeFirst();}//判断队列是否为空public boolean isQueueEmpty(){return queue.isEmpty();}//判断队列是否包含某一个元素public boolean contains(Object t){return queue.contains(t);}
}

这里书上多了一个函数,可能是作者忘记删除的

这两个函数的作用是一模一样的,所以只需要其中一个就行了,删除另外一个。

实现一个存储已经访问过的URL的队列

每当从URL队列中取得一个url进行查询之前,需要先在
visitedUrl队列中查询是否已经访问过该节点,然后才能对该节点进行处理

public class LinkQueue {//已经访问的URL集合private static Set visitedUrl = new HashSet();//带访问的url集合private static Queue unVisitedUrl = new Queue();//获得URL队列public static Queue getUnVisitedUrl() {return unVisitedUrl;}//添加到访问过的URL队列中public static void addVisitedUrl(String url) {visitedUrl.add(url);}//移除访问过的URLpublic static void removeVisitedUrl(String url) {visitedUrl.remove(url);}//未访问的URL出队列public static Object unVisitedUrl(String url) {return unVisitedUrl.deQueue();}//保证每个URL只能被访问一次/*** 每一个url不是null并且字符串有效,不包含在已经访问过的节点集合中* 也不包含在没有访问过的节点集合中** @param url*/public static void addUnvisitedUrl(String url) {if (url != null && !url.trim().equals("") &&!visitedUrl.contains(url) &&!unVisitedUrl.contains(url))unVisitedUrl.enQueue(url);}//获得已经访问过的URL的数量public static int getVisitedUrlNum() {return visitedUrl.size();}//判断未访问的URL队列是否为空public static boolean unVisitedUrlEmpty() {return unVisitedUrl.isQueueEmpty();}
}

实现一个网页信息下载类

public class DownLoadFile {//根据URL和网页类型生成需要保存的网页的文件名,取出URL中的非文件名字符public String getFileNameByUrl(String url, String contentType) {//移除httpurl = url.substring(7);//text或者html类型if (contentType.indexOf("html") != -1) {url = url.replaceAll("[\\?/:*|<>\"]", "_") + ".html";return url;} else {//如application或者pdf类型return url.replaceAll("[\\?/:*|<>\"]", "_") + "."+ contentType.substring(contentType.lastIndexOf("/") + 1);}}//保存网页字节数组到本地文件,filepath为要保存的文件的相对地址private void saveToLocal(byte[] data, String filepath) {try {//System.out.println(filepath);DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(filepath)));for (int i = 0; i < data.length; i++) {out.write(data[i]);}out.flush();out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}//下载URL指向的网页public String downloadFile(String url) {String filepath = null;//1.生成HttpClient对象并设置参数HttpClient httpClient = new HttpClient();//设置HTTP链接超时5秒httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(5000);//2.生成GetMethod对象并设置参数GetMethod getMethod = new GetMethod(url);//设置GetMethod请求超时5秒getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 5000);//设置请求重试处理getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler());//3.执行HttpGet请求try {int statusCode = httpClient.executeMethod(getMethod);//判断访问状态码if (statusCode != HttpStatus.SC_OK) {System.err.println("Method failed:" + getMethod.getStatusLine());filepath = null;}//4.处理HTTP响应内容byte[] responseBody = getMethod.getResponseBody();//读取为字节数组//根据网页url生成保存时的文件名filepath ="temp//" +  getFileNameByUrl(url,getMethod.getResponseHeader("Content-Type").getValue());//System.out.println(filepath);saveToLocal(responseBody, filepath);} catch (IOException e) {e.printStackTrace();} finally {getMethod.releaseConnection();}return filepath;}
}

这里需要注意的是,书上的地址保存是temp/网页名,这里的temp文件夹需要自己新建,建在所在工程文件的文件夹下,之后所有爬到的网页都会在这个temp文件夹里面这是相对路径,你也可以在某一个盘上建一个绝对路径的文件夹,如D://temp//

页面解析工具类

这个类需要用到外部包HTMLParser 2.0的包,一定要下载最新的包,不然里面缺少某些类如org.htmlParser.Parser。

public class HtmlParserTool {//获取一个网页上的链接,filter用来过滤链接public static Set<String> extracLinks(String url, LinkFilter filter) {Set<String> links = new HashSet<String>();try {Parser parser = new Parser(url);parser.setEncoding("UTF-8");//过滤<frame>标签的filter,用来提取frame标签里面的src属性NodeFilter frameFilter = new NodeFilter() {@Overridepublic boolean accept(Node node) {if (node.getText().startsWith("frame src=")) {return true;} else return false;}};//OrFilter来设置过滤<a>标签和<frame>标签OrFilter linkFilter = new OrFilter(new NodeClassFilter(LinkTag.class), frameFilter);//得到所有经过过滤的标签NodeList list = parser.extractAllNodesThatMatch(linkFilter);for (int i = 0; i < list.size(); i++) {Node tag = list.elementAt(i);if (tag instanceof LinkTag)//<a>标签{LinkTag link = (LinkTag) tag;String linkUrl = link.getLink();if (filter.accept(linkUrl)) links.add(linkUrl);} else //<frame>标签{//提取frame里src属性的链接如<frame src="test.html">String frame = tag.getText();int start = frame.indexOf("src=");frame = frame.substring(start);int end = frame.indexOf(" ");if (end == -1) end = frame.indexOf(">");String frameUrl = frame.substring(5, end - 1);if (filter.accept(frameUrl)) links.add(frameUrl);}}} catch (ParserException e) {e.printStackTrace();}return links;}public interface LinkFilter {public boolean accept(String url);}
}

1.书上也说了LinkFilter是一个接口,并且实现为内部类
2.编码形式设置为UTF-8 parser.setEncoding("UTF-8");不然可能会出现中文字或者其他字符乱码的情况

主程序

public class Main {/*** 使用种子初始化URL队列** @param seeds*/private void initCrawlerWithSeeds(String[] seeds) {for (int i = 0; i < seeds.length; i++) {LinkQueue.addUnvisitedUrl(seeds[i]);}}/*** 抓取过程** @param seeds*/public void crawling(String[] seeds) {//定义过滤器,提取以http://www.lietu.com开头的链接HtmlParserTool.LinkFilter filter = new HtmlParserTool.LinkFilter() {@Overridepublic boolean accept(String url) {if (url.startsWith("http://www.lietu.com")) return true;else return false;}};//抓取过程initCrawlerWithSeeds(seeds);//循环条件:待抓取的链接不空并且抓取的网页数量不多于1000while (!LinkQueue.unVisitedUrlEmpty() && LinkQueue.getVisitedUrlNum() <= 1000) {//队头出队String visitUrl = (String) LinkQueue.unVisitedDeUrl();if (visitUrl == null) continue;DownLoadFile downLoad = new DownLoadFile();//下载网页downLoad.downloadFile(visitUrl);//将该URL放入已访问的URL队列中LinkQueue.addVisitedUrl(visitUrl);//提取出下载网页中的URLSet<String> links = HtmlParserTool.extracLinks(visitUrl, filter);//新的未访问的url入队for (String link : links) {LinkQueue.addUnvisitedUrl(link);}}}public static void main(String[] args) {Main crawler = new Main();crawler.crawling(new String[]{"http://lietu.com"});}
}

得到的结果

换一个百度搜索www.baidu.com,搜以www.baidu.com开头的网页结果

《自己动手写网络爬虫》笔记3-宽度优先遍历互联网相关推荐

  1. 记录《自己动手写网络爬虫 》书中涉及的内容学习一些算法

    第1篇  自己动手抓取数据 第1章  全面剖析网络爬虫 3 1.1  抓取网页 4 1.1.1  深入理解URL 4 1.1.2  通过指定的URL抓取 网页内容 6 1.1.3  Java网页抓取示 ...

  2. 用python写网络爬虫 -从零开始 3 编写ID遍历爬虫

    我们在访问网站的时候,发现有些网页ID 是按顺序排列的数字,这个时候我们就可以使用ID遍历的方式来爬取内容.但是局限性在于有些ID数字在10位数左右,那么这样爬取效率就会很低很低! import it ...

  3. java怎么写网络爬虫_教你如何编写简单的网络爬虫

    一.网络爬虫的基本知识 网络爬虫通过遍历互联网络,把网络中的相关网页全部抓取过来,这体现了爬的概念.爬虫如何遍历网络呢,互联网可以看做是一张大图,每个页面看做其中的一个节点,页面的连接看做是有向边.图 ...

  4. 网络爬虫笔记—滑动验证码识别

    网络爬虫笔记-滑动验证码识别 一.什么是滑动验证码 点击之前 点击之后 像这种通过滑动图片,补全缺口的方式,就是滑动验证码. 二.识别思路 1)使用selenium库操作谷歌浏览器,打开目标网站:关于 ...

  5. Python 网络爬虫笔记11 -- Scrapy 实战

    Python 网络爬虫笔记11 – Scrapy 实战 Python 网络爬虫系列笔记是笔者在学习嵩天老师的<Python网络爬虫与信息提取>课程及笔者实践网络爬虫的笔记. 课程链接:Py ...

  6. 网络爬虫笔记—Selenium

    网络爬虫笔记-Selenium 1.简介及环境安装 Selenium是一种自动化测试工具,利用它可以操作浏览器执行固定动作,例如点击.下拉等操作.在日常工作中,如果你需要用浏览器并且重复某项操作,那S ...

  7. 《用Python写网络爬虫第2版》PDF中英文+代码分析

    互联网包含了迄今为止最有用的数据集,并且大部分可以免费公开访问.但是,这些数据难以复用.它们被嵌入在网站的结构和样式当中,需要抽取出来才能使用.从网页中抽取数据的过程又称为网络爬虫,随着越来越多的信息 ...

  8. 网络爬虫笔记—图形验证码获取

    网络爬虫笔记-图形验证码获取 1.验证码获取思路 1)使用selenium库操作谷歌浏览器,打开目标网站: 2)对目标网站进行截图,并将图片保存到本地: 3)获取验证码元素节点在屏幕上的位置,即横纵坐 ...

  9. python爬虫教程:Python写网络爬虫的优势和理由

    在本篇文章里小编给各位整理了一篇关于选择Python写网络爬虫的优势和理由以及相关代码实例,有兴趣的朋友们阅读下吧. 什么是网络爬虫? 网络爬虫是一个自动提取网页的程序,它为搜索引擎从万维网上下载网页 ...

  10. 网络爬虫笔记—图形验证码识别

    网络爬虫笔记-图形验证码识别 <兄弟们,本文章开启了关注后阅读.大家如不想关注,可直接微信搜索"宏蜘蛛"或文章标题,查看文章.> 1.什么是图形验证码 像知网注册界面的 ...

最新文章

  1. C++ string 类常用函数
  2. 不吹牛,这样的面试官才牛逼!
  3. hadoop SecondaryNameNode和NameNode
  4. 数学--数论--快速幂--最大公约数--位运算模板
  5. 虚拟机安装docker_Docker 从入门到放弃:新手学习笔记(上)
  6. python获取token并登录,Python token的获取和再次登录验证
  7. 释放低代码小宇宙,微软 Power Platform 震撼来袭!
  8. [spring]spring boot项目实例
  9. SQL Server 2008性能故障排查(二)——CPU
  10. 互联网时代的软件革命——SaaS架构设计
  11. Egret入门学习日记 --- 第十七篇(书中 7.4~8.2节 内容)
  12. 游戏数据分析方法-活跃向
  13. Mac版哔哩哔哩视频下载工具
  14. Error response from daemon: failed to parse mydockerfile-centos: ENV must have two arguments
  15. 电容或电感的电压_磁场对于电感路径检测的影响
  16. 高端餐饮空间布局要点
  17. C语言递归(pta递归求简单交错幂级数的部分和)
  18. 【学术相关】为什么那么多博士延期毕业了?
  19. JavaScript用Math.asin()求反正弦值
  20. IDEA插件【美化插件】

热门文章

  1. 慕课马尔萨斯人口模型
  2. php常用字体大小,推荐:PHP编辑器常用的几种字体下载
  3. C语言读写txt文件
  4. 计算机网络技术毕业生实习报告_计算机网络专业毕业实习报告3000字
  5. 360云服务器合作,360云主机速度(云服务器)
  6. win10电脑360调用不到JAVA,win7/win10系统360浏览器打不开原因及解决方法
  7. Hyper-V虚拟机启动报错:IDE/ATAPI 帐户没有足够的权限
  8. Oracle备份与恢复介绍
  9. linux屏保程序,桌面技巧小贴士 Linux下制作屏保程序
  10. 一线大厂软件测试流程(思维导图)详解