第七章 到达哲学

原文:Chapter 7 Getting to Philosophy

译者:飞龙

协议:CC BY-NC-SA 4.0

自豪地采用谷歌翻译

本章的目标是开发一个 Web 爬虫,它测试了第 6.1 节中提到的“到达哲学”猜想。

7.1 起步

在本书的仓库中,你将找到一些帮助你起步的代码:

  • WikiNodeExample.java包含前一章的代码,展示了 DOM 树中深度优先搜索(DFS)的递归和迭代实现。
  • WikiNodeIterable.java包含Iterable类,用于遍历 DOM 树。我将在下一节中解释这段代码。
  • WikiFetcher.java包含一个工具类,使用jsoup从维基百科下载页面。为了帮助你遵守维基百科的服务条款,此类限制了你下载页面的速度;如果你每秒请求许多页,在下载下一页之前会休眠一段时间。
  • WikiPhilosophy.java包含你为此练习编写的代码的大纲。我们将在下面进行说明。

你还会发现 Ant 构建文件build.xml。如果你运行ant WikiPhilosophy,它将运行一些简单的启动代码。

7.2 可迭代对象和迭代器

在前一章中,我展示了迭代式深度优先搜索(DFS),并且认为与递归版本相比,迭代版本的优点在于,它更容易包装在Iterator对象中。在本节中,我们将看到如何实现它。

如果你不熟悉IteratorIterable接口,你可以阅读 http://thinkdast.com/iterator 和 http://thinkdast.com/iterable。

看看WikiNodeIterable.java的内容。外层的类WikiNodeIterable实现Iterable<Node>接口,所以我们可以在一个for循环中使用它:

Node root = ...
Iterable<Node> iter = new WikiNodeIterable(root);
for (Node node: iter) {visit(node);
}

其中root是我们想要遍历的树的根节点,并且visit是一个方法,当我们“访问”Node时,做任何我们想要的事情。

WikiNodeIterable的实现遵循以下惯例:

  • 构造函数接受并存储根Node的引用。
  • iterator方法创建一个返回一个Iterator对象。

这是它的样子:

public class WikiNodeIterable implements Iterable<Node> {private Node root;public WikiNodeIterable(Node root) {this.root = root;}@Overridepublic Iterator<Node> iterator() {return new WikiNodeIterator(root);}
}

内层的类WikiNodeIterator,执行所有实际工作。

private class WikiNodeIterator implements Iterator<Node> {Deque<Node> stack;public WikiNodeIterator(Node node) {stack = new ArrayDeque<Node>();stack.push(root);}@Overridepublic boolean hasNext() {return !stack.isEmpty();}@Overridepublic Node next() {if (stack.isEmpty()) {throw new NoSuchElementException();}Node node = stack.pop();List<Node> nodes = new ArrayList<Node>(node.childNodes());Collections.reverse(nodes);for (Node child: nodes) {stack.push(child);}return node;}
}

该代码与 DFS 的迭代版本几乎相同,但现在分为三个方法:

  • 构造函数初始化栈(使用一个ArrayDeque实现)并将根节点压入这个栈。
  • isEmpty检查栈是否为空。
  • nextNode栈中弹出下一个节点,按相反的顺序压入子节点,并返回弹出的Node。如果有人在空Iterator上调用next,则会抛出异常。

可能不明显的是,值得使用两个类和五个方法,来重写一个完美的方法。但是现在我们已经完成了,在需要Iterable的任何地方,我们可以使用WikiNodeIterable,这使得它的语法整洁,易于将迭代逻辑(DFS)与我们对节点的处理分开。

7.3 WikiFetcher

编写 Web 爬虫时,很容易下载太多页面,这可能会违反你要下载的服务器的服务条款。为了帮助你避免这种情况,我提供了一个WikiFetcher类,它可以做两件事情:

  • 它封装了我们在上一章中介绍的代码,用于从维基百科下载页面,解析 HTML 以及选择内容文本。
  • 它测量请求之间的时间,如果我们在请求之间没有足够的时间,它将休眠直到经过了合理的间隔。默认情况下,间隔为1秒。

这里是WikiFetcher的定义:

public class WikiFetcher {private long lastRequestTime = -1;private long minInterval = 1000;/*** Fetches and parses a URL string, * returning a list of paragraph elements.** @param url* @return* @throws IOException*/public Elements fetchWikipedia(String url) throws IOException {sleepIfNeeded();Connection conn = Jsoup.connect(url);Document doc = conn.get();Element content = doc.getElementById("mw-content-text");Elements paragraphs = content.select("p");return paragraphs;}private void sleepIfNeeded() {if (lastRequestTime != -1) {long currentTime = System.currentTimeMillis();long nextRequestTime = lastRequestTime + minInterval;if (currentTime < nextRequestTime) {try {Thread.sleep(nextRequestTime - currentTime);} catch (InterruptedException e) {System.err.println("Warning: sleep interrupted in fetchWikipedia.");}}}lastRequestTime = System.currentTimeMillis();}
}

唯一的公共方法是fetchWikipedia,接收String形式的 URL,并返回一个Elements集合,该集合包含的一个 DOM 元素表示内容文本中每个段落。这段代码应该很熟悉了。

新的代码是sleepIfNeeded,它检查自上次请求以来的时间,如果经过的时间小于minInterval(毫秒),则休眠。

这就是WikiFetcher全部。这是一个演示如何使用它的例子:

WikiFetcher wf = new WikiFetcher();for (String url: urlList) {Elements paragraphs = wf.fetchWikipedia(url);processParagraphs(paragraphs);
}

在这个例子中,我们假设urlList是一个String的集合 ,并且processParagraphs是一个方法,对Elements做一些事情,它由fetchWikipedia返回。

此示例展示了一些重要的东西:你应该创建一个WikiFetcher对象并使用它来处理所有请求。如果有多个WikiFetcher的实例,则它们不会确保请求之间的最小间隔。

注意:我的WikiFetcher实现很简单,但是通过创建多个实例,人们很容易误用它。你可以通过制作WikiFetcher“单例” 来避免这个问题,你可以阅读 http://thinkdast.com/singleton。

7.4 练习 5

WikiPhilosophy.java中,你会发现一个简单的main方法,展示了如何使用这些部分。从这个代码开始,你的工作是写一个爬虫:

  1. 获取维基百科页面的 URL,下载并分析。
  2. 它应该遍历所得到的 DOM 树来找到第一个 有效的链接。我会在下面解释“有效”的含义。
  3. 如果页面没有链接,或者如果第一个链接是我们已经看到的页面,程序应该指示失败并退出。
  4. 如果链接匹配维基百科页面上的哲学网址,程序应该提示成功并退出。
  5. 否则应该回到步骤1

该程序应该为它访问的 URL 构建List,并在结束时显示结果(无论成功还是失败)。

那么我们应该认为什么是“有效的”链接?你在这里有一些选择 各种版本的“到达哲学”推测使用略有不同的规则,但这里有一些选择:

  • 这个链接应该在页面的内容文本中,而不是侧栏或弹出框。
  • 它不应该是斜体或括号。
  • 你应该跳过外部链接,当前页面的链接和红色链接。
  • 在某些版本中,如果文本以大写字母开头,则应跳过链接。

你不必遵循所有这些规则,但我们建议你至少处理括号,斜体以及当前页面的链接。

如果你有足够的信息来起步,请继续。或者你可能想要阅读这些提示:

  • 当你遍历树的时候,你将需要处理的两种NodeTextNodeElement。如果你找到一个Element,你可能需要转换它的类型,来访问标签和其他信息。
  • 当你找到包含链接的Element时,通过向上跟踪父节点链,可以检查是否是斜体。如果父节点链中有一个<i><em>标签,链接为斜体。
  • 为了检查链接是否在括号中,你必须在遍历树时扫描文本,并跟踪开启和闭合括号(理想情况下,你的解决方案应该能够处理嵌套括号(像这样))。

如果你从 Java 页面开始,你应该在跟随七个链接之后到达哲学,除非我运行代码后发生了改变。

好的,这就是你所得到的所有帮助。现在全靠你了。玩的开心!

数据结构思维 第七章 到达哲学相关推荐

  1. 数据结构思维 第六章 树的遍历

    第六章 树的遍历 原文:Chapter 6 Tree traversal 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 本章将介绍一个 Web 搜索引擎,我们将在本书其余部分开 ...

  2. 【数据结构】第七章 查找

    第七章 查找 目录 第七章 查找 7.1 基本概念 7.2 顺序查找和折半查找 一.顺序查找 二.折半查找 三.分块查找 7.3 树形查找 一.二叉排序树(BST) 定义 查找具体值的代码实现 插入 ...

  3. 《数据结构》-第七章 查找(知识点总结)

    第七章 查找 本章开始介绍关于前几章这些数据结构的相应的运算-查找.关于查找的不同算法为每年考试考查的重点,主要分为线性结构的查找.树形结构的查找.散列结构的查找及字符串模式匹配,同时分析各个查找方法 ...

  4. 【算法基础】数据结构导论第七章-排序.pptx

    上课的课件分享,适合教学用. 文末提供下载 已发布: 数据结构导论第一章-绪论 数据结构导论第二章-线性表 数据结构导论第三章-栈.队列和数组 数据结构导论第四章-树 数据结构导论第五章-图 数据结构 ...

  5. 数据结构思维 第十三章 二叉搜索树

    第十三章 二叉搜索树 原文:Chapter 13 Binary search tree 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 本章介绍了上一个练习的解决方案,然后测试树 ...

  6. 数据结构思维 第十一章 `HashMap`

    第十一章 HashMap 原文:Chapter 11 HashMap 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 上一章中,我们写了一个使用哈希的Map接口的实现.我们期望这 ...

  7. 数据结构思维 第十七章 排序

    第十七章 排序 原文:Chapter 17 Sorting 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 计算机科学领域过度痴迷于排序算法.根据 CS 学生在这个主题上花费的时 ...

  8. 数据结构思维 第三章 `ArrayList`

    第三章 ArrayList 原文:Chapter 3 ArrayList 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 本章一举两得:我展示了上一个练习的解法,并展示了一种使用 ...

  9. 数据结构思维 第四章 `LinkedList`

    第四章 LinkedList 原文:Chapter 4 LinkedList 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 这一章展示了上一个练习的解法,并继续讨论算法分析. ...

最新文章

  1. Redis进阶-核心数据结构进阶实战
  2. django定义模型类-14
  3. Eclipse导入项目:No projects are found to import
  4. Windows.etc\hosts文件
  5. Linux文件属性4——读取目录文件
  6. 这真不是网友P的图?雷军微博曝光小米9 SE真机图 彩虹小米有点炫酷
  7. 使用 ASP.NET Core Razor 页、Web API 和实体框架进行分页和排序
  8. STM32工作笔记0076---UCOSIII任务基础API_任务创建和删除
  9. OpenSSL is not properly installed on your system.
  10. 5分钟学会两年经验Linux运维都不懂的内核问题
  11. 在线教学生计算机,洪恩老兔轻松教你学电脑
  12. 华为解锁刷机root教程详解
  13. 别做正常的傻瓜-读后感
  14. 杰理之低延时无线麦功能支持以下两种组合配置【篇】
  15. android刷原生rom教程,一加手机谷歌原生Android ROM包刷机教程
  16. POJ 3026 Borg Maze(BFS+最小生成树)
  17. 最先进的实体对齐方法的实验研究综述 An Experimental Study of State-of-the-Art Entity Alignment Approaches
  18. [转]全球付虚拟卡申请流程~
  19. 联邦学习开源框架FATE
  20. android高德地图marker图标,图标样式-字体图标标注-示例中心-JS API UI 组件示例 | 高德地图API...

热门文章

  1. (2)FPGA面试题竞争与冒险
  2. (11)verilog语言编写加减乘除
  3. html5多颜色灯笼旋转,HTML5 Canvas 漂亮的斑马条纹灯笼
  4. ffmpeg获取h264视频数据
  5. 1004.ubuntu16.04 安装protobuf
  6. boost any 实现万能容器_全面剖析 C++ Boost 智能指针!| CSDN 博文精选
  7. 公务员计算机软件及相关专业,公务员计算机专业考试大纲和真题3
  8. HTTP协议 (七) Cookie
  9. php表单密码由加密变明文,PHP 安全性漫谈 Linux+Apache+Mysql+PHP
  10. 进程通信方法的特点以及使用场景