Size: px
Start display at page:

Download ""

Transcription

1

2

3

4

5

6

7 第 1 篇 自己动手抓取数据

8 第 1 章 全面剖析网络爬虫 你知道百度 Google 是如何获取数以亿计的网页并且实时更新的吗? 你知道在搜索引擎领域人们常说的 Spider 是什么吗? 本章将全面介绍网络爬虫的方方面面 读完之后, 你将完全有能力自己写一个网络爬虫, 随意抓取互联网上任何感兴趣的东西 既然百度 Google 这些搜索引擎巨头已经帮我们抓取了互联网上的大部分信息, 为什么还要自己写爬虫呢? 因为深入整合信息的需求是广泛存在的 在企业中, 爬虫抓取下来的信息可以作为数据仓库多维展现的数据源, 也可以作为数据挖掘的来源 甚至有人为了炒股, 专门抓取股票信息 既然从美国中情局到普通老百姓都需要, 那还等什么, 让我们快开始吧

9 1.1 抓取网页 网络爬虫的基本操作是抓取网页 那么如何才能随心所欲地获得自己想要的页面? 这一节将从 URL 开始讲起, 然后告诉大家如何抓取网页, 并给出一个使用 Java 语言抓取网页的例子 最后, 要讲一讲抓取过程中的一个重要问题 : 如何处理 HTTP 状态码 深入理解 URL 抓取网页的过程其实和读者平时使用 IE 浏览器浏览网页的道理是一样的 比如, 你打开一个浏览器, 输入猎兔搜索网站的地址, 如图 1.1 所示 图 1.1 使用浏览器浏览网页 打开 网页的过程其实就是浏览器作为一个浏览的 客户端, 向服务器端发送了一次请求, 把服务器端的文件 抓 到本地, 再进行解释 展现 更进一步, 可以通过浏览器端查看 抓取 过来的文件源代码 选择 查看 源文件 命令, 就会出现从服务器上 抓取 下来的文件的源代码, 如图 1.2 所示 在上面的例子中, 我们在浏览器的地址栏中输入的字符串叫做 URL 那么, 什么是 URL 呢? 直观地讲,URL 就是在浏览器端输入的 这个字符串 下面我们深入介绍有关 URL 的知识 在理解 URL 之前, 首先要理解 URI 的概念 什么是 URI?Web 上每种可用的资源, 如 HTML 文档 图像 视频片段 程序等都由一个通用资源标志符 (Universal Resource Identifier, URI) 进行定位 URI 通常由三部分组成 :1 访问资源的命名机制 ;2 存放资源的主机名 ;3 资源自身的名称, 由路径表示 如下面的 URI: 4 1

10 第 1 章 全面剖析网络爬虫 图 1.2 浏览器端源代码 我们可以这样解释它 : 这是一个可以通过 HTTP 协议访问的资源, 位于主机 上, 通过路径 /html/html40 访问 URL 是 URI 的一个子集 它是 Uniform Resource Locator 的缩写, 译为 统一资源定位符 通俗地说,URL 是 Internet 上描述信息资源的字符串, 主要用在各种 WWW 客户程序和服务器程序上, 特别是著名的 Mosaic 采用 URL 可以用一种统一的格式来描述各种信息资源, 包括文件 服务器的地址和目录等 URL 的格式由三部分组成 : 第一部分是协议 ( 或称为服务方式 ) 第二部分是存有该资源的主机 IP 地址 ( 有时也包括端口号 ) 第三部分是主机资源的具体地址, 如目录和文件名等 第一部分和第二部分用 :// 符号隔开, 第二部分和第三部分用 / 符号隔开 第一部分和第二部分是不可缺少的, 第三部分有时可以省略 根据 URL 的定义, 我们给出了常用的两种 URL 协议的例子, 供大家参考 1.HTTP 协议的 URL 示例使用超级文本传输协议 HTTP, 提供超级文本信息服务的资源 例 : 其计算机域名为 超级文本文件( 文件类型为.html) 是在目录 /channel 下的 welcome.htm 这是中国人民日报的一台计算机 例 : 其计算机域名为 超级文本文件( 文件类型为.html) 是在目录 /talk 下的 5

11 talk1.htm 这是瑞得聊天室的地址, 可由此进入瑞得聊天室的第 1 室 2. 文件的 URL 用 URL 表示文件时, 服务器方式用 file 表示, 后面要有主机 IP 地址 文件的存取路 径 ( 即目录 ) 和文件名等信息 有时可以省略目录和文件名, 但 / 符号不能省略 例 :file://ftp.yoyodyne.com/pub/files/foobar.txt 上面这个 URL 代表存放在主机 ftp.yoyodyne.com 上的 pub/files/ 目录下的一个文件, 文 件名是 foobar.txt 例 :file://ftp.yoyodyne.com/pub 代表主机 ftp.yoyodyne.com 上的目录 /pub 例 :file://ftp.yoyodyne.com/ 代表主机 ftp.yoyodyne.com 的根目录 爬虫最主要的处理对象就是 URL, 它根据 URL 地址取得所需要的文件内容, 然后对它 进行进一步的处理 因此, 准确地理解 URL 对理解网络爬虫至关重要 从下一节开始, 我 们将详细地讲述如何根据 URL 地址来获得网页内容 通过指定的 URL 抓取网页内容 上一节详细介绍了 URL 的构成, 这一节主要阐述如何根据给定的 URL 来抓取网页 所谓网页抓取, 就是把 URL 地址中指定的网络资源从网络流中读取出来, 保存到本地 类似于使用程序模拟 IE 浏览器的功能, 把 URL 作为 HTTP 请求的内容发送到服务器端, 然后读取服务器端的响应资源 Java 语言是为网络而生的编程语言, 它把网络资源看成是一种文件, 它对网络资源的 访问和对本地文件的访问一样方便 它把请求和响应封装为流 因此我们可以根据相应内 容, 获得响应流, 之后从流中按字节读取数据 例如,java.net.URL 类可以对相应的 Web 服务器发出请求并且获得响应文档 java.net.url 类有一个默认的构造函数, 使用 URL 地 址作为参数, 构造 URL 对象 : 资源 : URL pageurl = new URL(path); 接着, 可以通过获得的 URL 对象来取得网络流, 进而像操作本地文件一样来操作网络 InputStream stream = pageurl.openstream(); 在实际的项目中, 网络环境比较复杂, 因此, 只用 java.net 包中的 API 来模拟 IE 客户 端的工作, 代码量非常大 需要处理 HTTP 返回的状态码, 设置 HTTP 代理, 处理 HTTPS 协议等工作 为了便于应用程序的开发, 实际开发时常常使用 Apache 的 HTTP 客户端开源 项目 HttpClient 它完全能够处理 HTTP 连接中的各种问题, 使用起来非常方便 只需 在项目中引入 HttpClient.jar 包, 就可以模拟 IE 来获取网页内容 例如 : // 创建一个客户端, 类似于打开一个浏览器 HttpClient httpclient=new HttpClient(); 6 1

12 第 1 章 全面剖析网络爬虫 // 创建一个 get 方法, 类似于在浏览器地址栏中输入一个地址 GetMethod getmethod=new GetMethod(" // 回车, 获得响应状态码 int statuscode=httpclient.executemethod(getmethod); // 查看命中情况, 可以获得的东西还有很多, 比如 head cookies 等 System.out.println("response=" + getmethod.getresponsebodyasstring()); // 释放 getmethod.releaseconnection(); 上面的示例代码是使用 HttpClient 进行请求与响应的例子 第一行表示创建一个客户 端, 相当于打开浏览器 第二行使用 get 方式对 进行请求 第三行 执行请求, 获取响应状态 第四行的 getmethod.getresponsebodyasstring() 方法能够以字符 串方式获取返回的内容 这也是网页抓取所需要的内容 在这个示例中, 只是简单地把返 回的内容打印出来, 而在实际项目中, 通常需要把返回的内容写入本地文件并保存 最后 还要关闭网络连接, 以免造成资源消耗 这个例子是用 get 方式来访问 Web 资源 通常,get 请求方式把需要传递给服务器的参 数作为 URL 的一部分传递给服务器 但是,HTTP 协议本身对 URL 字符串长度有所限制 因此不能传递过多的参数给服务器 为了避免这种问题, 通常情况下, 采用 post 方法进行 HTTP 请求,HttpClient 包对 post 方法也有很好的支持 例如 : // 得到 post 方法 PostMethod PostMethod = new PostMethod(" // 使用数组来传递参数 NameValuePair[] postdata = new NameValuePair[2]; // 设置参数 postdata[0] = new NameValuePair(" 武器 ", " 枪 "); postdata[1] = new NameValuePair(" 什么枪 ", " 神枪 "); postmethod.addparameters(postdata); // 回车, 获得响应状态码 int statuscode=httpclient.executemethod(getmethod); // 查看命中情况, 可以获得的东西还有很多, 比如 head cookies 等 System.out.println("response=" + getmethod.getresponsebodyasstring()); // 释放 getmethod.releaseconnection(); 上面的例子说明了如何使用 post 方法来访问 Web 资源 与 get 方法不同,post 方法可 以使用 NameValuePair 来设置参数, 因此可以设置 无限 多的参数 而 get 方法采用把参 7

13 数写在 URL 里面的方式, 由于 URL 有长度限制, 因此传递参数的长度会有限制 有时, 我们执行爬虫程序的机器不能直接访问 Web 资源, 而是需要通过 HTTP 代理服 务器去访问,HttpClient 对代理服务器也有很好的支持 如 : // 创建 HttpClient 相当于打开一个代理 HttpClient httpclient=new HttpClient(); // 设置代理服务器的 IP 地址和端口 httpclient.gethostconfiguration().setproxy(" ", 9527); // 告诉 httpclient, 使用抢先认证, 否则你会收到 你没有资格 的恶果 httpclient.getparams().setauthenticationpreemptive(true); //MyProxyCredentialsProvder 返回代理的 credential(username/password) httpclient.getparams().setparameter(credentialsprovider.provider, new MyProxyCredentialsProvider()); // 设置代理服务器的用户名和密码 httpclient.getstate().setproxycredentials(new AuthScope(" ", AuthScope.ANY_PORT, AuthScope.ANY_REALM), new UsernamePasswordCredentials("username","password")); 上面的例子详细解释了如何使用 HttpClient 设置代理服务器 如果你所在的局域网访问 Web 资源需要代理服务器的话, 你可以参照上面的代码设置 这一节, 我们介绍了使用 HttpClient 抓取网页的内容, 之后, 我们将给出一个详细的例 子来说明如何获取网页 Java 网页抓取示例 在这一节中, 我们根据之前讲过的内容, 写一个实际的网页抓取的例子 这个例子把 上一节讲的内容做了一定的总结, 代码如下 : public class RetrivePage private static HttpClient httpclient = new HttpClient(); // 设置代理服务器 static // 设置代理服务器的 IP 地址和端口 httpclient.gethostconfiguration().setproxy(" ", 8080); public static boolean downloadpage(string path) throws HttpException, IOException InputStream input = null; OutputStream output = null; // 得到 post 方法 PostMethod postmethod = new PostMethod(path); // 设置 post 方法的参数 NameValuePair[] postdata = new NameValuePair[2]; postdata[0] = new NameValuePair("name","lietu"); postdata[1] = new 8 1

14 第 1 章 全面剖析网络爬虫 NameValuePair("password","*****"); postmethod.addparameters(postdata); // 执行, 返回状态码 int statuscode = httpclient.executemethod(postmethod); // 针对状态码进行处理 ( 简单起见, 只处理返回值为 200 的状态码 ) if (statuscode == HttpStatus.SC_OK) input = postmethod.getresponsebodyasstream(); // 得到文件名 String filename = path.substring(path.lastindexof('/')+1); // 获得文件输出流 output = new FileOutputStream(filename); // 输出到文件 int tempbyte = -1; while((tempbyte=input.read())>0) output.write(tempbyte); // 关闭输入输出流 if(input!=null) input.close(); if(output!=null) output.close(); return true; return false; /** * 测试代码 */ public static void main(string[] args) // 抓取 lietu 首页, 输出 try RetrivePage.downloadPage(" catch (HttpException e) // TODO Auto-generated catch block e.printstacktrace(); catch (IOException e) // TODO Auto-generated catch block e.printstacktrace(); 上面的例子是抓取猎兔搜索主页的示例 它是一个比较简单的网页抓取示例, 由于互联网的复杂性, 真正的网页抓取程序会考虑非常多的问题 比如, 资源名的问题, 资源类型的问题, 状态码的问题 而其中最重要的就是针对各种返回的状态码的处理 下一节将重点介绍处理状态码的问题 9

15 1.1.4 处理 HTTP 状态码上一节介绍 HttpClient 访问 Web 资源的时候, 涉及 HTTP 状态码 比如下面这条语句 : int statuscode=httpclient.executemethod(getmethod);// 回车, 获得响应状态码 HTTP 状态码表示 HTTP 协议所返回的响应的状态 比如客户端向服务器发送请求, 如果成功地获得请求的资源, 则返回的状态码为 200, 表示响应成功 如果请求的资源不存在, 则通常返回 404 错误 HTTP 状态码通常分为 5 种类型, 分别以 1~5 五个数字开头, 由 3 位整数组成 1XX 通常用作实验用途 这一节主要介绍 2XX 3XX 4XX 5XX 等常用的几种状态码, 如表 1.1 所示 表 1.1 HTTP 常用状态码 状态代码 代码描述 处理方式 200 请求成功 获得响应的内容, 进行处理 201 请求完成, 结果是创建了新资源 新创建资源的 爬虫中不会遇到 URI 可在响应的实体中得到 202 请求被接受, 但处理尚未完成 阻塞等待 204 服务器端已经实现了请求, 但是没有返回新的信 丢弃 息 如果客户是用户代理, 则无须为此更新自身的文档视图 300 该状态码不被 HTTP/1.0 的应用程序直接使用, 只是作为 3XX 类型回应的默认解释 存在多个 若程序中能够处理, 则进行进一步处理, 如果程序中不能处理, 则丢弃 可用的被请求资源 301 请求到的资源都会分配一个永久的 URL, 这样 重定向到分配的 URL 就可以在将来通过该 URL 来访问此资源 302 请求到的资源在一个不同的 URL 处临时保存 重定向到临时的 URL 304 请求的资源未更新 丢弃 400 非法请求 丢弃 401 未授权 丢弃 403 禁止 丢弃 404 没有找到 丢弃 5XX 回应代码以 5 开头的状态码表示服务器端发现自己出现错误, 不能继续执行请求 丢弃 当返回的状态码为 5XX 时, 表示应用服务器出现错误, 采用简单的丢弃处理就可以 解决 10 1

16 第 1 章 全面剖析网络爬虫 当返回值状态码为 3XX 时, 通常进行转向, 以下是转向的代码片段, 读者可以和上一 节的代码自行整合到一起 : // 若需要转向, 则进行转向操作 if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY) (statuscode == HttpStatus.SC_MOVED_PERMANENTLY) (statuscode == HttpStatus.SC_SEE_OTHER) (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) // 读取新的 URL 地址 Header header = postmethod.getresponseheader("location"); if(header!=null) String newurl = header.getvalue(); if(newurl==null newurl.equals("")) newurl="/"; // 使用 post 转向 PostMethod redirect = new PostMethod(newUrl); // 发送请求, 做进一步处理 当响应状态码为 2XX 时, 根据表 1.1 的描述, 我们只需要处理 200 和 202 两种状态码, 其他的返回值可以不做进一步处理 200 的返回状态码是成功状态码, 可以直接进行网页抓 取, 例如 : // 处理返回值为 200 的状态码 if (statuscode == HttpStatus.SC_OK) input = postmethod.getresponsebodyasstream(); // 得到文件名 String filename = path.substring(path.lastindexof('/')+1); // 获得文件输出流 output = new FileOutputStream(filename); // 输出到文件 int tempbyte = -1; while((tempbyte=input.read())>0) output.write(tempbyte); 202 的响应状态码表示请求已经接受, 服务器再做进一步处理 1.2 宽度优先爬虫和带偏好的爬虫 1.1 节介绍了如何获取单个网页内容 在实际项目中, 则使用爬虫程序遍历互联网, 把网络中相关的网页全部抓取过来, 这也体现了爬虫程序 爬 的概念 爬虫程序是如何遍历互联网, 把网页全部抓取下来的呢? 互联网可以看成一个超级大的 图, 而每个页面可以看作是一个 节点 页面中的链接可以看成是图的 有向边 因此, 能够通过图 11

17 的遍历的方式对互联网这个超级大 图 进行访问 图的遍历通常可分为宽度优先遍历和深度优先遍历两种方式 但是深度优先遍历可能会在深度上过 深 地遍历或者陷入 黑洞, 大多数爬虫都不采用这种方式 另一方面, 在爬取的时候, 有时候也不能完全按照宽度优先遍历的方式, 而是给待遍历的网页赋予一定的优先级, 根据这个优先级进行遍历, 这种方法称为带偏好的遍历 本小节会分别介绍宽度优先遍历和带偏好的遍历 图的宽度优先遍历下面先来看看图的宽度优先遍历过程 图的宽度优先遍历 (BFS) 算法是一个分层搜索的过程, 和树的层序遍历算法相同 在图中选中一个节点, 作为起始节点, 然后按照层次遍历的方式, 一层一层地进行访问 图的宽度优先遍历需要一个队列作为保存当前节点的子节点的数据结构 具体的算法如下所示 : (1) 顶点 V 入队列 (2) 当队列非空时继续执行, 否则算法为空 (3) 出队列, 获得队头节点 V, 访问顶点 V 并标记 V 已经被访问 (4) 查找顶点 V 的第一个邻接顶点 col (5) 若 V 的邻接顶点 col 未被访问过, 则 col 进队列 (6) 继续查找 V 的其他邻接顶点 col, 转到步骤 (5), 若 V 的所有邻接顶点都已经被访问过, 则转到步骤 (2) 下面, 我们以图示的方式介绍宽度优先遍历的过程, 如图 1.3 所示 B G F A C D E H I 图 1.3 宽度优先遍历过程 12 1

18 第 1 章 全面剖析网络爬虫 选择 A 作为种子节点, 则宽度优先遍历的过程, 如表 1.2 所示 表 1.2 宽度优先遍历过程 初始 A 入队列 A 出队列 BCDEF 入队列 B 出队列 C 出队列 D 出队列 E 出队列 H 入队列 F 出队列 G 入队列 H 出队列 I 入队列 G 出队列 I 出队列 操作队列中的元素空 A 空 BCDEF CDEF DEF EF F FH H HG G GI I 空 在表 1.2 所示的遍历过程中, 出队列的节点顺序既是图的宽度优先遍历的访问顺序 由 此可以看出, 图 1.3 所示的宽度优先遍历的访问顺序为 A->B->C->D->E->F->H->G->I 本节讲述了宽度优先遍历的理论基础, 把互联网看成一个 超图, 则对这张图也可以采用宽度优先遍历的方式进行访问 下面将着重讲解如何对互联网进行宽度优先遍历 宽度优先遍历互联网 节介绍的宽度优先遍历是从一个种子节点开始的 而实际的爬虫项目是从一系列 的种子链接开始的 所谓种子链接, 就好比宽度优先遍历中的种子节点 ( 图 1.3 中的 A 节点 ) 一样 实际的爬虫项目中种子链接可以有多个, 而宽度优先遍历中的种子节点只有一个 比如, 可以指定 和 两个种子链接 如何定义一个链接的子节点? 每个链接对应一个 HTML 页面或者其他文件 (word excel pdf jpg 等 ), 在这些文件中, 只有 HTML 页面有相应的 子节点, 这些 子节点 就是 HTML 页面上对应的超链接 如 页面中 ( 如图 1.4 所示 ), 招聘 网 址 更多 以及页面下方的 搜索产品, 技术文档, 成功案例, 猎兔新 闻, 联系猎兔, 关于我们, ENGLISH 等都是 的子节点 这些子节点本身又是一个链接 对于非 HTML 文档, 比如 Excel 文件等, 不能从中提取超链接, 因此, 可以看作是图的 终端 节点 就好像图 1.3 中的 B C D I G 等节点一样 13

19 图 1.4 猎兔搜索主页 整个的宽度优先爬虫过程就是从一系列的种子节点开始, 把这些网页中的 子节点 ( 也就是超链接 ) 提取出来, 放入队列中依次进行抓取 被处理过的链接需要放入一张表 ( 通常称为 Visited 表 ) 中 每次新处理一个链接之前, 需要查看这个链接是否已经存在于 Visited 表中 如果存在, 证明链接已经处理过, 跳过, 不做处理, 否则进行下一步处理 实际的过程如图 1.5 所示 初始 URL 地址 TODO 表 解析 URL Visited 表 新解析出的 URL 图 1.5 宽度优先爬虫过程 如图 1.5 所示, 初始的 URL 地址是爬虫系统中提供的种子 URL( 一般在系统的配置文件中指定 ) 当解析这些种子 URL 所表示的网页时, 会产生新的 URL( 比如从页面中的 <a href= 中提取出 这个链接 ) 然后, 进行以下工作 : (1) 把解析出的链接和 Visited 表中的链接进行比较, 若 Visited 表中不存在此链接, 表示其未被访问过 (2) 把链接放入 TODO 表中 (3) 处理完毕后, 再次从 TODO 表中取得一条链接, 直接放入 Visited 表中 (4) 针对这个链接所表示的网页, 继续上述过程 如此循环往复 表 1.3 显示了对图 1.3 所示的页面的爬取过程 表 1.3 网络爬取 A BCDEF CDEF DEF TODO 表 空 A A,B A,B,C Visited 表 14 1

20 第 1 章 全面剖析网络爬虫 续表 TODO 表 Visited 表 EF FH HG GI I 空 A,B,C,D A,B,C,D,E A,B,C,D,E,F A,B,C,D,E,F,H A,B,C,D,E,F,H,G A,B,C,D,E,F,H,G,I 宽度优先遍历是爬虫中使用最广泛的一种爬虫策略, 之所以使用宽度优先搜索策略, 主要原因有三点 : 重要的网页往往离种子比较近, 例如我们打开新闻网站的时候往往是最热门的新闻, 随着不断的深入冲浪, 所看到的网页的重要性越来越低 万维网的实际深度最多能达到 17 层, 但到达某个网页总存在一条很短的路径 而宽度优先遍历会以最快的速度到达这个网页 宽度优先有利于多爬虫的合作抓取, 多爬虫合作通常先抓取站内链接, 抓取的封闭性很强 这一小节详细讲述了宽度优先遍历互联网的方法 下一节将给出一个详细的例子来说明如何实现这种方法 Java 宽度优先爬虫示例本节使用 Java 实现一个简易的爬虫 其中用到了 HttpClient 和 HtmlParser 两个开源工具包 HttpClient 的内容之前已经做过详细的阐述 有关 HtmlParser 的用法, 我们会在 4.2 节给出详细的介绍 为了便于读者理解, 下面给出示例程序的结构, 如图 1.6 所示 初始化 URL 队列 URL 队列为空或爬取到 指定数量的网页 判断条件 退出程序 队头 URL 出队列 下载 URL 指定的页面 抽出网页中的 URL 新 URL 入队 图 1.6 爬虫示例程序结构 15

21 队列 首先, 需要定义图 1.6 中所描述的 URL 队列, 这里使用一个 LinkedList 来实现这个 Queue 类 : /** * 队列, 保存将要访问的 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(); // 判断队列是否包含 t public boolean contians(object t) return queue.contains(t); public boolean empty() return queue.isempty(); 除了 URL 队列之外, 在爬虫过程中, 还需要一个数据结构来记录已经访问过的 URL 每当要访问一个 URL 的时候, 首先在这个数据结构中进行查找, 如果当前的 URL 已经存 在, 则丢弃它 这个数据结构要有两个特点 : 结构中保存的 URL 不能重复 能够快速地查找 ( 实际系统中 URL 的数目非常多, 因此要考虑查找性能 ) 针对以上两点, 我们选择 HashSet 作为存储结构 LinkQueue 类 : public class LinkQueue // 已访问的 url 集合 private static Set visitedurl = new HashSet(); // 待访问的 url 集合 private static Queue unvisitedurl = new Queue(); 16 1

22 第 1 章 全面剖析网络爬虫 // 获得 URL 队列 public static Queue getunvisitedurl() return unvisitedurl; // 添加到访问过的 URL 队列中 public static void addvisitedurl(string url) visitedurl.add(url); // 移除访问过的 URL public static void removevisitedurl(string url) visitedurl.remove(url); // 未访问的 URL 出队列 public static Object unvisitedurldequeue() return unvisitedurl.dequeue(); // 保证每个 URL 只被访问一次 public static void addunvisitedurl(string url) if (url!= null &&!url.trim().equals("") &&!visitedurl.contains(url) &&!unvisitedurl.contians(url)) unvisitedurl.enqueue(url); // 获得已经访问的 URL 数目 public static int getvisitedurlnum() return visitedurl.size(); // 判断未访问的 URL 队列中是否为空 public static boolean unvisitedurlsempty() return unvisitedurl.empty(); 下面的代码详细说明了网页下载并处理的过程 和 1.1 节讲述的内容相比, 它考虑了更 多的方面 比如如何存储网页, 设置请求超时策略等 DownLoadFile 类 : public class DownLoadFile /** * 根据 URL 和网页类型生成需要保存的网页的文件名, 去除 URL 中的非文件名字符 */ public String getfilenamebyurl(string url,string contenttype) // 移除 http: url=url.substring(7); //text/html 类型 if(contenttype.indexof("html")!=-1) 17

23 url= url.replaceall("[\\?/:* <>\"]", "_")+".html"; return url; // 如 application/pdf 类型 else return url.replaceall("[\\?/:* <>\"]", "_")+"."+ contenttype.substring(contenttype.lastindexof("/")+1); /** * 保存网页字节数组到本地文件,filePath 为要保存的文件的相对地址 */ private void savetolocal(byte[] data, String filepath) try 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 (IOException e) e.printstacktrace(); // 下载 URL 指向的网页 public String downloadfile(string url) String filepath = null; // 1. 生成 HttpClinet 对象并设置参数 HttpClient httpclient = new HttpClient(); // 设置 HTTP 连接超时 5s httpclient.gethttpconnectionmanager().getparams().setconnectiontimeout(5000); // 2. 生成 GetMethod 对象并设置参数 GetMethod getmethod = new GetMethod(url); // 设置 get 请求超时 5s getmethod.getparams().setparameter(httpmethodparams.so_timeout,5000); // 设置请求重试处理 getmethod.getparams().setparameter(httpmethodparams.retry_handler, new DefaultHttpMethodRetryHandler()); // 3. 执行 HTTP GET 请求 try int statuscode = httpclient.executemethod(getmethod); // 判断访问的状态码 if (statuscode!= HttpStatus.SC_OK) 18 1

24 第 1 章 全面剖析网络爬虫 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()); savetolocal(responsebody, filepath); catch (HttpException e) // 发生致命的异常, 可能是协议不对或者返回的内容有问题 System.out.println("Please check your provided http address!"); e.printstacktrace(); catch (IOException e) // 发生网络异常 e.printstacktrace(); finally // 释放连接 getmethod.releaseconnection(); return filepath; 接下来, 演示如何从获得的网页中提取 URL Java 有一个非常实用的开源工具包 Html Parser, 它专门针对 Html 页面进行处理, 不仅能提取 URL, 还能提取文本以及你想要的任 何内容 关于它的有关内容, 会在第 4 章详细介绍 好了, 让我们看看代码吧! HtmlParserTool 类 : 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("gb2312"); // 过滤 <frame > 标签的 filter, 用来提取 frame 标签里的 src 属性 NodeFilter framefilter = new NodeFilter() public boolean accept(node node) if (node.gettext().startswith("frame src=")) return true; else return false; ; 19

25 // 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();// URL 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; 最后, 来看看宽度爬虫的主程序 MyCrawler 类 : public class MyCrawler /** * 使用种子初始化 URL 队列 seeds 种子 URL */ private void initcrawlerwithseeds(string[] seeds) for(int i=0;i<seeds.length;i++) LinkQueue.addUnvisitedUrl(seeds[i]); 20 1

26 第 1 章 全面剖析网络爬虫 /** * 抓取过程 seeds */ public void crawling(string[] seeds) // 定义过滤器, 提取以 开头的链接 LinkFilter filter = new LinkFilter() public boolean accept(string url) if(url.startswith(" return true; else return false; ; // 初始化 URL 队列 initcrawlerwithseeds(seeds); // 循环条件 : 待抓取的链接不空且抓取的网页不多于 1000 while(!linkqueue.unvisitedurlsempty() &&LinkQueue.getVisitedUrlNum()<=1000) // 队头 URL 出队列 String visiturl=(string)linkqueue.unvisitedurldequeue(); if(visiturl==null) continue; DownLoadFile downloader=new DownLoadFile(); // 下载网页 downloader.downloadfile(visiturl); // 该 URL 放入已访问的 URL 中 LinkQueue.addVisitedUrl(visitUrl); // 提取出下载网页中的 URL Set<String> links=htmlparsertool.extraclinks(visiturl,filter); // 新的未访问的 URL 入队 for(string link:links) LinkQueue.addUnvisitedUrl(link); //main 方法入口 public static void main(string[]args) MyCrawler crawler = new MyCrawler(); crawler.crawling(new String[]" 上面的主程序使用了一个 LinkFilter 接口, 并且实现为一个内部类 这个接口的目的是 21

27 为了过滤提取出来的 URL, 它使得程序中提取出来的 URL 只会和猎兔网站相关 而不会提 取其他无关的网站 代码如下 : public interface LinkFilter public boolean accept(string url); 带偏好的爬虫 有时, 在 URL 队列中选择需要抓取的 URL 时, 不一定按照队列 先进先出 的方式 进行选择 而把重要的 URL 先从队列中 挑 出来进行抓取 这种策略也称作 页面选择 (Page Selection) 这可以使有限的网络资源照顾重要性高的网页 那么哪些网页是重要性高的网页呢? 判断网页的重要性的因素很多, 主要有链接的欢迎度 ( 知道链接的重要性了吧 ) 链接的 重要度和平均链接深度 网站质量 历史权重等主要因素 链接的欢迎度主要是由反向链接 (backlinks, 即指向当前 URL 的链接 ) 的数量和质量决 定的, 我们定义为 IB(P) 链接的重要度, 是一个关于 URL 字符串的函数, 仅仅考察字符串本身, 比如认为.com 和 home 的 URL 重要度比.cc 和 map 高, 我们定义为 IL(P) 平均链接深度, 根据上面所分析的宽度优先的原则计算出全站的平均链接深度, 然后 认为距离种子站点越近的重要性越高 我们定义为 ID(P) 如果我们定义网页的重要性为 I(P), 那么, 页面的重要度由下面的公式决定 : I(P)=X*IB(P)+Y*IL(P) (1.1) 其中,X 和 Y 两个参数, 用来调整 IB(P) 和 IL(P) 所占比例的大小,ID(P) 由宽度优先的 遍历规则保证, 因此不作为重要的指标函数 如何实现最佳优先爬虫呢, 最简单的方式可以使用优先级队列来实现 TODO 表, 并且 把每个 URL 的重要性作为队列元素的优先级 这样, 每次选出来扩展的 URL 就是具有最 高重要性的网页 有关优先级队列的介绍, 请参考 节中的内容 所示 例如, 假设图 1.3 中节点的重要性为 D>B>C>A>E>F>I>G>H, 则整个遍历过程如表 1.4 表 1.4 带偏好的爬虫 TODO 优先级队列 Visited 表 A D,B,C, E,F B,C,E,F C,E,F E,F F,H G,H null A A,D A, D, B A,D,B,C A,D,B,C,E A,D,B,C, E,F 22 1

28 第 1 章 全面剖析网络爬虫 续表 Todo 优先级队列 Visited 表 H I null A,D,B,C,E,F,G A,D,B,C,E,F,G,H A,D,B,C,E,F,G,H,I Java 带偏好的爬虫示例 在 节中, 我们已经指出, 可以使用优先级队列 (Priority Queue) 来实现这个带偏好 的爬虫 在深入讲解之前, 我们首先介绍优先级队列 优先级队列是一种特殊的队列, 普通队列中的元素是先进先出的, 而优先级队列则是 根据进入队列中的元素的优先级进行出队列操作 例如操作系统的一些优先级进程管理等, 都可以使用优先级队列 优先级队列也有最小优先级队列和最大优先级队列两种 理论上, 优先级队列可以是任何一种数据结构, 线性的和非线性的, 也可以是有序的 或无序的 针对有序的优先级队列而言, 获取最小或最大的值是非常容易的, 但是插入却 非常困难 ; 而对于无序的有衔接队列而言, 插入是很容易的, 但是获取最大和最小值是很 麻烦的 根据以上的分析, 可以使用 堆 这种折中的数据结构来实现优先级队列 从 JDK 1.5 开始,Java 提供了内置的支持优先级队列的数据结构 java.util.priorityqueue 我们在上边的代码中, 只要稍微修改一下, 就可以支持从 URL 队列中选择优先级高的 URL LinkQueue 类 : public class LinkQueue // 已访问的 URL 集合 private static Set visitedurl = new HashSet(); // 待访问的 URL 集合 private static Queue unvisitedurl = new PriorityQueue(); // 获得 URL 队列 public static Queue getunvisitedurl() return unvisitedurl; // 添加到访问过的 URL 队列中 public static void addvisitedurl(string url) visitedurl.add(url); // 移除访问过的 URL public static void removevisitedurl(string url) visitedurl.remove(url); // 未访问的 URL 出队列 public static Object unvisitedurldequeue() return unvisitedurl.poll(); 23

29 // 保证每个 URL 只被访问一次 public static void addunvisitedurl(string url) if (url!= null &&!url.trim().equals("") &&!visitedurl.contains(url) &&!unvisitedurl.contains(url)) unvisitedurl.add(url); // 获得已经访问的 URL 数目 public static int getvisitedurlnum() return visitedurl.size(); // 判断未访问的 URL 队列中是否为空 public static boolean unvisitedurlsempty() return unvisitedurl.isempty(); 在带偏好的爬虫里, 队列元素的优先级是由 URL 的优先级确定的 关于如何确定 URL 的优先级, 有一些专用的链接分析的方法, 比如 Google 的 PageRank 和 HITS 算法 有关这些算法的内容, 将在第 8 章详细介绍 1.3 设计爬虫队列 通过前几节的介绍, 可以看出, 网络爬虫最关键的数据结构是 URL 队列 通常我们称之为爬虫队列 之前的几节一直使用内存数据结构, 例如链表或者队列来实现 URL 队列 但是, 网络中需要我们抓取的链接成千上万, 在一些大型的搜索引擎中, 比如百度和 Google, 大概都有十几亿的 URL 需要抓取 因此, 内存数据结构并不适合这些应用 最合适的一种方法就是使用内存数据库, 或者直接使用数据库来存储这些 URL 本节将讲解爬虫队列的基本知识, 并且介绍一种非常流行的内存数据库 Berkeley DB, 最后介绍一个成熟的开源爬虫软件 Heritrix 是如何实现爬虫队列的 爬虫队列爬虫队列的设计是网络爬虫的关键 爬虫队列是用来保存 URL 的队列数据结构的 在大型爬虫应用中, 构建通用的 可以扩缩的爬虫队列非常重要 数以十亿计的 URL 地址, 使用内存的链表或者队列来存储显然不够, 因此, 需要找到一种数据结构, 这种数据结构具有以下几个特点 : 能够存储海量数据, 当数据超出内存限制的时候, 能够把它固化在硬盘上 存取数据速度非常快 能够支持多线程访问 ( 多线程技术能够大规模提升爬虫的性能, 这点将在之后的章节详细介绍 ) 24 1

30 第 1 章 全面剖析网络爬虫 结合上面 3 点中对存储速度的要求, 使 Hash 成为存储结构的不二选择 通常, 在进行 Hash 存储的时候,key 值都选取 URL 字符串, 但是为了更节省空间, 通 常会对 URL 进行压缩 常用的压缩算法是 MD5 压缩算法 MD5 算法描述 对 MD5 算法的简要叙述为 :MD5 以 512 位分组来处理输入的信息, 且每一分组又 被划分为 16 个 32 位子分组, 经过一系列的处理后, 算法的输出由 4 个 32 位分组组成, 将这 4 个 32 位分组级联后将生成一个 128 位的散列值 在 MD5 算法中, 首先需要对信息进行填充, 使其位长度对 512 求余的结果等于 448 因此, 信息的位长度 (Bits Length) 将被扩展至 N* , 即 N*64+56 个字节 (Bytes), N 为一个正整数 填充的方法如下 : 在信息的后面填充一个 1 和无数个 0, 直到满足上面的条件时才停止用 0 对信息的 填充 然后, 在这个结果后面附加一个以 64 位二进制表示的填充前信息长度 经过这 两步的处理, 现在的信息字节长度 =N* =(N+1)*512, 即长度恰好是 512 的整 数倍 这样做的原因是为了满足后面处理中对信息长度的要求 MD5 中有 4 个 32 位被称作链接变量 (Chaining Variable) 的整数参数, 它们分别为 : A=0x , B=0x89abcdef, C=0xfedcba98, D=0x 当设置好这四个链接变量后, 就开始进入算法的四轮循环运算 循环的次数是信息 中 512 位信息分组的数目 将上面 4 个链接变量复制到另外 4 个变量中 :A 到 a,b 到 b,c 到 c,d 到 d 主循环有四轮 (MD4 只有三轮 ), 每轮循环都很相似 第一轮进行 16 次操作 每次 操作对 a b c 和 d 中的三个做一次非线性函数运算, 然后将所得结果依次加上第四个 变量 文本的一个子分组和一个常数 再将所得结果向右循环移动一个不定的数, 并加 上 a b c 或 d 中之一 最后用该结果取代 a b c 或 d 中之一 以下是每次操作中用到的四个非线性函数 ( 每轮一个 ) F(X,Y,Z) =(X&Y) ((~X)&Z) G(X,Y,Z) =(X&Z) (Y&(~Z)) H(X,Y,Z) =X^Y^Z I(X,Y,Z)=Y^(X (~Z)) (& 是与, 是或,~ 是非,^ 是异或 ) 这四个函数的说明 : 如果 X Y 和 Z 的对应位是独立和均匀的, 那么结果的每一位 也应是独立和均匀的 F 是一个逐位运算的函数 即, 如果 X, 那么 Y, 否则 Z 函数 H 是逐位奇偶操作符 假设 Mj 表示消息的第 j 个子分组 ( 从 0 到 15), FF(a,b,c,d,Mj,s,ti) 表示 a=b+((a+(f(b,c,d)+mj+ti),gg(a,b,c,d,mj,s,ti) 表示 a=b+((a+(g(b,c,d)+mj+ti), HH(a,b,c,d,Mj,s,ti) 表示 25

31 a=b+((a+(h(b,c,d)+mj+ti),ii(a,b,c,d,mj,s,ti) 表示 a=b+((a+(i(b,c,d)+mj+ti) 这四轮 (64 步 ) 是 : 第一轮 FF(a,b,c,d,M0,7,0xd76aa478) FF(d,a,b,c,M1,12,0xe8c7b756) FF(c,d,a,b,M2,17,0x242070db) FF(b,c,d,a,M3,22,0xc1bdceee) FF(a,b,c,d,M4,7,0xf57c0faf) FF(d,a,b,c,M5,12,0x4787c62a) FF(c,d,a,b,M6,17,0xa ) FF(b,c,d,a,M7,22,0xfd469501) FF(a,b,c,d,M8,7,0x698098d8) FF(d,a,b,c,M9,12,0x8b44f7af) FF(c,d,a,b,M10,17,0xffff5bb1) FF(b,c,d,a,M11,22,0x895cd7be) FF(a,b,c,d,M12,7,0x6b901122) FF(d,a,b,c,M13,12,0xfd987193) FF(c,d,a,b,M14,17,0xa679438e) FF(b,c,d,a,M15,22,0x49b40821) 第二轮 GG(a,b,c,d,M1,5,0xf61e2562) GG(d,a,b,c,M6,9,0xc040b340) GG(c,d,a,b,M11,14,0x265e5a51) GG(b,c,d,a,M0,20,0xe9b6c7aa) GG(a,b,c,d,M5,5,0xd62f105d) GG(d,a,b,c,M10,9,0x ) GG(c,d,a,b,M15,14,0xd8a1e681) GG(b,c,d,a,M4,20,0xe7d3fbc8) GG(a,b,c,d,M9,5,0x21e1cde6) GG(d,a,b,c,M14,9,0xc33707d6) GG(c,d,a,b,M3,14,0xf4d50d87) GG(b,c,d,a,M8,20,0x455a14ed) GG(a,b,c,d,M13,5,0xa9e3e905) GG(d,a,b,c,M2,9,0xfcefa3f8) GG(c,d,a,b,M7,14,0x676f02d9) GG(b,c,d,a,M12,20,0x8d2a4c8a) 26 1

32 第 1 章 全面剖析网络爬虫 第三轮 HH(a,b,c,d,M5,4,0xfffa3942) HH(d,a,b,c,M8,11,0x8771f681) HH(c,d,a,b,M11,16,0x6d9d6122) HH(b,c,d,a,M14,23,0xfde5380c) HH(a,b,c,d,M1,4,0xa4beea44) HH(d,a,b,c,M4,11,0x4bdecfa9) HH(c,d,a,b,M7,16,0xf6bb4b60) HH(b,c,d,a,M10,23,0xbebfbc70) HH(a,b,c,d,M13,4,0x289b7ec6) HH(d,a,b,c,M0,11,0xeaa127fa) HH(c,d,a,b,M3,16,0xd4ef3085) HH(b,c,d,a,M6,23,0x04881d05) HH(a,b,c,d,M9,4,0xd9d4d039) HH(d,a,b,c,M12,11,0xe6db99e5) HH(c,d,a,b,M15,16,0x1fa27cf8) HH(b,c,d,a,M2,23,0xc4ac5665) 第四轮 II(a,b,c,d,M0,6,0xf ) II(d,a,b,c,M7,10,0x432aff97) II(c,d,a,b,M14,15,0xab9423a7) II(b,c,d,a,M5,21,0xfc93a039) II(a,b,c,d,M12,6,0x655b59c3) II(d,a,b,c,M3,10,0x8f0ccc92) II(c,d,a,b,M10,15,0xffeff47d) II(b,c,d,a,M1,21,0x85845dd1) II(a,b,c,d,M8,6,0x6fa87e4f) II(d,a,b,c,M15,10,0xfe2ce6e0) II(c,d,a,b,M6,15,0xa ) II(b,c,d,a,M13,21,0x4e0811a1) II(a,b,c,d,M4,6,0xf7537e82) II(d,a,b,c,M11,10,0xbd3af235) II(c,d,a,b,M2,15,0x2ad7d2bb) II(b,c,d,a,M9,21,0xeb86d391) 常数 ti 可以如下选择 : 在第 i 步中,ti 是 *abs(sin(i)) 的整数部分,i 的单位是弧度 ( 等于 2 的 32 次方 ) 所有这些都完成之后, 将 A B C D 分别加上 a b c d 然后用下一分组数据 继续运行算法, 最后的输出是 A B C 和 D 的级联 27

33 在 Java 中,java.security.MessageDigest 中已经定义了 MD5 的计算, 只需要简单地调 用即可得到 MD5 的 128 位整数 然后将此 128 位 (16 个字节 ) 转换成十六进制表示即可 下 面是一段 Java 实现 MD5 压缩的代码 MD5 压缩算法代码 : /* 传入参数 : 一个字节数组 * 传出参数 : 字节数组的 MD5 结果字符串 */ public class MD5 public static String getmd5(byte[] source) String s = null; char hexdigits[] = '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'; // 用来将字节转换成十六进制表示的字符 try java.security.messagedigest md = java.security.messagedigest.getinstance( "MD5" ); md.update( source ); byte tmp[] = md.digest(); // MD5 的计算结果是一个 128 位的长整数, // 用字节表示就是 16 个字节 char str[] = new char[16 * 2]; // 每个字节用十六进制表示的话, 使用两个字符, // 所以表示成十六进制需要 32 个字符 int k = 0; // 表示转换结果中对应的字符位置 for (int i = 0; i < 16; i++) // 从第一个字节开始, 将 MD5 的每一个字节 // 转换成十六进制字符 byte byte0 = tmp[i]; // 取第 i 个字节 str[k++] = hexdigits[byte0 >>> 4 & 0xf]; // 取字节中高 4 位的数字转换, // >>> 为逻辑右移, 将符号位一起右移 str[k++] = hexdigits[byte0 & 0xf]; s = new String(str); catch( Exception e ) e.printstacktrace(); return s; // 取字节中低 4 位的数字转换 // 将换后的结果转换为字符串 Hash 存储的 Value 值通常会对 URL 和相关的信息进行封装, 封装成为一个对象进行存储 综合前面分析的信息, 选择一个可以进行线程安全 使用 Hash 存储, 并且能够应对海量数据的内存数据库是存储 URL 最合适的数据结构 因此, 由 Oracle 公司开发的内存数据库产品 Berkeley DB 就进入了我们的视线 下一节, 将集中精力介绍 Berkeley DB 以及它的 28 1

34 第 1 章 全面剖析网络爬虫 用法 29

35 1.3.2 使用 Berkeley DB 构建爬虫队列 Berkeley DB 是一个嵌入式数据库, 它适合于管理海量的 简单的数据 例如,Google 用 Berkeley DB HA (High Availability) 来管理他们的账户信息 Motorola 在它的无线产品中 用 Berkeley DB 跟踪移动单元 HP Microsoft Sun Microsystems 等也都是它的大客户 Berkeley DB 不能完全取代关系型数据库, 但在某些方面, 它却令关系数据库望尘莫及 关键字 / 数据 (key/value) 是 Berkeley DB 用来进行数据库管理的基础 每个 key/value 对 构成一条记录 而整个数据库实际上就是由许多这样的结构单元所构成的 通过这种方式, 开发人员在使用 Berkeley DB 提供的 API 访问数据库时, 只需提供关键字就能够访问到相应 的数据 当然也可以提供 Key 和部分 Data 来查询符合条件的相近数据 Berkeley DB 底层实现采用 B 树, 可以看成能够存储大量数据的 HashMap Berkeley DB 简称 BDB, 官方网址是 : Berkeley DB 的 C++ 版本首先出现, 然后在此基础上又实现了 Java 本地版本 Berkeley DB 是通过环境对 象 EnvironmentConfig 来对数据库进行管理的, 每个 EnvironmentConfig 对象可以管理多个 数据库 新建一个 EnvironmentConfig 的代码如下 : EnvironmentConfig envconfig = new EnvironmentConfig(); envconfig.settransactional(false); envconfig.setallowcreate(true); exampleenv = new Environment(envDir, envconfig); 其中,envDir 是用户指定的一个目录 只要是由同一个 EnvironmentConfig 指定的数据 库的数据文件和日志文件, 都会放在这个目录下 EnvironmentConfig 也是一种资源, 当使 用完毕后, 需要关闭 exampleenv.sync(); exampleenv.close(); exampleenv = null; 创建好环境之后, 就可以用它创建数据库了 用 Berkeley DB 创建数据库时, 需要指 定数据库的属性, 就好比在 Oracle 中创建数据库时要指定 java_pool buffer_size 等属性一 样 Berkeley DB 使用 DatabaseConfig 来管理一个具体的 DataBase: String databasename= "ToDoTaskList.db"; DatabaseConfig dbconfig = new DatabaseConfig(); dbconfig.setallowcreate(true); dbconfig.settransactional(false); // 打开用来存储类信息的数据库 // 用来存储类信息的数据库不要求能够存储重复的关键字 dbconfig.setsortedduplicates(false); Database myclassdb = exampleenv.opendatabase(null, "classdb", dbconfig); // 初始化用来存储序列化对象的 catalog 类 catalog = new StoredClassCatalog(myClassDb); TupleBinding keybinding = 30 1

36 第 1 章 全面剖析网络爬虫 TupleBinding.getPrimitiveBinding(String.class); // 把 value 作为对象的序列化方式存储 SerialBinding valuebinding = new SerialBinding(catalog, NewsSource.class); store = exampleenv.opendatabase(null, databasename, dbconfig); 当数据库建立起来之后, 就要确定往数据库里面存储的数据类型 ( 也就是确定 key 和 value 的值 ) Berkeley DB 数据类型是使用 EntryBinding 对象来确定的 EntryBinding keybinding =new SerialBinding(javaCatalog,String.class); 其中,SerialBinding 表示这个对象能够序列化到磁盘上, 因此, 构造函数的第二个参数 一定要是实现了序列化接口的对象 最后, 我们来创建一个以 Berkeley DB 为底层数据结构的 Map: // 创建数据存储的映射视图 this.map = new StoredSortedMap(store, keybinding, valuebinding, true); 使用 Berkeley DB 构建爬虫队列示例 上一节我们讲述了 Berkeley DB 的基础知识, 这一节要讲述如何使用 Berkeley DB 来构 建一个完整的爬虫队列 首先,Berkeley DB 存储是一个 key/value 的结构, 并且 key 和 value 对象都要实现 Java 序列化接口 因此, 我们先来构建 value 对象, 即一个封装了很多重要属性的 URL 类 Berkeley DB 中存储的 Value 类 : public class CrawlUrl implements Serializable private static final long serialversionuid = L; public CrawlUrl() private String oriurl;// 原始 URL 的值, 主机部分是域名 private String url; // URL 的值, 主机部分是 IP, 为了防止重复主机的出现 private int urlno; // URL NUM private int statuscode; // 获取 URL 返回的结果码 private int hitnum; // 此 URL 被其他文章引用的次数 private String charset; // 此 URL 对应文章的汉字编码 private String abstracttext; // 文章摘要 private String author; // 作者 private int weight; // 文章的权重 ( 包含导向词的信息 ) private String description; // 文章的描述 private int filesize; // 文章大小 private Timestamp lastupdatetime; // 最后修改时间 private Date timetolive; // 过期时间 private String title; // 文章名称 31

37 private String type; // 文章类型 private String[] urlrefrences; // 引用的链接 private int layer; // 爬取的层次, 从种子开始, 依次为第 0 层, 第 1 层... public int getlayer() return layer; public void setlayer(int layer) this.layer = layer; public String geturl() return url; public void seturl(string url) this.url = url; public int geturlno() return urlno; public void seturlno(int urlno) this.urlno = urlno; public int getstatuscode() return statuscode; public void setstatuscode(int statuscode) this.statuscode = statuscode; public int gethitnum() return hitnum; public void sethitnum(int hitnum) this.hitnum = hitnum; public String getcharset() return charset; 32 1

38 第 1 章 全面剖析网络爬虫 public void setcharset(string charset) this.charset = charset; public String getabstracttext() return abstracttext; public void setabstracttext(string abstracttext) this.abstracttext = abstracttext; public String getauthor() return author; public void setauthor(string author) this.author = author; public int getweight() return weight; public void setweight(int weight) this.weight = weight; public String getdescription() return description; public void setdescription(string description) this.description = description; public int getfilesize() return filesize; public void setfilesize(int filesize) this.filesize = filesize; public Timestamp getlastupdatetime() return lastupdatetime; 33

39 public void setlastupdatetime(timestamp lastupdatetime) this.lastupdatetime = lastupdatetime; public Date gettimetolive() return timetolive; public void settimetolive(date timetolive) this.timetolive = timetolive; public String gettitle() return title; public void settitle(string title) this.title = title; public String gettype() return type; public void settype(string type) this.type = type; public String[] geturlrefrences() return urlrefrences; public void seturlrefrences(string[] urlrefrences) this.urlrefrences = urlrefrences; public final String getoriurl() return oriurl; public void setoriurl(string oriurl) this.oriurl = oriurl; 写一个 TODO 表的接口 : 34 1

40 第 1 章 全面剖析网络爬虫 public interface Frontier public CrawlUrl getnext()throws Exception; public boolean puturl(crawlurl url) throws Exception; //public boolean visited(crawlurl url); 使用一个抽象类来封装对 Berkeley DB 的操作 : public abstract class AbstractFrontier private Environment env; private static final String CLASS_CATALOG = "java_class_catalog"; protected StoredClassCatalog javacatalog; protected Database catalogdatabase; protected Database database; public AbstractFrontier(String homedirectory) throws DatabaseException, FileNotFoundException // 打开 env System.out.println("Opening environment in: " + homedirectory); EnvironmentConfig envconfig = new EnvironmentConfig(); envconfig.settransactional(true); envconfig.setallowcreate(true); env = new Environment(new File(homeDirectory), envconfig); // 设置 DatabaseConfig DatabaseConfig dbconfig = new DatabaseConfig(); dbconfig.settransactional(true); dbconfig.setallowcreate(true); // 打开 catalogdatabase = env.opendatabase(null, CLASS_CATALOG, dbconfig); javacatalog = new StoredClassCatalog(catalogdatabase); // 设置 DatabaseConfig DatabaseConfig dbconfig0 = new DatabaseConfig(); dbconfig0.settransactional(true); dbconfig0.setallowcreate(true); // 打开 database = env.opendatabase(null, "URL", dbconfig); // 关闭数据库, 关闭环境 public void close() throws DatabaseException database.close(); javacatalog.close(); env.close(); //put 方法 protected abstract void put(object key,object value); //get 方法 protected abstract Object get(object key); //delete 方法 35

41 protected abstract Object delete(object key); 实现真正的 TODO 表 : public class BDBFrontier extends AbstractFrontier implements Frontier private StoredMap pendingurisdb = null; // 使用默认的路径和缓存大小构造函数 public BDBFrontier(String homedirectory) throws DatabaseException, FileNotFoundException super(homedirectory); EntryBinding keybinding =new SerialBinding (javacatalog,string.class); EntryBinding valuebinding =new SerialBinding(javaCatalog, CrawlUrl.class); pendingurisdb = new StoredMap(database,keyBinding, valuebinding, true); // 获得下一条记录 public CrawlUrl getnext() throws Exception CrawlUrl result = null; if(!pendingurisdb.isempty()) Set entrys = pendingurisdb.entryset(); System.out.println(entrys); Entry<String,CrawlUrl> entry=(entry<string,crawlurl>)pendingurisdb.entryset().iterator().next(); result = entry.getvalue(); delete(entry.getkey()); return result; // 存入 URL public boolean puturl(crawlurl url) put(url.getoriurl(),url); return true; // 存入数据库的方法 protected void put(object key,object value) pendingurisdb.put(key, value); // 取出 protected Object get(object key) return pendingurisdb.get(key); // 删除 protected Object delete(object key) 36 1

42 第 1 章 全面剖析网络爬虫 return pendingurisdb.remove(key); // 根据 URL 计算键值, 可以使用各种压缩算法, 包括 MD5 等压缩算法 private String caculateurl(string url) return url; // 测试函数 public static void main(string[] strs) try BDBFrontier bbdbfrontier = new BDBFrontier("c:\\bdb"); CrawlUrl url = new CrawlUrl(); url.setoriurl(" bbdbfrontier.puturl(url); System.out.println(((CrawlUrl)bBDBFrontier.getNext()).getOriUrl()); bbdbfrontier.close(); catch (Exception e) e.printstacktrace(); finally 以上就是一个使用 Berkeley DB 来实现 TODO 表的完整示例 使用布隆过滤器构建 Visited 表上一节内容介绍了如何实现 TODO 表, 这一节来探讨如何在一个企业级的搜索引擎中实现 Visited 表 在企业级搜索引擎中, 常用一个称为布隆过滤器 (Bloom Filter) 的算法来实现对已经抓取过的 URL 的过滤 首先来介绍什么叫布隆过滤器 (Bloom Filter) 算法 在日常生活中, 包括在设计计算机软件时, 经常要判断一个元素是否在一个集合中 比如在字处理软件中, 需要检查一个英语单词是否拼写正确 ( 也就是要判断它是否在已知的字典中 ); 在 FBI 中, 一个嫌疑人的名字是否已经在嫌疑名单上 ; 在网络爬虫里, 一个网址是否被访问过等 最直接的方法就是将集合中全部的元素存在计算机中, 遇到一个新元素时, 将它和集合中的元素直接比较即可 一般来讲, 计算机中的集合是用哈希表 (Hash Table) 来存储的 它的好处是快速而准确, 缺点是费存储空间 当集合比较小时, 这个问题不显著, 但是当集合巨大时, 哈希表存储效率低的问题就显现出来了 比如说, 一个像 Yahoo Hotmail 和 Gmail 那样的公众电子邮件 ( ) 提供商, 总是需要过滤来自发送垃圾邮件的人 (spamer) 的垃圾邮件 一个办法就是记录下那些发垃圾邮件的 地址 由于那些发送 37

43 者不停地在注册新的地址, 全世界少说也有几十亿个发垃圾邮件的地址, 将它们都存起来则需要大量的网络服务器 如果用哈希表, 每存储一亿个 地址, 就需要 1.6GB 的内存 ( 用哈希表实现的具体办法是将每一个 地址对应成一个八字节的信息指纹, 然后将这个信息指纹存入哈希表, 由于哈希表的存储效率一般只有 50%, 因此一个 地址需要占用十六个字节 一亿个地址大约要 1.6GB, 即十六亿字节的内存 ) 因此存储几十亿个邮件地址可能需要上百 GB 的内存 除非是超级计算机, 一般服务器是无法存储的 一种称作布隆过滤器的数学工具, 它只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题 布隆过滤器是由巴顿 布隆于 1970 年提出的 它实际上是一个很长的二进制向量和一系列随机映射函数 我们通过上面的例子来说明其工作原理 假定存储一亿个电子邮件地址, 先建立一个 16 亿二进制常量, 即两亿字节的向量, 然后将这 16 亿个二进制位全部设置为零 对于每一个电子邮件地址 X, 用 8 个不同的随机数产生器 (F1,F2,, F8) 产生 8 个信息指纹 (f1, f2,, f8) 再用一个随机数产生器 G 把这 8 个信息指纹映射到 1 到 16 亿中的 8 个自然数 g1, g2,, g8 现在我们把这 8 个位置的二进制位全部设置为 1 当我们对这 1 亿个 地址都进行这样的处理后 一个针对这些 地址的布隆过滤器就建成了, 如图 1.7 所示 图 1.7 布隆过滤器 (Bloom Filter) 现在, 来看看布隆过滤器是如何检测一个可疑的电子邮件地址 Y 是否在黑名单中的 我们用 8 个随机数产生器 (F1, F2,, F8) 对这个地址产生 8 个信息指纹 S1, S2,, S8 然后 将这 8 个指纹对应到布隆过滤器的 8 个二进制位, 分别是 T1, T2,, T8 如果 Y 在黑名单 中, 显然,T1, T2,, T8 对应的 8 个二进制位一定是 1 这样在遇到任何黑名单中的电子 邮件地址时, 我们都能够准确地发现 布隆过滤器绝对不会漏掉任何一个在黑名单中的可疑地址 但是, 它有一条不足之处 也就是它有极小的可能将一个不在黑名单中的电子邮件地址判定为在黑名单中, 因为有可 能某个好的邮件地址正巧对应 8 个都被设置成 1 的二进制位 好在这种可能性很小 我们 把它称为误识概率 在上面的例子中, 误识概率大概在万分之一以下 常见的补救办法是 建立一个小的白名单, 存储那些可能误判的邮件地址 下面是一个布隆过滤器 (Bloom Filter) 的实现 : public class SimpleBloomFilter implements VisitedFrontier private static final int DEFAULT_SIZE = 2 << 24; private static final int[] seeds = new int[] 7, 11, 13, 31, 37, 61, ; 38 1

44 第 1 章 全面剖析网络爬虫 private BitSet bits = new BitSet(DEFAULT_SIZE); private SimpleHash[] func = new SimpleHash[seeds.length]; public static void main(string[] args) String value = "stone2083@yahoo.cn"; SimpleBloomFilter filter = new SimpleBloomFilter(); System.out.println(filter.contains(value)); filter.add(value); System.out.println(filter.contains(value)); public SimpleBloomFilter() for (int i = 0; i < seeds.length; i++) func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]); // 覆盖方法, 把 URL 添加进来 public void add(crawlurl value) if (value!= null) add(value.getoriurl()); // 覆盖方法, 把 URL 添加进来 public void add(string value) for (SimpleHash f : func) bits.set(f.hash(value), true); // 覆盖方法, 是否包含 URL public boolean contains(crawlurl value) return contains(value.getoriurl()); // 覆盖方法, 是否包含 URL public boolean contains(string value) if (value == null) return false; boolean ret = true; for (SimpleHash f : func) ret = ret && bits.get(f.hash(value)); return ret; public static class SimpleHash private int cap; private int seed; public SimpleHash(int cap, int seed) this.cap = cap; this.seed = seed; 39

45 public int hash(string value) int result = 0; int len = value.length(); for (int i = 0; i < len; i++) result = seed * result + value.charat(i); return (cap - 1) & result; 如果想知道需要使用多少位才能降低错误概率, 可以从表 1.5 所示的存储项目和位数比 率估计布隆过滤器的误判率 表 1.5 布隆过滤器误判率表 比率 (items:bits) 误判率 (False-positive) 为每个 URL 分配两个字节就可以达到千分之几的冲突 比较保守的实现是, 为每个 URL 分配 4 个字节, 项目和位数比是 1 32, 误判率是 对于 5000 万数 量级的 URL, 布隆过滤器只占用 200MB 的空间, 并且排重速度超快, 一遍下来不到两分钟 详解 Heritrix 爬虫队列上一节介绍了 Berkeley DB 构建爬虫队列的基础和过程, 在许多开源爬虫软件中, 都是用 Berkeley DB 来实现爬虫队列 本节, 将分析一个常用的开源爬虫软件, 以增加读者对爬虫队列的理解 Heritrix 是一个开源的 可扩展的爬虫项目 它始于 2003 年, 最初的目的是开发一个特殊的爬虫, 对网上的资源进行归档 在 Heritrix 以及很多开源爬虫软件中, 爬虫队列有一个非常好听的名字 Frontier 在 Heritrix 中,Frontier 底层的数据结构也是使用了 Berkeley DB Heritrix 中的 Frontier 内部处理机制如图 1.8 所示 40 1

46 第 1 章 全面剖析网络爬虫 图 1.8 Heritrix 中的 Frontier 架构 1. BdbMultipleWorkQueues 它是对 Berkeley DB 的简单封装 在内部有一个 Berkeley Database, 存放所有待处理的 链接 代码如下 : public class BdbMultipleWorkQueues // 存放所有待处理的 URL 的数据库 private Database pendingurisdb = null; // 由 key 获取一个链接 public CrawlURI get(databaseentry headkey) throws DatabaseException DatabaseEntry result = new DatabaseEntry(); // 由 key 获取相应的链接 OperationStatus status = getnextnearestitem(headkey, result); CrawlURI retval = null; if (status!= OperationStatus.SUCCESS) LOGGER.severe("See ' NPE je-2.0 " + "entrytoobject '. OperationStatus " + " was not SUCCESS: " + status + ", headkey " + BdbWorkQueue.getPrefixClassKey(headKey.getData())); return null; try retval = (CrawlURI) crawluribinding.entrytoobject(result); catch (RuntimeExceptionWrapper rw) LOGGER.log(Level.SEVERE, "expected object missing in queue " 41

47 + BdbWorkQueue.getPrefixClassKey(headKey.getData()), rw); return null; retval.setholderkey(headkey); return retval;// 返回链接 // 从等待处理列表中获取一个链接 protected OperationStatus getnextnearestitem(databaseentry headkey, DatabaseEntry result) throws DatabaseException Cursor cursor = null; OperationStatus status; try // 打开游标 cursor = this.pendingurisdb.opencursor(null, null); status = cursor.getsearchkey(headkey, result, null); if (status!= OperationStatus.SUCCESS result.getdata().length > 0) throw new DatabaseException("bdb queue cap missing"); status = cursor.getnext(headkey, result, null); finally if (cursor!= null) cursor.close(); return status; /** * 添加 URL 到数据库 */ public void put(crawluri curi, boolean overwriteifpresent) throws DatabaseException DatabaseEntry insertkey = (DatabaseEntry)curi.getHolderKey(); if (insertkey == null) insertkey = calculateinsertkey(curi); curi.setholderkey(insertkey); DatabaseEntry value = new DatabaseEntry(); crawluribinding.objecttoentry(curi, value); if (LOGGER.isLoggable(Level.FINE)) tallyaverageentrysize(curi, value); OperationStatus status; if (overwriteifpresent) // 添加 status = pendingurisdb.put(null, insertkey, value); else 42 1

48 第 1 章 全面剖析网络爬虫 status = pendingurisdb.putnooverwrite(null, insertkey, value); if (status!= OperationStatus.SUCCESS) LOGGER.severe("failed; " + status + " " + curi); 2. BdbWorkQueue 代表一个链接队列, 该队列中所有的链接都具有相同的键值 它实际上是通过调用 BdbMultipleWorkQueues 的 get 方法从等待处理的链接数据库中取得链接的 代码如下 : public class BdbWorkQueue extends WorkQueue implements Comparable, Serializabl // 获取一个 URL protected CrawlURI peekitem(final WorkQueueFrontier frontier) throws IOException // 关键 : 从 BdbFrontier 中返回 pendinguris final BdbMultipleWorkQueues queues = ((BdbFrontier) frontier).getworkqueues(); DatabaseEntry key = new DatabaseEntry(origin); CrawlURI curi = null; int tries = 1; while(true) try // 获取链接 curi = queues.get(key); catch (DatabaseException e) LOGGER.log(Level.SEVERE,"peekItem failure; retrying",e); return curi; 3. WorkQueueFrontier 实现了最核心的方法 : public CrawlURI next() throws InterruptedException, EndedException while (true) long now = System.currentTimeMillis(); prenext(now); synchronized(readyclassqueues) int activationsneeded = targetsizeforreadyqueues() rea 43

49 dyclassqueues.size(); while(activationsneeded > 0 &&!inactivequeues.isempty()) activateinactivequeue(); activationsneeded--; WorkQueue readyq = null; Object key = readyclassqueues.poll(default_wait,timeunit.mil LISECONDS); if (key!= null) readyq = (WorkQueue)this.allQueues.get(key); if (readyq!= null) while(true) CrawlURI curi = null; synchronized(readyq) /** * 取出一个 URL, 最终从子类 BdbFrontier 的 *pendinguris 中取出一个链接 */ curi = readyq.peek(this); if (curi!= null) String currentqueuekey = getclasskey(curi); if (currentqueuekey.equals(curi.getclasskey())) noteabouttoemit(curi, readyq); // 加入正在处理队列中 inprocessqueues.add(readyq); return curi; // 返回 curi.setclasskey(currentqueuekey); readyq.dequeue(this);// 出队列 decrementqueuedcount(1); curi.setholderkey(null); else readyq.clearheld(); break; if(curi!=null) sendtoqueue(curi); else if (key!= null) logger.severe("key "+ key + " in readyclassqueues but not allqueues"); 44 1

50 第 1 章 全面剖析网络爬虫 if(shouldterminate) throw new EndedException("shouldTerminate is true"); if(inprocessqueues.size()==0) this.alreadyincluded.requestflush(); // 将 URL 加入待处理队列 public void schedule(candidateuri cauri) String canon = canonicalize(cauri); if (cauri.forcefetch()) alreadyincluded.addforce(canon, cauri); else alreadyincluded.add(canon, cauri); 4. BdbFrontier 继承了 WorkQueueFrontier, 是 Heritrix 唯一具有实际意义的链接工厂 代码如下 : public class BdbFrontier extends WorkQueueFrontier implements Serializable /** 所有待抓取的链接 */ protected transient BdbMultipleWorkQueues pendinguris; // 初始化 pendinguris, 父类为抽象方法 protected void initqueue() throws IOException try this.pendinguris = createmultipleworkqueues(); catch(databaseexception e) throw (IOException)new IOException(e.getMessage()).initCause(e); private BdbMultipleWorkQueues createmultipleworkqueues() throws DatabaseException return new BdbMultipleWorkQueues(this.controller.getBdbEnvironment(), this.controller.getbdbenvironment().getclasscatalog(), this.controller.ischeckpointrecover()); 45

51 protected BdbMultipleWorkQueues getworkqueues() return pendinguris; 5. BdbUriUniqFilter 它实际上是一个过滤器, 用来检查一个要进入等待队列的链接是否已经被抓取过 方法代码如下 : // 添加 URL protected boolean setadd(charsequence uri) DatabaseEntry key = new DatabaseEntry(); LongBinding.longToEntry(createKey(uri), key); long started = 0; OperationStatus status = null; try if (logger.isloggable(level.info)) started = System.currentTimeMillis(); // 添加到数据库 status = alreadyseen.putnooverwrite(null, key, ZERO_LENGTH_ENTRY); if (logger.isloggable(level.info)) aggregatedlookuptime += (System.currentTimeMillis() - started); catch (DatabaseException e) logger.severe(e.getmessage()); if (status == OperationStatus.SUCCESS) count++; if (logger.isloggable(level.info)) final int logat = 10000; if (count > 0 && ((count % logat) == 0)) logger.info("average lookup " + (aggregatedlookuptime / logat) + "ms."); aggregatedlookuptime = 0; // 如果存在, 返回 false if(status == OperationStatus.KEYEXIST) return false; else return true; 46 1

52 第 1 章 全面剖析网络爬虫 1.4 设计爬虫架构 上一节讲述了爬虫队列 本节要介绍如何设计爬虫架构 一个设计良好的爬虫架构必须满足如下需求 (1) 分布式 : 爬虫应该能够在多台机器上分布执行 (2) 可伸缩性 : 爬虫结构应该能够通过增加额外的机器和带宽来提高抓取速度 (3) 性能和有效性 : 爬虫系统必须有效地使用各种系统资源, 例如, 处理器 存储空间和网络带宽 (4) 质量 : 鉴于互联网的发展速度, 大部分网页都不可能及时出现在用户查询中, 所以爬虫应该首先抓取有用的网页 (5) 新鲜性 : 在许多应用中, 爬虫应该持续运行而不是只遍历一次 (6) 更新 : 因为网页会经常更新, 例如论坛网站会经常有回帖 爬虫应该取得已经获取的页面的新的拷贝 例如一个搜索引擎爬虫要能够保证全文索引中包含每个索引页面的较新的状态 对于搜索引擎爬虫这样连续的抓取, 爬虫访问一个页面的频率应该和这个网页的更新频率一致 (7) 可扩展性 : 为了能够支持新的数据格式和新的抓取协议, 爬虫架构应该设计成模块化的形式 爬虫架构爬虫的简化版本架构如图 1.9 所示 存储库 Web 爬虫 1 爬虫 2 储存体 储存体 用户界面 爬虫 n 储存体 储存体 用户 图 1.9 爬虫物理分布简化架构图 这里最主要的关注对象是爬虫和存储库 其中的爬虫部分阶段性地抓取互联网上的内容 存储库存储爬虫下载下来的网页, 是分布式的和可扩展的存储系统 在往存储库中加载新的内容时仍然可以读取存储库 实际的爬虫逻辑架构如图 1.10 所示 其中 : (1) URL Frontier 包含爬虫当前待抓取的 URL( 对于持续更新抓取的爬虫, 以前已经抓取过的 URL 可能会回到 Frontier 重抓 ) (2) DNS 解析模块根据给定的 URL 决定从哪个 Web 服务器获取网页 47

53 DNS 文档 语义指纹 URL 集 解析 Web 互联网下载 网页 页面 内容是 否重复? URL 过滤 去除 重复 URL URL Frontier 图 1.10 单线程爬虫结构 (3) 获取模块使用 HTTP 协议获取 URL 代表的页面 (4) 解析模块提取文本和网页的链接集合 (5) 重复消除模块决定一个解析出来的链接是否已经在 URL Frontier 或者最近下载过 DNS 解析是网络爬虫的瓶颈 由于域名服务的分布式特点,DNS 可能需要多次请求转发, 并在互联网上往返, 需要几秒有时甚至更长的时间解析出 IP 地址 如果我们的目标是一秒钟抓取数百个文件, 这样就达不到性能要求 一个标准的补救措施是引入缓存 : 最近完成 DNS 查询的网址可能会在 DNS 缓存中找到, 避免了访问互联网上的 DNS 服务器 然而, 由于抓取礼貌的限制, 降低了 DNS 缓存的命中率 用 DNS 解析还有一个难点 : 在标准库中实现的查找是同步的 这意味着一旦一个请求发送到 DNS 服务器上, 在那个节点上的其他爬虫线程也被阻塞直到第一个请求完成 为了避免这种情况发生, 许多爬虫自己来实现 DNS 解析 执行解析代码的线程 i 将发送一个消息到 DNS 服务器, 然后执行一个定时等待, 不超过这个设定的时间段, 这个线程会继续执行 一个单独的 DNS 线程侦听标准的 DNS 端口 (53 端口 ) 从名称服务器传入的响应数据包 一旦接受一个响应, 它就激活对应的爬虫线程 i 并把响应数据包交给 i 如果 i 因为等待超时还没有恢复运行, 则爬虫线程如果尝试 5 次全都失败, 下次发送一个新的消息给 DNS 服务等待的时间会延长一倍 鉴于有的主机名需要长达几十秒的时间来解析, 所以等待 DNS 解析的时间范围可以为 1~90 秒 设计并行爬虫架构整个爬虫系统可以由一台抓取机器或多个爬虫节点组成 多机并行抓取的分布式系统则需要考虑节点之间的通信和调度, 关于分布式爬虫系统将在第 2 章介绍 在一个爬虫节点上实现并行抓取, 可以考虑多线程同步 I/O 或者单线程异步 I/O 多线程爬虫需要考虑线程之间的同步问题 对单线程并行抓取来说, 异步 I/O 是很重要的基本功能 异步 I/O 模型大体上可以分 48 1

54 第 1 章 全面剖析网络爬虫 为两种, 反应式 (Reactive) 模型和前摄式 (Proactive) 模型 传统的 select/epoll/kqueue 模型, 以及 Java NIO 模型, 都是典型的反应式模型, 即应用代码对 I/O 描述符进行注册, 然后 等待 I/O 事件 当某个或某些 I/O 描述符所对应的 I/O 设备上产生 I/O 事件 ( 可读 可写 异常等 ) 时, 系统将发出通知, 于是应用便有机会进行 I/O 操作并避免阻塞 由于在反应式 模型中应用代码需要根据相应的事件类型采取不同的动作, 因此最常见的结构便是嵌套的 if else 或 switch, 并常常需要结合状态机来完成复杂的逻辑 前摄式模型则恰恰 相反 在前摄式模型中, 应用代码主动投递异步操作而不管 I/O 设备当前是否可读或可写 投递的异步 I/O 操作被系统接管, 应用代码也并不阻塞在该操作上, 而是指定一个回调函 数并继续自己的应用逻辑 当该异步操作完成时, 系统将发起通知并调用应用代码指定的 回调函数 在前摄式模型中, 程序逻辑由各个回调函数串联起来 : 异步操作 A 的回调发起 异步操作 B,B 的回调再发起异步操作 C, 以此往复 Java 6 版本开始引入的 NIO 包, 通过 Selectors 类提供了非阻塞式的 I/O Java 7 附带的 NIO.2 文件系统中包含了异步 I/O 支持 也可以使用框架实现异步 I/O, 例如 : Mina( 为开发高性能和高可用性的网络应用程序提供了 非常便利的框架 当前发行的 MINA 版本支持基于 Java 的 NIO 技术的 TCP/UDP 应用程序开发 MINA 是借由 Java 的 NIO 的反应式实现的模拟前摄式模型 Grizzly 是 Web 服务器 GlassFish 的 I/O 核心 Grizzly 通过队列模型提供 异步读 / 写 Netty 是一个 NIO 客户端服务器框架 Naga ( 是一个很小的库, 提供了一些 Java 类, 把普 通的 Socket 和 ServerSocket 封装成支持 NIO 的形式 从性能测试上比较,Netty 和 Grizzly 都很快, 而 Mina 稍慢一些 JDK 1.6 内部并不使用线程来实现非阻塞式 I/O 在 Windows 平台下, 使用 select(); 在 新的 Linux 核下, 使用 epoll 工具 Niocchi( 是 Java 实现的开源异步 I/O 爬虫 在爬虫中使用 NIO 的时候, 主要用到的就是下面两个类 java.nio.channels.selector:selector 类通过调用 select 方法, 将注册的 channel 中有事件发生的 SelectionKey 取出来进行处理 如果想要把管理权交到 Selector 类手中, 首先就要在 Selector 对象中注册相应的 Channel java.nio.channels.socketchannel:socketchannel 用于和 Web 服务器建立连接 下面是 Niocchi 中使用 NIO 下载网页的例子, 首先发送请求 : SocketChannel sc = SocketChannel.open(); sc.configureblocking( false ); sc.socket().settcpnodelay(true); boolean connected = false; try connected = sc.connect( query_.getinetsocketaddress() ); 49

55 catch( IOException e ) _logger.warn( "IOException " + query_.geturl(), e ); query_.setstatus(internal_error); processcrawledquery( query_ ); return; if( connected ) // 检查连接是否建立 if(! sendquery( query_, sc ) ) query_.setstatus(unreachable); processcrawledquery( query_ ); return ; sc.register( _selector, SelectionKey.OP_READ, query_ ); else sc.register( _selector, SelectionKey.OP_CONNECT, query_ ); 然后接收数据 : int i = _selector.select( _selecttimeout ); select_total_time += new Date().getTime() - t; if( i == 0 ) _logger.debug( "Select timeout" ); // 取消连接 Set keys = _selector.keys(); Iterator it = keys.iterator(); while( it.hasnext() ) SelectionKey key = (SelectionKey)it.next(); Query query = (Query) key.attachment(); if (_logger.isdebugenabled()) _logger.debug( "Select timeout for '" + query.geturl() + "'" ); query.setstatus(timeout); processkey( key ); 50 1

56 第 1 章 全面剖析网络爬虫 if(! _resolver_queue.hasnextquery() && (_reg_count == 0) && (!_redirection_resolver_queue.hasmore()) ) break; // 没有更多 URL continue; // 检查是否超时 long time = System.currentTimeMillis(); Set keys = _selector.keys(); Iterator it = keys.iterator(); boolean some_timeout = false; while( it.hasnext() ) SelectionKey key = (SelectionKey)it.next(); Query query = (Query) key.attachment(); if( time - query.getregistertime() > _timeout ) if (_logger.isdebugenabled()) _logger.debug( "Timeout for '" + query.geturl() + "'"); some_timeout = true; query.setstatus(timeout); processkey( key ); if( some_timeout ) i = _selector.selectnow(); if( i == -1 ) continue; Set skeys = _selector.selectedkeys(); it = skeys.iterator(); while( it.hasnext() ) SelectionKey key = (SelectionKey)it.next(); it.remove(); if( (key.readyops() & SelectionKey.OP_READ) == SelectionKey.OP_READ ) SocketChannel sc = (SocketChannel)key.channel(); Exception e = null; t = new Date().getTime(); 51

57 Query query = (Query) key.attachment(); Resource resource = query.getresource(); int r = 0; try r = resource.read( sc ); read_total_time += new Date().getTime() - t; if (_logger.istraceenabled()) _logger.trace( "read " + r + "for URL '" + query.geturl() + "'" ); catch( IOException ee ) _logger.warn( "For URL '" + query.geturl() + "' " + ee.getmessage() ); e = ee; catch( ResourceException ee ) _logger.warn( "For URL '" + query.geturl() + "' " + ee.getmessage() ); e = ee; // 资源完整的被抓取下来 if (e!= null) query.setstatus(incomplete); if ( r < 0 && e == null) if( resource.gethttpstatus() == 200 ) query.setstatus(crawled); else query.setstatus(httperror); // 如果抓取过程出错 if( e!= null r < 0 ) processkey( key ); else if( (key.readyops() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT ) SocketChannel sc = (SocketChannel)key.channel(); Query query = (Query) key.attachment(); try 52 1

58 第 1 章 全面剖析网络爬虫 sc.finishconnect(); catch( IOException e ) _logger.warn( "Connection error to '" + query.geturl() + '\'' ); processkey( key ); continue; if(! sendquery( query, sc ) ) processkey( key ); query.setstatus(unreachable); continue; sc.register( _selector, SelectionKey.OP_READ, query ); 详解 Heritrix 爬虫架构上一节开始了一个开源爬虫软件 Heritrix 现在, 我们来看一下它的架构是如何设计的 Heritrix 采用的是模块化的设计, 各个模块是由一个控制器类 (CrawlController 类 ) 来协调的, 因此控制器是它的核心 CrawlController 类结构如图 1.11 所示 采集 URL 采集范围候选 URL Frontier 处理器链 CrawlContorller 类 采集 URL 并提取链接 Toe 线程 图 1.11 CrawlContorller 类结构 CrawlController 类是整个爬虫的总控制者, 控制整个抓取工作的起点, 决定整个抓取任 务的开始和结束 CrawlController 从 Frontier 获取 URL, 传递给线程池 (ToePool) 中的 ToeThread 处理 53

59 Frontier( 边界控制器 ) 主要确定下一个将被处理的 URL, 负责访问的均衡处理, 避免对某一 Web 服务器造成太大的压力 Frontier 保存着爬虫的状态, 包括已经找到的 URI 正在处理中的 URI 和已经处理过的 URI Heritrix 是按多线程方式抓取的爬虫, 主线程把任务分配给 Teo 线程 ( 处理线程 ), 每个 Teo 线程每次处理一个 URL Teo 线程对每个 URL 执行一遍 URL 处理器链 URL 处理器链包括如下 5 个处理步骤 (1) 预取链 : 主要是做一些准备工作, 例如, 对处理进行延迟和重新处理, 否决随后的操作 (2) 提取链 : 主要是下载网页, 进行 DNS 转换, 填写请求和响应表单 (3) 抽取链 : 当提取完成时, 抽取感兴趣的 HTML 和 JavaScript, 通常那里有新的要抓取的 URL (4) 写链 : 存储抓取结果, 可以在这一步直接做全文索引 Heritrix 提供了用 ARC 格式保存下载结果的 ARCWriterProcessor 实现 (5) 提交链 : 做和此 URL 相关操作的最后处理 检查哪些新提取出的 URL 在抓取范围内, 然后把这些 URL 提交给 Frontier 另外还会更新 DNS 缓存信息 处理 URL 的流程如图 1.12 所示 在实现上, 所有的处理类都是一个名称为 Processor 类的子类 例如,PreconditionEnforcer 类继承了 Processor 预取链 Preselector PreconditionEnforcer 提取链 FetchDNS FetchHTTP 抽取链 ExtractorHTML ExtractorJS 写链 ARCWriterProcessor 提交链 CrawlStateUpdater Postselector 图 1.12 Heritrix 处理链图 Frontier 记录哪些 URI 被预订采集和哪些 URI 已经被采集, 并选择下一个 URI, 剔除已 经处理的 URI 处理器链包含若干处理器获取的 URI, 分析结果, 并将它们传回给 Frontier 54 1

60 第 1 章 全面剖析网络爬虫 服务器缓存 (Server cache) 存放服务器的持久信息, 能够被爬行部件随时查到, 包括被抓 取的 Web 服务器信息, 例如 DNS 查询结果, 也就是 IP 地址 分析完 Controller 之后, 我们就要看看 Heritrix 软件的整体架构, 如图 1.13 所示 Web 可管理控制台 抓取顺序 CrawlController next(crawlurl) Frontier 预取链 URL 工作队列 服务器缓存 提取链 抽取链 多线程 范围 已经包含的 URL 写链 schedule(url) 后处理链 finished(crawlurl) 图 1.13 Heritrix 软件的整体结构 图 1.13 把 Heritrix 分成三部分 : Web 可管理控制台 可以在界面设置运行时使用哪个模块 Heritrix 也是因为有良好的管理界面, 所以得到了广泛的应用 Web 管理界面默认运行在 Heritrix 安装包自带的 Java HTTP 服务器 Jetty 中, 但也可以作为 Web 应用运行在 Tomcat 或 Resin 等 Web 服务器中 操作者可以通过选择 Crawler 命令来操作控制台 抓取顺序配置文件 可以在配置文件 ORDER.XML 中指定抓取的顺序 总控整个爬虫的 CrawlController Heritrix 包含以下关键特性 : 用单个爬虫在多个独立的站点一直不断地抓取 从一个种子 URL 开始爬, 不断抓取页面所指向的 URL 主要是用宽度优先算法进行处理 主要部件都是高效的和可扩展的 可以配置的部分包括 : 可设置输出日志 归档文件和临时文件的位置 可设置下载的最大字节, 最大数量的下载文档和最大的下载时间 可设置工作线程数量 可设置所利用的带宽的上界 可在设置之后一定时间重新选择 包含一些可设置的过滤机制 表达方式 URI 路径深度选择等 尽管 Heritrix 是设计良好的爬虫, 但是它的局限包括 : 55

61 不支持多机分布式抓取 在有限的机器资源的情况下, 却要复杂的操作 只有官方支持, 仅仅在 Linux 上进行了测试 每个爬虫是单独进行工作的, 没有对更新进行修订 在硬件和系统失败时, 恢复能力很差 性能还不够优化 本节, 我们介绍了如何设计一个爬虫架构, 并且详细讲述了一个开源爬虫 Heritrix 的爬虫结构 为读者今后开发自己的爬虫提供了整体上的参考 1.5 使用多线程技术提升爬虫性能 上一节讲述爬虫架构时曾经提到过, 为了提升爬虫性能, 需要采用多线程的爬虫技术 并且开源软件 Heritrix 已经采用了多线程的爬虫技术来提高性能 而且很多大型网站都采用多个服务器镜像的方式提供同样的网页内容 采用多线程并行抓取能同时获取同一个网站的多个服务器中的网页, 这样能极大地减少抓取这类网站的时间 详解 Java 多线程 1. 创建多线程的方法多线程是一种机制, 它允许在程序中并发执行多个指令流, 每个指令流都称为一个线程, 彼此间互相独立 线程又称为轻量级进程, 它和进程一样拥有独立的执行控制, 由操作系统负责调度, 区别在于线程没有独立的存储空间, 而是和所属进程中的其他线程共享存储空间, 这使得线程间的通信较进程简单 多个线程的执行是并发的, 即在逻辑上是 同时 的 如果系统只有一个 CPU, 那么真正的 同时 是不可能的, 但是由于 CPU 切换的速度非常快, 用户感觉不到其中的区别, 因此用户感觉到线程是同时执行的 为了创建一个新的线程, 需要做哪些工作呢? 很显然, 必须指明这个线程所要执行的代码, 在 Java 语言中, 通过 JDK 提供的 java.lang.thread 类或者 java.lang.runable 接口, 能够轻松地添加线程代码 具体如何实现线程执行的代码呢? 先看一看 java.lang.thread 类 java.lang.thread 类最重要的方法是 run(), 它被 java.lang.thread 类的方法 start() 所调用, 提供线程所要执行的代码 也就是说,run() 方法里面的代码就是线程执行时所运行的代码 再来看 java.lang.runable 接口, 这个接口中只有一个 run() 方法, 和 java.lang.thread 类中的 run() 方法类似,java.lang.Runable 接口中的 run() 方法中的代码就是线程执行的代码 因此, 在 Java 语言中, 要创建一个线程, 可以使用以下两种方法 方法一 : 继承 java.lang.thread 类, 覆盖方法 run(), 在创建的 java.lang.thread 类的子类中重写 run() 方法 下面是一个例子 : 56 1

62 第 1 章 全面剖析网络爬虫 public class MyThread extends Thread int count= 1, number; public MyThread(int num) number = num; System.out.println(" 创建线程 " + number); public void run() while(true) System.out.println(" 线程 " + number + ": 计数 " + count); if(++count== 6) return; public static void main(string args[]) for(int i = 0; i<5; i++) new MyThread(i+1).start(); 这种方法简单明了, 但是, 它也有一个很大的缺陷, 如果线程类 MyThread 已经从一个类继承 ( 如小程序必须继承自 Applet 类 ), 而无法再继承 java.lang.thread 类时应该怎么办呢? 这时, 就必须使用下面的方法二来实现线程 方法二 : 实现 java.lang.runnable 接口 java.lang.runnable 接口只有一个 run() 方法, 库创建一个类实现 java.lang.runnable 接口并提供这一方法的实现, 将线程代码写入 run() 方法中, 并且新建一个 java.lang.thread 类, 将实现 java.lang.runnable 的类作为参数传入, 就完成了创建新线程的任务 下面是一个例子 : public class MyThread implements Runnable int count= 1, number; public MyThread(int num) number = num; System.out.println(" 创建线程 " + number); public void run() while(true) System.out.println(" 线程 " + number + ": 计数 " + count); if(++count== 6) return; public static void main(string args[]) 57

63 for(int i = 0; i < 5; i++) new Thread(new MyThread(i+1)).start(); 严格地说, 创建 java.lang.thread 子类的实例也是可行的, 但必须注意的是, 该子类不能覆盖 java.lang.thread 类的 run() 方法, 否则该线程执行的将是子类的 run() 方法, 而不是执行实现 java.lang.runnable 接口的类的 run() 方法, 对此读者不妨试验一下 方法二使得能够在一个类中包容所有的代码, 有利于封装 这种方法的缺点在于, 只能使用一套代码, 若想创建多个线程并使各个线程执行不同的代码, 则必须额外创建类, 如果这样的话, 在大多数情况下也许还不如直接用多个类分别继承 java.lang.thread 来得紧凑 2. Java 语言对线程同步的支持首先讲解线程的状态, 一个线程具有如下四种状态 (1) 新状态 : 线程已被创建但尚未执行 (start() 方法尚未被调用 ) (2) 可执行状态 : 线程可以执行, 但不一定正在执行 CPU 时间随时可能被分配给该线程, 从而使得它执行 (3) 死亡状态 : 正常情况下,run() 方法返回使得线程死亡 调用 java.lang.thread 类中的 stop() 或 destroy() 方法亦有同样效果, 但是不推荐使用这两种方法, 前者会产生异常, 后者是强制终止, 不会释放锁 (4) 阻塞状态 : 线程不会被分配 CPU 时间, 无法执行 编写多线程程序通常会遇到线程的同步问题, 什么是线程同步问题呢? 由于同一进程的多个线程共享存储空间, 在带来方便的同时, 也会带来访问冲突这个严重的问题 Java 语言提供了专门机制以解决这种冲突, 有效地避免了同一个数据对象被多个线程同时访问的问题 Java 语言中解决线程同步问题是依靠 synchronized 关键字来实现的, 它包括两种用法 : synchronized 方法和 synchronized 块 (1) synchronized 方法 通过在方法声明中加入 synchronized 关键字来声明该方法是同步方法, 即多线程执行的时候各个线程之间必须顺序执行, 不能同时访问该方法 如 : public synchronized void accessval(int newval); 在 Java 语言中, 每个对象都拥有一把锁, 当执行这个对象的 synchronized 方法时, 必须获得该对象的锁方能执行, 否则所属线程阻塞 而 synchronized 方法一旦执行, 就独占该锁, 直到从 synchronized 方法返回时才将锁释放, 之后被阻塞的线程方能获得该锁, 重新进入可执行状态 这种对象锁机制确保了同一时刻对于每一个对象, 其所有声明为 synchronized 的方法中至多只有一个处于可执行状态, 从而有效地避免了类成员变量的访问冲突 在 Java 中, 不光是对象, 每一个类也对应一把锁, 因此也可将类的静态成员函数声明为 synchronized, 以控制其对类的静态成员变量的访问 58 1

64 第 1 章 全面剖析网络爬虫 synchronized 方法的缺陷 : 若将一个执行时间较长的方法声明为 synchronized, 将会大 大影响程序运行的效率 因此 Java 为我们提供了更好的解决办法, 那就是 synchronized 块 (2) synchronized 块 通过 synchronized 关键字来声明 synchronized 块 语法如下 : synchronized(syncobject) // 允许访问控制的代码 synchronized 块是这样一种代码块, 块的代码必须获得 syncobject 对象 ( 如前所述, 可 以是类实例或类 ) 的锁才能执行 由于 synchronized 块可以是任意代码块, 且可任意指定上 锁的对象, 因此灵活性较高 3. Java 语言对线程阻塞的支持 讲完了线程的同步机制, 下面介绍 Java 语言对线程阻塞机制的支持 阻塞指的是暂停一个线程的执行以等待某个条件发生 ( 如等待资源就绪 ), 学过操作系统 的读者对它一定非常熟悉了 Java 提供了大量方法来支持阻塞, 下面逐一分析 (1) sleep() 方法 :sleep() 允许指定以毫秒为单位的一段时间作为参数, 它使得线程在指 定的时间内进入阻塞状态, 不能得到 CPU 时间片, 指定的时间一过, 线程重新进入可执行 状态 例如, 当线程等待某个资源就绪时, 测试发现条件不满足后, 让线程 sleep() 一段时 间后重新测试, 直到条件满足为止 (2) suspend() 和 resume() 方法 : 两个方法配套使用,suspend() 使线程进入阻塞状态, 并 且不会自动恢复, 必须对其应用 resume() 方法, 才能使得线程重新进入可执行状态 例如, 当前线程等待另一个线程产生的结果时, 如果发现结果还没有产生, 会调用 suspend() 方法, 另一个线程产生了结果后, 调用 resume() 使其恢复 (3) yield() 方法 :yield() 方法使得线程放弃当前分得的 CPU 时间片, 但不使线程阻塞, 即线程仍处于可执行状态, 随时可能再次分得 CPU 时间 (4) wait() 和 notify() 方法 : 两个方法配套使用,wait() 可以使线程进入阻塞状态, 它有 两种形式, 一种允许指定以毫秒为单位的一段时间作为参数, 另一种没有参数 前者当对 应的 notify() 被调用或者超出指定时间时, 线程重新进入可执行状态 ; 后者则必须在对应的 notify() 被调用时, 线程才重新进入可执行状态 在面向对象编程中, 创建和销毁对象是很费时间的, 因为创建一个对象要获取内存资 源或者其他更多资源 线程对象也不例外 当前, 比较流行的一种技术是 池化技术, 即在系统启动的时候一次性创业多个对象并且保存在一个 池 中, 当需要使用的时候直 接从 池 中取得而不是重新创建 这样可以大大提高系统性能 Java 语言在 JDK 1.5 以后的版本中提供了一个轻量级线程池 ThreadPool 可以使用 线程池来执行一组任务 简单的任务没有返回值, 如果主线程需要获得子线程的返回值时, 可以使任务实现 Callable 接口, 线程池执行任务并通过 Future 的实例返回线程的执行结果 Callable 和 java.lang.runnable 的区别如下 : 59

65 Callable 定义的方法是 call(), 而 Runnable 定义的方法是 run() Callable 的 call() 方法可以有返回值, 而 Runnable 的 run() 方法不能有返回值 Callable 的 call() 方法可以抛出异常, 而 Runnable 的 run() 方法不能抛出异常 Future 表示异步计算的结果, 它提供了检查计算是否完成的方法, 以等待计算的完成, 并检索计算的结果 Future 的 cancel() 方法取消任务的执行,cancel() 方法有一个布尔参数, 参数为 true 表示立即中断任务的执行, 参数为 false 表示允许正在运行的任务运行完成 Future 的 get() 方法等待计算完成, 获取计算结果 下面的例子使用 ThreadPool 实现并行下载网页 在继承 Callable 方法的任务类中下载 网页的实现如下 : public class DownLoadCall implements Callable<String> private URL url; // 待下载的 URL public DownLoadCall(URL u) url = public String call() throws Exception String content = null; // 下载网页 return content; 主线程类创建 ThreadPool 并执行下载任务的实现如下 : int threads = 4; // 并发线程数量 ExecutorService es = Executors.newFixedThreadPool(threads);// 创建线程池 Set<Future<String>> set = new HashSet<Future<String>>(); for (final URL url : urls) DownLoadCall task = new DownLoadCall(url); Future<String[]> future = es.submit(task);// 提交下载任务 set.add(future); // 通过 future 对象取得结果 for (Future<String> future : set) String content = future.get(); // 处理下载网页的结果 采用线程池可以充分利用多核 CPU 的计算能力, 并且简化了多线程的实现 爬虫中的多线程 多线程爬虫的结构如图 1.14 所示 60 1

66 第 1 章 全面剖析网络爬虫 取得 URL 检查是否结束 锁住 URL 列表 增加 URL 结束 取得 URL 增加 URL 检查是否结束锁住 URL 列表 结束 线程 从列表选出 URL 解锁 URL 列表 线程 从列表选出 URL 解锁 URL 列表 获取页面 获取页面 解析网页 解析网页 锁住 URL 列表 锁住 URL 列表 图 1.14 多线程爬虫的结构 对于并行爬虫架构而言, 处理空队列要比序列爬虫更加复杂 空的队列并不意味着爬 虫已经完成了工作, 因为此刻其他的进程或者线程可能依然在解析网页, 并且马上会加入 新的 URL 进程或者线程管理员需要给报告队列为空的进程 / 线程发送临时的休眠信号来解 决这类问题 线程管理员需要不断跟踪休眠线程的数目 ; 只有当所有的线程都休眠的时候, 爬虫才可以终止 一个简单的多线程爬虫实现 以下是一个多线程爬虫程序的主线程部分和子线程部分, 主线程启动子线程并等待所 有子线程执行完成后才退出, 实际代码如下 : threadlist = new ArrayList<Thread>(THREAD_NUM); for (int i = 0; i < THREAD_NUM; i++) Thread t = new Thread(this, "Spider Thread #" + (i+1)); t.start(); threadlist.add(t); // 当前线程等待子线程退出 while (threadlist.size() > 0) Thread child = (Thread)threadList.remove(0); child.join(); // 等待这个线程执行完 子线程主要的执行程序如下 : // 从 TODO 取出要分析的 URL 地址, 同时把它放入 Visited 表 public synchronized NewsSource dequeueurl() throws Exception while (true) if (!todo.isempty()) NewsSource newitem = (NewsSource)todo.removeFirst(); visited.add(newitem.url,newitem.source); 61

67 return newitem; else threads--; // 等待线程数的计数器减 1 if (threads > 0) // 如果仍然有其他的线程在活动则等待 wait(); threads++;// 等待线程数的计数器加 1 else // 如果其他线程都在等待, 则通知所有在等待的线程集体退出 notifyall(); return null; // enqueueurl 把新发现的 URL 放入 TODO 表 public synchronized void enqueueurl(newssource newitem) if (!visited.contains(newitem.url)) todo.add(newitem); visited.add(newitem.url,newitem.source); notifyall();// 唤醒在等待的线程 public void run() NewsSource item; try while ((item = dequeueurl())!= null) indexurl(item);// 包含把新的 URL 放入 TODO 表的过程 catch(exception e) e.printstacktrace(); threads--; 详解 Heritrix 多线程结构 要想更有效 更快速地抓取网页内容, 则必须采用多线程抓取 开源软件 Heritrix 采用 传统的线程模型实现了一个标准的线程池 ToePool, 它用于管理所有的抓取线程 ToeThread 则继承了 Thread 类, 并实现了 run 方法 ToePool 和 ToeThread 都位于 org.archive.crawler.framework 包中 ToePool 的初始化, 是在 CrawlController 的 initialize() 方法中完成的 以下是在 ToePool 进行初始化的代码 : // 构造函数 toepool = new ToePool(this); CrawlController 中用于对 62 1

68 第 1 章 全面剖析网络爬虫 // 按 order.xml 中的配置, 实例化并启动线程 toepool.setsize(order.getmaxtoes()); ToePool 的构造函数很简单, 如下所示 : public ToePool(CrawlController c) super("toethreads"); this.controller = c; 它仅仅是调用了父类 java.lang.threadgroup 的构造函数, 同时, 将注入的 CrawlController 赋给类变量 这样, 便建立起了一个线程池的实例了 真正的工作线程在线程池中的 setsize(int) 方法中创建 从名称上看, 这个方法很像是一 个普通的赋值方法, 但实际上, 这个方法调整了抓取的线程数量 代码如下 : public void setsize(int newsize) targetsize = newsize; int difference = newsize - gettoecount(); // 如果发现线程池中的实际线程数量小于应有的数量 // 则启动新的线程 if (difference > 0) for(int i = 1; i <= difference; i++) // 启动新线程 startnewthread(); // 如果线程池中的线程数量已经达到需要 else int retainedtoes = targetsize; // 将线程池中的线程管理起来放入数组中 Thread[] toes = this.gettoes(); // 循环去除多余的线程 for (int i = 0; i < toes.length ; i++) if(!(toes[i] instanceof ToeThread)) continue; retainedtoes--; if (retainedtoes>=0) continue; ToeThread tt = (ToeThread)toes[i]; tt.retire(); // ToeThread 中定义的方法, 通知这个线程尽早结束 // 用于取得所有属于当前线程池的线程 private Thread[] gettoes() 63

69 Thread[] toes = new Thread[activeCount()+10]; // 由于 ToePool 继承自 java.lang.threadgroup 类 // 因此当调用 enumerate(thread[] toes) 方法时, // 实际上是将该 ThreadGroup 中开辟的所有线程放入 // toes 这个数组中, 以备后面的管理 this.enumerate(toes); return toes; // 开启一个新线程 private synchronized void startnewthread() ToeThread newthread = new ToeThread(this, nextserialnumber++); newthread.setpriority(default_toe_priority); // 设置线程优先级 newthread.start(); // 启动线程 根据上面的代码可以得出这样的结论 : 线程池本身在创建的时候, 并没有任何活动的 线程实例, 只有当它的 setsize 方法被调用时, 才创建新线程 ; 如果当 setsize 方法被调用多 次而传入不同的参数时, 线程池会根据参数里设定的值的大小来改变池中所管理的线程数 量 当启动 Toe 线程后, 执行的是其 run() 方法中的代码 通过 run 方法中的代码可以看到 ToeThread 到底如何处理从 Frontier 中获得的要抓取的链接 public void run() String name = controller.getorder().getcrawlordername(); logger.fine(getname()+" started for order '"+name+"'"); try while ( true ) // 检查是否应该继续处理 continuecheck(); setstep(step_about_to_get_uri); // 使用 Frontier 的 next 方法从 Frontier 中取出下一个要处理的链接 CrawlURI curi = controller.getfrontier().next(); // 同步当前线程 synchronized(this) continuecheck(); setcurrentcuri(curi); /* * 处理取出的链接 */ processcrawluri(); setstep(step_about_to_return_uri); // 检查是否应该继续处理 continuecheck(); // 使用 Frontier 的 finished() 方法来对刚才处理的链接做收尾工作 // 比如将分析得到的新的链接加入到等待队列中 synchronized(this) controller.getfrontier().finished(currentcuri); 64 1

70 第 1 章 全面剖析网络爬虫 setcurrentcuri(null); // 后续的处理 setstep(step_finishing_process); lastfinishtime = System.currentTimeMillis(); // 释放链接 controller.releasecontinuepermission(); if(shouldretire) break; // from while(true) catch (EndedException e) catch (Exception e) logger.log(level.severe,"fatal exception in "+getname(),e); catch (OutOfMemoryError err) seriouserror(err); finally controller.releasecontinuepermission(); setcurrentcuri(null); // 清理缓存数据 this.httprecorder.closerecorders(); this.httprecorder = null; localprocessors = null; logger.fine(getname()+" finished for order '"+name+"'"); setstep(step_finished); controller.toeended(); controller = null; 工作线程通过调用 Frontier 的 next() 方法取得下一个待处理的链接, 然后对链接进行处 理, 并调用 Frontier 的 finished() 方法来收尾 释放链接, 最后清理缓存 终止单步工作等 另外, 其中还有一些日志操作, 主要是为了记录每次抓取的各种状态 以上代码中, 最重 要的语句是 processcrawluri(), 它调用处理链对链接进行处理 1.6 本章小结 本节介绍了爬虫的基本原理及开源爬虫实现 Heritrix 在本节的末尾, 再介绍几个相关的爬虫项目, 供读者参考 RBSE 是第一个发布的爬虫 它有两个基础程序 第一个程序 spider, 抓取队列中的内容到一个关系数据库中 ; 第二个程序 mite, 是一个修改后的 WWW 的 ASCII 浏览器, 负责从网络上下载页面 WebCrawler 是第一个公开可用的, 用来建立全文索引的一个子程序, 它使用 WWW 库下载页面, 使用宽度优先算法来解析获得 URL 并对其进行排序, 并包括 65

71 一个根据选定文本和查询相似程度爬行的实时爬虫 World Wide Web Worm 是一个用来为文件建立包括标题和 URL 简单索引的爬虫 索引可以通过 grep 式的 Unix 命令来搜索 CobWeb 使用了一个中央 调度者 和一系列的 分布式的搜集者 的爬虫框架 搜集者解析下载的页面并把找到的 URL 发送给调度者, 然后调度者反过来分配给搜集者 调度者使用深度优先策略, 并且使用平衡礼貌策略来避免服务器超载 爬虫是使用 Perl 语言编写的 Mercator 是一个分布式的, 模块化的使用 Java 语言编写的网络爬虫 它的模块化源自于使用可互换的 协议模块 和 处理模块 协议模块负责怎样获取网页 ( 例如使用 HTTP), 处理模块负责怎样处理页面 标准处理模块仅仅包括了解析页面和抽取 URL, 其他处理模块可以用来检索文本页面, 或者搜集网络数据 WebFountain 是一个与 Mercator 类似的分布式的模块化的爬虫, 但是使用 C++ 语言编写的 它的特点是一个管理员机器控制一系列的蚂蚁机器 经过多次下载页面后, 页面的变化率可以推测出来 这时, 一个非线性的方法必须用于求解方程以获得一个最大的新鲜度的访问策略 作者推荐在早期检索阶段使用这个爬虫, 然后用统一策略检索, 就是所有页面都使用相同的频率访问 PolyBot 是一个使用 C++ 和 Python 语言编写的分布式网络爬虫 它由一个爬虫管理者, 一个或多个下载者, 和一个或多个 DNS 解析者组成 抽取到的 URL 被添加到硬盘的一个队列里面, 然后使用批处理的模式处理这些 URL WebRACE 是一个使用 Java 实现的, 拥有检索模块和缓存模块的爬虫, 它是一个很通用的称作 erace 的系统的一部分 系统从用户方得到下载页面的请求, 爬虫的行为有点像一个聪明的代理服务器 系统还监视订阅网页的请求, 当网页发生改变的时候, 它必须使爬虫下载更新这个页面并且通知订阅者 WebRACE 最大的特色是, 当大多数爬虫都从一组 URL 开始的时候,WebRACE 可以连续地接收初始抓取的 URL 地址 Ubicrawer 是一个使用 Java 语言编写的分布式爬虫 它没有中央程序, 但有一组完全相同的代理组成, 分配功能通过主机前后一致的散列计算进行 这里没有重复的页面, 除非爬虫崩溃了 ( 然后, 另外一个代理就会接替崩溃的代理重新开始抓取 ) 爬虫设计为高伸缩性 FAST Crawler 是一个分布式的爬虫, 在 Fast Search & Transfer 中使用 节点之间只交换发现的链接 在抓取任务分配上, 静态的映射超级链接到爬虫机器 实现了增量式抓取, 优先抓更新活跃的网页 Labrador 是一个工作在开源项目 Terrier Search Engine 上的非开源的爬虫 TeezirCrawler 是一个非开源的可伸缩的网页抓取器, 在 Teezir 上使用 该程序被设计为一个完整的可以处理各种类型网页的爬虫, 包括各种 JavaScript 和 HTML 文档 爬虫既支持主题检索也支持非主题检索 Spinn3r 是一个通过博客构建 Tailrank.com 反馈信息的爬虫 Spinn3r 是基于 Java 的, 它的大部分体系结构都是开源的 66 1

72 第 1 章 全面剖析网络爬虫 HotCrawler 是一个使用 C 和 PHP 语言编写的爬虫 ViREL Microformats Crawler 搜索公众信息作为嵌入网页的一小部分 开源爬虫除了已经分析过的 Heritrix, 还有下面的一些 : DataparkSearch 是一个在 GNU GPL 许可下发布的爬虫搜索引擎 GNU Wget 是一个在 GPL 许可下, 使用 C 语言编写的命令行式的爬虫 它主要用于网络服务器和 FTP 服务器的镜像 Ht://Dig 在它和索引引擎中包括了一个网页爬虫 HTTrack 用网络爬虫创建网络站点镜像, 以便离线观看 它使用 C 语言编写, 在 GPL 许可下发行 ICDL Crawler 是一个用 C++ 语言编写 跨平台的网络爬虫 它仅仅使用空闲的 CPU 资源, 在 ICDL 标准上抓取整个站点 JSpider 是一个在 GPL 许可下发行的 高度可配置的 可定制的网络爬虫引擎 Larbin 是由 Sebastien Ailleret 开发的 C++ 语言实现的爬虫 Webtools4larbin 是由 Andreas Beder 开发的 Methabot 是一个使用 C 语言编写的高速优化的, 使用命令行方式运行的, 在 2-clause BSD 许可下发布的网页检索器 它的主要特性是高可配置性 模块化 ; 它检索的目标可以是本地文件系统,HTTP 或者 FTP Nutch 是一个使用 Java 编写, 在 Apache 许可下发行的爬虫 它可以用来连接 Lucene 的全文检索套件 Pavuk 是一个在 GPL 许可下发行的, 使用命令行的 Web 站点镜像工具, 可以选择使用 X11 的图形界面 与 GNU Wget 和 HTTrack 相比, 它有一系列先进的特性, 如以正则表达式为基础的文件过滤规则和文件创建规则 WebVac 是斯坦福 WebBase 项目使用的一个爬虫 WebSPHINX 是一个由 Java 类库构成的, 基于文本的搜索引擎 它使用多线程进行网页检索和 HTML 解析, 拥有一个图形用户界面用来设置开始的种子 URL 和抽取下载的数据 WIRE- 网络信息检索环境是一个使用 C++ 语言编写 在 GPL 许可下发行的爬虫, 内置了几种页面下载安排的策略, 还有一个生成报告和统计资料的模块, 所以, 它主要用于网络特征的描述 LWP:RobotUA 是一个在 Perl 5 许可下发行的, 可以优异地完成并行任务的 Perl 类库构成的爬虫 Web Crawler 是一个用 C# 语言编写的开放源代码的网络检索器 Sherlock Holmes 用于收集和检索本地和网络上的文本类数据 ( 文本文件, 网页 ), 该项目由捷克门户网站中枢 (Czech web portal Centrum) 赞助并且在该网站使用 ; 它同时也在 Onet.pl 中使用 YaCy 是一个基于 P2P 网络的免费的分布式搜索引擎 Ruya 是一个在宽度优先方面表现优秀, 基于等级抓取的开放源代码的网络爬虫 其在抓取英语和日语页面方面表现良好, 在 GPL 许可下发行, 并且完全使用 67

73 Python 语言编写 Universal Information Crawler 是快速发展的网络爬虫, 用于检索 存储和分析数据 Agent Kernel 是一个当爬虫抓取时, 用来进行安排 并发和存储的 Java 框架 Arachnod.net 是一个使用 C# 语言编写, 需要 SQL Server 2005 支持的, 在 GPL 许可下发行的 多功能的 开源的机器人 它可以用来下载 检索和存储包括电子邮件地址 文件 超链接 图片和网页在内的各种数据 Dine 是一个多线程的 Java 的 HTTP 客户端 它可以在 LGPL 许可下进行二次开发 JoBo 是一个用于下载整个 Web 站点的简单工具 它采用 Java 实现 JoBo 直接使用 socket 下载网页 与其他下载工具相比较, 它的主要优势是能够自动填充 form( 如自动登录 ) 和使用 cookies 来处理 session JoBo 还有灵活的下载规则 ( 如通过网页的 URL 大小 MIME 类型等 ) 来限制下载 虽然有这么多公开的资源可用, 但是, 很多企业和个人还在不断地开发新的爬虫, 尤其是主题爬虫, 互联网发展过程中出现了一波一波的新技术, 而在每一波都有开发爬虫的需要, 现在实时搜索又成了热门, 可能会需要实时爬虫, 下一步如果语义网络真正发展起来了, 也会需要语义搜索爬虫 68 1

74 第 2 章 分布式爬虫 随着互联网技术的发展以及风起云涌的云计算浪潮 爬虫技术也逐渐向着分布式方向发展 比如,Google 的爬虫就是使用成千上万台小型机和微机进行合作, 完成分布式抓取工作的 分布式技术不仅可以解决 IT 运营的成本, 还可以解决爬虫效率问题, 尤其是当今云计算的热潮, 更把分布式推向了极致

75 2.1 设计分布式爬虫 把抓取任务分布到不同的节点主要是为了抓取性能与可扩展性, 也可以使用物理分布的爬虫系统, 让每个爬虫节点抓取靠近它的网站 例如, 北京的爬虫节点抓取北京的网站, 上海的爬虫节点抓取上海的网站, 电信网络中的爬虫节点抓取托管在电信的网站, 联通网络中的爬虫节点抓取托管在联通的网站 分布式与云计算分布式技术是一种基于网络的计算机处理技术, 与集中式相对应 近些年来, 由于个人计算机的性能得到极大地提高及其使用的普及, 使得将处理任务分布到网络上的所有计算机成为可能 分布式计算是和集中式计算相对立的概念, 分布式计算的数据可以分布在很大区域 在分布式网络中, 数据的存储和处理都是在本地工作站上进行的 数据输出可以打印, 也可以保存在软盘上 通过网络能得到更快 更便捷的数据访问 因为每台计算机都能够存储和处理数据, 所以不要求服务器的功能十分强大, 其价格也就不必过于昂贵 这种类型的网络可以适应用户的各种需要, 同时允许他们共享网络的数据 资源和服务 在分布式网络中使用的计算机既能够作为独立的系统使用, 也可以把它们连接在一起得到更强的网络功能 分布式计算的优点是可以快速访问 多用户使用 每台计算机可以访问系统内其他计算机的信息文件, 系统设计上具有更大的灵活性 既可为独立计算机的地区用户的特殊需求服务, 也可为联网的企业需求服务, 实现系统内不同计算机之间的通信 每台计算机都可以拥有和保持所需要的最大数据和文件, 减少了数据传输的成本和风险 为分散地区和中心办公室双方提供更迅速的信息通信和处理方式, 为每个分散的数据库提供作用域, 数据存储于许多存储单元中, 但任何用户都可以进行全局访问, 使故障的不利影响最小化, 以较低的成本来满足企业的特定要求 云计算 (Cloud Computing) 是分布式处理 (Distributed Computing) 并行处理(Parallel Computing) 和网格计算 (Grid Computing) 的发展, 或者说是这些计算机科学概念的商业实现 云计算的基本原理是, 通过使计算任务分布在大量的分布式计算机上, 而非本地计算机或远程服务器中, 企业数据中心的运行将与互联网更相似 这使得企业能够将资源切换到需要的应用上, 根据需求访问计算机和存储系统 这可是一种革命性的举措, 打个比方, 就好比是从古老的单台发电机模式转向了电厂集中供电的模式 它意味着计算能力也可以作为一种商品进行流通, 就像煤气 水电一样, 取用方便, 费用低廉 最大的不同在于, 它是通过互联网进行传输的 云计算的蓝图已经呼之欲出 : 在未来, 只需要一台笔记本或者一部手机, 就可以通过网络服务来实现我们需要的一切, 甚至包括超级计算这样的任务 从这个角度而言, 最终 70

76 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 用户才是云计算的真正拥有者 云计算的应用包含这样一种思想, 把力量联合起来, 给其中的每一个成员使用 目前,PC 依然是我们日常工作生活中的核心工具 我们用 PC 处理文档 存储资料, 用电子邮件或 U 盘与他人分享信息 如果 PC 硬盘坏了, 我们会因为资料丢失而束手无策 而在 云计算 时代, 云 会替我们做存储和计算的工作 云 就是计算机群, 每一群都包括几十万台 甚至上百万台计算机 云 的好处还在于, 其中的计算机可以随时更新, 保证 云 长生不老 Google 就有好几个这样的 云, 其他 IT 巨头, 如微软 雅虎 亚马逊 (Amazon) 也有或正在建设这样的 云 届时, 我们只需要一台能上网的电脑, 不需关心存储或计算发生在哪朵 云 上, 但一旦有需要, 我们可以在任何地点用任何设备, 如电脑 手机等, 快速地计算和找到这些资料 我们再也不用担心资料会丢失了 云计算是虚拟化 (Virtualization) 效用计算(Utility Computing) IaaS( 基础设施即服务 ) PaaS( 平台即服务 ) SaaS( 软件即服务 ) 等概念混合演进并跃升的结果 云计算的特点如下 : (1) 超大规模 Google 云计算已经拥有 100 多万台服务器, Amazon IBM 微软 Yahoo 等的 云 均拥有几十万台服务器 企业私有云一般拥有数百至上千台服务器 云 能赋予用户前所未有的计算能力 (2) 虚拟化 云计算支持用户在任意位置 使用各种终端获取应用服务 所请求的资源来自 云, 而不是固定的 有形的实体 应用在 云 中某处运行, 但实际上用户无需了解 也不用担心应用运行的具体位置 只需要一台笔记本或者一部手机, 就可以通过网络服务来实现我们需要的一切, 甚至包括超级计算这样的任务 (3) 高可靠性 云 使用了数据多副本容错 计算节点同构可互换等措施来保障服务的高可靠性, 使用云计算比使用本地计算机可靠 (4) 通用性 云计算不针对特定的应用, 在 云 的支撑下可以构造出千变万化的应用, 同一个 云 可以同时支撑不同的应用运行 (5) 高可扩展性 云 的规模可以动态伸缩, 满足应用和用户规模增长的需要 (6) 按需服务 云 是一个庞大的资源池, 可以按需购买 ; 云可以象自来水 电 煤气那样计费 (7) 极其廉价 由于 云 的特殊容错措施可以采用极其廉价的节点来构成云, 云 的自动化集中式管理使得大量企业无需负担日益高昂的数据中心管理成本, 云 的通用性使资源的利用率较之传统系统大幅提升, 因此用户可以充分享受 云 的低成本优势, 经常只要花费几百美元 几天时间就能完成以前需要数万美元 数月时间才能完成的任务 分布式与云计算技术在爬虫中的应用 浅析 Google 的云计算架构分布式与云计算深刻地影响着搜索引擎的发展 与其说是云计算影响了搜索引擎, 不如说是搜索引擎的发展产生了云计算的概念 Google 正是由于在云计算领域的领先, 才能多年在搜索领域保持霸主的地位 本节以 Google 的架构为例, 看一下云计算是如何应用在 71

77 爬虫之中的 Google 的三大核心技术构成了实现云计算服务的基础 :GFS(Google 文件系统 ) MapReduce( 分布式计算系统 ) 和 BigTable( 分布式存储系统 ) GFS 位于这三项技术的最底层, 负责许多服务器 机器数据的存储工作 它将一个 大体积数据 ( 通常在百兆甚至千兆级别 ) 分隔成固定大小的数据块放到两到三个服务器上 这样做的目的是当一个服务器发生故障时, 可以将数据迅速地从另外一个服务器上恢复过来 在存储层面, 机器故障的处理由 Google 文件系统来完成 MapReduce ( 分布式计算系统 ) 是 Google 开发的编程工具, 用于 1TB 数据的大规模数据集并行运算 这项技术的意义在于, 实现跨越大量数据结点分割任务, 使得某项任务可以被同时分拆在多台机器上执行 例如把一项搜索任务拆分成一两百个小的子任务, 经并行处理后, 将运算结果在后台合并, 最后把最终结果返回到客户端 BigTable ( 分布式存储系统 ) 作为 Google 的一种对于半结构化数据进行分布存储与访问的接口或服务, 它是建立在 GFS 和 MapReduce 之上的结构化分布式存储系统, 可以帮助 Google 最大限度地利用已有的数据存储能力和计算能力, 在提供服务时降低运行成本 2.2 分布式存储 分布式存储是网络爬虫中的一个重要问题, 抓取下来的 URL 要存放在分布式环境中 在云计算热潮风起云涌的今天, 存储云的概念也被炒得沸沸扬扬 因此, 如何在分布式网络环境中存储数据, 也是分布式爬虫的重要课题 从 Ralation_DB 到 key/value 存储前几年,key/value 这个词还是和 hash 表联系在一起的 而现在, 程序员看见 key/value 这个词时, 马上联想到的就是 BigTable SimpleDB 和云计算 当下,key/value 存储 ( 或者叫 key/value Database 云存储等) 是个非常时髦的词汇, 越来越多的开发人员 ( 特别是互联网企业 ) 开始关注和尝试 key/value 的存储形式 key/value 形式的存储并不是凭空想象出来的 有两个原因导致了 key/value 存储方式的崛起 1. 大规模的互联网应用对于 Google 和 ebay 这样的互联网企业, 每时每刻都有无数的用户在使用它们提供的互联网服务, 这些服务带来的就是大量的数据吞吐量 同一时间, 会并发出成千上万的连接对数据库进行操作 在这种情况下, 单台服务器或者几台服务器远远不能满足这些数据处理的需求, 简单的升级服务器性能的方式也不行, 所以唯一可以采用的办法就是使用集群了 使用集群的方法有很多种, 但大致分为两类 : 一类仍然采用关系数据库管理系统 (RDBMS), 然后通过对数据库的垂直和水平切割将整个数据库部署到一个集群上, 这种方 72

78 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 法的优点在于可以采用 RDBMS 这种熟悉的技术, 但缺点在于它是针对特定应用的 由于应用的不同, 切割的方法是不一样的 关于数据库的垂直和水平切割的具体细节可以查看相关资料 还有一类就是 Google 所采用的方法, 抛弃 RDBMS, 采用 key/value 形式的存储, 这样可以极大地增强系统的可扩展性 (scalability), 如果要处理的数据量持续增大, 多加机器就可以了 事实上,key/value 的存储就是由于 BigTable 等相关论文的发表慢慢进入人们的视野的 2. 云存储如果说上一个问题还有可以替代的解决方案 ( 切割数据库 ) 的话, 那么对于云存储来说, 也许 key/value 的存储就是唯一的解决方案了 云存储简单点说就是构建一个大型的存储平台给别人用, 这也就意味着在这上面运行的应用其实是不可控的 如果其中某个客户的应用随着用户的增长而不断增长时, 云存储供应商是没有办法通过数据库的切割来达到扩展的, 因为这个数据是客户的, 供应商不了解这个数据自然就没法作出切割 在这种情况下, key/value 的存储就是唯一的选择了, 因为这种条件下的可扩展性必须是自动完成的, 不能有人工干预 这也是为什么目前几乎所有的云存储都是 key/value 形式的, 例如 Amazon 的 smipledb, 底层实现就是 key/value, 还有 Google 的 GoogleAppEngine, 采用的是 BigTable 的存储形式 key/value 存储与 RDBMS 相比, 一个很大的区别就是它没有模式的概念 在 RDBMS 中, 模式所代表的其实就是对数据的约束, 包括数据之间的关系 (relationship) 和数据的完整性 (integrity), 比如 RDBMS 中对于某个数据属性会要求它的数据类型是确定的 ( 整数或者字符串等 ), 数据的范围也是确定的 (0~255), 而这些在 key/value 存储中都没有 在 key/value 存储中, 对于某个 key,value 可以是任意的数据类型 在所有的 RDBMS 中, 都是采用 SQL 语言对数据进行访问 一方面,SQL 对于数据的查询功能非常强大 ; 另一方面, 由于所有的 RDBMS 都支持 SQL 查询, 所以可移植性很强 而在 key/value 存储中, 对于数据的操作使用的都是自定义的一些 API, 而且支持的查询也比较简单 正如前面反复提及的,key/value 存储最大的特点就是它的可扩展性 (scalability), 这也是它最大的优势 所谓的可扩展性, 其实包括两方面内容 一方面, 是指 key/value 存储可以支持极大的数据存储 它的分布式的架构决定了只要有更多的机器, 就能够保证存储更多的数据 另一方面, 是指它可以支持数量很多的并发的查询 对于 RDBMS, 一般几百个并发的查询就可以让它很吃力了, 而一个 key/value 存储, 可以很轻松地支持上千个并发查询 key/value 存储的缺陷主要有两点 : 由于 key/value 存储中没有 schema, 所以它是不提供数据之间的关系和数据的完备性的, 所有的这些东西都落到了应用程序一端, 其实也就是开发人员的头上 这无疑加重了开发人员的负担 73

79 在 RDBMS 中, 需要设定各表之间的关系, 这其实是一个数据建模的过程 (data modeling process) 当数据建模完成后, 这个数据库对于应用程序就是独立的了, 这就意味着其他程序可以在不改变数据模型的前提下使用相同的数据集 但在 key/value 存储中, 由于没有这样一个数据模型, 不同的应用程序需要重复进行这个过程 key/value 存储最大的一个缺点在于它的接口是不熟悉的 这阻碍了开发人员可以快速而顺利地用上它 当然, 现在有种做法, 就是在 key/value 存储上再加上一个类 SQL 语句的抽象接口层, 从而使得开发人员可以用他们熟悉的方式 (SQL) 来操作 key/value 存储 但由于 RDBMS 和 key/value 存储的底层实现有着很大的不同, 这种抽象接口层或多或少还是受到了限制 Consistent Hash 算法分布式存储常常会涉及负载平衡的问题, 由于有多个存储介质, 分布在不同的结点上 因此, 当一个对象被保存的时候, 它究竟应该保存在哪个存储介质上呢 ( 存储介质可以是数据库 Berkeley DB 等, 甚至可以是内存数据结构 )? 这就是负载均衡的问题, 如图 2.1 所示 图 2.1 负载均衡示意图面对云计算的热潮, 如何很好地分布存储数据, 是一个非常重要的话题 在分布式网络爬虫中, 抓取的页面非常多 ( 通常是数十亿级别 ), 因此, 分布式存储就非常有意义 那么, 如果抓取下来一个页面, 究竟要存放到哪个数据库中呢? 这就涉及负载均衡的问题 如果你有 N 个数据存储服务器, 那么如何将一个对象 (object) 映射到 N 个服务器上呢? 你很可能会采用类似下面的通用方法计算对象的 hash 值, 然后均匀地映射到 N 个服务器 : hash(object)%n 一切都运行正常, 但是要考虑以下两种情况 ; 74

80 Error! Use the Home tab to apply 标题 1, 部分标题 1 to (1) 一个服务器 m 挂掉了 ( 在实际应用中必须要考虑这种情况 ), 则所有映射到服务器 m 的对象都会失效 怎么办, 需要把服务器 m 移除, 这时候服务器为 N-1 台, 映射公式变成了 hash(object)%(n-1) (2) 由于访问加重, 需要添加服务器, 这时候服务器是 N+1 台, 映射公式变成了 hash(object)%(n+1) 在上面两种情况下, 突然之间几乎所有的服务器都失效了 ( 请看下面单调性的解释 ) 对于服务器而言, 这是一场灾难 再来考虑第三个问题, 由于硬件能力越来越强, 你可能会想让后面添加的节点多干点活, 显然上面的 hash 算法也做不到 有什么方法可以改变这个状况呢, 这就要用到 Consistent Hashing 算法 hash 算法的一个衡量指标是单调性 (Monotonicity), 定义如下 : 单调性是指如果已经有一些内容通过哈希分配到了相应的缓冲中, 而又有新的缓冲加入到系统中 哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去, 而不会被映射到旧的缓冲集合中的其他缓冲区 上面的简单 hash 算法 hash(object)%n 难以满足单调性要求 Consistent Hashing 是一种 hash 算法, 简单地说, 在移除 / 添加一个服务器时, 它能够尽可能小地改变已存在的 key 映射关系, 尽可能地满足单调性的要求 下面就按照 5 个步骤简单讲讲 Consistent Hashing 算法的基本原理 步骤一 : 环形 hash 空间 考虑通常的 hash 算法都是将 value 映射到一个 32 位的 key 值, 即 0~ 的数值空间 我们可以将这个空间想象成一个首 ( 0 ) 尾 (2 32-1) 相接的圆环, 如图 2.2 所示 步骤二 : 把对象映射到 hash 空间 接下来考虑 4 个对象 object1~object4, 通过 hash 函数计算出的 hash 值 key 在环上的分布如图 2.2 所示 hash(object1) = key1; hash(object4) = key4; 图 2.2 环形 hash 空间步骤三 : 把服务器映射到 hash 空间 Consistent Hashing 的基本思想就是将对象和服务器都映射到同一个 hash 数值空间, 并且使用相同的 hash 算法 假设当前有 A B 和 C 共 3 台服务器, 那么其映射结果将如图 2.3 所示, 它们在 hash 空 75

81 间中, 以对应的 hash 值排列 hash( 服务器 A) = key A; hash( 服务器 C) = key C; 图 个对象的 key 值分布步骤四 : 把对象映射到服务器 现在 cache 和对象都已经通过同一个 hash 算法映射到 hash 数值空间中了, 接下来要考虑的就是如何将对象映射到 cache 上面 在这个环形空间中, 如果沿着顺时针方向从对象的 key 值出发, 直到遇见一个服务器, 那么就将该对象存储在这个服务器上, 因为对象和服务器的 hash 值是固定的, 因此这个服务器必然是唯一和确定的 这样不就找到了对象和服务器的映射方法了吗? 依然继续上面的例子 ( 见图 2.4), 那么根据上面的方法, 对象 object1 将被存储到服务器 A 上,object2 和 object3 对应到服务器 C,object4 对应到服务器 B 图 2.4 服务器对象的 key 值表示 76

82 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 步骤五 : 考察服务器的变动 前面讲过, 通过 hash 算法然后求余的方法带来的最大问题就在于不能满足单调性, 当服务器有所变动时, 服务器会失效, 进而对后台服务器造成巨大的冲击, 现在就来分析 Consistent Hashing 算法 (1) 移除服务器 考虑假设服务器 B 挂掉了, 根据上面讲到的映射方法, 这时受影响的将只是那些沿 cache B 逆时针遍历直到下一个服务器 ( 服务器 C ) 之间的对象, 也即是本来映射到服务器 B 上的那些对象 因此这里仅需要变动对象 object4, 将其重新映射到服务器 C 上即可, 如图 2.5 所示 (2) 添加服务器 再考虑添加一台新的服务器 D 的情况, 假设在这个环形 hash 空间中, 服务器 D 被映射在对象 object2 和 object3 之间 这时受影响的仅是那些沿 cache D 逆时针遍历直到下一个服务器 ( 服务器 B ) 之间的对象, 将这些对象重新映射到服务器 D 上即可 因此这里仅需要变动对象 object2, 将其重新映射到服务器 D 上, 如图 2.6 所示 考量 hash 算法的另一个指标是平衡性 (Balance), 定义如下 : 平衡性是指哈希的结果能够尽可能分布到所有的缓冲中, 这样可以使所有的缓冲空间都得到利用 hash 算法并不能保证绝对的平衡, 如果服务器较少, 对象并不能被均匀地映射到服务器上, 比如在上面的例子中, 仅部署服务器 A 和服务器 C 的情况下, 在 4 个对象中, 服务器 A 仅存储了 object1, 而服务器 C 则存储了 object2 object3 和 object4, 分布是很不均衡的 图 2.5 服务器 B 被移除后的映射 图 2.6 添加服务器 D 后的映射关系 为了解决这种情况,Consistent Hashing 引入了 虚拟节点 的概念, 它可以如下定义 : 虚拟节点 ( virtual node ) 是实际节点在 hash 空间的复制品 ( replica ), 一个实际节点对应若干个 虚拟节点, 这个对应个数也称为 复制个数, 虚拟节点 在 hash 空间中以 hash 值排列 仍以仅部署服务器 A 和服务器 C 的情况为例, 在图 2.5 中我们已经看到, 服务器分布并不均匀 现在我们引入虚拟节点, 并设置 复制个数 为 2, 这就意味着一共会存在 4 77

83 个 虚拟节点, 服务器 A1 和服务器 A2 代表服务器 A; 服务器 C1 和服务器 C2 代表服务器 C, 假设一种比较理想的情况如图 2.7 所示 图 2.7 引入 虚拟节点 后的映射关系此时, 对象到 虚拟节点 的映射关系为 : objec1-> 服务器 A2;objec2-> 服务器 A1;objec3-> 服务器 C1;objec4-> 服务器 C2 因此对象 object1 和 object2 都被映射到服务器 A 上, 而 object3 和 object4 映射到服务器 C 上, 平衡性有了很大提高 引入 虚拟节点 后, 映射关系就从 对象 -> 节点 转换到了 对象 -> 虚拟节点 查询对象所在 cache 时的映射关系如图 2.8 所示 图 2.8 查询对象所在的 cache 虚拟节点 的 hash 计算可以采用对应节点的 IP 地址加数字后缀的方式 例如假设服务器 A 的 IP 地址为 引入 虚拟节点 前, 计算服务器 A 的 hash 值 : Hash(" "); 引入 虚拟节点 后, 计算 虚拟节点 服务器 A1 和服务器 A2 的 hash 值 : hash(" #1"); // cache A1 hash(" #2"); // cache A2 78

84 Error! Use the Home tab to apply 标题 1, 部分标题 1 to Consistent Hash 代码实现 上一节, 讲述了 Consistent Hash 算法的原理 本节, 我们实现一个简单的 Consistent Hash 算法 public class ConsistentHash<T> private final HashFunction hashfunction;//hash 算法 private final int numberofreplicas;// 虚拟节点数目 private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>(); public ConsistentHash(HashFunction hashfunction, int numberofreplicas, Collection<T> nodes // 物理节点 ) this.hashfunction = hashfunction; this.numberofreplicas = numberofreplicas; for (T node : nodes) add(node); public void add(t node) for (int i = 0; i < numberofreplicas; i++) circle.put(hashfunction.hash(node.tostring() + i), node); public void remove(t node) for (int i = 0; i < numberofreplicas; i++) circle.remove(hashfunction.hash(node.tostring() + i)); public T get(object key) // 关键算法 if (circle.isempty()) return null; // 计算 hash 值 int hash = hashfunction.hash(key); // 如果不包括这个 hash 值 if (!circle.containskey(hash)) SortedMap<Integer, T> tailmap = circle.tailmap(hash); hash = tailmap.isempty()? circle.firstkey() : tailmap.firstkey(); return circle.get(hash); 本节内容详细讲述了有关分布式和云计算存储的相关问题, 下面几节, 我们将讲述 Google 的分布式和云计算技术以及它们在网络爬虫中的应用 79

85 2.3 Google 的成功之道 GFS Google 之所以能成功, 很大程度上是应用了 GFS+BigTable+MapReduce 的架构 由于这种架构, 使得 Google 在当前风起云涌的云计算热潮中始终保持领先地位 Google 的爬虫也是采用 GFS 作为存储网页的底层数据结构 为何 GFS 能如此受 Google 的青睐? 本节将为你揭示这个谜团 GFS 详解 GFS(Google File System) 是 Google 自己研发的一个适用于大规模分布式数据处理相关应用的 可扩展的分布式文件系统 它基于普通的不算昂贵的硬件设备, 实现了容错的设计, 并且为大量客户端提供了极高的聚合处理性能 GFS 正好与 Google 的存储要求相匹配, 因此在 Google 内部广泛用作存储平台, 适用于 Google 的服务产生和处理数据应用的要求, 以及 Google 的海量数据的要求 Google 最大的集群通过上千个计算机的数千个硬盘, 提供了数百 TB 的存储, 并且这些数据被数百个客户端并行操作 Google 之所以要研发自己的分布式文件系统, 是因为在分布式存储环境中, 常常会产生以下一些问题 1. 在分布式存储中, 经常会出现节点失效的情况因为在分布式存储中, 文件系统包含了几百个或者几千个廉价的普通机器, 而且这些机器要被巨大数量的客户端访问 节点失效可能是由于程序的 bug, 操作系统的 bug, 人工操作的失误, 以及硬盘坏掉, 内存 网络 插板的损坏, 电源的坏掉等原因造成的 因此, 持续监视, 错误检测, 容错处理, 自动恢复必须集成到这个文件系统的设计中 2. 分布式存储的文件都是非常巨大的 GB 数量级的文件是常事 每一个文件都包含了很多应用程序对象, 比如 Web 文档等 搜索引擎的数据量是迅速增长的, 它通常包含数十亿数据对象 如果使用一般的文件系统, 就需要管理数十亿个 KB 数量级大小的文件 而且, 每次 I/O 只能读出几个字节也不能满足搜索引擎吞吐量的要求 因此,Google 决定设计自己的文件系统, 重新规定每次 I/O 的块的大小 3. 对于搜索引擎的业务而言, 大部分文件都只会在文件尾新增加数据, 而少见修改已有数据的对一个文件的随机写操作在实际上几乎是不存在的 一旦写完, 文件就是只读的, 并且一般都是顺序读取 比如, 在网络爬虫中, 把网页抓取下来之后不会做修改, 而只是简单地存储, 作为搜索结果的快照 在实际的系统中, 许多数据都有这样的特性 有些数据可能组成很大的数据仓库, 并且数据分析程序从头扫描到尾 有些可能是运行应用而不断地产生数据流 对于这些巨型文件的访问模式来说, 增加模式是最重要的, 所以我们首要优化性能的就是它 80

86 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 4. 与应用一起设计的文件系统 API 对于增加整个系统的弹性和适用性有很大的好处为了满足以上几点需要,Google 遵照下面几条原则设计了它的分布式文件系统 (GFS): (1) 系统建立在大量廉价的普通计算机上, 这些计算机经常出故障 则必须对这些计算机进行持续检测, 并且在系统的基础上进行检查 容错, 以及从故障中进行恢复 (2) 系统存储了大量的超大文件 数 GB 的文件经常出现并且应当对大文件进行有效的管理 同时必须支持小型文件, 但是不必为小型文件进行特别的优化 (3) 一般的工作都是由两类读取组成 : 大的流式读取和小规模的随机读取 在大的流式读取中, 每个读操作通常一次就要读取几百字节以上的数据, 每次读取 1MB 或者以上的数据也很常见 因此, 在大的流式读取中, 对于同一个客户端来说, 往往会发起连续的读取操作顺序读取一个文件 而小规模的随机读取通常在文件的不同位置, 每次读取几字节数据 对于性能有过特别考虑的应用通常会做批处理并且对它们读取的内容进行排序, 这样可以使得它们的读取始终是单向顺序读取, 而不需要往回读取数据 (4) 通常基于 GFS 的操作都有很多超大的 顺序写入的文件操作 通常写入操作的数据量和读入的数据量相当 一旦完成写入, 文件就很少会更改 应支持文件的随机小规模写入, 但是不需要为此做特别的优化 5. 系统必须非常有效地支持多个客户端并行添加同一个文件 GFC 文件经常使用生产者 / 消费者队列模式, 或者以多路合并模式进行操作 好几百个运行在不同机器上的生产者, 将会并行增加一个文件 6. 高性能的稳定带宽的网络要比低延时更加重要 GFS 目标应用程序一般会大量操作处理比较大块的数据 基于以上几点考虑,GFS 采用了如图 2.9 所示的架构 图 2.9 Google File System 架构如图 2.9 所示,GFS 集群由一个主服务器 (master) 和多个块服务器 (chunkserver) 组成, GFS 集群会有很多客户端访问 每一个节点都是一个普通的 Linux 计算机, 运行的是一个用户级别的服务器进程 81

87 在 GFS 下, 每个文件都被拆成固定大小的块 (chunk) 每一个块都由主服务器根据块创建的时间产生一个全局唯一的以后不会改变的 64 位的块处理 (chunk handle) 标志 块服务器在本地磁盘上用 Linux 文件系统保存这些块, 并且根据块处理标志和字节区间, 通过 Linux 文件系统读写这些块的数据 出于可靠性的考虑, 每一个块都会在不同的块处理器上保存备份 主服务器负责管理所有的文件系统的元数据, 包括命名空间 访问控制信息 文件到块的映射关系 当前块的位置等信息 主服务器也同样控制系统级别的活动, 比如块的分配管理, 孤点块的垃圾回收机制 块服务器之间的块镜像管理 连接到各个应用系统的 GFS 客户端代码包含了文件系统的 API, 并且会和主服务器及块服务器进行通信处理, 代表应用程序进行读写数据的操作 客户端和主服务器进行元数据的操作, 但是所有的数据相关的通信是直接和块服务器进行的 由于在流式读取中, 每次都要读取非常多的文件内容, 并且读取动作是顺序读取, 因此, 在客户端没有设计缓存 没有设计缓存系统使得客户端以及整个系统都大大简化了 ( 少了缓存的同步机制 ) 块服务器不需要缓存文件数据, 因为块文件就像本地文件一样被保存, 所以 Linux 的缓存已经把常用的数据缓存到了内存里 下面简单介绍图 2.9 中的读取操作分段 首先, 客户端把应用要读取的文件名和偏移量, 根据固定的块大小, 转换为文件的块索引 然后向主服务器发送这个包含了文件名和块索引的请求 主服务器返回相关的块处理标志以及对应的位置 客户端缓存这些信息, 把文件名和块索引作为缓存的关键索引字 于是这个客户端就向对应位置的块服务器发起请求, 通常这个块服务器是离这个客户端最近的一个 请求给定了块处理标志以及需要在所请求的块内读取的字节区间 在这个块内, 再次操作数据将不用再通过客户端 - 主服务器的交互, 除非这个客户端本身的缓存信息过期了, 或者这个文件重新打开了 实际上, 客户端通常都会在请求中附加向主服务器询问多个块的信息, 主服务器会立刻给这个客户端回应这些块的信息 这个附加信息是通过几个几乎没有任何代价的客户端 - 主服务器的交互完成的 块的大小是设计的关键参数 Google 选择的块大小为 64MB, 远远大于典型的文件系统的块大小 每一个块的实例 ( 复制品 ) 都是作为在主服务器上的 Linux 文件格式存放的, 并且只有当需要的情况下才会增长 滞后分配空间的机制可以通过文件内部分段来避免空间浪费, 对于这样大的块大小来说, 内部分段可能是一个最大的缺陷了 选择一个很大的块可以提供一些重要的好处 首先, 它减少了客户端和主服务器的交互, 因为在同一个块内的读写操作只需要客户端初始询问一次主服务器关于块的位置信息就可以了 对主服务器访问的减少可以显著提高系统性能, 因为使用 GFS 的应用大部分是顺序读写超大文件的 即使是对小范围的随机读, 客户端也可以很容易地缓存许多大的数据文件的位置信息 其次, 由于是使用一个大的块, 客户端可以在一个块上完成更多的操作, 它可以通过维持一个到主服务器的 TCP 持久连接来减少网络管理量 第三, 它减少了元数据在主服务器上的大小, 使得 Google 应用程序可以把元数据保存在内存中 下面简单介绍图 2.9 中的主服务器 : 主服务器节点保存了三个主要的数据类型 : 文件和块的命名空间, 文件到块的映射关 82

88 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 系, 每一个块的副本的位置 所有的元数据都保存在主服务器的内存里 头两个类型 (namepspaces 和文件到块的映射 ) 同时也保存在主服务器本地磁盘的日志中 通过日志, 在主服务器宕机的时候, 我们可以简单 可靠地恢复主服务器的状态 主服务器并不持久化保存块位置信息 相反, 它在启动的时候以及主服务器加入集群的时候, 向每一个主服务器询问它的块信息 因为元数据都是在内存保存的, 所以在主服务器上操作很快 另外主服务器也很容易定时扫描后台所有的内部状态 定时扫描内部状态可以用来实现块的垃圾回收, 当主服务器失效的时候重新复制, 还可以作为服务器之间的块镜像, 在执行负载均衡和磁盘空间均衡任务时使用 因为我们采用内存保存元数据的方式, 如果需要支持更大的文件系统, 我们可以简单 可靠 高效 灵活地通过增加主服务器的内存来实现 主服务器并不持久化保存块服务器上的块记录 它只是在启动的时候简单地从块服务器取得这些信息 主服务器可以在启动之后一直保持自己的这些信息是最新的, 因为它控制所有的块的位置 上文提到的主服务器的日志信息保存了关键的元数据变化历史记录, 它是 GFS 的核心 不仅仅因为它是唯一持久化的元数据记录, 而且也是因为日志记录也是作为逻辑时间基线, 定义了并行操作的顺序 块以及文件, 都是用它们创建时刻的逻辑时间基线来作为唯一的并且永远唯一的标志 由于日志记录是极关键的, 因此必须可靠保存, 在元数据改变并且持久化之前, 对于客户端来说都是不可见的 ( 也就是说保证原子性 ) 否则, 就算是在块服务器完好的情况下, 也可能会丢失整个文件系统, 或者最近的客户端操作 因此, 把这个文件保存在多个不同的主机上, 并且只有当刷新这个相关的日志记录到本地和远程磁盘之后, 才会给客户端操作应答 主服务器可以每次刷新一批日志记录, 以减少刷新和复制这个日志导致的系统吞吐量 主服务器通过自己的日志记录进行自身文件系统状态的反演 为了减少启动时间, 我们必须尽量减少操作日志的大小 主服务器在日志增长超过某一个大小的时候, 执行检查点动作, 这样可以使下次启动的时候从本地硬盘读出这个最新的检查点, 然后反演有限记录数 检查点是一个类似 B- 树的格式, 可以直接映射到内存, 而不需要额外的分析 这更进一步加快了恢复的速度, 提高了可用性 对于主服务器的恢复, 只需要最新的检查点以及后续的日志文件 旧的检查点及其日志文件可以删掉了, 但我们还是要保存几个检查点以及日志文件, 用来防止发生比较大的故障 GFS 是一个松散的一致性检查的模型, 通过简单高效的实现, 来支持高度分布式计算的应用 下面详细讲解 GFS 的一致性模型 文件名字空间的改变 ( 如文件的创建 ) 是原子操作, 由主服务器来专门处理 名字空间的锁定保证了操作的原子性以及正确性, 主服务器的操作日志定义了这些操作的全局顺序 什么是文件区, 文件区就是在文件中的一小块内容 不论对文件进行何种操作, 文件区所处的状态都包括三种 : 一般成功 并发成功和失 83

89 败 表 2.1 列出了这些结果 当所有的客户端看到的都是相同的数据的时候, 并且与这些客户端从哪个数据的副本读取无关的时候, 这个文件区是一致性的 当一个更改操作成功完成, 而且没有并发写冲突时, 那么受影响的区就是确定的 ( 并且潜在一致性 ): 所有客户端都可以看到这个变化是什么 并发成功操作使得文件区是不确定的, 但是是一致性的 : 所有客户端都看到了相同的数据, 但是并不能确定到底什么变化发生了 通常, 这种变化由好多个变动混合片断组成 一个失败的改变会使得一个文件区不一致 ( 因此也不确定 ): 不同的用户可能在不同时间看到不同的数据 如果表 2.1 中数据更改可能是写一个记录或者是一个记录增加 写操作会导致一个应用指定的文件位置的数据写入动作 记录增加会导致数据 ( 记录 ) 增加, 这个增加即使是在并发操作中也至少是一个原子操作, 但是在并发记录增加中,GFS 选择一个偏移量增加 ( 与之对应的是, 一个 普通 增加操作是类似写到当前文件最底部的一个操作 ) 我们把偏移量返回给客户端, 并且标志包含这个记录的确定的区域的开始位置 另外,GFS 可以在这些记录之间增加填充, 或者仅仅是记录的重复 这些确定区间之间的填充或者记录的重复是不一致的, 并且通常是因为用户记录数据比较小造成的 表 2.1 文件区所处状态 写记录 一般成功 定义 定义 并发成功 一致定义 失败 非一致 增加记录 在一系列成功的改动之后, 改动后的文件区是确定的, 并且包含了最后一个改动所写入的数据 GFS 通过对所有的数据副本, 按照相同顺序对块进行提交数据的改动来保证这样的一致性, 并且采用块的版本号码控制机制来检查是否有过期的块改动, 这种检查通常在主服务器宕机的情况下使用 另外, 由于客户端会缓存这个块的位置, 因此可能会在信息刷新之前读到这个过期的数据副本 这个故障潜在发生的区间受块位置缓存的有效期限制, 并且也受到下次重新打开文件的限制, 重新打开文件会把这个文件所有的块相关的缓存信息全部丢弃而重新设置 此外, 由于多数文件都只是追加数据, 过期的数据副本通常返回一个较早的块尾部 ( 也就是说这种模式下, 过期的块返回的仅仅是说, 这个块它以为是最后一个块, 其实不是 ), 而不是返回一个过期的数据 开源 GFS HDFS Google 的文件系统究竟是如何写的, 我们不得而知 但是根据 Google 发表的论文以及 GFS 的相关资料, 可以知道 apache 下有一个开源实现 HDFS 这一小节, 我们来介绍 HDFS 的架构与设计 HDFS 的架构如图 2.10 所示 根据 GFS 中主服务器 / 块服务器的设计,HDFS 采用了主服务器 / 从属服务器架构 一个 HDFS 集群是由一个名称节点和一定数目的数据节点组成的 名称节点是一个中心服务器, 84

90 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 负责管理文件系统的名称空间和客户端对文件的访问 数据节点在集群中一般是一个节点一个, 负责管理节点上附带的存储 在内部, 一个文件会分成一个或多个块, 这些块存储在数据节点集合里 名称节点执行文件系统的名称空间操作, 例如打开 关闭 重命名文件和目录, 同时决定块到具体数据节点的映射 数据节点在名称节点的指挥下进行块的创建 删除和复制 名称节点和数据节点都是设计成可以运行在普通的 廉价的机器上 HDFS 采用 Java 语言开发, 因此可以部署在不同的操作系统平台上 一个典型的部署场景是一台机器运行一个单独的名称节点, 集群中的其他机器各自运行一个数据节点实例 这个架构并不排除一台机器上运行多个数据节点, 不过这比较少见 图 2.10 HDFS 架构名称节点运行在单一节点上大大简化了系统的架构 名称节点负责保管和管理所有的 HDFS 元数据, 而用户与数据节点的通信就不需要通过名称节点 ( 也就是说文件数据直接在数据节点上读写 ) HDFS 支持传统的层次型文件组织, 与大多数其他文件系统类似, 用户可以创建目录, 并在其中创建 删除 移动和重命名文件 名称节点维护文件系统的名称空间, 任何对文件系统的名称空间和文件属性的修改都将被名称节点记录下来 用户可以设置 HDFS 保存的文件的副本数目, 文件副本的数目称为文件的复制因子, 这个信息也是由名称节点保存 HDFS 被设计成在一个大集群中可靠地存储海量文件的系统 它将每个文件存储成块序列, 除了最后一个块, 所有的块大小都是相同的 文件的所有块都会被复制 每个文件的块大小和复制因子都是可配置的 复制因子可以在文件创建的时候配置, 而且以后也可以改变 HDFS 中的文件是单用户写模式, 并且严格要求在任何时候都只能有一个用户写入 名称节点全权管理块的复制, 它周期性地从集群中的每个数据节点接收心跳包和一个数据块报告 (Blockreport) 心跳包的接收表示该数据节点正常工作, 而数据块报告包括该数据节点上所有的块组成的列表 名称节点存储 HDFS 的元数据 对于任何修改文件元数据的操作, 名称节点都用一个名为 Editlog 的事务日志记录下来 例如, 在 HDFS 中创建一个文件, 名称节点就会在 Editlog 中插入一条记录来表示 ; 同样, 修改文件的复制因子也会在 Editlog 中插入一条记录 名称 85

91 节点在本地 OS 的文件系统中存储这个 Editlog 整个文件系统的名称空间, 包括块到文件的映射 文件的属性, 都存储在名为 FsImage 的文件中, 这个文件也放在名称节点所在系统的文件系统中 名称节点在内存中保存着整个文件系统的名称空间和文件块的映像 这个关键的元数据设计得很紧凑, 一个带有 4GB 内存的名称节点足以支撑海量的文件和目录 当名称节点启动时, 它从硬盘中读取 Editlog 和 FsImage, 将 Editlog 中的所有事务作用在内存中的 FsImage, 并将这个新版本的 FsImage 从内存中创新到硬盘上 这个过程称为检查点 (checkpoint) 在当前实现中, 检查点只在名称节点启动时发生 所有的 HDFS 通信协议都是构建在 TCP/IP 协议上的 客户端通过一个可配置的端口连接到名称节点, 通过各个端协议组件 (Client Protocol) 与名称节点交互 而数据节点是使用数据节点协议组件 (Datanode Protocol) 与名称节点交互 使用 HDFS 的应用都是处理大数据集合的 这些应用都是写数据一次, 而读是一次到多次, 并且读的速度要满足流式读 HDFS 支持文件的一次写入多次读取 一个典型的块大小是 64MB, 因而, 文件总是按照 64MB 大小切分成块, 每个块存储于不同的数据节点中 客户端创建文件的请求其实并没有立即发给名称节点, 事实上,HDFS 客户端会将文件数据缓存到本地的一个临时文件 应用的写操作被透明地重定向到这个临时文件 当这个临时文件累积的数据超过一个块的大小 ( 默认为 64MB), 客户端才会联系名称节点 名称节点将文件名插入文件系统的层次结构中, 并且给它分配一个数据块, 然后返回数据节点的标识符和目标数据块给客户端 客户端将本地临时文件刷新到指定的数据节点上 当文件关闭时, 在临时文件中剩余的没有刷新的数据也会传输到指定的数据节点, 然后客户端告诉名称节点文件已经关闭 此时名称节点才将文件创建操作提交到持久存储 如果名称节点在文件关闭前挂了, 则该文件将丢失 当某个客户端向 HDFS 文件写数据的时候, 一开始是写入本地临时文件, 假设该文件的复制因子设置为 3, 那么客户端会从名称节点获取一张数据节点列表来存放副本 然后客户端开始向第一个数据节点传输数据, 第一个数据节点一小部分一小部分 (4kb) 地接收数据, 将每个部分写入本地仓库, 并且同时将该部分传输到第二个数据节点 第二个数据节点也是这样边收边传, 一小部分一小部分地收, 存储在本地仓库, 同时传给第三个数据节点, 第三个数据节点就仅仅是接收并存储了 这就是流水线式的复制 HDFS 给应用提供了多种访问方式, 可以通过 DFSShell 命令行与 HDFS 数据进行交互, 也可以通过 Java API 调用, 还可以通过 C 语言的封装 API 访问, 并且提供了浏览器访问的方式 最后, 通过一个简单的小例子, 展示一下如何使用 Java 访问 HDFS import java.io.inputstream; import java.net.url; import org.apache.hadoop.fs.fsurlstreamhandlerfactory; import org.apache.hadoop.io.ioutils; public class DataReadByURL static 86

92 Error! Use the Home tab to apply 标题 1, 部分标题 1 to URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory()); public static void main(string[] args) throws Exception InputStream in = null; try in = new URL("hdfs:// :9000/data/mydata").openStream(); IOUtils.copyBytes(in,System.out,2048,false); finally IOUtils.closeStream(in); 上面讨论了利用 HDFS 的 URL 方式读取 HDFS 内文件内容的方法, 下面讨论如何使用 HDFS 中的 API 读取 HDFS 内的文件 HDFS 主要通过 FileSystem 类来完成对文件的打开操作 和 Java 使用 java.io.file 来表示文件不同,HDFS 文件系统中的文件是通过 Hadoop 的 Path 类来表示的 FileSystem 通过静态方法 get(configuration conf) 获得 FileSystem 的实例 通过 FileSystem 的 open() seek() 等方法可以实现对 HDFS 的访问, 具体的方法如下所示 : public FSDataInputStream open(path f) throws IOException public abstract FSDataInputStream open(path f, int buffersize) throws IOException; 下面来看一个通过 HDFS 的 API 访问文件系统的例子 : import org.apache.hadoop.fs.*; import org.apache.hadoop.conf.*; import org.apache.hadoop.io.*; public class HDFSCatWithAPI public static void main(string[] args) throws Exception // 指定 Configuration Configuration conf = new Configuration(); // 定义一个 DataInputStream FSDataInputStream in = null; try // 得到文件系统的实例 FileSystem fs = FileSystem.get(conf); // 通过 FileSystem 的 open 方法打开一个指定的文件 in = fs.open(new Path("hdfs://localhost:9000/user/myname/input/fixFontsPath.sh")); // 将 InputStream 中的内容通过 IOUtils 的 copybytes 方法复制到 System.out 中 IOUtils.copyBytes(in,System.out,4096,false); //seek 到 position 1 in.seek(1); // 执行一边复制一边输出工作 87

93 IOUtils.copyBytes(in,System.out,4096,false); finally IOUtils.closeStream(in); 输出如下 : #!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version...( 中间内容略去 ) </map:sitemap> EOF!/bin/sh # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version...( 中间内容略去 ) </map:sitemap> EOF 2.4 Google 网页存储秘诀 BigTable 在前面的章节里, 提到过分布式系统通常都采用 key/value 的形式存储数据 比如爬虫抓取页面后, 页面的存储就是采用 key/value 形式 针对这一特点,Google 在 GFS 文件系统的基础上, 设计了一种名为 BigTable 的 key/value 型分布式数据库系统 应用程序通常都不会直接操作 GFS 文件系统, 而直接操作它的上一级存储结构 BigTable 这正如一般文件系统和关系数据库的道理一样 这一节, 我们将详细讲述 BigTable 的相关知识 详解 BigTable Bigtable( 下文中简称 BT) 是用来存储大规模结构化数据的, 它最多可以存储 2 50 个字节, 分布在几千个普通的服务器上 Google 的很多项目都使用 BT 存储数据, 包括网页查询 Google 地图和 Google 金融 这些应用对 BT 的要求各不相同 : 数据大小 ( 从 URL 到网页到卫星图像 ) 不同, 反应速度不同 ( 从后端的大批处理到实时数据服务 ) 对于不同的项目需求, 88

94 Error! Use the Home tab to apply 标题 1, 部分标题 1 to BT 都提供了灵活高效的服务 设计 BT 的目标是建立一个可以广泛应用的 高度扩缩的 高可靠性和高可用性的分布式数据库系统 现在,Google 至少有 60 多个产品和应用都采用 BT 作为存储结构 下面我们从几个方面介绍 BT 1. BT 与关系数据库 BT 在很多地方和关系数据库类似 : 它采用了许多关系数据库的实现策略 但和它们不同的是,BT 采用了不同的用户接口 BT 不支持完全的关系数据模型, 而是为客户提供了简单的数据模型, 让客户来动态控制数据的分布和格式 ( 就是只存储字符串, 格式由客户来解释 ), 这样能大幅度地提高访问速度 数据的下标是行和列的名字, 数据本身可以是任意字符串 BT 的数据是字符串, 没有具体的类型 客户会把各种结构化或者半结构化的数据 ( 比如说日期串 ) 序列化成字符串 最后, 可以使用配置文件来控制数据是放在内存里还是在硬盘上 2. BT 逻辑存储结构 BT 的本质是一个稀疏的 分布式的 长期存储的 多维度的和排序的 Map Map 的 key 是行关键字 (Row) 列关键字(Column) 和时间戳 (Timestamp) Value 是一个普通的 bytes 数组 如下所示 : (row:string, column:string,time:int64)->string 图 2.11 是 BT 存储网页的底层数据结构示意图 (webtable) 其中, 每个网页的内容与相关信息作为一行, 无论它有多少列 如图 2.11 所示, 以反转的 URL 作为 Row, 列关键字是 contents anchor:my.look.ca 和 anchor:cnnsi.com, 时间戳是 t3, t5, t6, t8, t9 等 如果要查询 t5 时间 URL 为 的页面内容, 可以使用 com.cnn.www,contents,t5 作为 key 去 BT 中查询相应的 value 图 2.11 BT 存储示意图表中的行 (Row) 可以是任意长度的字符串 ( 目前最多支持 64KB, 多数情况下 10~100 个字节就足够了 ) 在同一行下的每一个读写操作都是原子操作( 不管读写这一行里多少个不同列 ), 这使得在对同一行进行并发操作时, 用户对于系统行为更容易理解和掌控 BT 通过行关键字在字典中的顺序来维护数据 一张表可以动态划分成多个连续 子表 (tablet) 这些 子表 由一些连续行组成, 它是数据分布和负载均衡的单位 这使得读取较少的连续行比较有效率, 通常只需要少量机器之间的通信即可 用户可以利用这个属性来 89

95 选择行关键字, 从而达到较好的数据访问 局部性 举例来说, 在 webtable 中, 通过反转 URL 中主机名的方式, 可以把同一个域名下的网页组织成连续行 具体而言, 可以把站点 maps.google.com/index.html 中的数据存放在关键字 com.google.maps/index.html 所对应的数据中 这种存放方式可以让基于主机和基于域名的分析更加有效 一组列关键字组成了 列族 (column famliy), 这是访问控制的基本单位 同一列族下存放的所有数据通常都是同一类型的 列族 必须先创建, 然后才能在其中的 列关键字 下存放数据 列族 创建后, 其中任何一个 列关键字 都可使用 列关键字 用如下语法命名 : 列族 : 限定词 列族 名必须是看得懂的字符串, 而限定词可以是任意字符串 比如,webtable 可以有个 列族 叫 language, 存放撰写网页的语言 我们在 language 列族 中只用一个 列关键字, 用来存放网页的语言标识符 该表的另一个有用的 列族 是 anchor 列族 的每一个 列关键字 代表一个锚链接, 访问控制 磁盘使用统计和内存使用统计, 均可在 列族 这个层面进行 在图 2.11 的例子中, 可以使用这些功能来管理不同应用 : 有的应用添加新的基本数据, 有的读取基本数据并创建引申的 列族, 有的则只能浏览数据 ( 甚至可能因为隐私权的原因不能浏览所有数据 ) BT 表中的每一个表项都可以包含同一数据的多个版本, 由时间戳来索引 BT 的时间戳是 64 位整型, 表示准确到毫秒的 实时 需要避免冲突的应用程序必须自己产生具有唯一性的时间戳 不同版本的表项内容按时间戳倒序排列, 即最新的排在前面 在图 2.11 中, contents: 列存放一个网页被抓取的时间戳 BT 使用 Google 分布式文件系统 (GFS) 来存储日志和数据文件 一个 BT 集群通常在一个共享的机器池中工作, 池中的机器还运行着其他分布式应用,BT 和其他程序共享机器 (BT 的瓶颈是 I/O 内存, 可以和 CPU 要求高的程序并存 ) BT 依赖集群管理系统来安排工作, 在共享的机器上管理资源, 处理失效机器并监视机器状态 3. BT 内部存储格式 SSTable BT 内部采用 SSTable 格式存储数据 SSTable 提供了一个从关键字到值的映射, 关键字和值都可以是任意字符串, 如图 2.12 所示 64KB 64KB 64KB SSTable block block block index 图 2.12 SSTable 示意图在 SSTable 格式中, 映射是排序的 存储的 ( 不会因为掉电而丢失 ) 不可更改的 并且可以进行如下操作 : (1) 通过关键字查询相关的值 (2) 根据给出的关键字范围遍历所有的关键字和值 SSTable 内部包含一列数据块, 通常每个块的大小是 64KB, 但是大小是可以配置的 SSTable 中块索引 (index) 大小是 16 位 块索引 ( 存储在 SSTable 的最后 ) 用来定位数据块 当 90

96 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 打开 SSTable 的时候, 块索引被读入内存 每次查找都可以用一次硬盘搜索完成, 首先在内存中的索引里进行二分查找, 获得数据块的位置, 然后根据位置信息直接到硬盘读取数据块 最佳情况是 : 整个 SSTable 可以被放在内存里, 这样一来就不必访问硬盘了 4. BT 的锁 BT 还依赖一个高度可用的分布式数据锁服务 (Chubby) 一个 Chubby 由 5 个 活跃 的备份构成, 其中一个被这些备份选成主备份, 并且处理请求 这个服务只有在大多数备份都是 活跃 的并且互相通信的时候, 才是 活跃 的 当有机器失效的时候,Chubby 使用一定的算法来保证备份的一致性 Chubby 提供了一个名字空间, 里面包括目录和一系列文件 每个目录或者文件可以当成一个锁来用, 读写文件操作都是原子操作 Chubby 客户端的程序库提供了对 Chubby 文件的一致性缓存 每个 Chubby 客户维护一个和 Chubby 通信的会话 如果客户不能在一定时间内更新自己的会话, 会话就失效了 当一个会话失效时, 其拥有的锁和打开的文件句柄都失效 Chubby 客户可以在文件和目录上登记回调函数, 以获得改变或者会话过期的通知 BT 使用锁服务来完成以下几个任务 : (1) 保证任何时间最多只有一个活跃的主备份 (2) 存储 BT 数据的启动位置 (3) 发现 子表 服务器, 并处理 tablet 服务器失效的情况 (4) 存储 BT 数据的 模式 信息 ( 每张表的列信息 ) (5) 存储访问权限列表 在 Chubby 中, 存储了 BT 的访问权限, 如果 Chubby 不能访问, 那么由于获取不到访问权限, 因此 BT 就也不能访问了 5. BT 的主要组成部件 BT 主要由三个构件组成 : (1) 一个客户端的链接库 (2) 一个主服务器 (3) 许多 子表 服务器 子表 服务器可以动态地从群组中被添加和删除, 以适应流量的改变 主服务器的作用是给 子表 服务器分配 子表 探测 子表 服务器的增加和缩减 平衡 子表 服务器负载, 以及回收 GFS 系统中文件的碎片 此外, 它还可以创建模式表 一个 子表 服务器管理许多子表 ( 一般每个 子表 服务器可以管理 10 到 1000 个子表 ) 子表 服务器处理它所管理的 子表 的读写请求, 还可以将那些变得很大的 子表 分割 像许多单主机的分布式存储系统一样, 客户端数据不是通过主服务器来传输的 : 客户端要读写时直接与 子表 服务器通信 因为 BT 客户端并不依赖主服务器来请求 子表 本地信息, 大多数客户端从不与主服务器通信 因此, 实际上主机的负载往往很小 一个 BT 群组可以存储大量的表 每一个表有许多 子表, 并且每个 子表 包含一 91

97 行上所有相关的数据 最初, 每个表只包含一个子表 随着表的增长, 自动分成了许多的 子表, 每个子表的默认大小为 100~200MB BT 用三层体系的 B+ 树来存储子表的地址信息, 如图 2.13 所示 图 2.13 BT 的 B+ 树的体系结构第一层是一个存储在 Chubby 中的文件, 它包含 根子表 (root tablet) 的地址 如图 2.13 所示, 根子表 包含一些 元数据表 (MetaData tablets) 的地址信息 这些 元数据表 包含用户 子表 的地址信息 根子表 是 元数据表 中的第一个 子表, 但它从不会被分割 客户端缓存 子表 地址 如果客户端发现缓存的地址信息是错误的, 那么它会递归地提升 子表 地址等级 如果客户端缓存是空的, 寻址算法需要三个网络往返过程, 包括一次从 Chubby 的读取 如果客户端缓存是过期的, 那么寻址算法可能要用 6 个往返过程 尽管 子表 地址缓存在内存里, 不需要 GFS 访问, 但还是可以通过客户端预提取 子表地址 进一步降低性能损耗 子表 一次被分配给一个子表服务器 主服务器跟踪 活跃 的 子表 服务器集合以及当前 子表 对 子表服务器 的分配状况 当一个 子表 还没有被分配, 并且有一个 子表 服务器是可用的, 主机就通过传输一个 子表 装载请求到 子表 服务器来分配 子表 BT 使用 Chubby 来跟踪 子表 服务器 当 子表 服务器启动时, 它在一个特别的 Chubby 目录中创建一个文件, 并且获得一个互斥锁 主机通过监听这个目录 ( 服务器目录 ) 来发现 子表 服务器 如果 子表 服务器丢失了自己的互斥锁, 就会停止为它的 子表 服务 主机负责探测何时 子表 服务器不再为它的 子表 服务, 以便可以尽快地分配那些 子表 为了达到这个目的, 主机会周期性地询问每个 子表 服务器的锁的状态 如果一个 子表 服务器报告它丢失了锁, 主机会尝试在服务器的文件中获取一把互斥锁 如果主机能获得这把锁, 则表示 Chubby 是可用的并且 子表 服务器已经失效 因此主机通过删除 子表 服务器的服务文件来确保它不会再工作 一旦服务文件被删除, 主机可以把先前分配给这台服务器的所有 子表 移到未分配的 子表 集合中 子表 的持久化状态存储在 GFS 文件里, 如图 2.14 所示 92

98 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 图 2.14 子表持久化每次更新 子表 前都要更新 子表 的重做日志 (redo log) 并且最近更新的内容( 已经提交的但还没有写入到磁盘的内容 ) 会存放在内存中, 称为 memtable 之前的更新( 已经提交的并且固化在磁盘的内容 ) 会被持久化到一系列的 SSTable 中 当一个写操作请求过来时, 子表 服务器会先写日志, 当提交的时候, 就把这些更新写入 memtable 中 之后等系统不繁忙的时候, 就写入 SSTable 中 ( 这个过程和 Oracle 数据库写操作基本一致 ) 如果请求是读操作, 则可以根据当前的 memtable 和 SSTable 中的内容进行合并, 然后对请求返回结果 因为 memtable 和 SSTable 有相同的结构, 因此, 合并是一个非常快的操作 开源 BigTable HBase Google 提出 云计算 理论之后, 很多团队都开始根据这一理论, 开发自己的云计算存储模型 其中,HBase 是一个比较成功的实例 本小节就讲述 HBase 的相关知识 HBase 是一个 Apache 开源项目, 它的目标是提供一个在 Hadoop 分布式环境中运行的类似于 BT 的存储系统 正如同 Google 将 BT 架设在自己的分布式存储系统 GFS 中一样, HBase 是基于 HDFS 的 在 HBase 中, 数据在逻辑上被组织成为表 行和列 客户可以使用类似于 iterator 的接口来遍历行数据, 同时也可以通过行值来获取列值 同一个行对应多个不同版本的列值 1. 数据模型 HBase 使用的数据模型和 BT 类似 应用程序将数据行存储在 打上标签 的表中 一个数据行包含一个排好序的行键值和任意长度的列值 列名的格式为 <family>:<label>, 其中 <family> 与 <label> 可以为任意的字节数组 表的 <family> 集合是固定的 如果希望修改表的 <family> 字段, 需要管理员来操作 ; 但可以随时增加 <label>, 而无需事先声明它 HBase 在磁盘上按照 <family> 储存数据, 所以 <family> 里的所有项应该有相同的读 / 写方式 默认每次将一行数据进行锁定, 行数据的写入是原子操作, 写入时这行数据将被锁定, 93

99 同时进行读和写的操作 2. 概念模型从概念上来看, 一个表由多个数据行组成, 每个数据行中的列不一定有值 表 2.2 展示了一个来自 BigTable 的数据 表 2.2 BigTable 中的数据 Row Key Time Stamp Column "contents:" Column "anchor:" Column "mime:" "com.cnn.www" t9 "anchor:cnnsi.com" "CNN" t8 "anchor:my.look.ca" "CNN.com" t6 "<html>..." "text/html" "com.cnn.www" t5 t3 "<html>..." "<html>..." 3. 物理存储模型从概念上看, 表数据是按照稀疏的行来存储的, 但是物理上, 它们是按照列来存储的 理解这个概念对考虑表的设计和程序设计是非常重要的 回到先前的那个例子, 物理存储结构如表 2.3~ 表 2.5 所示 表 2.3 物理存储结构 (1) Row Key Time Stamp Column "contents:" "com.cnn.www" t6 "<html>..." t5 t3 "<html>..." "<html>..." 表 2.4 物理存储结构 (2) Row Key Time Stamp Column "anchor:" "com.cnn.www" t9 "anchor:cnnsi.com" "CNN" t8 "anchor:my.look.ca" "CNN.com" 94

100 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 表 2.5 物理存储结构 (3) Row Key Time Stamp Column "mime:" "com.cnn.www" t6 "text/html" 从上面的表中可以看出 : 空的列并没有被存储 所以当请求列值为 "contents:" 的时候, 会返回空值 如果请求数据的时候, 没有提供时间戳 (time stamp) 的值, 那么请求的数据将返回最新的值 ( 时间戳最近的值 ), 并且在实际的存储中, 所有的值都是按照时间戳降序排列的 4. 行的范围 : 区域 数据表是由一系列按升序排序的数据行组成的, 在同一行中, 数据列按名字的升序排列, 时间戳按降序排列 物理上, 数据表被拆分成由多个数据行组成的区域 ( 这个概念在 BT 中就是 tablet) 区域包含从开始行( 包含 ) 到终止行 ( 排除 ) 所有区域构成一个数据表 与 BT 不同的是, 在 BT 中, 数据行区域由表名和结束行值决定, 而 HBase 的区域由表名和开始行值决定 区域中的每一个 列族 都由一个名为 HStore 的对象管理 每个 HStore 由一个或多个 MapFiles(Hadoop 中的一个文件类型 ) 组成 MapFiles 的概念类似于 Google 的 SSTable MapFiles 一旦被关闭, 就是不可修改的类型了 MapFiles 被存储在 Hadoop 的 HDFS 中, 其与 SSTable 的不同之处如下 : MapFiles 目前还无法进行内存的映射 MapFiles 由一些稀疏的索引文件来维护, 而 SSTable 在其文件的最后 HBase 扩展了 MapFiles, 所以使用布隆过滤器的时候可以提高查找的性能 5. 架构与实现 HBase 由以下三个主要构件组成 1) HBaseMaster( 类似于 BT 主服务器 ) HBaseMaster 负责给 HRegionServer 分配区域 分配的第一个区域就是 ROOT 区域, 它用于定位所有的 META 区域 一旦所有的 META 区域被分配好,master 便开始分配用户区域给 HReginServer, 同时维护每个 HReginServer 的负载平衡 HBaseMaster 还含有一个指向包含有 ROOT 区域的 HReginServer 地址 同时,HBaseMaster 会监控每台 HReginServer 的健康状况, 如果某台 HReginServer 不可用, 它将会把不可用的 HReginServer 替换成其他 HReginServer 另外,HBaseMaster 还负责对数据表进行管理, 比如让表处于有效 (online) 或失效 (offline) 的状态, 改变表的结构 ( 增加或者减少列族 ), 等等 与 BT 不同的是, 如果 HBaseMaster 失效了, 整个集群就会关闭 在 BT 中, 即使主服务器失效了,Tablet 服务器还是可以提供服务的 而 HBase 使用单点访问模式 : 所有的 HReginServer 都访问 HBaseMaster 95

101 2) HRegionServer( 类似于 BT tablet 服务器 ) HReginServer 负责处理用户读和写的操作 HReginServer 通过与 HBaseMaster 通信获取需要服务的数据表, 并向 HBaseMaster 反馈自己的运行状况 HReginServer 的任务主要包括以下几类 : (1) 写请求 (Write Request) 当一个写的请求到来时, 它首先会写到一个称为 HLog 的日志模块中 HLog 被缓存在内存中, 称为 Memcache, 每一个 HStore 只能有一个 Memcache (2) 读请求 (Read Request) 当读取的请求到来时,HReginServer 会先在 Memcache 中寻找该数据, 当找不到时, 才会去 MapFiles 中寻找 (3) 刷新缓存 (Cache Flushe) 当 Memcache 到达配置的大小以后, 会创建一个 MapFile, 将其写到磁盘中 这将减少 HReginServer 的内存压力 在读和写的过程中,Cache Flushe 会经常发生 当创建一个新的 MapFile 时, 读和写的操作会被挂起, 直到新的 MapFile 创建好, 并被加入 HStroe 的管理中才可以使用 (4) 压缩 (Compaction) 当一定数量的 MapFiles 超过一个配置的阈值之后, 压缩操作就开始执行 压缩操作的主要工作就是周期性地将一些 MapFiles 合并成一个 MapFile 在执行压缩操作的过程中,HReginServer 的读和写操作将被挂起, 直到操作执行完毕 (5) 区域切分 (Region Split) 当一个 HStore 所管理的 MapFiles 超过一个配置 ( 当前是 256MB) 的值以后, 将会执行区域的切分操作 区域的切分操作将原先的区域对半分割为两个新的区域 在进行区域切分的操作过程中, 读和写的操作将被挂起, 直到完成为止 3) HBase 客户端 ( 由 org.apache.hadoop.hbase.client.htable 定义 ) HBase 客户端负责寻找提供需求数据的 HRegin Server 在这个过程中,HBase 客户端将首先与 HBase 主服务器通信, 找到 ROOT 区域 这个操作是客户端和主服务器之间仅有的通信操作 一旦 ROOT 区域找到以后, 客户端就可以通过扫描 ROOT 定位实际提供数据的 HRegin Server 当定位到提供数据的 HRegin Server 以后, 客户端就可以通过这个 HRegin Server 找到需要的数据了 这些信息将会被客户端缓存起来, 当下次请求的时候, 就不需要再走上面的这个流程了 当这些区域中的某个区域不可用时, 客户端将会逆向执行上面的过程, 直到找到实际提供数据的 HRegin Server 为止 接下来, 简单介绍如何操作 HBase HBase 与我们常用数据库的最大差别就是列存储和无数据类型, 所有数据都以 string 类型存储 并且如果在 HBase 中存储了 5 个字段, 但实际只有 4 个字段有值, 那么为空的那个字段不占用空间 具体使用请参考下面代码 : /** 96

102 Error! Use the Home tab to apply 标题 1, 部分标题 1 to * 定义几个常量 */ public static HBaseConfiguration conf = new HBaseConfiguration(); static HTable table = null; /** * 创建 hbase table table IOException */ public static void creattable(string tablename) throws IOException HBaseAdmin admin = new HBaseAdmin(conf); if (!admin.tableexists(new Text(tablename))) HTableDescriptor tabledesc = new HTableDescriptor(tablename); tabledesc.addfamily(new HColumnDescriptor("ip:")); tabledesc.addfamily(new HColumnDescriptor("time:")); tabledesc.addfamily(new HColumnDescriptor("type:")); tabledesc.addfamily(new HColumnDescriptor("cookie:")); // 注意这个 C 列, 下面简单以此列来说明列存储 tabledesc.addfamily(new HColumnDescriptor("c:")); admin.createtable(tabledesc); System.out.println("table create ok!!!"); else System.out.println("table Already exists"); /** * 录入数据 Exception */ public static void insertdata() throws Exception // 读取日志文件 BufferedReader reader = new BufferedReader(new FileReader("log file name")); if(table==null) table = new HTable(conf, new Text(tablename)); String line; while((line = reader.readline())!= null) // 这里就不介绍了, 先前有说明 LogAccess log = new LogAccess(line); // 这里使用 time+cookie 为 row 关键字, 确保不重复, 如果 cookie 记录有重复, // 将区别对待, 这里暂不多做说明 String row = createrow(log.gettime(),log.getcookie()); long lockid = table.startupdate(new Text(row)); if(!log.getip().equals("") && log.getip()!=null) table.put(lockid, new Text("ip:"), log.getip().getbytes()); if(!log.gettime().equals("") && log.gettime()!=null) 97

103 table.put(lockid, new Text("time:"), log.gettime().getbytes()); if(!log.gettype().equals("") && log.gettype()!=null) table.put(lockid, new Text("type:"), log.gettype().getbytes()); if(!log.getcookie().equals("") && log.getcookie()!=null) table.put(lockid, new Text("cookie:"), log.getcookie().getbytes()); if(!log.getregmark().equals("") && log.getregmark()!=null) table.put(lockid, new Text("c:_regmark"), log.getregmark().getbytes()); if(!log.getregmark2().equals("") && log.getregmark2()!=null) table.put(lockid, new Text("c:_regmark2"), log.getregmark2().getbytes()); if(!log.getsendshow().equals("") && log.getsendshow()!=null) table.put(lockid, new Text("c:_sendshow"), log.getsendshow().getbytes()); if(!log.getcurrenturl().equals("") && log.getcurrenturl()!=null) table.put(lockid, new Text("c:_currenturl"), log.getcurrenturl().getbytes()); if(!log.getagent().equals("") && log.getagent()!=null) table.put(lockid, new Text("c:_agent"), log.getagent().getbytes()); // 存入数据 table.commit(lockid); 本小节是针对 HBase 的简单介绍, 如果想深入了解的话, 请参考相关的内容 2.5 Google 的成功之道 MapReduce 算法 MapReduce 算法是处理 / 产生海量数据集的编程模型 在 MapReduce 算法中, 用户指定一个 map() 函数, 通过这个 map() 函数处理键 / 值 (key/value) 对, 产生一系列的中间键 / 值 (key/value) 对, 并且使用一个 reduce() 函数来合并具有相同键 (key) 的中间键值 (key/value) 对中的值 (value) 现实生活中, 很多任务的实现都是基于这个模式的 使用 MapReduce 算法的程序可以将任务自动分布到一个由普通机器组成的超大规模集群上并发执行 大多数实现 MapReduce 算法的框架 ( 比如 Hadoop) 都会解决输入数据的分布细节, 跨越机器集群的程序执行调度, 处理机器的失效和机器之间的通信等问题 使用框架来实现 MapReduce 算法, 程序员可以不需要有什么并发处理或者分布式系统的经验, 就可以处理超大规模的分布式系统的资源 考虑这样一个例子, 在很大的文档集合中统计每个单词出现的次数 写出如下类似的伪代码 : map(string key, String value): 98

104 Error! Use the Home tab to apply 标题 1, 部分标题 1 to // key: 文档名 // value: 文档内容 for each word w in value: EmitIntermediate(w, "1"); reduce(string key, Iterator values): // key: a word // values: 一个针对这个 word 的计数列表 ( 每个列表记录文档中 word 出现的次数 ) int result = 0; for each v in values: result += ParseInt(v); Emit(AsString(result)); map() 函数检查每个单词, 并且每检查一次, 就将当前检查的单词的计数器加 1 Reduce() 函数将特定单词出现的次数进行合并 上面的例子是用字符串作为输入和输出的, 从概念上讲,map() 和 reduce() 函数的输入 / 输出类型遵循以下原则 : map (k1,v1) list(k2,v2) reduce (k2,list(v2)) list(v2) 也就是说, 输入的键 (key) 值和输出的键 (key) 值属于不同的域 而中间的键值和输出的键值属于相同的域 MapReduce 算法在实际的系统中使用非常广泛, 下面是一些简单有趣的例子, 它们都可以使用 MapReduce 算法来进行计算 分布式 Grep: 如果 map() 函数检查输入行, 满足条件的时候,map() 函数就把本行输出 Reduce() 函数就是一个直通函数, 简单地把中间数据输出就可以了 URL 访问频率统计 :map() 函数处理请求和应答 (URL, 1) 的 log Reduce() 函数把所有相同的 URL 的值合并, 并且输出一个成对的 (URL, 总个数 ) 逆向 Web-Link 图 :map() 函数输出所有包含指向目标 URL 的网页, 用 ( 目标 URL, 源 URL) 这样的结构对输出 Reduce() 函数聚合所有关联相同目标 URL 的列表 源 URL 并且输出一个 ( 目标 URL,list( 源 URL)) 的结构 主机关键向量指标 (Term-Vector per Hosts): 关键词向量指标简而言之就是指在一个文档或者一组文档中的重点词出现的频率, 用 (word,frequency) 表达 map() 函数计算每一个输入文档 ( 主机名字是从文档的 URL 取出的 ) 的关键词向量, 然后输出 (hostname, 关键词向量 (Term-Vector)) Reduce() 函数处理所有相同主机的所有文档关键词向量 去掉不常用的关键词, 并且输出最终的 (hostname, 关键词向量 ) 对 逆序索引 :map() 函数分析每一个文档, 并且产生一个序列 (word, document ID) 组 Reduce() 函数处理指定 word 的所有序列组, 并且对相关的 document ID 进行排序, 输出一个 (word,list(document ID)) 组 所有的输出组, 组成一个简单的逆序索引 通过这种方法可以很容易地保持关键词在文档库中的位置 分布式排序 :map() 函数从每条记录中抽取关键字, 并且产生 (key,record) 对 Reduce() 函数原样输出所有的关键字对 99

105 2.5.1 详解 MapReduce 算法 map() 函数把输入数据进行切割 ( 比如分为 M 块 ) 之后, 分布到不同的机器上执行 ( 例如前面介绍的单词统计例子, 可以把每一个文件分配到一台机器上执行 ) Reduce() 函数通过产生的键 key( 例如可以根据某种分区函数 ( 比如 hash(key) mod R),R 的值和分区函数都是由用户指定 ) 将 map() 的结果集分成 R 块, 然后分别在 R 台机器上执行 图 2.15 是 MapReduce 算法示意图 当用户程序调用 MapReduce 函数时, 就会引起如下的操作 图 2.15 MapReduce 运行机制 (1) MapReduce 函数库首先把输入文件分成 M 块, 每块大概 16MB 到 64MB 接着在集群的机器上执行处理程序 如图 2.14 所示,MapReduce 算法运行过程中有一个主控程序, 称为 master 主控程序会产生很多作业程序, 称为 worker 并且把 M 个 map 任务和 R 个 reduce 任务分配给这些 worker, 让它们去完成 (2) 被分配了 map 任务的 worker 读取并处理相关的输入 ( 这里的输入是指已经被切割的输入小块 splite) 它处理输入的数据, 并且将分析出的键 / 值 (key/value) 对传递给用户定义的 reduce() 函数 map() 函数产生的中间结果键 / 值 (key/value) 对暂时缓冲到内存 (3) map() 函数缓冲到内存的中间结果将被定时刷写到本地硬盘, 这些数据通过分区函数分成 R 个区 这些中间结果在本地硬盘的位置信息将被发送回 master, 然后这个 master 负责把这些位置信息传送给 reduce() 函数的 worker 100

106 Error! Use the Home tab to apply 标题 1, 部分标题 1 to (4) 当 master 通知了 reduce() 函数的 worker 关于中间键 / 值 (key/value) 对的位置时, worker 调用远程方法从 map() 函数的 worker 机器的本地硬盘上读取缓冲的中间数据 当 reduce() 函数的 worker 读取到了所有的中间数据, 它就使用这些中间数据的键 (key) 进行排序, 这样可以使得相同键 (key) 的值都在一起 如果中间结果集太大了, 那么就需要使用外排序 (5) reduce() 函数的 worker 根据每一个中间结果的键 (key) 来遍历排序后的数据, 并且把键 (key) 和相关的中间结果值 (value) 集合传递给 reduce() 函数 reduce() 函数的 worker 最终把输出结果存放在 master 机器的一个输出文件中 (6) 当所有的 map 任务和 reduce 任务都已经完成后,master 激活用户程序 在这时, MapReduce 返回用户程序的调用点 (7) 当以上步骤成功结束以后,MapReduce 的执行数据存放在总计 R 个输出文件中 ( 每个输出文件都是由 reduce 任务产生的, 这些文件名是用户指定的 ) 通常, 用户不需要将这 R 个输出文件合并到一个文件, 他们通常把这些文件作为输入传递给另一个 MapReduce 调用, 或者用另一个分布式应用来处理这些文件, 并且这些分布式应用把这些文件看成为输入文件由于分区 (partition) 成为的多个块文件 MapReduce 容错处理由于 MapReduce 函数库是设计用于在成百上千台机器上处理海量数据的, 所以这个函数库必须考虑到机器故障的容错处理 下面就详细介绍 MapReduce 的容错处理机制 如图 2.14 所示,master 会定期发送命令轮询每一台 worker 机器 如果在一定时间内有一台 worker 机器一直没有响应,master 就认为这个 worker 失效了 所有这台 worker 完成的 map 任务都被设置成为它们的初始空闲状态, 并且因此可以被其他 worker 调度执行 类似的, 所有这个机器上正在处理的 map 任务或者 reduce 任务都被设置成为空闲状态, 被其他 worker 重新执行 在失效机器上的已经完成的 map 任务还需要再次重新执行, 这是因为中间结果存放在这个失效的机器上, 所以导致中间结果无法访问 已经完成的 recude 任务无需再次执行, 因为它们的结果已经保存在全局的文件系统中了 当 map 任务首先由 A worker 执行, 随后被 B worker 执行的时候 ( 因为 A 机器失效了 ), 所有执行 reduce 任务的 worker 都会被通知 所有还没有来得及从 A 上读取数据的 worker 都会从 B 上读取数据 大多数 MapReduce 函数库都能够有效地支持很多 worker 失效的情况 比如, 在一个网络例行维护时, 可能会导致每次大约有 80 台机器在几分钟之内不能访问 master 会简单地使这些不能访问的 worker 上的工作再执行一次, 并且继续调度进程, 直到所有任务都完成 在 master 中, 定期会设定检查点 (checkpoint) 如果 master 任务失效了, 可以从上次最后一个检查点开始启动另一个 master 进程 map 和 reduce 任务可靠性是由输出进行原子提交来完成的 每一个正在进行的任务把输出写到一个私有的临时文件中 当全部写完之后, 进行提交操作, 并把这些临时文件变为永久保存的文件 101

107 2.5.3 MapReduce 实现架构 当前, 针对 MapReduce 算法的实现架构如图 2.16 所示 主应用程序 (Main Application) 协调其他实例进行 map() 或者 reduce() 操作, 然后从每个 reduce 操作中收集结果 图 2.16 通用 MapReduce 实现架构主应用程序负责把基础的数据集分解到 桶 (bucket) 中 桶的最佳大小依赖于应用 结点的数量和可用的 I/O 带宽 这些 桶 通常存储在磁盘, 如果有必要也可能分散到主存中, 这依赖于具体的应用 桶 将作为 map() 函数的输入 主应用程序也负责调度和分散几个 MapReduce 的核心备份, 这几个备份是完全一致的 每个核心备份中有一个控制者, 这个控制者会持续跟踪每个 map() 和 reduce() 任务的状态, 并且可以作为 map() 和 reduce() 任务之间路由中间结果的管道 每个 map() 任务处理器完全指派给 桶, 然后产生一个保存到共享存储区域 (Shared Memery) 的中间结果集 共享存储可以设计成分布缓存 磁盘或其他设备等形式 当一个新的中间结果写入共享存储区域后, 任务就向控制者发出通知, 并提供指向其共享存储位置的句柄 当新的中间结果可用时, 控制者分配 reduce() 任务 这个任务通过应用独立的中间键值 (key/value) 来实现排序, 使相同的数据能聚集在一起, 以提供更快的检索 大块的结果集可以进行外部排序,reduce() 任务遍历整个排序的数据, 把唯一的键和分类的结果传递到用户的 reduce() 函数进行处理 经过 map() 和 reduce() 的过程, 当所有的 桶 都用完时, 全部的 reduce() 任务都会通知 102

108 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 控制者, 以说明它们的结果产生了 控制者就向主应用程序发出检索这个结果的信号 主应用程序可能就直接操作这些结果, 或者重新分配到不同的 MapReduce 控制者和任务进行进一步的处理 当前, 很多企业应用系统都是建立在 Java 技术上, 它们依赖于已有的文件系统 通信协议和应用栈 一个基于 Java 的 MapReduce 实现应该考虑到已存在的数据存储设备, 将来部署的结构里面支持哪种协议, 有哪些内部 API 和支持部署哪种第三方产品 ( 开源的或商业的 ) 图 2.17 显示了通常的架构是如何通过映射到已有的 健壮的 Java 开源架构来实现的 图 2.17 MapReduce Java 开源实现架构这个架构采用了已有的工具, 比如 Terracotta 和 Mule, 它们经常用来架构企业级应用系统 以物理或虚拟系统形式存在的 白盒子 通过简单的配置和部署, 设计成 MapReduce 群组中的一部分 为了提高效率, 一个庞大的系统可以分解到多个虚拟机器上, 如果需要可以分配更多的结点 Terracotta 集群技术是 map 和 reduce 任务之间共享数据的良好选择, 因为它把 map() 函数和 reduce() 函数之间的通信过程, 包括共享文件或者使用 RPC 调用以及初始处理结构都做了良好的封装 从前面的描述可知,Map 和 reduce 任务是在同一个核心应用中实现的 用来共享中间结构集的数据结构可以保持在内存的数据结构中, 通过 Terracotta 透明地共享交换 由跨域集群的 MapReduce 产生的进程内通信问题, 自从 Terracotta 在运行时掌管着这 103

109 些共享数据结构后就不存在了 在 Terracotta 中, 所有的 map() 任务都需要标记内存中的中间结果集, 然后 reduce() 任务就根据标记直接在内存中提取它们 控制者和主应用程序都是通过 Mule 的 ESB 传递信号的 通过主流的企业应用协议或者完全的原始 TCP/IP Sockets Mule 支持在内存中进行同步和异步的数据传输 Mule 可用于在同一台机器执行的应用系统 跨越不同的数据中心或者完全不同的地方且被程序员分开标识的本地终端结点之间传递输出结构集 大多数 Java 语言的 MapReduce 算法的实现都是基于 Hadoop 的 Hadoop 是一个开源的点对点 通用的 MapReduce 实现 有关它的介绍, 我们在下一节详细讲解 Hadoop 中的 MapReduce 简介 Hadoop 是 Apache 下的一个分布式并行计算框架 Hadoop 的核心设计思想是 MapReduce 和 HDFS,MapReduce 在前文中已经做了详细的介绍 ; 而 HDFS 是 Hadoop Distributed File System 的缩写, 即 Hadoop 的分布式文件系统, 它也是基于我们前面介绍的 Google File System 的原理开发的, 它为分布式计算存储提供底层支持 在 Hadoop 官方文档介绍了 Hadoop 中 MapReduce 的三个步骤 :map( 主要是分解并行的任务 ) combine( 主要是为了提高 reduce 的效率 ) 和 reduce( 把处理后的结果再汇总起来 ) 1.map 由于 map 是并行地对输入的文件集进行操作, 所以它的第一步 (FileSplit) 就是把文件集分割成一些子集 如果单个的文件大到影响查找效率时, 它会被分割成一些小的文件 要指出的是, 分割这一步是不知道输入文件的内部逻辑结构的 比如, 以行为逻辑分割的文本文件会被以任意的字节界限分割, 所以这个具体分割要由用户自己指定 然后每个文件分割体都会对应地有一个新的 map 任务 当单个 map 任务开始时, 它会对每个配置过的 reduce 任务开启一个新的输出流 (writer), 这个输出流会读取文件分割体 Hadoop 中的类 InputFormat 用于分析输入文件并产生键值 (key/value) 对 Hadoop 中的 Mapper 类是一个可以由用户实现的类, 经过 InputFormat 类分析的键值 (key/value) 对都传给 Mapper 类, 这样, 用户提供的 Mapper 类就可以进行真正的 map 操作 当 map 操作的输出被收集后, 它们会被 Hadoop 中的 Partitioner 类以指定的方式区分地写入输出文件里 2.combine 当 map 操作输出它的键值 (key/value) 对时, 出于性能和效率的考虑,Hadhoop 框架提供了一个合成器 (combine) 有了这个合成器,map 操作所产生的键值 (key/value) 对就不会马上写入输出文件, 它们会被收集在一些 list 中, 一个 key 值对应一个 list, 当写入一定数量的键值 (key/value) 对时, 这部分 list 会被合成器处理 比如,hadoop 案例中的 word count 程序, 它的 map 操作输出是 (word,1) 键值对, 在 map 操作的输入中, 词的计数可以使用合成器来加速 合成操作会在内存中收集处理 list, 一个 104

110 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 词一个 list 当一定数量的键值对输出到内存中时, 就调用合成操作的 reduce 方法, 每次都以一个唯一的词为 key,values 是 list 的迭代器, 然后合成器输出 (word, count-in-this-partof-the-input) 键值对 3.reduce 当一个 reduce 任务开始时, 它的输入分散在各个节点上的 map 的输出文件里 如果在分布式的模式下, 需要先把这些文件拷贝到本地文件系统上 一旦所有的数据都被拷贝到 reduce 任务所在的机器上时,reduce 任务会把这些文件合并到一个文件中 然后这个文件会被合并分类, 使得相同的 key 的键值对可以排在一起 接下来的 reduce 操作就很简单了, 顺序地读入这个文件, 将键 (key) 所对应的值 (values) 传给 reduce 方法完成之后再读取一个键 (key) 最后, 输出由每个 reduce 任务的输出文件组成 而它们的格式可以由 JobConf.setOutputFormat 类指定 wordcount 例子的实现 还记得我们在 2.5 节介绍的单词统计的例子么, 它是学习 map/reduce 最好的例子 本节我们将使用 Java 语言来实现这个例子, 其中用到了 hadoop 开源云计算包 (hadoop 相关 API 请参见 hadoop 站点 ) 好了, 先让我们来看看代码吧 package org.myorg; import java.io.*; import java.util.*; import org.apache.hadoop.fs.path; import org.apache.hadoop.filecache.distributedcache; import org.apache.hadoop.conf.*; import org.apache.hadoop.io.*; import org.apache.hadoop.mapred.*; import org.apache.hadoop.util.*; public class WordCount extends Configured implements Tool public static class Map extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable> static enum Counters INPUT_WORDS private final static IntWritable one = new IntWritable(1); private Text word = new Text(); private boolean casesensitive = true; private Set<String> patternstoskip = new HashSet<String>(); private long numrecords = 0; private String inputfile; public void configure(jobconf job) casesensitive = job.getboolean("wordcount.case.sensitive", true); inputfile = job.get("map.input.file"); if (job.getboolean("wordcount.skip.patterns", false)) Path[] patternsfiles = new Path[0]; try 105

111 106 patternsfiles = DistributedCache.getLocalCacheFiles(job); catch (IOException ioe) System.err.println("Caught exception while getting cached files: " + StringUtils.stringifyException(ioe)); for (Path patternsfile : patternsfiles) parseskipfile(patternsfile); private void parseskipfile(path patternsfile) try BufferedReader fis = new BufferedReader(new FileReader(patternsFile.toString())); String pattern = null; while ((pattern = fis.readline())!= null) patternstoskip.add(pattern); catch (IOException ioe) System.err.println("Caught exception while parsing the cached file '" + patternsfile + "' : " + StringUtils.stringifyException(ioe)); public void map(longwritable key, Text value, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException String line = (casesensitive)? value.tostring(): value.tostring().tolowercase(); for (String pattern : patternstoskip) line = line.replaceall(pattern, ""); StringTokenizer tokenizer = new StringTokenizer(line); while (tokenizer.hasmoretokens()) word.set(tokenizer.nexttoken()); output.collect(word, one); reporter.incrcounter(counters.input_words, 1); if ((++numrecords % 100) == 0) reporter.setstatus("finished processing " + numrecords + " records " + "from the input file: " + inputfile); public static class Reduce extends MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable> public void reduce(text key, Iterator<IntWritable> values, OutputCollector<Text, IntWritable> output, Reporter reporter) throws IOException

112 Error! Use the Home tab to apply 标题 1, 部分标题 1 to int sum = 0; while (values.hasnext()) sum += values.next().get(); output.collect(key, new IntWritable(sum)); public int run(string[] args) throws Exception JobConf conf = new JobConf(getConf(), WordCount.class); conf.setjobname("wordcount"); conf.setoutputkeyclass(text.class); conf.setoutputvalueclass(intwritable.class); conf.setmapperclass(map.class); conf.setcombinerclass(reduce.class); conf.setreducerclass(reduce.class); conf.setinputformat(textinputformat.class); conf.setoutputformat(textoutputformat.class); List<String> other_args = new ArrayList<String>(); for (int i=0; i < args.length; ++i) if ("-skip".equals(args[i])) DistributedCache.addCacheFile(new Path(args[++i]).toUri(), conf); conf.setboolean("wordcount.skip.patterns", true); else other_args.add(args[i]); FileInputFormat.setInputPaths(conf, new Path(other_args.get(0))); FileOutputFormat.setOutputPath(conf, new Path(other_args.get(1))); JobClient.runJob(conf); return 0; public static void main(string[] args) throws Exception int res = ToolRunner.run(new Configuration(), new WordCount(), args); System.exit(res); 下面是 WordCount 的运行样例及结果 输入样例 : $ bin/hadoop dfs -ls /usr/joe/wordcount/input/ /usr/joe/wordcount/input/file01 /usr/joe/wordcount/input/file02 $ bin/hadoop dfs -cat /usr/joe/wordcount/input/file01 Hello World, Bye World! $ bin/hadoop dfs -cat /usr/joe/wordcount/input/file02 107

113 Hello Hadoop, Goodbye to hadoop. 运行程序 : $ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.wordcount /usr/joe/wordcount/input /usr/joe/wordcount/output 输出 : $ bin/hadoop dfs -cat /usr/joe/wordcount/output/part Bye 1 Goodbye 1 Hadoop, 1 Hello 2 World! 1 World, 1 hadoop. 1 to 1 现在通过 DistributedCache 插入一个模式文件, 文件中保存了要被忽略的单词模式 $ hadoop dfs -cat /user/joe/wordcount/patterns.txt \. \, \! to 再运行一次, 这次使用更多的选项 : $ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.wordcount -Dwordcount.case.sensitive=true /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt 应该得到这样的输出 : $ bin/hadoop dfs -cat /usr/joe/wordcount/output/part Bye 1 Goodbye 1 Hadoop 1 Hello 2 World 2 hadoop 1 再运行一次, 这一次关闭大小写敏感性 (case sensitivity): $ bin/hadoop jar /usr/joe/wordcount.jar org.myorg.wordcount -Dwordcount.case.sensitive=false /usr/joe/wordcount/input /usr/joe/wordcount/output -skip /user/joe/wordcount/patterns.txt 输出 : $ bin/hadoop dfs -cat /usr/joe/wordcount/output/part bye 1 108

114 Error! Use the Home tab to apply 标题 1, 部分标题 1 to goodbye 1 hadoop 2 hello 2 world Nutch 中的分布式 前面介绍了很多分布式的内容, 具体在网络爬虫中如何应用这些知识呢? 下面以一款开源的搜索引擎为例 介绍如何设计一个分布式爬虫 和 Heritrix 一样,Nutch 是一个用 Java 语言开发的开源搜索引擎 现在, 越来越多的人从 Heritrix 转向了 Nutch 的学习与开发 与 Heritrix 相比,Nutch 的爬虫具有以下特点 : 简单 Nutch 的代码简单易懂 容易修改, 核心的爬虫类不超过 10 个, 而 Heritrix 比较复杂 读懂 Nutch 的代码轻而易举, 而要想读懂 Heritrix 的代码则要费很大的功夫 Nutch 支持分布式爬虫 Nutch 最新版本的底层实现使用 Hadoop 在云计算风起云涌的今天, 确实得到众多开发者的青睐 Nutch 爬虫详解 Nutch 爬虫使用宽度优先搜索的技术进行抓取 Nutch 爬虫的设计着重两个方面 : 存储与爬虫过程 首先, 看一下 Nutch 的存储 Nutch 存储主要使用数据文件 它的数据文件有三类 : 分别是 Web database segment 和 index Web database, 也叫 WebDB, 用于存储爬虫抓取的网页之间的链接结构信息,WebDB 只在爬虫中使用 WebDB 内存储了两种实体的信息 :Page 和 Link Page 实体描述互联网中网页的特征信息, 主要包括网页内的链接数目, 抓取此网页的时间等相关抓取信息, 对此网页的重要度评分等 Link 实体描述的是两个 Page 实体之间的链接关系 WebDB 中存储了一个所抓取网页的链接结构图, 在链接结构图中,Page 实体是图的节点, 而 Link 实体则代表图的边 一次爬行会产生很多个段 (segment), 段存储的是爬虫在一次抓取过程中抓到的网页以及这些网页的索引 爬虫爬行时会根据 WebDB 中的链接关系按照一定的爬行策略生成每次抓取循环所需的预取列表 (fetch list), 然后 Fetcher 类通过预取列表中的 URL 抓取这些网页并索引, 然后将其存入段中 段是有时效的, 网页被爬虫重新抓取后, 先前抓取产生的段就作废了 存储时, 段文件夹是以产生时间命名的, 方便用户删除作废的 segments 以节省存储空间 index 是爬虫抓取的所有网页的索引, 它是将所有 segment 中的索引合并处理后得到的 Nutch 利用 Lucene 技术进行索引 但是需要注意的是,Lucene 中的段和 Nutch 中的段不同, Lucene 中的段是索引的一部分, 但 Nutch 中的段和索引是各自独立的 在分析了爬虫工作中设计的文件之后, 接下来研究 Nutch 爬虫的抓取流程以及这些文 109

115 件在抓取中扮演的角色 Nutch 爬虫的工作原理主要是 : 首先根据 WebDB 生成一个待抓取网页的 URL 集合 预取列表, 接着下载线程 Fetcher 类开始根据预取列表进行网页抓取 如果下载线程有很多个, 那么就生成很多个预取列表, 也就是一个 Fetcher 类的线程对应一个预取列表 爬虫根据抓取回来的网页更新 WebDB, 根据更新后的 WebDB 生成新的预取列表 接着下一轮抓取循环重新开始 这个循环过程可以叫做 产生 / 抓取 / 更新 循环 指向同一个主机上 Web 资源的 URL 通常被分配到同一个预取列表中, 这样可以防止过多的 Fetcher 线程对一个主机同时进行抓取而导致主机负担过重 另外 Nutch 遵守 Robots 协议, 网站可以通过自定义 Robots.txt 控制 Nutch 爬虫的抓取 在 Nutch 中, 抓取操作的实现是通过实现一系列子操作来完成的 Nutch 提供了子命令行可以单独调用这些子操作 下面就是这些子操作的功能描述以及对应的命令行, 其中命令行写在括号中 (1) 创建一个新的 WebDB (admin db -create), 并且将起始 URL 写入 WebDB (inject) (2) 根据 WebDB 生成预取列表并写入相应的 segment(generate) (3) 根据预取列表中的 URL 抓取网页 (fetch) (4) 解析 (parse) 获得的网页 (5) 根据网页内的 URL 更新 WebDB(updateDB) (6) 循环进行 (2)~(5) 步直至预先设定的抓取深度 (7) 根据 WebDB 得到的网页评分和链接更新 segments (updatesegs) (8) 对所抓取的网页进行索引 (index) (9) 在索引中丢弃有重复内容的网页和重复的 URL (dedup) (10) 将 segments 中的索引进行合并生成用于检索的最终 index(merge) Nutch 爬虫的详细工作流程是 : 在创建一个 WebDB 之后 ( 步骤 1), 根据一些种子 URL 开始启动 产生 / 抓取 / 更新 循环 ( 步骤 (2)~(6)) 当这个循环彻底结束, 爬虫根据抓取中生成的 segments 创建索引 ( 步骤 (7)~(10)) 在重复清除 URL( 步骤 (9)) 之前, 每个 segment 的索引都是独立的 ( 步骤 (8)) 最终, 各个独立的 segment 索引被合并为一个最终的索引 index( 步骤 (10)) 整个 Nutch 的流程图如图 2.18 所示 其中, 标出来的 (1) 到 (5) 步是 Nutch 爬虫的过程 下面, 结合 Nutch 的源代码, 看一下 Nutch 爬虫的实现 Nutch 的爬虫代码部分主要集中在 package org.apache.nutch.fetcher 和插件 protocol-file Protocol-ftp protocol-http protocol-httpclient 以及相应的 Parser 插件中 最主要的类是 Fetcher 类, 它控制了整个爬虫的工作流程, 我们从它入手一步步跟踪整个代码 Fetcher 类是一个线程类, 它在 run() 中采用多线程运行 FetcherThread 类, 并调用恰当的 Protocol 插件 ( 支持 http ftp 等协议 ) 获取内容, 当内容被获取之后, 调用恰当的 Parser 插件将内容分析为文本, 然后把这些文本内容放到 FetcherOutput 类里, 最后由 FetcherOutputFormat 类写到段中 下面从 run() 函数开始分析, 它的关键代码如下 : for (int i = 0; i < threadcount; i++) FetcherThread thread = new FetcherThread(THREAD_GROUP_NAME+i); thread.start(); 110

116 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 图 2.18 Nutch 工作流程 在上面的 run() 函数的关键代码中, 建立了多个 FetcherThread 线程来抓取网页, threadcount 可以配置或者使用默认值 在这段代码之后是一个 while(true) 的循环 : int n = group.activecount(); Thread[] list = new Thread[n]; group.enumerate(list); boolean nomorefetcherthread = true; for (int i = 0; i < n; i++) if (list[i] == null) continue; String tname = list[i].getname(); if (tname.startswith(thread_group_name)) nomorefetcherthread = false; if (LOG.isLoggable(Level.FINE)) LOG.fine(list[i].toString()); if (nomorefetcherthread) if (LOG.isLoggable(Level.FINE)) LOG.fine("number of active threads: "+n); if (pages == pages0 && errors == errors0 && bytes == bytes0) status(); pages0 = pages; errors0 = errors; bytes0 = bytes; break; 上面这段代码相当于维护一个线程池, 并在日志中输入抓取页面的速度 状态之类的 111

117 信息 下面看看抓取的线程 FetcherThread 是如何工作的 : FetchListEntry fle = new FetchListEntry(); 首先, 建立一个抓取列表类, 然后又是一个 while (true) 循环 : if (fetchlist.next(fle) == null) break; url = fle.getpage().geturl().tostring(); 从当前的 FetchListEntry 中获得一个要抓取的 URL, 然后进行抓取 : if (!fle.getfetch()) if (LOG.isLoggable(Level.FINE)) LOG.fine("not fetching " + url); handlefetch(fle, new ProtocolOutput(null, ProtocolStatus.STATUS_NOTFETCHING)); continue; if (fetchlist.next(fle) == null) break; url = fle.getpage().geturl().tostring(); 上面代码表明, 如果不需要抓取, 则在 handlefetch 类中进行相应的处理 接着又是一个 do while 循环, 用来处理抓取过程中重定向指定的次数 : 整个循环的条件是需要重新抓取并且重定向次数没有超出最大次数 接下来, 使用 ProtocolFactory 工厂类创建 Protocol 类实例 : Protocol protocol = ProtocolFactory.getProtocol(url); Protocol 的实现是以插件的形式提供的, 可以从 Protocol 中获取 Fetch 的输出流 : ProtocolOutput output = protocol.getprotocoloutput(fle); 通过输出流可以获取抓取的状态 ProtocolStatus 和抓取的内容 Content: ProtocolStatus pstat = output.getstatus(); Content content = output.getcontent(); ProtocolStatus pstat = output.getstatus(); Content content = output.getcontent(); 然后判断抓取的状态 : switch(pstat.getcode()) 如果抓取成功, case ProtocolStatus.SUCCESS: if (content!= null) 如果抓取到的内容不为空则修改抓取的页数, 抓取的字节数, 并且每抓取 100 页, 就在日志中记录抓取的速度等信息 112

118 Error! Use the Home tab to apply 标题 1, 部分标题 1 to synchronized (Fetcher.this) pages++; bytes += content.getcontent().length; if ((pages % 100) == 0) status(); 在 handlefetch 中进行相应的处理 : ParseStatus ps = handlefetch(fle, output); 如果处理返回的状态不为空, 并且成功地重定向 : if (ps!= null && ps.getminorcode() == ParseStatus.SUCCESS_REDIRECT) 获取重定向的链接并进行过滤 : String newurl = ps.getmessage(); newurl = URLFilters.filter(newurl); 如果重定向的链接 newurl 不为空并且和现在的 URL 不同 : if (newurl!= null &&!newurl.equals(url)) refetch = true; url = newurl; redircnt++; 创建当前页面的 FetchListEntry: fle = new FetchListEntry(true, new Page(url, NEW_INJECTED_PAGE_SCORE), new String[0]); 如果链接页面已经转移或者临时转移 : case ProtocolStatus.MOVE D: case ProtocolStatus.TEMP_MOVED: 立即重定向, 处理抓取的结果 : handlefetch(fle, output); 获取重定向的 URL: String newurl = pstat.getmessage(); newurl = URLFilters.filter(newurl); if (newurl!= null &&!newurl.equals(url)) refetch = true; url = newurl; redircnt++; fle = new FetchListEntry(true, new Page(url, NEW_INJECTED_PAGE_SCORE), new String[0]); 113

119 整个获取重定向的 URL 过程和上面的重定向类似 如果获得的状态是以下几种状态之一, 直接交由 handlefetch 类来处理 case ProtocolStatus.GONE: case ProtocolStatus.NOTFOUND: case ProtocolStatus.ACCESS_DENIED: case ProtocolStatus.ROBOTS_DENIED: case ProtocolStatus.RETRY: case ProtocolStatus.NOTMODIFIED: case ProtocolStatus.GONE: case ProtocolStatus.NOTFOUND: case ProtocolStatus.ACCESS_DENIED: case ProtocolStatus.ROBOTS_DENIED: case ProtocolStatus.RETRY: case ProtocolStatus.NOTMODIFIED: 如果发生异常, 则在日志中记录异常信息, 然后交给 handlefetch 类处理 : case ProtocolStatus.EXCEPTION: logerror(url, fle, new Exception(pstat.getMessage())); handlefetch(fle, output); 其他情况为未知状态, 在日志中记录当前的状态, 然后交给 handlefetch 处理 : default: LOG.warning("Unknown ProtocolStatus: " + pstat.getcode()); handlefetch(fle, output); 循环结束 最后如果完成的线程数等于 threadcount, 则关闭所有的插件 : synchronized (Fetcher.this) atcompletion++; if (atcompletion == threadcount) try PluginRepository.getInstance().finalize(); catch (java.lang.throwable t) // do nothing 从上面的代码分析中可以看到, 获取页面后大多数的处理都交给了 handlefetch 类 下面, 就来看看 private ParseStatus handlefetch(fetchlistentry fle, ProtocolOutput output) 的代码 : 根据 output 获取到内容和 URL: Content content = output.getcontent(); MD5Hash hash = null; 114

120 Error! Use the Home tab to apply 标题 1, 部分标题 1 to String url = fle.getpage().geturl().tostring(); 如果 content 为 null, 新建 content: if (content == null) content = new Content(url, url, new byte[0], "", new Properties()); hash = MD5Hash.digest(url); else hash = MD5Hash.digest(content.getContent()); 在获取 ProtocolStatus 时 : ProtocolStatus protocolstatus = output.getstatus(); 如果 Fetcher 不进行解析 (parse), 直接把抓取的页面写入磁盘 if (!Fetcher.this.parsing) outputpage(new FetcherOutput(fle, hash, protocolstatus), content, null, null); return null; 如果 Fetcher 需要进行解析, 则首先获取页面 contenttype, 以便根据正确编码进行解析 : String contenttype = content.getcontenttype(); 下面是使用 Parser 类进行页面解析的过程 : Parse parse = null; ParseStatus status = null; try parser = ParserFactory.getParser(contentType, url); parse = parser.getparse(content); status = parse.getdata().getstatus(); catch (Exception e) e.printstacktrace(); status = new ParseStatus(e); 如果提取页面成功 : if (status.issuccess()) 将 FetcherOutput 提取的内容以及状态保存 : outputpage(new FetcherOutput(fle, hash, protocolstatus), content, new ParseText(parse.getText()), parse.getdata()); 否则将 FetcherOutput 和空的内容保存 : LOG.info("fetch okay, but can't parse " + url + ", reason: " + status.tostring()); 115

121 outputpage(new FetcherOutput(fle, hash, protocolstatus), content, new ParseText(""), new ParseData(status, "", new Outlink[0], new Properties())); 在抓取过程中使用的 Protocol 采用了 Nutch 的插件机制, 任何实现 Protocol 接口的实现类都可以负责抓取数据 这使得 Nutch 可以使用更多的网络协议获得数据 例如 :Http FTP 等 有关插件机制的代码, 这里不再一一讲述, 有兴趣的读者可以参考相关资料 Nutch 中的分布式 本节将讲述 Nutch 中的分布式机制 首先,Nutch 对于抓取的数据和索引是采用分布式存储的 ; 其次, 在爬虫和建立索引的许多环节,Nutch 都采用了 Map/Reduce 算法进行分布式计算 本节将从这两个方面对 Nutch 的分布式进行讲解 1. Nutch 的分布式文件系统 Nutch 抓取内容之后就要开始对文件进行管理 Nutch 分布式文件系统的基础架构是 Hadoop 文件系统 NDFS Nutch 的整个分布式文件系统工作架构如图 2.19 所示 2. Nutch 中的 MapReduce 算法 在介绍 Nutch 中的 MapReduce 算法应用之前, 先介绍 Nutch 中的 CrawlDB 目录 Nutch 中的 CrawlDB 目录中存储着一系列的文件, 这些文件中每一行都存储 <URL,CrawlDatum> 数据结构 其中,CrawlDatum 数据结构保存了对应 URL 的一系列属性 包括抓取时间 状态 抓取时间间隔 链接数目等 上一节讲述的 Nutch 运行过程的 10 个步骤中有 6 个步骤, 包括插入 URL 列表 (inject), 生成抓取列表 (generate), 抓取内容 (fetch), 分析处理内容 (parse), 更新 Crawl DB 库 (updatedb) 和建立索引 (index) 都是采用 MapReduce 算法来完成的 具体技术实现细节如下 : 1) 插入 URL 列表 (inject) MapReduce 程序 1: 目标 : 转换 input 输入为 CrawlDatum 格式输入 :URL 文件步骤 : (1) map(line) <url, CrawlDatum> (2) reduce() 合并多重的 URL 输出 : 临时的 CrawlDatum 文件 MapReduce 程序 2: 目标 : 合并上一步产生的临时文件到新的 CrawlDB 输入 : 上次 MapReduce 输出的 CrawlDatum 步骤 : 116

122 Error! Use the Home tab to apply 标题 1, 部分标题 1 to (1) map() 过滤重复的 URL (2) reduce: 合并两个 CrawlDatum 到一个新的 CrawlDB 输出 :CrawlDatum 图 2.19 Nutch 分布式文件系统架构 2) 生成抓取列表 (Generate) MapReduce 程序 : 目标 : 选择抓取列表输入 :CrawlDB 文件步骤 : (1) map() 如果抓取当前时间大于现在时间, 转换成 <CrawlDatum,URL> 格式 (2) reduce: 取最顶部的 N 个链接输出 :< URL,CrawlDatum> 文件 3) 抓取内容 (Fetch) MapReduce 程序 : 目标 : 抓取内容输入 :< URL,CrawlDatum>, 按主机划分, 按 hash 排序步骤 : (1) map(url,crawldatum) 输出 <URL,FetcherOutput> (2) 多线程, 调用 Nutch 的抓取协议插件, 抓取输出 <CrawlDatum, Content> 117

123 输出 :<URL,CrawlDatum> 和 <URL,Content> 两个文件 4) 分析处理内容 (Parse) MapReduce 程序 : 目标 : 处理抓取的内容输入 : 抓取的 < URL, Content> 步骤 : (1) map(url,content) <URL,Parse> (2) raduce() 函数调用 Nutch 的解析插件, 输出处理完的格式是 <ParseText,ParseData> 输出 :< URL,ParseText> <URL,ParseData> <URL,CrawlDatum> 5) 更新 CrawlDB 库 (updatedb) MapReduce 程序 : 目标 : 将 fetch 和 parse 整合到 DB 中输入 :<URL, CrawlDatum> 现有的 DB 加上 fetch 和 parse 的输出, 合并上面 3 个 DB 为一个新的 DB 输出 : 新的抓取 DB 6) 建立索引 (index) MapReduce 程序 : 目标 : 生成 Lucene 索引输入 : 多种文件格式步骤 : (1) parse 处理完的 <URL,ParseData> 提取 title metadata 信息等 (2) parse 处理完的 <URL,ParseText> 提取 text 内容 (3) 转换链接处理完的 <URL,Inlinks> 提取 anchors (4) 抓取内容处理完的 <URL,CrawlDatum> 提取抓取时间 (5) map() 函数用 ObjectWritable 包裹上面的内容 (6) reduce() 函数调用 Nutch 的索引插件, 生成 Lucene Document 文档输出 : 输出 Lucene 索引 2.7 本章小结 本章介绍了分布式爬虫的基本原理以及开源分布式爬虫 Nutch 的实现 主要内容包括当前比较流行的开源分布式架构 :Map/Reduce+BigTable+GFS 以及负载平衡的 ConsistentsHash 算法 最后, 分析了开源分布式爬虫架构 Nutch 的实现 下面是一些相关的资料 是 Google 介绍 MapReduce 的权威论文 是 Google 介绍 BigTable 的权威论文 118

124 Error! Use the Home tab to apply 标题 1, 部分标题 1 to 是 Google 介绍 GFS 的权威论文 网站详细讲述了 Consistent Hash 的相关内容 是 Apache 下的 Nutch 项目的站点, 网上有关 Nutch 的资料不是很多, 这个站点是不错的一个 119

125

126 ~~-~~~. ~-~-~~" ~ ~ ~ ~P~~--*~~~-M~. * M~-~~~-~- ~~. ~~~-~~-~~-~. ~ ~ aa ~ N~~wew ~ :W: o 1t Jl ff.tfu URL #~ * ~' I~J, B ~ ~ïf-1~ (.i] ~- ~~ ~ J)ï:, -te~ í!l~;h.!li3 A. 1lUí :fjrq ~ ijgji!!. li~1llff~f7tff. - ~~~;:;~~~~*i'j-~ixxjm, :farm~t-~ Session ID, ~15~ ~ ID tffxa.j LJ5-tf*~l4'ï~ JN:!ff~/fri'f9Um. ~ -~~~;:JI$~~X*i'J~~~ifii~, ~Witl -~~~~ Session ID. ~ ::h ~!tlïjj!l9í-$::ïf'~~jj!~~~ji! ::.li!:la.l -~1tj ï3 ~-~;H., mlll -e;:tejljt~j j.litr3161o ~J!Jt * ~jft fiil-~ 9roo ~':IIT~Iil.~i.X. ~x*x-1-rtt~;;;l~ e!1, 1\lG ~511 f~~ ;;; I 1i'Hfl ffl? 1#. ~~!i!:!il.:tf fj(] ~51!, f51 :YïJ ~li:~~ ~li!~ fj(] ~~Ma~~~~*~m~"m~"~~~~? ~*~~M. -~-@~~-~~-~~~~~~~~ re ~*A. MMC&M RffJWfi#.~~ff.iJ). i.jhjij~j~ixlcj:vib1,.r ffli~~ URL 'fl~is tl:iji fpj.ii5, * fpj.ii5 ft-j~~~~~ ~o~~sm~~-~w~ ~~~~ ~m. ~~~~~~~~~ ~~~LffM BAA. #S*m~.f5-~~~00~M. -~~~M~~Bffi~tt~ -~~~.f5, ~~~~~~ )J(:J -~~~~0 j(ij ~ 1l ~ ïj 1 (!<] Jnl!Riilí 'ili\! l\!f ~ Jtt 7it foj 1i5 (!<] URL, ~:'hi~~ URL ~fi~~~ jf -~~~o Google B~:i&~ 7X>fJS: &>ft~aht9~11hmo Google ~illí~-ij1- fï55- *j!j!fk.~!p.*~5~jj:a<j/i~. Z Fltr1t~Bft-J Visited *jt~-tl1i1!\iij7 -f![tllj ~JN:!.!H" ~~ tij" a<jftffl. ~~ :!41*~~ Visited * ~fij~mcpit :$.Mff.I:Ef~. 9~:001: a->b- >c->d->b- >e ~~ a<jjf~tllm. IN:!.!HM~~ :i1t~. &J!~I& b, e, d ~JL~ URL Hx189~iií o mï~ T Visited ~. ~a~i!li!;t.~~frij/m. ~i:t :t: a<j JL ::q:: ep, e<j " I~ ifaj " ftjl ~ ~ t~-& 133:fJtW~ i31 $ 7f ~~ f~ ~:lij:lfrfl~, ~B~~1H~:ïf'1JIH,. lft~7 o ~:ki!! 4i: JYT~e<J~M ~:f:e SE0(1l~ïJI$1t-f.t) o *~.R!i!:iJ::k~ JM"~~"ff.J ~~~a*~m~~~o~~:ïf'~$~*~ JIM~~ntelEB. ~@*A~~-f.te<J*~ ~=- ~*Wf~fflP~-~A.OA~B~~--~-~m Fl!i*A.~E:iiü~* o fiijm. i!~~lllíj'ih9l~51 ~ êl "HtrtfJliJ!ítf: ~í!fr, ~~~l!flf~~ ~li~~~-~ooe<jmaa:ïf'~~ a<jo#x>t~* ~ am~w-~*~m a,~ ~~mh~a., ]!!if~~~ íjñ~~jli1i~it~~l ~. flffr l1h9l~, ~mtt~~~~ttff51 $ B ~M*it!&~ fij ffl? a<j AA:i!l!. ifii~~ba<j~;h.8~"~~~~-.~&~~~-&e<j~~wm~m~?~ ~~, -&89~~~~-"~~~-~.w ~ ~~~:ffr ~~~"m~"a<j~oo. ~-. Nr <<

127 llh ~~ <j I~~.R tiri)( fit l'ih o ~ft~~w~~~~a~~-~~~~?~-~~* ~m~. -~ 11tm-.ú:bff~~~ o ~t:twijg1jl~.1t~~. Wl~~Ji!~::khit~0 ~ ~M.fU1~f:!A~M...tfiia<J emo ~~M~0~~-a~~(R~2o$a), ftra~ma<jea&~-~~. ~~.tir ~ ( ] rr-t ft~rar I;!.. :f& Mi!.ú:b jijj M 1~E $tj tir~ o & JJJt ~:f&.~ -1' ~ M BJ.J :iíhm P3 ~. ihjt B 1fl ( ] ~ 1~~a<J~ftJ. ~~ ' ~ c.fm~( Jffi,~.?iAe $"P o ~*rf'ji~~~/nt~a<j1r.ill::tt~sl$.. ~Tiñ ~;:E #itltirllx.f!l~ffi'.tir~ Wtl T- : public clas~. RetrivePage private istatic HttpClient httpclient = new HttpClient(); I I 1Q:~1-t:~1l!:l~~ static 1/iQ:W~~~*~~ IP~hl~~D 11 httpclient.gethostconfiguration(). setproxy( " ", 8080); ) public static boolean downloadpage(string path) throws HttpException, IOException, Exception InputStream input = null; OutputStream output = -null; I I f.jjtl get 1:!~ GetMethod getmethod = new GetMethod(path); I I iq:w get 1:!~(11.]~~ NameValuePair[ J postdata= new NameValuePair [8]; postdata[oj = new NameValuePai r("dcityl", "BJS " ); postdata [! ] = new NameValuePair("ACity1", "SHA"); postdata[2] = new NameValuePair("DDatel", " " ); postdata ( 3] = new NameValuePair("ClassType", ""); postdata[4] = new NameValuePair("PassengerQuantity", "l"); postdata(s] = new NameValuePair( "SendTicl<:.etCity", "%u5317%u4eac"); postdata [ 6] = new NameValuePair ( "Airline", " ) ; postdata [?] = new NameValuePair("PassengerType ", "ADU"); getmethod. setquerystring(postdata); I I!1\.-tr :\1t *' lii?j int statuscode = httpclient.executemethod(getmethod); 1 I tl' xt tlt ~ lii?j i1hr :lli::$ < 'ífíl i! ~.R :lli: l'lll~ 1 f.il1; 2 o o B'-1 :t1t *'~ l if (statuscode == HttpStatus. SC_ OK) I I ~tij é'-1 F'3 ~ input = getmethod.getresponsebodyasstream(); String charset = getmethod.getresponsecharset(); I I ~i~:_:ttj:~ string filename = path. substring(path.lastindexòf('/ ' ) + 1); filename + = System. currenttimemillis(); /1 ~~::t#~l±lmt File tempfile = new Fil e(filename); if (!tempfile.exists()) tempfile. createnewfile();... >>... - ~

128 output = new FileOutputStream(tempFile); I I! tl:l3ilj X 1tf int tempbyte = -1; while ((tempbyte = input. read()) >O) output.write(tempbyte); li ::kt':fl*6ira~i±ivlf. if (input! = null) input. close() ; if (output!= null) output.close(); li ftffl htmlpaser Wf1;tfi Parser parser = new Parser(filename) ; parser. setencodi ng(charset); NodeVisitor nodevisitor = new NodeVisitor() private boolean flag =false; private int index = - 1; public voi d visittag (Tag tag) if(tag. gettagname().equals("tbody" )) Syst ero.out. println("begin ); flag = true; índex = O; if(tag.gettagname().equal s("td")&&flag) swi tch ( index) case O: System. out.println("froitlto:"+tag. toplaintextstring().triro()); break; case 1: System.out.println("carrier: +tag. toplaintextstring().triro()); break; case 2 : System.out.println( "type :"+tag. toplaintextstring(). trim()); break; case 3: System. out. println( "number:"+tag. toplaintex tstring().trim()) ; break; case 4: System.out.println( dicount:"+tag. toplaintextstring(). t rim()); break; <<

129 3 ll!iliii.j "ññilili" case 5: System.out.println( price:" +tag. toplaintextstring().trim()); break; index++; public void visitendtag (Tag tag) if (tag.gettagname(). equals("tbody")) System.out.printl n( end... ") ; flag = false; ; parser.visitallnodeswith(nodevisitor); return true; return f alse; I ** $1 :ut 1"1! WJ *I public static void main(stri ng[) args) I I ~.Jtlt lietu 1i"Ji, ffltl:l try RetrivePage. downloadpage(" coml DomesticiShowFareFirst.aspx ); catch (HttpException e) ( e.printstacktrace(); catch!ioexception e) e. printstacktrace(); catch (Exception el e.printstacktrace(); =~mmamm ~~~~~~8. ~~~~~~-~~~~N~*-~* -~~ ~-~ ~~~~~.~~~ ~~ fi~. ~x.m ~ ~~~-~ ~~-~~ ~~~ ~-~OO~~~~m-~. ~~~~- ~~. - ~ ~~-~«~. «*~M :fo.±h2rffi * ~ URL. ~~~~~~~ñ~~~~. M-~~~~~~~~~~~I~W: =~~-~ -~~~B~*~~~~~. rn~~~~m~~~~~~~~~~~.~~~~~~m~ >>

130 ~* ~ ~ ~~. «mmm=*~~-~"*~ ".~I ~~~~~ ~ ~ ~A ~~ - ~ m. ~~ m~ ~ **~ ~ -~~~~~ ~-~ $.am~ ~~x~ ; ~ ~~«~ a ~mxam.ma~~ ~m*~~~~~ ~]):(~~~,~~~~. fi -R~tlw llj!'i:mï.tft~~. ~gw~ ~nj iiu~~fi~lil *~ ~.~-~ ~ -~~m~~#~~ = (I) ~I ~_'Rj(t- fll ~ lnj ~.fo~ ffj~ I~) ír. (2) m~~ll~iuj ~JJJ~~~51 ~ 'fl1i:t~ tl:l~~~jxxj:w:. (3) ~tz 11ï tr.j L:tf?JJ~ JR -~~it tf.ijxxjjjr ( b~ jzo;t' ;f,zf.r :f! 10, JiWi'iJ~.iZli!On 1'-~ :vi). ~ m~ajxxj:w:m ~ ~~~-~-~~JJr~W. ~~- ~ lnj~. ~ ~-~"~ "~~~~--t~~~~~nj~&«mtr.j~ = ~ t;] iff) Eh~ ~U JO ~ llt JO ~~ JO ~g!j JO ~*~ JO ~f:h 8 I!Gffl 8 ~Wi-F 5 ~Jl 9 ~oo:.lï!: URL t't5t~a(]--tit1t:0ï.t. lf73!1'0j.\, IJtffJ mtilj~re VRL Jt -T-m~ ~l)!.?'&fo~m1>cí«rfj*'j ~ ~HdúE~1t~l9l~1' URL. Pirority(URL) == a 1 x domain_ weight + a2xlink_popularity + ajxpriority(parent_url) + a4 x directory _ depth Jt!:f, domain_weight * 7J' URL 9='~1'-~a<J:& 'ÍR link_popularity ~ /J\~Ij El íltr~ 11:., ~ 1' Ml~ 1l:ftlt Jt 1tB ~ ]í:( ~I ffl fi<j?;\fi parent_ url :& 7F 'B 1Y-J..t.- tli.xx~ URL ~ :& 1i directory_depth ~7f\3!1' URL rfl E13RB<l~&. it:j&~(l'-j jf(, ~1' URL 8(];&:1'i~ 1 ff;. ai ;&/J\ ïhl\1?t 1Yf 6 8<3 t:t f ij ~~.~7~~nJ~~1'~~. ~Am&~~z~a : Pirority(URL) == al x domain_weight + a2 x link_popularity + a3 x priority(parent_url) + a4 x directory _ depth + a5 x L Oi Jt!:f, Oi ~ tij~~---~~~~tl:l-b<j?;\. : a5 ~~ tij~~~1'w#~a~ JíJT (f]~ 91J o ~(tijiiijf.~:f±m-1" URL ;i1hfw?ta<jbt1~. -~~~~~~ URL 1tí~ B<J:ïiUm, ~ )g;t>f URL ~ffi-sf?t, 1/J.À TODO ;' \( 9=' ~ff ~HI. ~tr J&j! 1' URL 8(] ot f~, :iie~~ff-(jcf lt, ~ ~ M 1XX.1 ~ ~ ~ B<J YiH% ~F~.:-;'\: o ~ ot~ ~ 71HH -- ''~' JjJ 1H!<J 1f~ltt ff~ J!l. f!p iifi M~l1hi J;E - 1-~M. ~--ili~w?t.::j;;:f~1'~ -~~~ff~-~~lhi. #H~~~M ~8JJr <<

131 3. ll!llatj "ññiiii" m~ ili~uli mfiw*~.~~r ~~ wmffw~. ffihlp"pj. n~b>t, Nmu. *~~~~ *!~H:!t1fltDR15'é::fitl±ll-f*~f1!. TñJ~:fl-m-#Ji!U4L ftffj:mt~:m 8 J;rljt~ PageRank ~:±ijijj JJ:ïWt!ff17Fi91Jftfi.q. _m _fsi'jii"!~.t~ M ~ JJ:ï titf~j1hfilfl#( ~.f'p1ii't..r :Jè ~ ~ Z. ïitr Jre1 ;R.~IfR (f]ñf -~~ ~ ïitr ~ URL :lzhfiijz#, mi~~&~htr~ff.fú<ji*j~ ). te~!r:ct;ij rp, ~tm~-~ URL, ~W- 1'-lli inj~ixx.jff.f ~;M;1tt!. ~g.[( J,~~. fíj\_;ç HitNumber. Jlfê!TI.4.ïhtilh1-1'~(fJ~jñ, m~~-~íi~ 1'-~ff.f(f]mw ~-~-~@~ mmwm~b~~~moon~. ~~~ 1'- s~ i.ñfb1i1é<jixxjjñ(fl HitNumber ~~~~;ffl!.y.~1ju I. ~~tl~.'t.!.b~~ib1tltfj~jji~1-l".lè~;:(fj B>t~~(Jm~tlft7L~m1'Jijj~ ). HitNumber ~;:. ~71'~1'~9H&JitlA 51J'IH~~~, E13JltPTI].L ~rr~1'1xx1ff.f~~~~.~fslr~:m:~~~te~ifr~:m:1ioo. ~~~~~~~~itfflf~~ ~~1i00. ~$~~tett~~~~~~. ib fi!:, i [!.~ tt!g jhlj/ '- ~ff.[ rfj H itnumber, ;(;f Ht f~ PT ti~7c ~1i51-t l±\ ~ W3 1' ~ J.iïHifU ~ 1!~ ~if.ifj!f. tt!nj:fif :Jj~~:Dí, "t~fjte Internet _t lii.ltf..r:lh~li3 1 FH 7 -?j(. -1'~-17! B~ult-t ~1' Arsl)Jj, ~-1-1' A~MJYr'JiftL ~T f'f~l]j.5'ht~~3tw A* L': ~H~-1-~~7Ç~ ~~lt:;k@j~ii~, ~ Yahoo! JñïIA'J, fij:-fjy~~ilitlt~.ftit\l.~li'il.jl:h~.~fii]j. s ittf.jaix'j ~. ~B't1ti'~ft~EI3ïlij1'1~1f!jl:~ HitNumber - ~(~:Ji': 18,lt~l±HJj1'~Jll:(:f lnternet..t-tf!l ~~ ~ ~" ~ ~-~~~-À~ ~*w.~ ~oo ~~~~w* (PageRank)(tE :<f\: ï!ï, a flr ~ PageRank rfj ~.'@:fiy>t fiu i f!. 8] :fl- ~B, ïtiï /f' itv.dl 51!t ~À B-tl i.mduo 1~~9;;J.W., X1' PageRank ~JJHm:fO-\:liJb~:E.J\1., ~ffj~:te~ 8 ~i$~ i.#~).fr~ ~-1' Wl!ii ~~ - 1'- ~ ]J[ ~I ffht:j IM'~. ~!i!: J:)i~±t!F#HJk 51 ffl ~!n ~ HitNwnber j]q I, ïtiï :1! #H I Jf1 1»9!íHI~ fiu~j,t tt.::9 ~, li.illt-t #f~ ïj I.ffl 1»9!íHt-J 11! J! 1í &~ ~-*< :fflfl: ffñ ~ J!J (t.]ffftff, Yahoo! ~ I ffl l'"lfj~í.il!lt!.\ 1±1' A~1tí'71ffl l'"lfjii1ij'ilfi:~, f5l ;ç Yahoo!.<t;:~~m:~). -~~-~r ~~í.[w* ~ w~~~w*~*#1i~~ Go~e.~mm~T-1' " ~;ffl. itp r~ " m * ~ Wï ~ ij ~ m f' m jxl<j ff.f ~Y-J i.jj bjff;ç +:~t ~ & 1~aa -F, (I) ffl P Ml~l)2! ~-1'~9H'F;ç J::~ ft'~~!é~jñ. w -~~1'~:m:~.M~~:m:~m~Bf1m ~~m- ~-1']J[W~ ~~ D) $ ~~ -~ ~NJï~. m~~~-t~~--~ ~ m~~- 1'~Jritl:~ij1Jl'.[, 1f!EYJ~(2) j7lu~(3 t. 1t(~ I]).J:í!'~m F~f;ç:m~L 4ii1'-~Jïrijf!5~w fa.j l tj tfj (Xllc~ïJt ~ ~ ~ JHt''J ~~;& 1lt. PageRank *m l].l "F 0 :A.# ff: m ttii: N w W = (1-d) + d 1,, - 1 i=l.i J n 1 X~. ~ft-mj1'~~00u ffl : 0 RifRO. 1 -.~-M~~~~~]J[j~N#.fr : ~ft~~ ]Jf ~~~1' ~X~~:Dí~ :dft."mm~ "~$G M~~~~ ~ ~?jz.~ ~m~ ~a~m~1:0~. ~m ~ ~~~ ~ ~1J~flE~ jç~&:ttmjh~ïiíwj.1~lf]:~ ~ I~~ IY-J JJJ'i~, lïij" f!~~:fif ~:ttl!.~j.tt. ~!ft~ ~ :?t m~~~~$~~~. llsl.ll.tpt l]j.~x J!ítW?t~~~~JJL mtf-11=~4l~tl:ntr ~rz.11j! :ffl :te~~~u/!~~ffl f' f(.j~!jit.ir.r~ J:, X1-fM >.

132 -~-# ~--~~~*~fi. ~-~~~~ tt~ " E U". Mli8~~G~ -~~~~~. -~~@W& ~~~~~~HM-~.li ~--~-*~~"~-~ ;$:t,ff;&" (HypertextLink) ~f.w. :!i ll*~. PJ~l!:t$i3L ~::g "i1:1!t&)c* ~", 1i.:It*~ ~~1~ 71'~ - )C. 4-1-~tlHiï~tfF(;f- ~W ~)C*(AnchorText), l!1-)c;.$:&~ T~~]J[!: j ~~ ~~~~ * ~~#~. ~-~~-~~-~~~fii!~ ~~* am~~~~ ~~ fbj 11! JHt-1 :.*: ~-~~~ ~ o~ ~ ~~ ~~~w~ ~x* ~ f ~x* ~~mm~~ ~ M~~- - ~~. ~-~~x* ~m ~ n~ Moo. ~w~~~~~ & w. ~~ &w~ ~~ - ~ ~x* ~~ ~~ ~x*w~~~ JS:-t-~ Yl:; ;lt?x. ~~)C*tJJ iül tr-j:tt~ CJ I~ :Jt: ;;f9mtjll!.~ïqg~ J: é<j WT 1,i.ft't ~t. tt1jll-.t!:t00l, ~ff., -~~~~ ~ ~~ x #. ~ A* ~ m~ ~x*~;lt*&, ~~ * ~ ff: ;ijt iif,. a<j;jntlx ~.t~:t ~ i\ (f]..lt 14', & ï'if ~f i!j li!: Jl:l1:ft~ èj I ~ 5t ~ K i!j I ~ )C 1tf &. ~ i!~---~- -~~~. aqa3~~ "~-"~B. ~~OO~*M:lt:~Wa ;Jnt~J!: --~~)(#, WR~~*7&aM-~X* m~l!: ~~-R~M~)C*, W~ JS:J?Jè~ "?ï"eltt" m tnj ~x 1/f~*/l':fftr Java 3:: ~.re!r ~-~ft-~ Java 3:: ~~ tfja~~jjr., :!moo 3.1 JiJT;F ~.±!mjr!~ URL ~Jll!.mli :E!!Rl!R URL ~Jilil~ ~~m~. re- ~~-~~ 3::ER~ ffi~*~~~ ~ ~ tr-j ~ E~ tr-j~ ~~--~-~~~W~ ffl ~~~~~tr-jffi~~. ~*~~-~~ ~. --~~ ~!i!jj. m M!.~*~:ïn. ~00#~-~ -~ ~00~- ~ ~~~-~ ~00~: public i n t e rfa ce ComputeUrl public boolean accept(string url, Strjng pagecontent) ; public class PageRankComputeUrl imp l ements Computeurl f-i M URL ~Hrtii~ 7HJT, ~!f! Wilt~~J,W.. ~~J!f 8 ~ ~ ~ Il PageRank.f! public boolean accept (String url,string pagecontentl dlh // ~ '"' ' '' ''' ~ - '' '

133 return false; public class MyCrawler private ComputeUrl computeurl = null; public MyCrawler() computeurl = new PageRankComputeUrl(l ~.~----~ I ** * Vijf'l'f'.W~t URL seeds #7- URL *I private void initcrawlerwithseeds(string[) seeds) for( int i=o;i<seeds. length;i++l LinkQueue. addunvisitedurl(seeds[i] ); ~m PageRank ~~i~hf~ tft. g RI Uk ~Jtft!!.n ~,.R.~~f)ll. ComputeUrl ~ Cl seeds *I public void crawling(stri ng (] seeds) li ~.)(.tt~u~. tf!l(r! _ http: I lwww. li e tu. com 7f5k~tïUi~ LinkFilter filter = new LinkFilter() public boolean accept(string url) if(url.startswith(" return true; e l s e return false; - ll :fjj~tf:. URL 11.\Jtl initcrawl erwithseeds(seeds); I I qm :tf ~ ftj: : ffl trftl(r 8'1 ~:m'l'~..ei.~iutlt ~ ~ ):['l'~ T- 1 O o =1 000) while(!linkqueue.unvisitedurlsempty()&&lin~queue.getvisitedurlnum()< I 1~5k URL W~~J String visiturl=(string)linkqueue.unvisitedurldequeue(); I 11m* URL :*.JS!!iX:ltt5t~f)JJlrífit~?H1f(!<Jtt-n if(visiturl==null) continue; DownLoadFile downloader=new DownLoadFi le(); o o. o o. o. o o o o o o o o o o o o. o o o o o o o o o o. o o o o o o. o o o o. o o. o o o o o o. o. o o o>>.

134 I I r~jij]!ji String content = downloader.downloadfile(visiturl); if(computeurl. accept(visiturl.content)) continue; I/~ URL til(aijj Bijj' fêj~ URL ep LinkQueue.addVisitedUrl (visiturl) I Im!UI:\ -n.!h~ij ]j:f ep (f] URL accept ~thl~)(. ~~~ I true, ~71' URL ~.±:~ -1'G;X::, fili!!ji:i~w Set<String> links=htmlparsertool.extrac Links(v i s iturl, filter) ; I lfli(jh~'\1íià](f] URL À~ for(string l ink : links) LinkQueue.addUnvisitedUrl(link); l I /ma i n ñliacj public static void main(string[]args) MyCrawler crawler = new MyCrawler(); crawler.crawling(new String[]"http: /lwww. twt.edu. cn"); 11-~H. ~~ PageRank.ComputeUrl (IJ~lJll,. ~ffjiii.fflm 8 ïiïwm-vtm PageRank ~~(IJiltf~.ïl ll*l~lue:~!à ~sm~~~ttn~. 7 ~ ~~~~. ~ m~5& ~~"oo~.~~.~ ~ :!Hil J:E OO.!R P~? 1:ïiii o ~~oo~ ~~oo~m~ B9~m~mmM-~~-. ~* ~~~~~*~~~~ (l) ~IH'E~;g rfloo!il. tt:tzll, Rlmlfí( edu.cn!s~ q~;g. 0) ~~~-- aq-~. ~:tzll, ~~RM 2 ~ ~ ( 3) 11M ~ IP (/]:lm lfx: H; j u, R 1Jit. rp 00 ro; IE pg ~ IP. ~ ~~~~~-~- ~~. RM~~~~~oo. ~T-I!liE~;g é<j:tjj\lfí(, ;Jè- #:liifal~a<jilfue:jmlfj. 1~::E.mgts7HJ. edu.cn ~~f!pí5j. R~1.tJ lt!t ~Hír URL ~~* (IJj, 1t llllj:e~.!roojtia<jjihx. lt~~~~~~. ~íltr:ft"'p~.y URL. ~~-~~~ *~~. J.W.tJ:, :m:~~ URL ~HrJ:tm, ~.:ll.-~5ti~a<j URL m~. #-.Eltm~am :é:~ 8 f?:a<jti~?hff~~ll". rïfillh:lt11'jm URL m:n :mwr ~m!y-11-\:~: public class Vrl <<

135 3!118fJ "1i1iil"'ili" I I E\~ URL ~ft, ~ ;fflr: ~~:ti(~ priva t e String or iurl; 1 1 url ]«t.,.:tm fflí1t ~ r p ~ T ~j.t.t :!lt!l.::t m privat e String url; 1/URL NUM private int urlno ; I I ~Jr( URL :@ lfl<jft.*~ pri vate int stat uscode; I I Jlt URL i/i~frl!.jt~'31ffl~èx~ private int hitnum; li Jlt URL ~@Jt:t;ït'f.HJI.~~~ private String charset; 11 xt;tm~ privat e String abst racttext; // f'f;/f private String author; I I Xl\i J<J t!r < 1\l. * ~ flij ijj l'f.hi1.@. l private int ~tteight; I I :SC ï;í é<j #li j t private String description; I I Xf.t.:k 'J' private int filesize; I I Jfkm~i.!.JI:Eft(!ij pri vat e Timestamp lastupdatetime; I I i1jtt:ll-111'ül private Date timetolive; I I Xití~f$ private String title; 11 Jt~~~ private String type; é<j f:l:l.fj/1. 11 '3 1JfHñfit~ private Url[] urlrefrences; 1 ;~Jr(WJm~. M.~.:r -!l.fl, 1«.~1<1~ o~. ~ 1 ~ privat e int layer; I / get set /J?Z~ ~~H~~~-.#~~~m~~~~o.~~-#~~~~~~~oo~.rex~~ lit~~à urlrefrences rp, jf:jljeiè~t:hiu~aq layer _mj::jjll l, t&fo:ffi.t/l.à TODO ~tp. ~m~ ~"* ~m~*~iey& ~~~~~,~~ aqm~.~-~,~~~. fjt~~jn:!. ~!i!tl~#. ~~ ~ ~-~--~-~~--~-~.m~~~ w. ~~~-~~~ w~ ~~~ - :*!!! fk 8<J IP. ~E *f JE lp $1t.iD1?iU~ ~JA.~~...illtt URL ±.tfl $ ~ ~ f.f $, ijj l.l $H~ :± m ip -hl.~ ±mip~~~ m~aq~~~. ~~-~-~.~~.-~~~~ I ft >>

136 public class IP public static void main(string(j args) throws IOException String hostnarne; BufferedReader input = new BufferedReader(new I nputstreamreader( System. in)); System.out.print("\n"); System. out.print("host name : ); hostname = input. readline () ; t ry ( InetAddress ipaddress = InetAddress.getByName(hostname); System. out. println ( I P address: + ipaddress. gethostaddress () ) ; catch (UnknownHostException e) System.out.println ( could not find IP address for : + hostname) ; ) Host name : Host name: IP address : ~--~~~~~*~~ w ~~~ ~ ~ -~ w ~-~~s~~~--~-~~# *~~~M ~ m~-aac. ~ "~a ~ ~ " IP ~X'f&:;)Ç~. ey~ m*hl~ ~~ ~;r ~ ~~! ;:" ~- ~.13.7il QQWry.dat é<jjtlj:, Bff El C.rfJ Jttf~~. iilímtm~ ~Jtlf ~.ft). ïij~~j.w. IP *tl:~ir~rfj~m~j'f. QQWry.dat Jt#f±~.ft)_t?j-~_:1) : Jttf:;5k, 12.jR~~~'1112s:. - AU!tfi'J ~1!HJG ~ ll't, 5'étE~ iji ~! HJGi-C.:*:fAIU$. Z.Fo ~tl 12.jRt2i ~ tl:\fïlr,@,. d3-t~~~ B<J~jR~7f' J;E* ~. FJT lèa 1ïftific'.jRIR r:ri!~ ~::fïij (J~~. rn -T iè3kbi:tt&~. ~ $:i@l)lj~~iir m~~t~'lt. - fllt *~ ~1nPJlèAm=?t=!!tft$1!~~!r511R. ;tt:ja!j3í~tl!!m~i311r~~=f fíi!m. oo 3.2 J9T7J' ~~;R~~-~Jttj:~ftJ. ~73:~~~. QQWry.dat )C1tf~$*ffl T Little-endian ~~~. QQWry.dat ~Jttf::5k.Rff 8 ~ q"'\'~i, ;lu~.ftj ~ F'm'1Wf. ~íjlm~?- ï!ï ~m-*~'11 é<jffi ~,!itt$, Fc lm~~1!:jildiv6-~ ~'1 1 ~~)(;f.ii.f$. 4It* IP 1GjR jsei3aij~~*l8:~mmt. 00 *!8:tE~I*7f'~~$W. ~.7il~~~-tl:\ ~~m$::k~# m "z~~~-. ~- i~ ::k ~~IVG T oo ~ T, ~.lltig jr~t'lht~ -~~ QName, I!P El:!~fi!Hffl?t~f.ijlS!Hlll?t~ ñ.lt. m*~~a~m~ ~~~rg~~~~. :f&flij $.tjim1 r, - *i-è.jr~#h\:@ ~~ [W M!hl][~*~ ][tt!!~~ ]. OO~~.m*IR~ ïij ag~ =ff~i.h~ ~:l!j[, :WJ~'Iïj *-i-è.~~f*.ff- 1'-.Ji:lïa<J~~~!JU~:~~~ ~11~99. IEJ!!t~~ <<

137 3 m~ "n1riliili" -~~~~-~~o~t~~-~00 ~-~~~~. 8WTM~ey- : M- a Mm ~n*~~ooa~: =~~*ffl-~4 ~~~~~. m-~~~*~t~~~~m~. moo J~*~~~ ~ ~~~~~~ Q~ ~Mfooa~*~ ~~eyn~ ~ ~. ~~~~~-~~-$~-~M~ o )C+t~ ] ~ll',ll.:fol-!1'?-"\i ig ~ IK I iiliil 18: :1-)i!*.iJIJ:K ] -~~~* '1-lll:ltfi'~Ji! lli 3.2!ili~ IP ll!t18~3t i!f~~ JJ~.Z. ft.z. :me ~;f~~? ~th~l.t üñ,p)fií, -~i~j] l'fj.m 5:\.:a[IP ~:lli:] [ 00 a ia~][~ ~ i C. ~. ~- 00 ~~~-~~~ ~. ~~-g~~w~ &w, fabwtm#m~. BW ~114$:-Ut~ 1.f:Oft,t:rt 2o 81fJF I~HlH#.fiJI).!ll:b~;rt~ift(to ~~ T llifaï if!-iw IP ic. ~ l4l :rt o oo~iè3jt(!j:'~$~~. :ltl!ikig~i*w4!~~. om.lft!.l Orli~) P iè :lr a~ ílilf.!)1 ff~~ 003A-~T ~~-~~~-~-~~-~a ~ ~~-&~.~~ia~~ooaia~ - ~ ~T.mW3~~~~~ T -~m~. m~t~~~oo ~ ~mx ~g ~o -~ 1 ~~;Fig*~J! OxOJ o o 4 5!Pltltf4'i'Til. ll;fll l!i ~l' OxO l 8 ~<tia# : OxXXXXXX fll*g('i''lii1'ji).òt, Oll!R!l Jt!K 11 ('1'~1!l)l);l;, Ot~~) lll3.4 Ï;EiaJ.m~ ~T!I!l:EIÍiJ~~:rt 2 ~f-j'tl!f&. :te.m:rt 2 ~M&rc;lttf~.R ~ Ox02), ~!Ric.~ ~~ ooa1c. ~ ~~~~ ic. ~m4~ ~~m~ ~~rric.* ~sft ~at:m~i.f:om:rt2~fr~, ~ : -~ l ~OOaiè.~~üñ~~W~-IRiè.. :rt21'fj ooaia m~~-~ic. -roo* -r~ ~l'fjmmo >>.

138 l JtliU'II!f<llfi!I_,A, Olli~ lll 3.5 ~iol-~ a~ 7 ~ ~ ~ ~ ~ l~~ ma ili~~ea ~.~~~-~T. 2 ~m~~~~m~~~~2~.~~m=~~~~~ ~2.~~~2~~~R~~~ * ~~-~~ 7 - ~~~~. ~~-~~-~ 2. W~~~-~R~~~~OO ~-~. ~ r :f:t!! ~Riè. n.h:\ 1 ~~:r.t 2 ~-~~. :f:t!!ikic. m/f'~tt~ 2 ~~:m:2r6j. /l'rt, oo 3.s J:E i!ji J.~a~~. o 5 ~!lli;~ 3.6 :fooo 3.7 ïij;p r~~. IPJIII!tl<'i< Vil. >i!ii>ji!ijl;, l : OxOJ 8 lf!xfliij: OxXXXXXX!l;:ê!i1Jll;(;2 : Ox02 laxflifj: OxXXXXXX Jl lï[1,;\~1iil'l!s;l;., Otlilil I illlk1:1'i'"<iij5>1:, OtN"l I o 5 IPJütl<'i' -1\') ali!lfiii!i;(;l OxO J 8 lfl~iaij : OxXXXXXX. li!il'l. >l:2: ox02!axfliuj: OxXXXXXX -I cuc 11 ("i'1i 111 $JI:. ot8lll) I!l; ~f;j: OxOI or0x02 lf!xft~ij : OxXJO(XXX - HIIK t\1"7-l'f 111!Us':, Otlilo6l I.~~:tzllT: -~ IP 1G:JJEI3[IP tt!!hl] [ OO ig ][ :Ii!!!KiG. ] í.jl~. mr~ ig~ PJI ).ff -" ~~~: ~ *~ ~r6j ~l~ 2~ ~2. ~rtt!!~r~ ~ ~w~"* ~~~ : ~~**~8~~r6J.~,~w- w~: ~~-~t~ ~ ic.~~~n tt!!~k ~~. ~-~~am. a~~~~~~--~. -~dt IP iè.-~mw~rm~. <<

139 3. ll.!lletj "ññiliii" _t ~~ajl T ~#~~~_t:j!i)ij-1-flfttt-, ~f$' : !7f71' o?fjümjp1 7 ~-* ~5lln &FP-~~ 51 aqf:'fsm o ft ~ l. 7'i'W!II-;Uf ~ IGI8 : OxXXXXXXXX 4 K.ft 8."" ~ l«<8 : OxXXXJO<XXX Xfl'~ J!~ ~l * ~1. 7'i<W Jt#~f,fiaJ~'.ll!2illl;r- ~~~~tl f~i(!. ::f:;!~? M.X~tf~mtPJitJ.~lli.3W~5l!R. ~Ji WtPJitJ-3f~1l~ IP 7! 4i~~51*&;kJ 1-t-~ lt. mr 4 1'~1! ~~Ml IP M:W:. J = 1-!j!:f.í.ttmr~ 7 w ic:sko )!li! 1P~~~~~1$ti!J3 -""f, 1r~:N!il ~ IP, JJ~ ~:fiï~ lftti* IP? ith~:ff~~ - ~ic5r:: , 1JIS~ ff.t~~m IP, ~~~:r,r IP, ~!JR IP ~~ IP ic~cpaq~ 4 1'~~ o f~. : J~~i31~'*-*~~. ~~P.tT-1'- IP VG: mj, 5zll*~:ftt~ 166. lll ljf~~jlli.ji, ~~~JJQ.I ~tet ~1-mm!I*J, ll~~fj\jtitï:if lt).ji~f 'i~ak~ 5 1 *~.itt!e~c~o±&l?i.:g T. Tiiñ~ l:l:l - 1-MwiBIIY-JOOM. tm OO 3.9 fjt7j\. o *ft ~I Gil~ : ()x)(j()()()()o t~- - ~ IU xli'!i<,i,imjip, 4'i''W l1piè~gi8, 3'1', M- f<ll'leloi J!~- ~ 1 I!~IPièil< li:ii!ip, 4'7 1\" l lpièo!fiij, 3'í''(r - ~ 1 1!< liul- f<ipièil \ t#~~~tQ!JII.tt:-mm~~ 7 ~::f:~? tl! i.q:kf~-.é: fff-/f'fr!f~, QQWry.dat!Y-J~*-ffi.fi!,~iFí:EillJII!i! 7 ~? ~~~ : ~) -* IP ie.;jt~ w-..t.~~~*-ffi.@., lii!i5 - *i2.~iq.7j'é<j~-t~ : ~:lti~h~ 2004 $ 6 J3 25 B IP ~~o M-7, JtJJ~i'f:fff,Jiiz~~ $7tr~ >>.

140 ~i! 7 ~~n~~~yji~w X 14 B%tïtq, J.l~ ~, :tm W WHJTX 14 ~:.&.if~ IP 511 ~ B9!m!R PJ!l? ~ 11'J ~-~~~~~~~-~.~-~~!R ~~. ~km-t~~~~-~m~-~~mm~~ ~~ mm~~~s~o ~~R~~~~--~~ f.tm o ~-~:te""ftt-~ JJfiiií~Y-J!l1fl~, 5t~w~~*-~ i!n~~*fiifiei-'8-~ o 1s~. ~.W!jg11f\!R:iitl37F1tE o IZSl tj fai~jffi.~/fm~iiuflí!m~:i&m~ 1~ -~o ~:fm, UTF8 ~tliê~, f.ijíi~ ~~x. ~~- ~ xom~.:iitl~*m~-~b~*~~. ~:tm. ~oo ~eg~~. :iitl~8~ffl~~ow 8 *~~M. S.~fflBXo!ZS!~.i!M~M IP~b~ ~~f.ij~m ~~J ~~ ~Mi-it-;;- o Java ~I.UE:~.!:RïF_1 1J mífiil~~~~~lt~ IP Jí.JTMm~~R ~1-tfi!? :t~o-r, publie e l ass IPSeeker II~Jt IP $:imfffiij private String IP_FILE,."QQWry. Dat" ; I I fli1: ff é<j )( ftf ~ private String I NSTALL_DI R= "e:\\qqwry"; I I -~~EM' :ii:.!.t:!zfl IG;!k-K:~~ private statie final int IP_ RECORD_LENGTH = 7; private statie final byte REDI RECT_MODE_l = OxOl; private statie final byte REDIRECT_MODE_2 = Ox02; 1 1 ffl, E-f : ::.9 e a eh e, 1ll:-ifU-1' I P Dt 1t 5t fi ;'f e a eh e ' i.l. ~&/f~'~ l'f1 11UH1H~ private Map<String, IPLoeation> ipcache; I I ~tj].jtftfijï(oj~ private RandomAeeessFile ipfile; 11 pgfr:~mjt11f pri vate MappedByteBuffer mbb; ~~--~~l'fl*m~m-i'flmm~s private long ipbegin. ipend; I 1 ::.9 1R; i\ilí ~~mi ~H!Hf-J 1\ài Dt ~íl! private IPLoeation loe; private byte[] buf; private byte [ ] b4 ; p rivate byte [ ] b3; pub l ic IPSeeker(String filename,string dir) thi s.install_ DIR=dir ; this.ip_f ILE=fileName; ipcache = new HashMap<Stri ng, IPLoeation>(); loe = new I PLoeation(); buf~ new byte[loo]; b4 = new byte [4]; ~ // ~,, ~ ".

141 b3 = new byte [ 3] ; try ( ipfile = new RandomAccessFile(IP_ FILE, "r" ); catch (Fil enotfoundexception e) ( l/~*~;if'fuli.:-1-jtlf, ~~iitte~ïitr El ~Til~. ii>?;:~~e5c,efltj,~jt14~ I /~:.*J:fr ~~JfoJ~~~g:'l:k+~. fi'~~;;ffl] I P Jmill:wL@.Jtlj: String filename = new File(IP_FILE).getName().toLowerCase(); File(] files = new File I NSTALL_OIR).. listfiles(); for(int i :: O; i < files.length; i++) if(files[i). i sfi le()) if(fil es[i).getname().tolowercase(.equals(filename)) try ipfile = new Ran.domAccessFile(fi les [ij, " r "); catch (Fi lenotfoundexception el) ( break; LogFactory.log (" IP :it!! i.ji:~,@_jtlj:~:fft;utl, :ltfx~ij!ffl, Level. ERROR, el) ; ipfi le = nul l ; ;~*n*jt#~~. ~Jt#~MA if(ipfile! = null) try ( ipbegin = readlong4(0); ipend = readlong4(4); if(ipbegin ='= -1 li i p End ) ipfile. close ( l ; i p File = null; I P.l!b.J'~~ catch (IOException e) LogFactory. 1og ( IP :lt!!:!l.l:ffi.@,jttf:#f~:ffttt~. IP.!m?FJ;J~~:Itf7é~1J!fl3", Level. ERROR, e); ipfil e = null;! ** * -1'-:lt!!~~~~~~ -~fts*s~$~ip~~~- s :lt!!~-t$ -E!'ê" IPEntry ~mtj<j List *I public List geti PEntriesOebug(String sl >>

142 List<IPEntry> ret= new ArrayList<IPEntry>(); long endoffset = ipend + 4; for (long offset = ipbegi n + 4; offset <= endoffset; offset += IP_RECORD_LENGTH) I I ~li)(!'li JR I P f.lil # long t e mp = readlong3(offset); I I ~* t emp '1' ~7--L ~11)( IP l'fj*t!!bfff.f;!. if(temp!= - 1) ) IPLocation iploc = getiplocation(temp); I I #lj WT~1'±1B B.ff! OO.:llB!l'-E1.* s T-$, :tln*'fl1.1f, ~:bll~1' iè.:lft~1 List 9~. I I :tm JI-Hi :ff. ~Ut if (i ploc. getcountry (). i ndexo f (sl! = - 1 I I iploc.getarea(). indexof(s)! = - 1) IPEntry entry = new IPEntry(); entry.country = iploc.getcountry(); entr y.area = iplo c.getarea() ; I I ~~tlis:\~a I P read!p(offset - 4, b4); entry. beginip = Util.getipStringFromBytes(b4); I I ~JiJ~>Ii: IP readi P(temp, b4l ; entry.endip = Uti l.getipstringfromgytes(b4 ) ; return ret; I I ~)Jn~iè)K ret. add ( e n try) ; public IPLocation getiplocation(string i pl IPLocation location=new IPLocation( ); location.setarea(thi s.getarea(ip)) ; l ocation. set Country(this.getCountry(ip)) ; return location; / ** M~-1'-~I'fJ~~*~ ~. --fts *s T- s.~r$ eturn - 15 IPEntry ~tt!a'-.j List * I public List<IPEntry> getipentries(string s) Li st<ipentry> ret = new ArrayList<IPEntry>(); try I I IJ*f-t I P l!ï,@.)ctf~ tlfi;j :f?~ if (mbb = = null) <<

143 3..!ll fj "1i'1i'lili" FileChannel fc = ipfile.getchannel(); mbb = fc.map(fílechannel.mapmode.read_only, O, ipfile. length()); mbb.order(byteorder.little_endian); í nt endoffset = (in t )ipend; for (int offset = (int)ipbegi n + 4; offset <~ endoffset; offset += IP_RECORD_LENGTH) i nt temp = readint3(offset) ; if ( temp! = -1) IPLocation iploc = getiplocation(temp); 11 j!ljlfjt~1-.it!l.é:!l!iiii :lih!l' * s T-litl. :flll!.l'@.~. te Líst <f'~:jj ~-'j'-lé'.~. I I :tui*'lifl", ~ij'; if(iploc.getcountry().indexof(sl! = - 1 I I iploc.getarea( ).indexof(s)!= -1 ) ( IPEntry entry = new I PEntry(); entry.country = iploc.getcountry() ; entry.area = iploc. getarea(j; I I ffl~lj~~ IP readip(offset - 4, b4); entry.begi nip = Util.getipStri ngfrombytes(b4); I I ~1!J!U~ I P readip(temp, b4); entry.endip = Util.getipStringFromBytes(b4 ) ; I I ~tm~lè.~ ret.add(entryj; catch (IOException e) LogFactory. log("",level. ERROR,e); return ret:; I ** * M. ;I;Jff:pj.(MX14 offset tz:w.:ff.mitr.j~ 3 1- ~~ ijlj.]jj-1- offset * I private int readint3(int offsetl mbb.position(offset); ret:urn mbb. get:int() & OxOOFFFFFF; ) I ** >>.

144 * M ~.ffll!utjt # ~ íïú 'Íll~7f fla 'JJ ~ 3 1'~ 1i Jt!l i?lil!x -1' in t * I pri vate int readint3() return mbb.getint () & OxOOFFFFFF; I ** * mm rp ~~JrEiJi(~ ip IP 911~1i~ffi.m:à: Lil*~~f.f$ * I public String getcountry(byte [J ip) I I tut I P :tl!;ill: Jt #: ;lè: ili' IE 1t if ( ipfile === nul U \ return Message.bad_ip_file ; ' I f*:(+ IP, f~~ IP lf- 1i~ffi.~~11'$%:i\: String ipstr = Util.getlpStringFromBytes(ip); I 1 ;t;t.hf cache rtt~~bfè-.~~1' IP éll!i!hjl ~~:p: :~~Xf!f if(ipcache.containskey(ipstr) IPLocation iploc = ipcache.get(ipstr); return iploc.getcountry() ; else IPLocation iploc = getiplocation(ip); ipcache.put(ipstr, iploc. getcopy()); return iploc. getcountry() ; l I ** * mm rp ~jij~~~ ip IP é':j~.nltl%~ m~~~f<fl$ " I public String getcountry(string ip) ( r eturn getcountry(util.getipbytearrayfromstring(ip)) ; I * * * tttm IP mfij±t!!ik~ ip IP é':j *ls~m%:it ±t!!ik~~f.f$ * I public String getarea(byte[] ip) I I ~ ~ I P ±t!! hl:jt i 1 f ;I! '.!f.ïe 1t if(ipfile == null) return Message.bad_ i p_fi l e; << '

145 M 3 sm "1i1iilil" o I I ~ff p, ~~ I P ~il~l!!r!ll.:kj~~ijl~~ String ipstr = Util ogetipstr ingfrombytes(ip) ; I I ;ttut ca che <f :li!: :S E. f&- * i! 1- I P ( < t\*1 *, lq:.fi" l'hf~.)e!f if(ipcache ocontainskey(ipst r)) IPLocation iploc = ipcacheo get(ipstr); return iploc ogetarea() ; else IPLocation i ploc = getiploca tion(ip) ; ipcache oput(ipstr, iploc ogetcopy ()); return iplocogetarea(); / ** * ~~ IP ~~ljjjj;~~ i p IP ( < ~~ ijl~,t ff!l!r~ ~~ $ * f public String getarea (String ip) return getarea(utilogetipbytearrayfromstring(ip)); / * * * ~~ ip ~~ IP ffl.@..)cf!f, ~Jrj I PLocation ~:j;tj. lfrtl!l.è<j ip ~I!I:JA~!VG!T.t ip "ftfnrj ip JH!hiill'l9 IP IPLocation ~~ * I pr ivate IPLocation getiplocation(byte[) ip) IPLocation info = null; long offset = locateip(ip) ; if (offset 1= - 1) info = ge tiplocation(offset) ; if(info == nulll info = new IPLocation() ; infoosetcountry (Message ounknown_country); infoosetarea(message ounknown_area) ; l return info; / ** offset ~JN.( < long R, g@l-1 ~7J'~IN..)C1!f9çM! * I private long readlong4(long offset) long ret = O; try o o. o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o. o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o>>

146 ipfile.seek(offset) ; ret I= (ipfile.readbyte() & OxFF); ret I= ((ipfile.readbyte() << 8) & OxFFOO); ret I= ((ipfil e.readbyte() << 16) & OxFFOOOO); ret I= ((ipfil e.readbyte() << 24) & OxFFOOOOOO); r eturn ret ; catch (IOException e) return - 1 ; /* * offset ~t!l:btl~~íij~ 0;1!>:é<J long ffi., ~@1-1 *7J'~I!>:X1't~P& *I pri vate l ong readlong3(long offset) long ret = O; try ipfile.seek(offset); ipfile.readfully(b3); ret \ = (b3[0] & OxFF) ; ret I= ((b3[1 ] << 8) & OxFFOO) ; ret I= ((b3 [ 2 ] << 161 & OxFFOOOO); return ret; cat ch (IOException e) return - 1; /* * * M~ïltrfll~~lfX 3 1-~~~~fX long i~jl)(.btl long li, ~@1-1 ~7f\~lfX)C14~9& * I private long readlong3(1 long ret = O; try ipfile.readfull y(b3); ret I= (b3(0 ] & OxFF); ret I= ((b3[1) «81 & OxFFOO) ; ret \= (b3[2] << 1 6) & OxFFOOOO) ; return ret; catch (IOException e) return -1 ;!* * <<

147 3. ll!kfj "1i1fiiJi" * M. o ff set i!l:jï~iril!l-1'~"\!ít-j IP ft!!:l.li:$à ip ~íll. Jtl. ~.IfXFo ít-j ip :Jg Big- endian #f * ~. f.!à:jik:)[f4'1-'~ Little-endian ~~. :tff~iihftt~ offset ip * I private void readip(long offset, byte[) ip) try ipfile.seek(offset); ipfile. readfully(ip) ; byt e t emp = i p[o]; i p[ O = i p [3); ip[3) = temp; t emp = ip(l ] ; ip[l) = ip [2 ) ; ip[2) = temp; ) c a tch (IOException e) LogFactory. l og("",level.error,e); /** *... o f fset iil-~iril!l-1'~~89 I P ft!!:l.li:$a ip ~m_ <F, i$;1r./êié9 ip 1-J Bi g - endian #t * l ittle-endian%:rt. :tff~iiht~~ offset ip * I private void readi P(int offset, byte(] ip) mbb. positi on(of fset); mbb.get (ip) ; byte temp = ip(o) ; ip (ol = ip ( 3 ) ; ip[3) = temp; temp = i p [l); ip[l) = ip(2]; ip[2) = temp;!** * 1e~Ji.\Z9,! ip #I beginip!:t~, il:~jl>-1' beginip ~ Big- e ndian ~:;t[l9 ip ~~ijjl'l<j IP beginip ~~Wiiil IP :ffi!:t~tfl IP eturn :6' ip ;fil begin :ffi ~~lill O, ip ;;k:-f beginip Jiltl~lill l.,,-'fjt!lill-1 * I p rivate int compareip(byt e[ ) ip, byte[) b eginlp) for (int i = O; i < 4; i++) int r = comparebyte( i p[i), beginipi) ; if(r!= Ol >>

148 return O; return r; I** * rewi-t- byte ~ 11=x~%~i1Hr tt~ b l b2 36' bl j(-'f b2.!i!jj:is Jl. ffi~:is@i o. /Jvr:JSIEJ-1 *I private int comparebyte(byte b1, byte b2) I** if ( (b1 & OxFF > (b2 & OxFF)) I I tt~;!!:sj( -T return 1; else if((b1 A b2) == 0)11 ~ljtrfi~:sttl~ return O; el se return -1; * ~1'-~~- m rp ~~ -Q~S*~-1'- rp~--~~hl-~.:is@i-1'-m~~ * ~m =~r1dt~ i p ~~ilij(f(j IP :!m~tuut. :JS l~* I P ~iiêf$, :!m;l!!~fl"~jij, :JSIEJ-1 *I private long locateip(byte[ ] ip) l ong m = O; int r; I I tt~:m ip JYi readip(ipbegin, b4); r = compareip(ip, b4); if(r == 0) return ipbegin; else if (r < 0 ret urn - 1; I I ff P& = ~ :J!.I! :ti for(long i = ipbegin, j = ipend; i < j; ) m = getmiddleoffset(i, j); readip (m, b4) ; r = compareip(ip, b4); 11 log.debug(utils.geti pstringfrombytes(b)); if(r > 0) i = m; el se i f ( r < O if (m == j) j -= IP_RECORD_LENGTH; m = j; else <<

149 j = m; e l se return readlong3(m + 4); 11 ~ --7. ~~i~j~~~~ ~ ~~~-~~~-~~-. ~~#~~ ~~ ~~~~. ~~~~-r. ~ ~~ ~~~~~~~~ m = readlong3(m + 4); readip(m, b4); r = compareip(i p, b4); if(r <= 0) return m; e l se return - 1 ; o o. o o. :o. I ** * ~'ftl begin -l!iè~31ll end i.liê~ NaJ-IJL~1c3k~~~ begin end * I private long getmiddleoffset(long begin, long end) long records = (end - begin) I IP_RECORD_ LENGTH; records >>"' 1; i f(records == 0) records = 1 ; return begin + records * I P_ RECORD_ LENGTH; I ** * ~.@-~ IP ~~±th ikiè:íihj'.j-i!iè~. J!% 1-~ IPLocation ~;Ml offset ~i\:': ètjji ~~~ IPLocation ~ ~ * / pri vate I PLocation getiplocation(long offset) o try I I i.!jèi:.t 4 '*;J ip ipfile.seek (offset + 4) ; 11 ~ -~ ;J~ ~- ~ y byte b = ipfile. readbyte(); i f(b ="' REDIRECT_MODE_l) I I ~~~-u;nt long countryoffset = readlong3 (); I I ~Wi~ íli!~:lll: ipfile.seek(countryoffset); ~~ ~~~-~~~ ;J. ~~~~~~~~~~~~~~~~~~~ b = i pfile.readbyte() ; if(b == REDIRECT_MODE_ 2 l oc. setcountry ( readstring(readlong3() )); ipfile. seek(countryoffset + 4); >> o o o o o o o o o o. o o o. o o. o o o o o o o o o o. o o o o o o. o. o o

150 else loc. setcountry ( r eadstri ng(countryoffset)); I I i$~ :ttl!!?f :f,f.,$ loc.setarea( r eadarea(ipfile. getfilepointer ())) ; else i f(b == RED I RECT_MODE_2) loc. setcountry ( r eadst ring(readlong3())) ; loc.setar ea r eadarea(offset + 8)); else l oc. setcountry ( readstring ( ipfi le. get FilePo i nter () - 1)) ; l oc.setar ea( readarea(ipfile. getfi l epointer ( ))); return l oc; catch (! OException e) return null; I* * * ~ 5;E-1- IP ~*:l:ll!ir-\g~~~~. ~@1- -t- IPLocation ~;t!;j. llt:ñy*isyji'l"'fp3+f~m:lt * -ft.f:ñ~ offset ~*ia~a<jij!\~-j ~ I PLocation )(<t~ * I p r ivate I PLocation ge tiplo cation( ~d t offset) I I ~tj:l: 4 q.:'i) IP mbb.position(offset + 4 l ; I IJ!IliiJT~-1-q.:lïM~ :.9 t'f~ -*11 byte b = mbb.get(); if(b == RED I RECT_MODE_ l) I I ~ I!)( til *!iu~ i nt countryoffset = readint3(); I I ~~~ ~.. ~~M: mbb.positi on(countryoffset); ~~~ -~ **~ ~.::9~-t~a~-t:ttl!:ñm ~~ -t ~ ~ b = mbb. get ( ) ; i f( b == REDIRECT_MODE_2) l oc. setcountry ( r eadst r i ng(readint3())); mbb.position(countryoffset + 4 ) ; else loc. setcountry(readstri ng(countryoffset)); I I ~ JlR il!;!?f :tif-je; loc.setarea (readarea(mbb. position())); else if (b == REDIRECT_MODE_2) loc.setcountry(readstring(readi nt3())); loc. setarea(readarea(offset + Bl); else l o c. setcount ry(readstring(mbb. position () - 1 )) ; o <<. "

151 3..!ll8CJ "1:fñlili" loc.setarea(readarea(mbb.position())); return Ioc; I *" *».o ff set ~#7fflt:r.itl;Vf,Sï:W(!J~~, ij;l:f:j -1-iml8:~ arn o ff s et.l:t!! 18: i2. 3R l'f).il!l Pà~ ;f$ i:ti\18a~!'f.f<f$ IOException *I private String readarea(long offset) throws IOException ipfile.seek(offset); byte b = ipfile.readbyte(); if(b == REDIRECT_MODE_l I I b :: REDIRECT_MODE_2l long areaoffset = readlong3(offset + l) ; if(areaoffset == O) return Message.unknown_area; els e return readstring(areaoffset); else return readstring(offset ; / ** offset.l:t!!ikièjj!; t'f.l~~tlii~ $ * I private String readarea(int offset) mbb.position(offset); ) byte b = mbb.get(); ifb == REDIRECT_MODE_l I I b == REDIRECT_MODE_2) int areaoffset = readint3 (); if(areaoffset ==Ol return Message.unknown_area; els e return readstringareaoffset); else return readstring(offset); I *" *». offset ílii#~0;jtj!.- 1'- J. O!;) JR(f.!'f.~~ offset *f-f*~~à# ii~t'!<j!?-f.f$, lilfl~ )~*f<fffl * I private String readstring(long offset) >>

152 try ipfile.seek(offset); int i; for( i =O, buf(i) = ipfil e.readbyte(); buf(i]!=o; buf(++i) = ipfile.readbyte()) ; i f(i! = 0) return Util.getString(buf, O, i, "GBK') ; catch (IOException e) LogFactory. log ("", Level. ERROR, e); return... I / ** * JAP'lfi:~MJt#(IJ offset -&,._~fl]-1- O ~~.!:f:~lf\ offset!1-rt1'$i8l~~l$ ~~írtl!1-:?\r~, l:l:\11l~iei~~r1$ *I private String readstring(int offset) try mbb.position(offset); int i; for(i =O, buf(i) = mbb.get(); buf (i]! = O; buf[++i] = mbb.get()); if(i! = 0) return Util.getString(buf, O, i, "GBK"); catch (IllegalArgumentException e) ( LogFactory.log("',Level. ERROR, e); return.. ". / ** * IA~. ti!*- Jtt1fJ!(JI.rJJ71; *I public class Util private static StringBuilder sb = new StringBuilder() ; / ** * MIP~*R$-~W~ ~---~ ip *~* ~~ IP ~~ IP * I public stati c byte [] getipbytearrayfromstring(string ip) byte[] ret = new byte[4); StringTokenizer st = new StringTokenizer(ip, '."; << '

153 3. mag "ññiliili" try ret ( O] = ret(l] = ret[2) = (byte) (byte) (byte) (byte ) (!nteger.parseint(st. nex t Token ( )) & OxFF); (Integer. parseint(st. n exttoken()j & OxFF); (!nteger.parsernt(st.next Token()) & OxFF); ( I nteger. parseint(st. nexttoken()) & OxFFJ ; ret[3) = catch (Exception el LogFactory. log ("JA I P tfj~ ~~~j~1j~m.j~:i\:1li!.ff", Level. ERROR, e) ; return ret; / ** ip * IP l'fj-*~ ~ffi.~i\: eturn ~r~!$\~:i\.l't-j!p * I publ ic stati c Strin g geti pstringfrombytes(byte[ ) i p) sb.delete(o, sb. length()); sb. append(ip[o l & OxFF) ; sb. appen d '. ' J ; s b. app e nd ( ip 1 ) & OxFF) ; sb. append (. l ; sb. append( i p(2] & OxFF) ; sb. append ( '. ' ) ; sb.append(ip (3] & OxFF) ; return sb. tostring(); / ** * m~~~~~~~~*~~m~~~*~* b *1l~ffi. o ffset ~~~i!<j@!lii1ñ::i' l en ~~~ífl-j5;:jt encod ing ~liij~:i\: :lm* encoding ::f:si:~, jg l-1"!il:ia~61!1't-j-*~1* * I publ i c static String getstring(byt e[j b, int offset, i nt len, String encoding) try ( return new String(b, offset, l en, encoding ); catch (UnsupportedEncodi ngexception el return new String(b, offset, len); public int erface Message >>

154 String bad_ip_fil e=" IP j:!lt.jf:~)cftfiih~ '' ; String unknown_countcy = " *~ll ie isi:"; String unknown_area= "*~:ltl!ie"; / ** * <pre> * -*" I P 1i1 BlJ i2. ;Sk, :f l( f~iji'i [!ij j (.fll ()( ~, tb :JíSJtY.fia I P $1 #i* I P * </pre> * I public class IPEntry pub li e String beginip; pub li e String endip; public String country; pub li e String area; / ** * ~l; :ii'j ii ~ * I public IPEntry() beginip = endip = country = area = ; ) / ** * ~category :ij*:tl~ IP ;ffix«j,@., Elïlíf.R~"Wí-1-~~. IP mt~:l'fl!ei t~'tl:ltk!e * i public class IPLocation private String country; private String area; public IPLocation() country = area = public IPLocation getcopy() IPLocation ret = new IPLocation(); ret.country = country; ret.area = area; retu;-n ret; public String getcountry() return country; <<

155 a mag "1iñiiili" public void setcountry(string count ry) this. country = country; public String getarea() return area; publi c void setarea (String areal ( I l:lm*~jililtim, ~J. IP it!!.i.ll:w~jt!!ik~r71' CZ88. NET, ~!IHE't"i!i:f ï if (a r ea.trim().equals ( "CZ88.NET ) ) this. area= ; fi::t1j.!\t ; fi:~~ ; el se this.area = area ; / ** * 8~I) *I public c l ass LogFactory pri vate static fina l Logger l ogger ; static logger = Logger.getLogger( stdout ) ; logger. setleve l(level.debug) ; publ ic static void log(string info, Level level, Throwable ex) logger. log(level, inf o, e x); public static Level getloglevel( return logger.getleve l(); public class IPTest publ ic stat ic void main(stri ng[] args) ltffl~~-~--~x#~~m~x#~ I PSeeker ip = new IPSeeker ( "QQWry.Dat", c:\\qqwry ); /1 ~itip Systern.out.print l n ( ip. getiplocation (" ). getcountry () + + ip.getiplocation( " " ).getarea()); >>

156 mü~jt-, ~l±l= ~m1!ite:tbm = "*:iilí i#.iy3 IP M!:hl J-J fi(] 13: ~J~Ff~ ~, K ip rtf o Wl:~IH8'~ ll!hiltl ~ ~ ljx ~ ~ K ip mé(]~~~. ~-~~~~~m~x#o ~J-J-~oo~w~. ~~~m~~~~~~ o~~oo~~?~j-j~ ~~~~~~WD, #~-~~~-UoftX ~~-~~. ~-~~~~-~~MB(]M 1~7f JS $~~fi, ~.Fo~fo!H~$ Session ;i!l;ff~~. OO!f)(~]f.i o ~.ff., f~~~l11íjjx;~m~~' ~-~~ ~-o~~-#ffl~~~m~*$*b~f~b~"mm", *M ~-~~ ~~ ~-~~~-~~ ffi~~~m~m~"~&"o J-JT ~~~-~~~~. ~-~D-~*fflTM~M~. M -~~tt~m~-~-~ ftl'jl-~ X ftf,.itg~:h robots. txt, ;l't. tp ~ Tl!ll~ ll:h :J; $~- ~~o~-~.lv-~~ ~.li Robots Meta.tF~o robots.txt ~14&::,~:$ii1:E~.~IY-J :5?:~, ï'lilei..)c!f~&::,~jj ;J<~ o ~)C!f-@.15-~BJG J!$~ièjjt, ~ll:hic3itj=ff~~t7t7f(~ CR, CRINL B.)G~ NL f'fj-j~ïli*:(~), te~)c!ftp~~ ffl #i~hri:hrlf, 11.-f*~ffl JJ~~ UNIX tp ~ tm #íj - f-f o ~~!f~ fi(] ièjk)! ~!». -~'f!ll $ tr U ser-agent 7FM:l, Follfi1J0~3!ST- Disallow ff, i$ffi!t~~1ll11' o User-agent: ~Jiií~1Rfflrtiiit1l~i31 robots~~~. :te robots.txt )Cféffl=l, "tlll*:ff$ * User-agent i(i.jt, i)ía~~$~ robots ~~3'!~ ~#1-iSllY-.l~ilitl, X;j~)Cflf*i#., ~d,>~:(f- ~ User-agent iè.::fko ~.* User-agent 89-íi~J-J, JJ!tl~tiiJLX;jiffüJ:fJl.~A:f$)i!f~, :te robots.txt Xf!fr:fl, "U ser-agent:*" ~ffb9iè.jt.r fi~ :ff-~ o Disallow: ~tffi-(f.jiffl~tiiit~~ ':!~ijjfojf:tla<j URL, ~~ URL ~i A~- ~ñ!'jfa<j.i! ~, È. ï'ij \ ), ~ $ 7t l~1h~, 1f iij ~ Disallow 7f ~ a<j URL :1$) ~~fbi robots ~ fo] ~tl o ~J :!m "Disallow:/help"X;j/help.html ~/helpfindex.html j3~ fc i.t :i.9l~ ï31 $i.jj foj, ffiï" Disallow:/help/" JJ!ljfè it : robots ijj i:l]/help.html. ffif~ (j~i.jj foj/help/index.htmlo ffiij-~ Disallow iè:5rj-j~, i$í!j3~~mé"jpjt:ff$7 j3fèil :~ijj i:l], :te robots.txt )Cf!f tp, ~&~:ff-~ Disallow iè.jro jzu:li\! robots.txt ~- ~~)Cftf, 51$ robots, j3~7f;qj :a<j. -~ robots.txt a<jftlr~ol': User- agent: * Disallow: /cgi- bin/ Disallow: I tmp/ Disallow : /private/ JJ!U~~MX1-T!Yf:ffa<JW~ ~J:'f?tlr~aJJ~ -t-~ma<j robots #J.i)l.x<j4ij:~~.'Rm~.ffl o ff..~.ftilf~ijx/cgi-binl,/tmp/ ~/private/~ a<j )e 14 o rílña<j191j T-~ Heritrix rp~ij robots.txt t!j-i.sl.a<jf-'ç~: e <-<

157 3.!ll tlf.j "1i1iiliJi" I I ;@:S ft ~!f i1f IPl public boolean disall ows(crawluri curi, String useragent) if (this == ALLOWALL) return false; if (this == DENYALL) return true; I I Ja:J- f; B~~H~J: if((honori ngpolicy.istype(curi, RobotsHonoringPolicy.CLASSIC) I I honoringpolicy. istype(curi, RobotsHonoringPolicy.CUSTOM)) && (lastuseduseragent == null I I! lastuseduseragent.equals(useragent))) lastuseduseragent = useragent; useragentstotest = new ArrayList(); I terator iter = useragents.iterator(); while ( iter.hasnext()) String ua = (String)iter. next(); if (useragent. indexof(ua)>- 1) useragentstotest.add(ua); ) break; I l:i13.@!fi:, fn~fi::;/;j true boolean disallow = false; boolean examined = false; String ua ~ null; I I W ~~ ~ ïit i.jj fñ] ífj Jn:l!Il.~~ 3l. ífj ''l * Iterator uas = useragentstotest. iterator() ; while(uas. hasnext() && examined == false) disal low = false; u a = (String)uas.next() ; Iterator dis= ((List) disallows.get(ua)).iterator(; 1 I *.Ht ~iltrífj~!i=l.:!!s i'ij 12). ijj fa) while(dis.hasnext() && examined == false && disal low - - false) String disallowedpath = if(disallowedpath.length() == 0) examined = true ; disallow = fal se; break; (String) d i s.next(); try String p = curi.getuuri().getpathquery() ; if (p!= null && p. startswith(disallowedpathl ).... ' >>

158 ) ) disallow = t rue; catch (UR!Exception e). logger. log (Leve l. SEVERE, "Failed getpathquery f rom... curi, e) ; ) if (di sallow. == false ) examined = true ; i:(honoringpolicy. shouldmasquerade (curi) && ua! = null &&!ua.equals("")) curi. setuseragent (ua) ; return d i sallow; te HttpClient 'É!. r:p, ~ tmt get.5i!g::ff post Jj~lif, ~ ~ia.j:tl!tfhj!;~ robots.txt ( J:)! :~, ~ 111\IfX DisaJiow ~~ ~J gj 3K rffl ~~ o ~i-!\. HttpClient 1:!211!~ T ~ia.~ t.!íi. ET I;.U,.f:~E'J ( (]~ $;f~ robots.txt t:jh)l.é]!*!1m, 13 ;ll1::, 1'1" JJ- 1' fxxl ~00.$. f'l:1ï, ~1fJkf :!HJld~F(f " i1;i.í~" ~ :tm llx I?J f* w~.?i It* ~ ~'f.'&~'* ï:t. ~ l,w.el\t1i'h-ji~"trfii~ =# :1J7!. ttp;ilii11)[ji Robots Meta é]ti*~l1uiítl~sl.é]l..'h" 1DJ o ~f.!p 1J'i't ~-;f4l i1!uy:& (JJ :ñ ~, í4lt~~ lltl lttl ~ tjtj 4ij 1-~ Ji5: o ~ 1;11!! é] MET A tf-~(jm ~ ffl l'f. i! ~, fifiif (J~J&, 7( -iffj~)-ff, Robots Meta tf-~!:!..f&:efiïfñ((.j<head> < lhead> r:p, ~ f1 ffl7ih!i i/f :ff j '71 ~ Ro bots :tzn Vii tij\ IlJt i:f< fi ((.] 1*1 ~ o Robots Meta f f,~r:p ~~jç/j, 'l1z7, name="robots"~7fpjf ffb<j:tffgl$. PJI?JftM;l!; 1- ~ ff;: t!t~ '31 $:!% :7.; name="baiduspider". Content -~Ht fi 4 1- tl'ht :lí\ :tyi : INDEX, NOINDEX, FOLLOW.fiJ NOFOLLOW, f' j~fa]i;j "," 71%. INDEX :f!í~i5-i)f~~,m~ )...11JtfJt *:iñliñ; FOLLO W te'~~ 7Frli ~ tj1.1!*)... i'ij I?J %\'lt ~]li iii J:-. éfj iihj~ ~111\15( r f; : Robots Meta f~~b<j~!a íil.je INDEX.fiJ FOLLOW,.Rfi~j) inktomi jjtffi~jl# ~~. ~t rb, l!l..kia.1j!l!è fndex.fil NOFOLLOW. ~ff. -~-oi 4 ff 1 tli1f: <META NAME = ROBOTS" CONTENT= I ND EX, FOLLOW" > <META NAME= "ROBOTS" CONT~~="NOINDEX, FOLLOW"> <~ETA NAME ="ROBOTS" CONTENT= "INDEX,NOFOLLOW"> <l.ffita NAME= " ROBOTS" CONTEN'l'= " NOINDEX, NOFOLLOW " > ;!t.:p : < MET A NAME="ROBOTS" CONTENT="TNDEX,FOLLOW" > ñj! J. ~ JJ.lè:. < MET A 8 << o - o o I

159 3. 11!1189 "ññwji" NAME="ROBOTS" CONTENT=" ALL"> < META NAME="ROBOTS" CONTENT="NOINDEX,NOFOLLOW" > PJ I». :!?J rn. < META NAME="ROBOTS" CONTENT="NONE"> ïitr;rr*, ~j;:$~ ft-j~~ sl $til.fia jiil~ robots.tx.t l'fjmjj!u, ïfñ~r Robots Meta.f/ï~. ïltr~ t-f(fj;f:ïf'~. ffl.~ie~~~!f(1jm, Wl~~ítfií)l $ Google Y.t%~:Ji:t.f, ïfij... Google if±l:híl T -1'-1~~ archive, i'ij ~ll&$jj Google :l:lp~h:wdil Jijj]A '!*Jffi. f91jwi: < META NAME:: googl ebot " CONTENT= " índex, fol low, noarchi ve" > ~~~!JX ~ l'tc!í,è: r:f:t é<j ])f iii* Yfift :1ñ: iii 9=' rt-j Tli~m ~ :J o~x, iii rt-j ~ ]í:f ~ Jffi ili H: :ïf' te Goolge. t. 'i!~hfl ~ Jiï * ~-~~T~H~-1'-~ ~" ~ ft~~ &é<j-8~ ~1».&M*~ ~ ~. ~ MT~~ é<j ".R"~ ill ~~r~~ ~ Jijjl'fJ~M. ~~ I'fJ E~~~~*~&. ~ ~. *~~~~H~MBé<JM~n~. ~~. w~~~~ T ±~~~~~~OO~.~~T ±.II!Jl!J:J S=!.~ ~IUEmê.!t1.rt-J-lft2.fOll.=! ' l'fj M fk. 1i $:. iftfu!j 7 =!::.tm #I Yéa.rt-Jff. ~ 1»..& QQ Wry.dat é<jmk~ ~-~~~TR.!t1.é<Jm. w~m 7 ~botsm ~#.~OO~-~~~ rt-jms. n~* -$ ~~mm ~. Jlt ~ ~ ~~T ± R!OO!Ré<J-.~~tM~- M~/J~~.&Jd:Jmé<J~~tvf~ iiij. l.html ~r:ififuf Y!rt-J±Il~V~.!:k, :tea I~ e ftsffl~~~~. ~- -t~ml'fj~ ~ ~/J~. Article/j sjy/jsjy200909/jsjy l.html--;11!i T.!ï.! tri-wt7t ~rt-j±~~$. ~m~~~~~~rt-jm~ PJ ~~~~~. i'i!k. ~ ~ oo~rt-j- e ~~ >>

160

161

162

163 "~il" HTML ~mi -(;E. f#l ~~~~'f, ~1fl.jlf-JfllJit~ fi-],i;j ~:f.t~ HTML Yf ti1, ~Jt, if- ~ ~ 'Ñ\ ~I ~~*,ftfi-j,i;j ~JtFtffi. -t HTML 'f é~; m ~~Yf dq '*-~. ~II<H~, $>1-T?t~~Jfll!.J HTML Yf dq, ~:J.~ *~$~«~fi-jj~;j~. :M.A~~~'f-~t~fi-J~M.~-* ~~a~ ~-~~+ ~:Ra.

164 te HTML ~JII!cp, ~'ijtf ffljtl.ie.!jltl;&:it.it. l:tjw, :E1IIJ~~JJHl~IJ1", JU!! 1IIJ.IIU~f4> ~~-.it~~~~atooo ;&~. w~~~~~-~~ ~ ~~.~~oo~~mtl;& k&:ï\: <[aa)\s+.*? [hh) [rr) [ ee) [ ff)=\s*("l\' )?(.*) (\1) (\s[a> ] *)*?>(. *? ) <\/[aa)> <a href="htt p : / / com">baidu</a> <a href= ' com'>baidu</ a> <a href=http :// com>baidu</ a > <a class= "l23" title="abc " href=" com">baidu</a> <a href="http: // <a href=http: // com >Baidu</a> I2SI Jlt, :ff re~~ ; ;t <[aa]\s+. *?[hhj[rr]f ee][ff] \s*("j\ ')?(. *)(\1)(\s["'> ]*)*?>(. *?)<V[ aa]>.is)zjfff~.!kl]ï,.!jl!jj!-1-19! ~ tpjyf~f'jl~ URL tt~1çà-=f J:ññ,O~I±B~~ URL, 'ffiï Jt~m :ït (f] URL V!tl-1&:W!1f:, ~ :itf:hi!of~. ~mtl ~.it:te~ oo.!k~. ~-~ ~ : X1 URL fi.:i1hri1~...rf.%llhq-8-ttje :ï\~fl!m. m~~:w~~. 1! -~, ~fn $t:»...ïe mu ~z :a:~ ~lil:ll vt:., :JHe.g. - J.I:h ~J ~ aq 7FW'J :ft-m IE mu ~z.it:tf Java.:P ~@.ffl $3:1 le9!1j~hts~.ïev!tl~~.it(regular Expres sion):tfit m~~.:p, ;ll!:ffi- 1-ffl *~~~:#~PE-~Utl~ *~1-~~-~~ ~ aq 1- ~ :tem$~*~m Jt~IAa..iE~~~.it~ -m-~.m*~j lll/~~ ~I!J.I:h~*~-1-m.it~~*~w.. i.lf$~~~*iit-atu1i:m=lf!jm.ïemu z.itiihr~~o!fi f»!ff:. l:t:ya Java ~1!. te IDK 1.4 Z.Fo. ~~~7-1-Wfi~~.i:kú<J.iE.!Jltl ~~:ï\.ie~ ~:ït~~mm.it~s B<J~:ït*~~.ïf~ ~:ït~3 ~ ~ d. rm/f ~$~-~ ~:tfl!j.i:h ~~~ -~~~~*~ ~~~7~~~~= 0 ~71'~- : 71'~$ a:i<7f -fi~ L!f! ~ -&J:~-8- *mm.ïflj!ij;~~j.\~)l Tr~tlg~: ( "1$ ~" )RS 11i71'~ir a,b 1 a E RA p E S, '~!Lo, "ab"l"c""d"l"ef'="abd","abef", "cd", "cef' << ;....

165 RIS ~7F R ~S fr-j#~o R * ~71'-@. ~ e # 1l ffi- :f:e ~ 1\1 $ $:fi r a<j R a<j :fr 'j' iij. o ïlj ~lllljj: R "f ag tt!il ~ 1' ~fj $tf]lf,~f!!j.!ijyt 1f!i<-r~$tfl~it. ~J"9P. "ab", "e"* = e, "ab", "e", "abab", "abc", "cab", "cc", "ababab",... o Kleene &~fï:fnii\lljfït)'é~, f~jb::iik$:fl, ~3f:t~#~ o 3m=*~~ Jl.i50!1JïlJ~~~1i5 Ji.o ~tl:9jl, (ab)c ïlj~~j.j abc, ífü aj(b(c*)) ïlj~sj~ albc*o ~Ff : alb* *71' e, a, b, bb, bbb,... o (alb)* ~~EI3~~' 1f~ 1'- a~b ~ ~:mpx;ff..l.p1f~*1'1*b<:lm ~o ~ ~ e)57j'*~~-1'-a GAOd$1'-b, ma- 1'-~ft~~ c~ ~ o.ie.q!ij ~~it ff~ ;ff' /f' ~ B<:l JXI.~ ~ 4.1 ~"tll±l T 1iUIHf1.iE.Q!tl5it :d:.&jt ):E.)( o ~ r.f #8 ~ \ A $ +? n n, ~"f - 1'-~r.f.f: ; igj.j- 1'-#'*!f.f-f, - -1'-Jilt)(!f.~, - -t-(1;].libijflrug- 1'-J\. lit$1j~ )( ~. i9ll Jm, "n" 121l!e~~ " n", "\n" u:j;;l'k!-1-~rr~. JT:jl] "" "!211ft:..,.. rfii "'0" Jll1JI21l!r. "O" o ll];í\li:.~a~~_.)'$ R]Jf!lfl![lt.!zlt:ll!'&~ T ie!jli]~jii~\lt'fit(f] Multiline ml fi, """ tbil!di! "\n" rot "\r" ;a RJ ii:'ft. V.C~~À~~f$1 (fl!t ];f[ -&1l. "lln:ll!~fl T ie!lltl:&jii"'jc<j-. (1<) Multiline att. "$" -tb!zk~ "\n" lljt "\r" fi'lf aq t'! Jï. ll]; ~liíjïiñ(i<)-t~ji;"'~ IX!ilt$ ~. 7\]W, "z.o*" fi~[çi3 "z" ~.&"zoo", " " ~jf -F " 0," [ÇA:! jí flü(i'.j-1-~~~- (X~$~. ~p. "zo+" tmiffil!ï! "z.o" ~.&"zoo". ffi:f~l[!;ftè "z": "+" ~fftt- "I," 121 ~ftïiii(i'.j -T ~~ ~~ - ~.~~. " do(es)?" PJ!:J.I21re "do"~ "does" 'flil':l "do", "?" ~fftt- lf 0,1" o n - 1'-$~-~. ~~~~~ n ~.~-. "o2" ::.ffm ll!!~ "Bob" tprj "o". ffil!fm ~ ftè "food " r:f1 (!'.] Wf 1' " o ". n :li!- 1'- ~~ia JU'x. ~ &ll!!~ n lx. ~J"!!ll. "o2," :::fj'mjzç"ift: "Bob" tp(i'.j " o", fi~l/1 li! " foooood " 'Í'l (Iq m :tf "o" "oi," ~1ftr "o+", "oo," Jiltl~fft'f- "o " m~n ~~~~- ~~.X'fln ~ m. ~j.> ll!! ~n ~A~~ I11~m~.~-. "o\,3" ~~ n,m IJè "fooooood" '1" (t-j;jtr -1'- "o", " o O, I " ~ 1ft -'- " o? " il!it:t.~ :(E~ -i~ -m 1' l'i Z fiïj ::f tfhr~~ >>.

166 * f: m ~ ~~~rq= ~Ji!~tEff for- 1';tf;MIIl1Uj JJf=q=(*,+,?, n, n,, n,m ).fl5í!iill't. ll!! ftè~ :i\: :li!:'! I= ím~ é<j? ~F~ ~t!i! :i\: g. 'ET ~y:tt!! 111lfèJ'fr rl ~ l'f.j ~.f'\1 $ ito~ 1A l'f.j~ ~ t~ :it!jn~ 'ET fi~~ fi!!.l11 ~èj'fr ~ ~ l'f.j ~rq:$, -wtj~. Mr~n-$ "oooo". "o+?" :tfil!!agjp-1' "o". rfii "o+ '' ~ll!!l'ièj'fr :ft "o". ll!!llic~ "\n" Z.;tH!'-Jff:fnf.tl!i'~rq. ~ll!!l!c-etïi "\n" ter Bllif:fnJ ~~. i~h.tffl ~I;). "[.\n]" l'f.j~~. ll!!!!c! pattem 1f~lj~-ll!!re.Pft~~éllll!!l!eï:iJ t;t.ja.f1:.a<j Matches ~il'~~'l, te VBScript (pattem) lf'1.tffl SubMatches ~il. te JScript o:fll!jh.t.lfl$0 $9 Jf.l!tt. ~llf~m!h~~~n-. ~1.tm "\0" ~ ''\" o (?:pattem) [!;!!E pattem fb ;r-tuxizhíèf;!i!il1.:. illmt~i#.i!~--1'~~=~~ 1l!!re. ;r-:i1l: rr :rht ~ i )_! i -!tm -wthzll."industr(?:ylies)" ~~-1'tt "industryjindustries" ~?m~l'f..l~i<s:~. ie (tij fn 1,t :te if:frif llf ~ pattem éij ~ ~ $ 7t Ml~ llf IG 1U~ ~~ $. ik :li':: -1' <li= ~1l llf re. 'tè. ~~i~l ~llhlè::f'~!jh~ifx~i J_J;5f~: ffl. ~~~. "Windows(?=95198JNTJ2000)"íf~ll!!llè Windows (?=pattem) 2000 t:j:ll'f.j Windows, ill:f'iie~re Windows 3.1 t:j:là!.j Windows. f!jblt::::f'yfllf ~~. lli~~i#.. :te- 1'~~-~Jii.tEMro-~ll!!EZ.!ii~~*~T-~Il!!EI'f..Jrl rro;r-~.ja~-~-à!.j *-~Z.fr:Uf~. ff!. [Í'i] ffl11f. teif:fnf :::f' 111 l'f. pattem ffj *-~o$ ff~!li: 111 E lh~ *-1tf o$ ~~--Nio~~ ll!! E. BS')(;~i#.. i.~ll!!e :t:f«i~~~fttl2j- Fo -ltffl. 91]~11 Windows(?!95198!NT\2000)íiEI21ilïC. Windows (?!pattem) 3.1 9='é<J Windows. a;r-~ll!!il!t Windows 2000 "FBIJ Windows. B!ft:.f~JH *~ te.~;!!i#., :tt- 1' 111 E:&1:..Fo. :te~ro-& 121 ~z.ro :lr.l>!ll ff ~fn -~ll!!l!eà!.j :jll~. rro ~i JA~ -s-ffi ~ a<j!f~z.foff~tt... xjy ~)liè x ~ Y f?jj:lzll, "z!food" ff~ll!!i.!g "z" Wi: "food", "(z!f)ood"!i\1][!;9!:! "zood" ~ "food"..[xyz] ~ il' ~ ~J'frft*l'f.Jif: rq=.~~ " [abc]" EJI ),ITI!l5è "plain" 9='8"J "a" ("x.yz] [ a-z.j ["a-z] \b " ["abc]" ~- ~ ~ ~re*~-s-~a ~.~~. 'EJI;lll!!l!ï'. "plain" "PI'f..l "p" ~~m.[!;~w2mm~~ff *~-~~. " [a-z].. i'ij i )_ lffi ftè a i~ z. m jjl Vil ífj if: ~+Sj!!J:*-:M' ~- ~-~.~~~~m~mffi~a<jh *:M' ~~. "["a-z)" 'EJ~llt:;llie! :t:te a!~ z m tlll ~ l'f.j ff~ n- ll!!re - 1' ~~~, &a w8~& ~Bil~~.~~. "er\b" ïij~[l!; f! "never" if' B'~ "er" í f.l /f~ ff~ 121 re "verb" ep 8~ "er" e <<

167 M 4. "tt " HTML Jili * r:f m it \B [!:!; l!è ~ ~$ Villiz 1f- "er\b" fi~i[!;ilè "verb" tpi](j " er", Jà:; \fthj&ftè ''never" cpe<j "er" [!; l!è Eti' x m ajj Brr.:l ft~j!;f-~ o ff!j :9o. "\cm"!ll;re- -t Control-M ~@1$f<t. x (!J f.r1~híli.1j A-Z \ex *.alg a-z Z.-. is Ji!lJ, e ~;:k]-1'-.!ili:.)( e<j "e" ~~o \d [ÇftG--t~~~~. ~iftr "[o-9r \0 J;fe-1'-~~~~?-r.f. ;?;!Jift T- ""0-9]" \f ~ l!è-1'-tk:9'lt-f. ~ift"f "\xoc" ;W "\el" \n lll!l!è-1'-~rr~. ~iftf "\xoa" ;W "\cj" \r l&l!è-1'- 1$~. ~ift -T "\xod" ;W "\cm" o \s ~&l!èff~~e~~. ~ ~ ~~ ~ift"f "[\t\n\r\t\v]" \S l&l!èff:~~~:21~pf-~. ~if)""f "["\t\n\r\t\v]" \t lll! l!è-1'-í!ilj:l(~. ~ifl"f "\x09" ;W " \ci" \v l&l!è--1'-~ihtlm~r.f. ~ift"f "\xob" ;W " \ck" \w l& ftè :f.srtj~i](j-fffcij!' tirj~~. ~ifl"t- "[A-Za-z0-9_]" \W Cll!ftèf:ffiT~~.t'(!. W~~. ~iftf "[-"A-Za-z0-9 _] " o \xn ll!!i'ig n. :;ep n ;:k+ /'\:it!: ffi~~ xm:. +7'\:it!:llliJ.ft.)( ff!!.iz> fjtijl,;?ifh~ e<jfw-t~~* ff/j:9o' "\x4 l, I& li! " A " ' "\x04 1" Jlllj~ift-F "\x04&1".ieji!~~:i<!i~\:cpiiji.lfj!jfl ASCll t\196-~. \num!&~ num. jtr:j-1 num -1!-"i'-.ïEjl~. ~j~. "(.)\\" IZ.!!l!è~ -1'-~!te<J*"" aj~f.f. ~l-r- -t-j\1~!:11itj~xú'l~--t-lnlfti<ji.ifl. Yll* "\n" Z.Rtr?ê~ n -1'-~Jtlte<J-T.Q;:i\:, Jill] "n" \n jl,; lnjfc5l.lfl. ~Jill], 1m!+!: n )J)\ltt$1~~(0-7), Jillj n ji,; --1'-J\ltt$]~.)(-fiï, ~i.r-1' \jltlf ij~.)( <na~g- -t-lnl Fc'llffl. Yll:ll! "\nm" Z.lltr.?ti~fl" m "i'~lllt. J)!lj "nm n j, \nm lnlfti 'Jiffl. :9o::!l! "\nm" Z.ílil?f.~:f'f n-i'~~. Jl!U "n" ji,: - 1-.Fo~.>c* m e<jj:um'jiffl. jm :ll!ïltri.fiía<j~flfm=1'ti11iti!. ;?5 n ;ftl m :l$jji,; J\illl!i~l!l~(0-7), li!tl\nm :14fl&l!èJ\illlMf#.:X.ffi nm. \nm I jm* n ji,; J\JzHiltl~*<0-3) m ;W I :l$j~j\illlt~ ~.!'f'(0-7), JlliJ[ÇIJGJ\ :ltt l!ltj~xúï nml. l&l!ii'. n. ;Jtcp n ~--t-.fihffi-1'-+r.i!tlfil]~~-?f-(fj Unicode ~f.f. ~]~. "\uooa9" 1!1l!èl\& \un t5l. f.f~(q) ~ >>

168 4.1.2 Java.IEOOIJ*its~ M. JDK 1.4 :Ff~ Java.util.regex t!!.i!!~t»jiey! IJ~~~tt-rJ(:f#. ~iida<j.jl/rf!jrwí:llltai ~T.ffl Java ~-e-~~iejiltl:&~~a<jjjl*. ~J : ~H~!;J. Java 7f!k, if:~ttíftl!a<j~~$. Pattern pattern = Pattern.compile( AJava. * "); Matcher matcher = pat tern.matcher( "Java ~- f1~;n~1f ; boolean b = matcher. matches (); /t~-*11f~.li!.l?'t, #f~! true. :S:9(1j~@ false System. out.println(b); Pat tern pattern = Pa ttern.compile( [, 11+"); String ] strs :: pattern. split ("Java Hello World Java, Hello,, World 1 Sun " ) ; for (int i=o ; i<strs. length; i++) System.out.println(strsi)J; Pattern pattern = Pattern. compil e( ie!jl1j:$<:i<!s::r.\: "); Matcher match er = pattern. matcher ( ie.!i!ll~:i<!s:a Hello World. l.e!j!ij~:i;!s:;r.l: Hello World" ) ; ; lf~m- -t-1-t- ï ïelïl'j~~j\:l'f.lll1~ System.out. println(matcher. replacefirst( "Java" )l ; Pattern pattern = Pattern. compile (.ïe.001];1;i6;r.l: J ; Matcher matcher = pattern. matcher (" ie!jllj~~:r.\: Hello World, IE!il tl ~:i<!s:j:t Hello World"); I I HJfHil- 1-.f.f- g-le!jl'j~ :i<!s: :r\: èij li~ system. out. println(matcher.replaceall("java")); Pattern pattern = Pa ttern.comp i le (" ie.\lll] ~lt:r.\:") ; Matcher matcher :: pattern.matcher l " JE!JliJ ~lt:r.\: Hello world, IE li!1j;$(jt:r.\: Hello Worl d ); StringBuffer sbr = new StringBuffer (); while (matcher. find()) matcher. appendreplacement(sbr, "Java ); matcher. appendtail(sbr); << l '

169 System. out. príntin(sbr.tostringq); Stri ng s t r= "c e ponline@y ahoo. com. cn"; Pat t ern pat ter n = Pattern. compile ( ( \ \w\ \. \ \-)+@ ( [\ \w\ \- ] +\ \. J + (\ \ w\ \-) +", Pattern.CASE_I NSENSlTIVEJ; Matcher match e r ~ pattern. matcher(str ) ; System.out.prin tln(matcher.matches ()) ; Pattern pattern ~ Pattern. compile("<.+?>", Pattern. DOTALLJ ; Matcher matcher = pattern.matcher(" <a href=\"index. html\ ">~Jt<la>"); String s t ring = matcher. replaceall( " ") ; System. out.pri n tln(string); Pattern patt ern = Pattern. comp i le("href=\"(.+? ) \ "" ) ; Matcher ma t c her = pattern.matcher( "<a href=\ "index. html\ " >.:l(ili</a> " ) ; if(matcher.find()) System. out. printl n(matcher. group(1 )) ; ~]: tgjj( I I Je.Qj URL Pattern pattern = Pattern. compile ( " (http: /llhttps : I/ J 1 \\w\\. \\-/ : ) + " ); Matcher matc her = pattern. matcher ("dsdsds <http : l/dsds//gfgffdfd>fdf " ) ; StringBuffer b u ffer = new StringBuffe r ( ); while(matcher. find()) buffer. append(matcher. group ()) : buffer. a ppend ( \r\n"): System. out. príntln(buffer.tostring()) ; f91j : ~~ ep ít-j )C-1 o String s tr = "Java ~íitre<j~~5t~eb 0 1f: - 11f: "; Strin g [] (J objec t=new String(J "\\ 0\\ "," 1995 ", new s tri ng [J " \\ 1 \ \ ", " 2 O O 7 " ; System. ou c.prin tln(replace(str, object )); public sta t ic String replace(final String sourcestring, Object(] ob ject String temp=sourcestring ; for(int i=o ; i<obj ect. length;i++) String() result=(string[))object[i) ; Pattern pattern = Pattern. compile(result[o)) ; Matcher matcher = pattern.matcher(temp); temp=matcher. replaceall(result(1jj ; >>.

170 return temp; M: ~~~ #ft~m~ ~~~~#. 11 J~Fftlf.ff.J'CfHiJ;& private ArrayList files = new ArrayList(); // 3& 7J'J'Cflf~~ private String _path ; I I~ 11' * * :tl' OfJ.ïE ~!IJ 3t< :JZ;::.;;t private String _re gexp ; class MyFileFilter implements FileFilter I** I ** *!l'!! lèj'c f!f ~ ~ *I public boolean accept (File file) try Pattern pattern = Pattern.compile(_regexp); Matcher ma tch= pattern. matcher(file.getname()); return match. matches(); catch (Exception e) returo tru e ; inputs *I FilesAnalyze (String path,string regexp) getfilename(path,regexp); I ** *?ttfi'jtflf~#.1.ma f iles input *I private void getfi lename(string p a t h, St ring regexp) /113jjt path=path; _ regexp=regexp; File directory; new File(_path); File[) filesfile = directory. listfiles(new MyFile Filter()) ; if (filesfile == nulll return; for (int j = O; j < filesfile. length; j++) files.add( f ilesfile[j)); <<

171 "~ " HTML Jiii return; I*" " ~ll'~l±lfe.~ out *I public void print (PrintStream out) Itera tor elements= files.iterator(); while (elements.hasnex t()) File file~(file) elements.next(); out.println(file.getpath()) ; public static void output(string path,string regexp) FilesAnal yze filegroupl ~ new Fil esanal yze(path,regexp); filegroupl.print(system.out); public static void main (String[) args) output("c:\\", [A-z l. ]*"l; ~00~~~-A~ili-kfflT hw~-~~-~~~~~-~*~~-~. I*J~. ~l±\--l';frjjji!~l2;t11:*~~j-=f -ftffl Java.iE!i!IJ~~~mltt URL: M~*~ public class 'I'estString ( I "* $i,x1fffl B"J-1!, =1'~J'tm:~t\ièwiEJï!IJ~.it:t.\, M-F í~~fflf?é~iwí%1$* I public static final String patternstringl="[a\\s ) *( (<\\s*[aa)\\s+(href\\ s *=[A>l +\\s* l>l (.*)<l [ aa)>). *"; public static final String patternstring2=".*(<\\s*[aa) \\s+(href\\s*=[ A> J +\\s* ) > (. * ) <I l a A J >). * ; public static f i nal String patternstring3=". *href\ \ s*=\\s*(\"l' ll http :ll *". ' publi c static Pattern patternl =Pattern.compil e(patternstringl,pattern. D OTALL) ; publi c static Pattern pattern2 =Pattern.compile(patternstring2, Pattern. D OTALL); public static Pattern pat tern3 =Pattern. compile(patternstring3,pattern.d OTALL); I ** args *I publ ic static void main(string[ J args) I * * ili!u i:i\ B"J ij& 1J.5 * I ' '... ' ' '..... '... >>

172 String ss= ~JiH!~iA<a ~T ; 1 **-f!.ihh!:è~lli*a<j url A3 Set.JA*ïliL.t~:lf!* 1 Set<String> set=new HashSet<String>(l; I * M ;tjt URL ;):f-f* f? te set -' * I parseurl(set,ss); I * * H x--1 M tjt lli* ffj URL i\ll ~ :f!ll * I System.out.println(replaceHtml(set,ssl); href=http : I l www. google. cn>www. google. cn<la>jt!fl;lè~ 1 ** ~~1'- URLJJil..l: target ~té* / public static String replacehtml(set<string> set,string var) String result=null ; I * * :IR:Ièr:.:r-~-ê&t3:~ r esult=var; Iterator<String> ite=set. iterator(); while(ite.hasnext()) String url=ite.next() ; if(url! =nu l l) result=result.replaceall(url,url+" target=\"_blank\ ""); return result; public static void parseurl(set<string> set,string var) Matcher matcher=null; String result=null; 1 \~b'ihiil't~ a ~U~ <a href=http: 1 l www. a. en>< l a>. 9!tl#-$f:È(!I)i(:Jjt~ 28 if(var!=null && var.length()>28) matcher=pattern3. matcher(var); I I lifú ~ ~ T H!i!!!:frfl. +lï~ if(matcher!=null && matcher.matches()) matcher=patternl. matcher(var); String astring=null; String bstring=null; while(matcher!=null && matcher.find()) if(matcher.groupcount()>3) 8 << o o

173 4 "~ " HTML JiJi bstring:matcher.group(matcher.groupcount() - 3); I 1~1' group 'El.~ffrf'.:f~*IE!l!U!$<i.!i:r.tl'fJ~~$. astring:matcher.group(matcher.groupcount()-2); I 1 ~1' g roup -El.* URL ( ]HTML tm~ String urll=matcher.group(matcher.groupcount()-1) ; I 1~/5-1' group it~ URL set. add (urll) ; I / ~ttj!j( (j URL ~ff. * bstring=bstring.replaceall(astring, ); I ~~~tt~j(t. URL (f] HTML~~ if(bstring!:null) parseurl (set, bstring) ; I ;m~v ~1ff!UCF-1' url * ie!í!ij:~jtit:fi!:.il ~~I $ 7f ~ep a<j ~!fi ::k*j ft. 13 w ffil ijt ~ 7 ie VliJ ~~xt ((.]t!* %1 i.h.fll m ~a ff~ie~-~~a<j~ti~~-m~*(!j~~-w~~~~m~~~~s~~m.ie~ *5;5~. 4.2 ~~~ HTML.iEX ~~~~~ffl?a<j~~~~~ba<j~;;t;:.~~. ~~~~~~~~~. ~~~~~~ wa~~~x;;t;:~$. &~~~~-~~~-~ffl~vl~-~it~ -~ ~M~ IA (~~ HtmlParser), ff~~~ll.flr:i:i!!.~~~ji:i:;~~.:;t;:ilítffj.!i!i-8- HtmlParser ~J:-ïii.Jj:~iE9ltl ~ ~. *wffilijt~1úl~ HTML Jt;,$: ' <]~ ~ HtmlParser HtmlParser ~-~JlH1MfJT HTML ~#((. Java'.,.3::~Mr$t~, Mlf50JJi1'1Jïiï. ;ftl.ffi HtmlParser, UJt;J.~~\2A~P'l$8<J1úl~ : )C*:ftll~. 1"1=~-Jfu~I(il~ïjl!ftl'fl~~ ~$$:A~ilil1Wt?. T.i.U~1!b~ o ~:lm PI!;).»..~ íitrjfoo tp 1!b ~ ftu~:iità TODO tf -~1!b -~~M-~OO ~~- -~ iiu&~~. 'f*i.ih:if~~1f ffl 89 o M~~~.~~ ~m~~~*~ooa<j ~~~~~-~. m~ ~ >>

174 jlfid)o HtmlParser flj ~~JJfJ~iili ~ ffle i2a r JL-1'-:ffiií: URL 1t ~ o fj~f9$1~ ie Jñ ITi:ï!:f (f] flhr lii~ o r-'*~~ o m~:rn:oo.:p ft<:lr--a- ~~*lltti!rjj ~ i5-fjj tii* o ~~HTML jljjj$tf.tnjg XML jlïfj o e HTML jf[fñ jflf~ o r ïiïit1i'híffl *tlt.iy3 HtmlParser ;l!'tmfrijwhjt HTML jltifff. ] o e HtmiParser ~~~~ :Pil FJT?F, HtmlParser * Jf.l T r~a f. ] Composite :flj.\, liitl RemarkNode, TextNode, TagNode, AbstractNode ~ Tag *11li~ HTML jlïiffrp f. ]~7G~ o r "ïiifrm HtmlParser!:f :!I ~B":l~o 1. org.htmlparser.node Node~ 1J )E;X. T itt~j'~%~~1~ ~~\H'FB<:l~.f'PA~t~d1= :1J~, -El..:Jïi : ~- ~3itl html)(;;$:, text )C;js:fjJ/Ji'!:: topiaintextstring, tohtmlo A m ~%!!.#;) :i!!i JJj f. ] 1J i'!:~ : getparent, getchildren, getfirstchild, getlastchild, getprevioussibling, getnextsibling, gettext ~. ~:HIR 1J ~.x-1 f!yb<:l~%~~ t'f1191m 1J 2 Page.x-1~ B<:l/J~: TITñ!ti.fffi J;tJfJMoci.Xi.Jt.M o ~Jt ij 2.t9Ml1:V:W a<j1ji't:: getstartposition, getendposition :i8 JJj "T.i J~. B"J"Jjrt;: accept (Node Visi tor visi tor) Filter :;/Ji'!:: collectlnto (NodeList list, NodeFilter filter) 2. org.htmlparser. nodes.abstractnode AbstractNode.Jl!:%P.J(; HTML ~%~ ttjb<jm!;!.~~. E HtmlParser ep, Node 71-P.J(;=~o e RemarkNode: 1~~ HTML!:f l'fj?'.:h!f o getpage ~JJJ T Node~ CJ o <<

175 .. ~., HTML Jiili TagNode:.ti-~ï!R: o TextNode: Jt:<iS:il R,( o ~- ~ "'P r.'i($~ jf( AbstractNodeo 3. org.htmlparser.nodes.tagnode TagNode ' ~ 7.x>t HTML ;ct:l agft,c,,eg~.-r~. J!Pfi::(f TAG ag~~. Jttp X'lt ~J"~~ ;ltf!b TAG é<j ~'*l'l #.- ComositeTag ~ ::f'' ~;!t-ftt!, TAG é<jilf.:t-1) B Tago :!'! '* ïl #.- CompositeTag : AppletTag, BodyTag, Bullet, BulletList, DefinitionList, DefinitionListBullet, Div, FormTag, FrameSetTag, HeadingTag, HeadTag, Html, Labe!Tag, LinkTag, ObjectTag, ParagraphTag, ScriptTag, SelectTag, Span, StyleTag, TableColumn, TableHeader, TableRow, TableTag, TextareaTag, TitleTago 11-~.:t-il #. TAG ~fis : Meta Tag, Processinglnstruction Tag o HtmlParser fl" wg# ::tfj:\ïlf l2j-~ [l:ij E:~f)(X] : ::tf;a:ijj fil) HTML agt~~ur : BaseHrefrag, DoctypeTag, FrameTag, ImageTag, lnputtag, JspTag, Visitor 1JJ:\~ Fil ter ::tf~ o ;lttj:l, * ffl Visitor t ry Parser parser ~ new Parser( ); parse r.se t URL(" le. com" l; parser. setencod i ng(parser.getenco d ing () ; NodeVisitor visi tor ~ new NodeVisitor) public void visittag (Tag tag) l ogger. fat al("testvisitorall() Tag name is + t a g. get TagName(l + " \n Clas s is : " + t a g.getclass()) ; ; parse r.visitallnodeswith(visitor) ; catch (ParserException e) e. p r i ntstacktrace() ; " ~Uf.l Filter 1JJ:\iJi l'lij HTML a<j ft~"! or, try NodeFilter filter = new NodeCl a ssfilter(linktag.clas s) ; Par ser p a rser = new Parser(); parse r. seturl( "http: I lwww. google. corn") I ; parser.set Encoding(p a r ser. get Encoding( ) ) ; NodeLi s t list = parser.extractal lno d e sthatma t c h (filter l ; f or ( i nt i = O; i < list. size() ; i++) Li n ktag node~ (LinkTag ) l ist. elernentat(i); logge r. fatal( NtestLinkTag() Link is :" + node.extrac tlink()) ; c a tch (Exception e) >>.

176 e.printstacktrace(); fim.ïewrj ~ iis ~:fltl!fi/ffjrj Y M HtmlParser é]~;;fl:~r:r ifl.z.j ',!lt1fj:w- :W%1-fiiJ1E HtmiParser ll'lffl ~lj ~MtOO.!:t!. r:p, ib 8,t :li!: Wl iii 1!ffi HtmlParser :~IHEII ~!!t 1fJ JiJT m :l!hf.j ~ fi. o te~~~ ER!:f, ~H~ Hm Ri, Ml Itt ~if*~ tl ~ B<J I 1"F, l.lll~ 3tr:r iiitffl HtmlParser *MI~Jrïií!:f a<j~~ ~~? ;fl;~~~~fij ~, ff,t;!il:ffl Visi tor :JJP.:*iJ'J rllj Html "P é]~ F.L ~l'i,b~~~ LinkTag ( ]11-jf~, -í*~f'i~ : public void testtagvi s i tor(string url ) Parser parser = null ; NodeVi sitor visitor = null ; try parser = new Parser(url) ; v i sit or = new NodeVisi t or public void visittag(tag tag) if (t ag instanceof LinkTag) LinJcTag link = (LinkTag)tag;II~J!Iltag String linkst ring = link. getlinjc ();I ~~~liufz I I Miii~ f$:izj:-2b~j!il ; parser.visitallnodeswith(visitor); catch (ParserExcept ion e) ~i:-..t- lt ~ 33 é] ie Ji.!IJ :;ttjt::rt: AS P3 $, PJ l;j. ~ f,w, ~ ihf:ffl HtmlParser ~.ïe Ji.!IJ;& Jt: P.: a<1 Ml Jtt.lï~ o :;k ~ ~ ~.!:t!.~f'f ~ j;1uf1 ~ 1''P 1J ~, 7t ;lt:li!:-.tj:b ~ 1I 1t ~ i!ji $, ~ 1t ~ ~ [tij :tm~ ~iit! r:p éj~~~j]!e) ~ P3 $o rtfíiff,t~ ti:l jf!Jffl ieji.! ij;&~:r.t:-*0 HtmlParser é]~i!k$:~ [ti]:tffijtt(fj19~t o ifi::k*!l!r.i.*8: public List extracthtml(node nodep) i!ej~, ~ffi~jftjffl i!iv::! ~:Jj~~ffi:!Wliio rïii~~~~ljffl Y HtmlParser Jr-J:tm!tt?HJT:rJfl~o publ ic c l ass TestTabDivSerial I** *!fftr *I private static final String NEWLINE = System.getProperty ( "line. separa tor" ); I ** * *.n-**li *I G<<

177 "~JI" HTML l'iii private static final private String url ; int NEWLINE_ SIZE = NEWLINE.length(); private final String oriencode = "gb2312,utf-8, gbk,iso l " ; private ArrayList htmlcontext = new ArrayList() ; priva t e Str ing urlencode ; private int t a b l enumber; private int c hannelnumber; private int totalnumber; I /URL ielí!tl7tü5 private String domain; private String urloomaipattern; private String urlpattern; private Pattern pattern; private Pattern patternpost ; public void channelparseprocess() I * t~!jr* Mfifí.li!.B<J.iEPI'I~iii~\** I u r ldomaipattern = "( ht tp: ll[ ~ l] *? " + domain + " /) (. *?)"; u r lpa ttern = " ( http : //[~/] *?" + domain + " /[".)*? ). ( s html lhtmllh t mlshtmlphp\asp#lasplcgiljspl a spx)" ; pattern = Pattern.comp ile(urldomaipattern. Pattern. CASE_INSENSITIVE + Pattern.DOTALL); patternpost = Pattern. compile(urlpattern, Pa ttern. CASE_INSENSITIVE + Pattern.DOTALL); I "* s&~~ill~il **I SplitManager splitmanager = (SplitManager) ExtractLinkConsole. co ntext. getbean( "splitmanag er" ) ; urlencode = dectedencode(url ) ; i f (urlencode == null) r e turn; singcontext(url); Iterator hi= htmlcontext. iterator(); if (htmlcontext.size() == Ol return; totalnumber = htmlcontex t.size() ; 11?HR*JMfHt while (h i.hasnext()) ( TableContext te= (TableContext) hi. nex t (); this. totalnumber = tc. g e ttablerow() ; if (tc.gettablerow() == this. channelnumber) I I (this. channelnumber == - 1)) System. out.println(" *********************~ ~ + tc.gettablerow() + "****************") ; List linklist = tc.getlinklist() ; I I P!J ~ 3i -ff tf<ij itt~ i f ( ( linklist == null) I I ( linklist. size () -- O) l : >>

178 continue; Iterator hl = linklist.iterator(); l ** :?t~-&!1'-~$**1 while (hl. hasnext () l LinkTag l t = (LinkTag) hl.next(); / **i:t~ ~~'i't link"* I if (isvalidlink(lt.getlink()) - - SpiderConstant.OUTD OMAINLINKTYPE) continue; if (lt.getlinktext(). length() < 8) continue; l* * i:t~x~ link**l if (splitmanager. ischannellink( l t.getlinktext(ll!= SpiderConstant.COMMONCHANNEL) continue; I **1:.!DZ l i nk (!() hashcode* * I System.out.println("URL:" + lt.getlinktext() + + lt. getlink () l ; I ** * ~J *i;li! ili' ~J fl~~t:& * I public int isval idlink (String linkl Matcher matcher = pattern.matcher(link) ; while (matcher. find( ) int start = matcher. start(2); int end = matcher.end(2); String posturl = link.substring(end). trim(); if ((posturl. length( == 0) I I (posturl.indexof("."l < 0)) return Spi derconstan t.channellinktype; else Matcher matcherpost = patternpost.matcher(linkl ; if (matcherpost. find ( l) return SpiderConstant.COMMONLINKTYPE; else return SpiderConstant.OUTDOMAINLINKTYPE; << :

179 return Spí derconstan t. OUTDOMAI NL!NKTYPE; I ** * i&tl! HTML ~ffiif l,@. *I publíc void singcontext (St ring url) try Parser parser = new Parser(url) ; parser.setencoding(urlencode); tablenumber = O; for (Nodeiterator e= parser. elements(j; e.hasmorenodes() ;) Node node= (Nod e ) e. nextnode() ; íf (node i nstanceof Html) extracthtml (node); / ** catch (Exception el * lil J/3ftlï 11! ffi.~ * I publíc List extractht ml(node nodepj NodeList nodelíst = nodep.getchildren(); boolean bl = false; if ((node List == null) f f (nodelist.size () == 0)) ( return null ; i f ((nodep instanceof TableTag) f f (nodep instanceof Div)) bl = true; ArrayLíst t ablelist = new ArrayList(J; try for!nodeiterator e= nodelist.elements(); e.has MoreNodes() ; J Node node= (Node) e. ne xt Node() ; i f node instanceof LinkTag) tablelist.add(nodel; else í f (node instanceof ScriptTag f f node instanceof StyleTag ( f node i nstanceof SelectTag) ( e l se if (node instanceof TextNodel if (node.gettext(j.trim(j. l ength() > 0) ( tablelist.add(nodel ; else Li st t emplist = extracthtmlnode); if ((templist!= nullj && (templíst.size(j > 0)) Iterator ti = templist.iterator() ; >>

180 /** catch (Exception e) while (ti ohasnext()) tablelist oadd(tionext()); if ((tablelist!= null ) && (tablelistosize(j > 0)) if (bl). TableContext te= new TableContext(); tc osetlinklist(new ArrayList()); tcosettextbuffer(new StringBuffer()); tablenumber++; tcosettabl erow(tablenumber); Iterator ti = tabl elist oiterator() ; whi le (tiohasnext()) Node node = (Node) ti onext () ; if (node instanceof Li nktag) tcogetlinklist() oadd(node); ) else tcogettextbuffer() oappend(collapse(nodeogettext () oreplaceall (" "") )) ; htmlcontextoadd(tc); return null; else return tablelist; return null; * ~~5E%~1f * I protected String collapse (String string) int chars; int length; i nt state; char character; StringBuffer buffer = new StringBuffer(); c hars = stringolength(); if (0! = chars) length = buffero l ength(); state = ((0 == length) 11 (bufferocharat(l ength- 1) --' ') 11 << o o o o o o o o o o. o o o. o. o o o. o o o o o o o.. o o. o. o o o o o o o o o o o o o o o o o o o. o o o o o o o o o o o o o o o o o o o.. o o o o. o o o o o o o o o o o o o o o. o o. o o o o

181 "tt " HTML ji[ji ) ( (NEWLI NE_SIZE <= lengthl && buffer.substr ing(length - NEWLINE_ SIZE, l engthl. equals(n EWLINE)))? O : 1 ; for (int i = O; i < chars; i++) character = string.charat (i); switch (character) case '\u0020': case 0 \u0009 ': case '\uoooc o : case o \u200b' : case ' \uooao' : case '\r' : case ~. \n : if (0!"' state) state "' 1 ; break; default: if (1 ="' state) buffer. append(' '); state = 2; buffer. append(character); return buffer. tostring(); * I private String dectedencode(string url) s tring[ J encodes "' oriencode. split("," ); for (int i "' O; i < encodes. length; i++) i f (dectedcode(url, encodes) return encodes; return null; publ ic boolean dectedcode(string url, String encode) t ry l Parser parser = new Parser(url ; parse r. setencoding(encode; for (Nodeiterator e = parser.elements() ; e.hasmorenodes() ;) Node node= (Node) e. nextnode(); if (node i nstanceof Html) >>

182 return true ; catch (Exception el r eturn false; public String getdomain() return domain; public void setdomain(string domain) this. domain = doma in; public Pattern getpatt ern() return pattern; public void setpattern(pattern pattern) ( this.pattern = pattern; ) public Pattern getpatternpost(j return patt ernpost; public void setpatternpost(pattern patternpost) l this.patt ernpost = pattern Post; public String geturldomaipattern() return urldomaipattern; public void seturldomaipattern(string urldomaipattern) this. urldomaipattern = urldomaipattern; public String geturlpattern() return urlpattern; public void seturlpattern(string urlpattern) ( this.urlpattern = urlpattern; public int getchannelnumber() return channelnumber; public voi d setchannelnumber(int channelnumber J ( t h is. channelnumber = channelnumber ; public int gettotalnumber() return totalnumber; << '..... '... '... '... '....'... '....

183 M 4. "ttji" HTML )fili public void settotalnurnber(int totalnumber) this. total Number = totalnurnber; publ íc String geturlencode() return urlencode; public voi d seturlencode(string urlencode) this.urlencode = urlencode ; publíc Strí ng geturl () return url; publíc void seturl(st ring url) this.url = url; ~00~~~-~ H~Wu~~~M ~~~~~~~--fi T~ M.~-~ ~ ffil :!:1!! ~~:tm fcïj 3m!Ut ~ Jír.iE )C o 4.3 j~ ~ ie Jt M ~)C~~--~~Awm ~. ~-~~-~:l:t!! ~x ~~-~~~. -~~ ili*~*w ~ ~~ * ~~~m~ ~x3m ~ ~~ = ~ ~~-~~ ~m o ~~~~--~~~~~~0~~~~-~~mm~.a*~~ ~m ~~~~M. 1E *l9:~flij] "* Jjèjl~ o ~~----~~~-m~&* WARmfflf~M~~Mo-~4~ $~ M~MM. r 1 -~~~ "1. o W:l!i~ 1t1t1f.li:ll:f ~ ür ~ Iii~ T r i!r -~~fl"~w~~~i'tll;t? fff~a<j.ie)c~~-ff~~jrj~*f.~. Jjfi.f.t~.IE)C~*/Jllil~~r.t;Jt-ftB.Jt~fflit-~*!Jt, ~&&il:.fl-idiiia<jiejt. ~~&~ff *mr&i-ffff-n!a<j~x. m t;j~~j5y~~vg~-~~~ o o ~~* w-~~m ~~~ ~*~X*<~~-~.IE)C~X~~~-~? - ~~~)C:.fs:~~~iii~:Jè =é:-~ TABLE. DIV l~ ParagraphTag Io ~.1ft. ~'@.*)C~:Ii$1Y-J DJV ~~ TABLE, jf! ~8~t!G~~ 7 if)co tt~ujjji ~~:ïf'd> HTML :ifiïti, HTML J;~~*ll~rt7.IE)C~*/Jt, Wll11~~$À1&~ JavaScripto H~IParser ~ 'M'~:#f~~n~-ij ij.-~jjk.ïe)cjq 12). iju)ij, ~~~&~ IE.)(J'l~;l! :;t JavaScripto ~JltW~~J.9j:;ljk~l@. l!.m.ooj!1f.jj~~ HTML cp1jl;ffl~~~. t»..&~~m., IE :>crf!*~<*~~m:w - ~~~tf.l~~. n4~:in*~ ilhfj~~ 7 ~~ vmm:l:t!!frm~ Jñ -t~)o. TW~--~~~~~~00~~~*~~: I /table 1HHI:fl<liè)k >>.

184 pub li e e l a ss TableVal id priva te int trnurn; privat e int tdnum; private int linknum; private int textnum; private int scriptnurn; public int getscriptnum() return scriptnum; publ ic void setscriptnum(int scriptnum) this.. scriptnum = scrip t nurn; publ ic int getlinknum() return l inknum ; publ ic void setlinknum(in t linknum) this.linknum = l i nknum; publ ic int gettdnum() return tdnum; publ ic void settdnurn(int tdnum) this.tdnum = tdnum ; public int gettextnum() return textnurn; publ ic void settextnum(int textnum) t his. t extnum = t e xtnum; public i nt gettrnum) return trnum; public void settrnum(int trnum) this. t rnurn = trnum; I I t abl e 9'l B<J ;f;]?# publ ic class TableContext private List linklist; private StringBuffer textbuffer; private i nt tab l erow; private int totalrow ; private String sign; << '... '...'.....' '.'...'... '. '...'...

185 "tt " HTML iliiti public String getsign( ) return sign; public voi d setsign(string sign) this.sign = sign; public int gettotalrow() return totalrow; public voi d settotalrow(int totalrow) this. t otalrow = totalrow; public i nt gettablerow() return tablerow; public void settablerow(int tab l erow) this.tablerow = tablerow; public List getlinklist( ) return l i nklist; public void s etlinklist(list linklist ) this.li nklist = linklist; public StringBuffer gettex tbuffer() return textbuffer; public void settextbuffer(stringbuffer textbuffer) this. textbuf fer = textbuffer ; I / col umn fï~i'.í tr..liè~ public c l ass TàbleColumnValid int tdnum; boolean valid; public int gettdnum) return t dnum; public void settdnum(int tdnum) this.tdnum = tdnum; public boolean isvalid() ( return valid; public void setvalid(boolean valid) this.valid = vali d; >>

186 I 19i: úiï~~ public class PageContext pri vate StringBuffer textbuf f er; private int number; private Node node ; ) public Node getnode(l return node; public void setnode(node node) this.node = node; public int getnumber () ( return number; public void setnumber(int n umber) this.number = number; public StringBuffer gettextbuffer() return tex tbuffer; public void settextbuffer(stringbuffer tex tbuffer) t h is.textbuffer = textbuffer; I I ie )C 1tll Jtl(.:E~~ public class ExtractContext protected static final Str ing linesign = System.getProperty( "line. separator J; protected static final int l i nesign_size = linesign. length(); 1 ** ~5l.~m..trx * 1 publ ic static final Appl icationcontext con text = new ClassPathxmlApplicationContext(new String[) ( newwatchlpersistence. xrnl", "newwatch/bi z -util. xrnl ", newwatchlbiz- dao.xrnl ) ; I ** args * I public static void main(string[] argsl ExtractContextConsole c onsole = new Ex tractcontextconsole( ) ; << :

187 "tt " HTML Jfi'ii ChannelLinkDO e = new Channel LinkOO() ; c.setencode( "gb2312 "); c.setlink("http: // he. eom.cn/files/200712/ shtml ) ; c. setlinktext ("test") ; console. makecontext(c) ;! ** *!&#! HTML 9J:!llim.~ url urlencode * I public void makecontext(channellinkdo e ) String metakeywords = "<META content=o name==keywords>"; String metatitle = "<TITLE>O</TITLE>"; String metadesc = "<META content: Q name=description>"; String netshap = "<P> iejt'l'r!!«: IM'Ili]O<fp> ; String templeate = "<LI class=active><a href=\ " (0 \ " target=_blank >l</a></li>" ; String crop = "<P><A href=\ "0 \ " target=_blank>l</a></p> ; try St ring siteurl = getlinkurlc.getlink()) ; Parser parser = new Parser(c.getLink()); parser. setbncoding (c.getencode()) ; for (Node i terator e = parser. elements(); e. hasmorenodes() ; ) Node node = (Node) e. nextnode() ; if (node instanceof Html) PageContext context= new PageContex t(); context.setnumber (O) ; context. settextbuffer(new s tringbuffer()) ; I I :j \,Jr l±l R ~ e xtracthtml (node, context, siteurll ; StringBuffer testcontext = conte xt. g ettext Buffer () ; String srcfilel?ath = o: lkuaiso/site/templeate/cont:ext. vm ; String destfilepath = "0: / kuaisolsite/test/test.htm ; BufferedReader reader = new BufferedReader (new InputStreamReader ( new FileinputStream(srcfilePath), "gbk " )) ; Buffered'lriterwri ter = new BufferedWriter (new0utputstrearri'lriter ( new FileOUtputStream(destfilePath), gbk "ll ; String l i neconte xt = context.gettextbuffer (l.tostri ng () ; String line; while ((line = reade r. readline())!=nul!) i nt start = line. indexof( #context ); if (start >= Ol String tempcrop = StringUtil.replace(crop, "0", >>

188 ) e. getlink () ) ; tempcrop = s tríngutil.replace(temp Cr o p, "1) ", ".ffii:xiiu~: + e. getlink ( 1 ) ; wríter.writ e( t empcrop + linesign) ; wr i ter. write(netshap + linesign ) ; wr i ter.write ( l i necontex t + l inesi gn) ; contin ue; int startl = line. indexof( "#titlede sc ) ; if (startl >= O) String templine = StringUtil. replace (templeate, 0, test. htm"); templin e = Str ingutil.replace(templin e, "1", ~f&:. + e. getlinktex t (I ) ; writ e r.write(templine + lines i g n ); conti nue; int start2 = l ine.indexof("#metati t le" ); if (start2 >= Ol metatitle = StringUtil. replace(me t atitle, "0", c.getlinktext()); writer.write(metatitle + linesign); continue; int start3 = l ine. indexof( "#metadesc") ; if (start 3 >= O) metadesc = StringUti l. replace(met adesc, " 0) ", c.getlinktext()); writer.wr i t e (me tadesc + linesign) ; continue; writer.write(line + linesign); writer. flush() ; writer.close() ; reader.close ( ) ; catch Exception e) ( Sy stem. out.println (e) ; /I M.-1'~~$tpí~È;Itrt\i!~ pri vate String getlinkurl(st ring link) Stríng urldomaipattern = " (htt p:// ("IJ *? " + "/(.*?) " ; <<

189 "~" HTML Jfii W [.. Pattern pattern = Patternocornpile(urlDornaiPattern, Pattern ocase_ INSENSITI VE + PatternoOOTALL); Matcher matcher = patternomatcher(link; String url = ""; while (rnatcherofind( )) int start = ma tcherostart (l) ; i n t end = matcheroend(l) ; url = linkosubstri ng(start, end - 1 ) otrim() ; return url; / ** * Jà1 J)3 fó~ jf )t~ ~-@. nodep *I protected List extracthtrnl(node nodep, PageContext context, String siteurl) throws Exception NodeList nodelist = nodepogetchil dren () ; boolean bl = fal se; if ((nodelist == null) I I (nodelistosize() -- 0)) if (nodep instanceof ParagraphTag) ArrayList tablelist = new ArrayList(); StringBuffer temp = new StringBuffer(); ternp oappend("<p style=\ "TEXT- INDENT : 2ern\ ">"); tablelistoadd (temp) ; ternp = new StringBuffer (); tempoappend("</p>" ) oappend(linesign) ; t a blelistoadd(temp) ; return tablelist; ) ) return null; i f ((nodep instanceof TableTag) I I (nodep i nstanceof Div)) bl = true; if CnodeP instanceof ParagraphTag) ArrayList tablelist = new ArrayList() ; StringBuffer ternp = new StringBuffer(); ternp oappend ("<P style=\"text-indent: 2em\">"); tablelist oadd(ternp); extractparagraph(nodep, siteurl, tab l elist); ternp = new StringBuffer(); ternpoappend ( "</P>"l oappend(linesign); tableli st oadd(ternp); o o o o o o o o o o o o o o o o o o o o 0o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o>>

190 return tablelist; A rray~ist try tablelist = new ArrayList(); for (Nodeiterator e= nodelis t.elements() ; e.hasmorenodes( ) ;) Node node = (Node ) e. nextnode (); if (node instanceof LinkTag) tablelist. add(node); setlinkimg(node, siteurl); else if (node instanceof ImageTag) ImageTag img = (ImageTag) node; if (img. getirnageurl(). tolowercase().indexof(" <O) img. setimageurl(siteurl + img.getimageurl(); else img. setimageurl (img.getimageurl()) ; tablelist. a dd(node) ; else if (node instanceof ScriptTag I I node instanceof StyleTag I I node instanceof SelectTag) else if (node instanceof Tex tnode) if (node. gettex t(). length( > 0) StringBuffer temp = new. StringBuffer() ; String text= collapse(node.gettext(). replaceall (, ). replaceall (, ) ) ; temp.append(text.trim()) ; tablelist.add(temp); else if (node instanceof TableTag I I node instanceof Div) TableValid t ablevalid = new TableValid() ; isvalidtable(node, tablevalid) ; if (tablevalid.gettrnum() > 2) tablelist. add(node) ; continue; ) List templist = e x t racthtml(node, context, s i teurl); if ( (templist!= nulll && (templist.size() >O)) ( Iterator ti = templist.iterator() ; while (ti.hasnext()) tablelist.add(ti. next)); catch (Exception e) <<

191 return n ull; i f ((tablelist!~ null) && (tablelist.size() > 0)) if (b l) StringBuffer temp = new StringBuffer(); I terator ti = tablelist.iterator(); int wordsi ze = O; StringBuffer node; i n t status = O; StringBuffer linestart = new StringBuffer( "<P style= \ "TEXT- INDENT : 2em\">"); StringBuffer lineend = n ew StringBuffer("</ P> " + linesign) ; while (ti. hasnext()) Object k = ti.next(); if (k i nstanceof LinkTag) if (status == O) temp. append(li nes tart); status = 1; node = new StringBuff er(((linktag) k).tohtml()) ; temp.append(node) ; else if (k i nstanceof ImageTag) i f (status == 0) temp. append(linestart); status = 1; ) node= new StringBuffer(((ImageTag ) k). toht ml ()) ; t e mp.append (node); e l se i f (k i nstanceof TableTag) if (status == 0) temp. append(linesta rt) ; status = 1; node= new Strin gbuffer(((tabletag) k). t ohtml ()); temp.append(node); else if (k instanceof Div) l i f (status == 0) temp.append(linestart); status = 1 ; node= new StringBuffer(((Div) k). tohtml()); t emp.append(node); else node = (StringBuffer) k ; if (status == 0) if (node.indexofi"<p" l < 0) '. '.. '... '... '. ' ' '... >>

192 temp.append(linestart); temp.append(node); '<lords i ze = wordsize + node. length () ; status = 1; else temp.append(nodel; status = l ; else if (status == 1 ) if (node.indexof("</p") <O) if (node. indexof("<p"l < 0) temp.append(node); wordsi ze = wordsize + node.length(); e l se ( temp.append(lineendl; temp.append(node) ; status = 1; e l se temp.append(nodel; status = O; if (status == 1) temp.append(lineendj ; if (wordsize > context. getnumber(jl context. s e tnumber(wordsize); conte xt.settextbuffer(temp); return null; else return tablelist; return nul l; / * * * ~~00-jg i!~ nodep s i teurl * I private void setlinki mg(node nodep, String siteurl ) 8<< ;

193 / ** NodeList nodelist = nodep.getchildren(); try for (Nodeiterator e= nodelist.elernents() ; e.hasmorenodes();) Node node = (Node) e.nextnode(); if (node instanceof ImageTag) IrnageTag img = (ImageTag) node; if (img.getirnageurl(). tolowerease(). indexof("http: //"l <Ol irng.setirnageurlsiteurl + irng.getirnageurl(l); else img. seti mageurl(img.geti rnageurl()j ; catch (Exception e) returo; return;.. f6 JfK fj!: li <P ( <] r"1 ~ nodep siteurl tablelist * I private List extra ctparagraph(node nodep, String siteurl, List tablelist) NodeList nodelist = nodep.getchildren(); if ((nodelist == null) li (nodelist.size() ==Ol) ( ) if (nodep instanceof ParagraphTag) StringBuffer temp = new StringBuffer() ; temp.append("<p style=\"text-indent: 2em\">") ; tablelist.add(temp); temp = new StringBuffer() ; temp.append("</p>"l. append(linesign); tablelist. add (temp) ; return tablelist; returo nul l; try ( for (Nodelterator e= nodelist.elements() ; e.hasmorenodes() ; l ( Node node = (Node) e.nextnode() ; if (node instanceof ScriptTag I I node instanceof StyleTag I I node instanceof SelectTag) else if (node instanceof LinkTag) tablelist.add(nodel; setlinkimg(node, siteur1); >>.

194 els e if (node instanceof ImageTag) I magetag img = (ImageTag) node; i f ( img. get imageurl (). tolowercase (). indexo f ( "ht tp: I I " ) < O) img. setimageurl (siteurl + img.getimageurl ()); e l se i mg.setimageurl(img.getimageurl()); tablelist.add(nodel; else if (node i nstanc eof Text Node) i f (node.gettext(). tri m(). length ( > 0) String text = collapse(node.gettext(). replaceall ( " ", "" ).replaceall (" lt " ) ) ; " StringBuffer temp = new StringBuffer(); t emp.append(text); tablelist.add(temp); else if (node i nstanceof Span) StringBuffer spanword = new StringBuffer() ; getspanword(node, spanword); if ((spanword!= null) && (spanword. l ength(l > 0)) String text = collapse(spanword.tostring(). replaceall ( " ", "" ). r eplaceall (, "" ) ) ; StringBuff e r temp = new StringBuffer () ; temp.append(tex t ; tablelist.add(temp) ; e l se if (node instanceof TagNode) Stri ng t ag = node.tohtml(); if (tag.length() < = 10) tag = tag.tolowercase(); if ((tag. indexof("strong") >= 0) 1 I (tag.indexof( "b" ) >= 0)) StringBuffe r temp = new StringBuffer(); temp. append(tag); tablelist.add(temp) ; else if (node instanceof Tabl etag I I node instanceof Div) Tab l eval id tabl eval id = new TableValid() ; isvalidtable (node, t ablevalid; if (tableval i d.gettrnum() > 2) tablelist.add(node); continue; <<

195 .. ~... HTML Jl'li ) ) ) catch Except ion e) return nul l; return tabl eli st; extractparagraph(node, siteurl, table List) ; protected void getspanword(node nodep, Str i ngbuffer spanword) Nod elis t nodelist = nodep.getchildren(); try for (Nodeiterat or e = nodelist.elements(); e.hasmorenodes() ;) Node node= (Node) e.nextnode() ; i f node instanceof ScriptTag I I node í nstanceof StyleTag I I node instanceof SelectTag) else i f (node instanceof TextNode) spanwo r d. append(node.gettext()) ; ) else i f (node ínstanceof Span) getspanword(node. spanword) ; ) else if (node instan ceof ParagraphTag) getspanword(node. spanwor dl; e l se í f (node ínstanceof TagNode) String t ag = node.tohtml ().tolowercase(); if (tag. l ength () < = 10) if ( tag.indexof("strong") >= 0) I I ( t ag. i ndexof ( " b") >= 0)) spanword.append(tag) ; catch (Exception e) ) r etu rn; ) I ** *!J!IJ!fi T AB LE ~ ~ ;ll!: ;& i(! nodep * I priva te void isvalidtable(node nodep, TableValid tablevali d ) NodeList nodelist = nodep.getchildren(); I ** :!ul~~~lfl:.&~ -Til f. i.jj!jj~ l * *I i f ((nodelist == null ) I I (nodelist. s ize(l ==Ol) ' o ' ' ' o ' ' ' ' ' o o o ' ' ' o o ' ' o o ' o o o o o ' o o o o o o ' o ' ' ' >>

196 return; try for (Nodeiterator e= nodelistoel ements(); eohasmorenodes();) Node node= (Node) e onextnode(); l **tlll*-tl'ï g.2j! ::Jit-\B~:$élit.OOIJ~ IID ** I if (node instanceof TableTag I I node instanceof Divl return; else if (node instanceof ScriptTag I I node instanceof StyleTag I I node i nstanceof SelectTag) return ; else if (node i nstanceof TableColumn) ret urn; else if (node instanceof Tabl erow) TableColumnValid tcvalid = new TableColumnValid (); tcvalidosetvalid(true) ; findtd(node, tcvalid); if (tcvalidoisvalid()) if (tcvalidogettdnum() < 2) if (tablevalidogettdnum() > 0) return; else continue; e l se i f (tableval idogettdnum() == 0) tablevalidosettdnum(tcvalidogettdnum()); tablevalido settrnum(tablevalidogettrnum() + 1); else if (tablevalidogettdnum() == tcvalidogettdnum() l tablevalidosettrnum(tablevalidogettrnum() + 1) ; else ret urn; else isvalidtable(node, tablevalidl; catch (Excepti on e) return; return; <<o o o o o o o o o o. o o o o o o.. o o o o o o o o o o o o. o o o o o o o o o o o o o o. o o o o o o o o o o. o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o. o o o

197 "tt " HTML Jiii /** * ~Jl'fJT:J!~:fl~ TR nodep TcValid *I private void findtd(node nodep, TableColumnValid tcvalid) NodeList nodelist = nodep.getchildren(); /* *:!ul*"ii<~j1t!.~:fl-r1j mj!~:is@j* * 1 i f ((nodelist == null) I I (nodelist. size() -- 0)) return; t ry for (Nodeiterator e= nodelist. elements(; e.hasmorenodes(;) Node node= (Node e.nextnode(); if (node instanceof TableTag I I node instanceof Div I I node instanceof TableRow I I node i nstanceof Tabl eheader) tcvalid. setvalid(fal se); return; else if (node instanceof ScriptTag ( I node instanceof StyleTag I I node inst anceof SelectTag) tcvalid.setvali d( f a l se); return; e lse if (node instanceof TableColumn) ( tcvalid.settdnum(tcvalid.gettdnum() + 1); else findtd(node, tcvalid); catch (Exception e) tcvalid. setvalid(false); return; return; protected String collapse(string string) i nt chars; i nt length; int state; char character; StringBuff er buffer = new StringBuffer(); chars = string.length(); if (0!= chars) length = buffer.length(); >>

198 s ta te = ( (O == length) I I (buffer. charat ( length - 1) ) 1 1 ((linesign_size <= length) && buffer. substring(length - linesign_size, l ength). equals(linesi gn)))? o : 1; for (int i = O; i < chars; i++) character = string.charat(i) ; switch (character) case '\u0020' : case '\u0009 ' : case '\uoooc : case ' \u200b' : case ' \uooao ' : case ' \r': case ' \n : i f (0 l = state) s tate =' 1; break; default: if (1 == state buffer.append(' ') ; ) ) state = 2; buffer.append(character); r eturn buffer. tostring () ; *~- ~~mt -~M~~%00~~.-~~- ~~. ~~~~--~- ~PS ~ M~~n~.~ f~m~ oo ~ ~M ~~~ ~-~~ ow~@~ ~~ if;g tlt :)< ~ ;flj. o te~ J.ï.:P, JêfF *f j;: t I'J<J J avascript 1-\:.ji!3 o liti* 1l'f ()(."-F, J avascript 1-\:.ji!3M~ ffj ~:ff 1t.Z. f1:ff:l o,13 ~. ~ a~ JavaScripd-'Ç~r:f, ~íitbjè.)( T i9=$ IEJti'J<J rj3 ~, ~:t!o)c~. 11!~~ o ~Jlt, ~111f~M JavaScript f\:~!:f~-~ff:iiy-jffi.~i:b~~i"~~ ~étj. ""f ffii~ W31'- JavaScript ~~é<jf91jt- o 911 I: :ftjjtl~~!:fff7f~:9r boardid == 1 (Wi.il!!~± I~~:-~ H LVft- ~~~~-). ~- * ~~. ~~~.:P~-1'-M~IY-J~~W : <<:

199 a 4 "~ " HTML Jilï < Script Language = JavaScript > document. write ( dvbbs - top2ic - 1ist ( TempStr, ' ,' 1 I,' ~tf':teílhr~~'t!ptf]!jf~tr~.,' susan 3 3 3, gun $ $ :23 :16 $1Z ~~~~! $$53304 $ $ I 1 I face1. gi f I I : 04 :17 I I 1252 I I I I 1 o 1,' :21 : 15 ','o 1,' o', ' o ',' o',' o',' 5 ','l')); hiddentr( 'fo11owl733888' ) ; < I Script > 19tl2: :te~jj.a,ftq: frj: : :çc]ñ book. so2hu. com/ r - zz O O. h~.~ * ~-~~.~-ft~~-~~~~~~~~~. ~~ ww~w.tt ~ ~, ' * ~J!Jt;l't: ~. : idli 1~ 1iij ep :ïf:#te o rn~~~~t~~.~~~html~~~mn~x~~~~~~oo±~~~&~m* mag~m hl~--i~omwte ~&~~OOABcp. ~5~~~s~~k. *~ JavaScript m~t;lftj ~ ~~~~ilñli:!k.iimtr o *i)ff..t il:~j nfuj JA JavaScript ep im Itt film JavaScript jftl~ñ5* ~~M JavaScript 911il!f:xwL~. :J~*~ffi-~.:ll!:Mt.ff JavaScript ft~. ~~W~m ~ -hl-~~m~h~~i~#~tt~ ~rr- E 0 Mïffli:!tm.~~Ji.ffli:ï± ~-Mfi'f! 1tm ~~~~fiïl~d> o ie/2i!w1lit. ~IfR-g.)à~JJA l*wj!~ ~I $. -Mil!M~ ñffií~ C. Jï!ll ;<$:./frfti ~~M~~~. B~~~~ ~M$ Ji.fOOM~I~~ ±~tt#. Rhino~--1'- E13 1 ava~!jfl. B<J J avascripdt -::TM~~ I $, Rhino tr-1 3:: ~ Jj rfhê " g. :r:fjt;ij ;<$: ítl.tr ~tr.r~fi~mo~rr~ ~mm*~~m~~rr~ *~~~ -~ ~ítl.rr~~~~~ rbj o ~ rr If1JUu~rr ~ ~:X:.:ll!:tArr JJ#41* i~1ij ~:tmjfr. ~ litf:e~m ~~ tf! W-tr ~ ifdmrhmo m: ~ B<JAPI (~Jfl~ff~ t:l ) t:ll-1'-~.ff!f.fj;.fll~ -T~~rr..t l' J'C. ~ffhtj.@ 8<J API lt.1ljj141* ij- ;;a<j I*J ~M~ o 9tl:!zll, ~oo~-&tll.üjavascriptft~b<j Rhino~~ = pub1ic class RhinoTest pub1ic static void main(string [] args) I * fttjj!- 1'- JavaScript AILl: T:X:j;f.lJt, RBI~#flll JavaScript AIJj;fJ3tfií.@. * I Context e x= Context.enter() ; try I * tjj11f:iitjavascript~)j;l!e)('.t~(f9il:9jjobjec t. Function, Array~) * I Scriptab1e scope = cx.initst andardobjects(); I * ~ltt -1- j s )'!:14 * I String script = ; File file : nu11; if(args. 1ength > 0) file= new Fi1e(args[0)); 11 ~ ~-AM-1- ~--~js~# ) >>

200 el se file = new File("script. js'); // ~:!l!&fl"~~. 9].1Ji?kAscri pt. js Bu f feredreader in= new BufferedReader(new Fil ereader(file)); String s = ; while(( s = in. readline ()!= nul l ) script += s + "\n"; catch (Exception e x) ( I * tmrf~~ * / cx. evaluatestring(scope, script, " [" + f ile.getname() + ")", 1, null) ; ex.prin tstacktrace(l; finally Context.exit() ; ~tlia\(11] script.js Jt#~ : var lang= new J avaimporter () ; l ang. i mportpackage( j ava. lang) ; lang. System. out.println("hello, World!-" l ; var swing = new Javaimporter() ; swing. importpackage(javax. swing) ; with(swi ng) var frame = new JFrame("Swing Applica tion " ) ; frame. setdefaultcloseoperation(jframe.exit_ ON_CLOSE); var button = new J Button("Hel lo, Rhino! "); button.addactionlistener(function() ( lang. System.out.prin tln("this is a text from s wi ng button ) ; ) ; ) frame. add(button); frame. pack(); frame. setresizable(false); frame.setvisible(true) ; O<< o

201 Rhino ~~tír JavaScript ijt#;;jf1jill;.j.:jj.l,qf, 1;ttJf )E ((.]~* llf l;aiittl't::t!if3hf.j API~ 1J ~ ~.M-TW~~ffl ~~~*((.J~ffl.~o~~~-~~~~~.~~r~~: (I) :ffim-t ~5<..Bt4l-*ñif~, E!J:E~MfJ~ Rhino i'ij~m~j.je~ft-j JavaScript mtt:#=:bll ~;.J.~ff. ~ m~ 7* ~~~~ ~ (2) JavaScript -*:!f ~t.fstjï'u~~~. (3) Rhino ~:(.f JavaScrípt 1.6 tif~. (4) fti.!f1i~te Java -f-~: ijtp*à Script. (5) J2SE 6 ~ Rhino fl::::*j~ia(t(j JavaScript JavaScript tm!lr;r-f IJ HíriiD1l-m 7 7f~a<J JavaScript Mtffi31$, TmïlitiCBI~~~9Ll'fPJ~JJ'fH! 1-~:ça<J IJl ~l.w. JavaScript i!bltlt>jfjg. El3-=f~; f;:fllllff7 1 1\t Rhino :t:rt=d.ju]tj JavaScript J!!P; f;:jtj'&r:f:l-e!*t:fj HTML DOM, te:jeiçj ~ ]jftti] Bt4J;;$: Jt!'& ~~~ Rhino íw, 1&~ 7t.X1 ~; fi: Jt fj rp (f] HTML DOM?kfJ\1.;;$:±1!! 'Jlt, ~ :±J<Iij~ HTML DOM a<j:ñ~~~ttfiü:it, ~ :JH~~~;;$:iErr:ttmJa:J:.~.c_,agif1:. J avascript ï;fj~ ffi:iff Jj!p; f;:.jt l'&.:=e ~ff!v-r-#:ff. ; :ñ ;i\: (1) :l&:;.';j~ ~(f] H;tl=f< script > ~U < I script > ;ffi~z. fbj o (2) t.l!fl1 "JavaScript :" JJI4!-*tàl:lí :ñ~... 7*1-HTML ;f/'j;-j.c,cp. (3) f.fl-t< script>~;f,~(fj SRC/ARCHIVE.11-iitttlt l~ t'fj?'hm js :tftfr:p o ~1-TRú~#ïiJ~fl!ffH~!Jté<J HTML tfic.ifuij/j~~.fji!.~*jtf:t(~lllt. ~~J~I!è<script > ~</script > fffiigm.fo "JavaScript:"!'Zr.flfl o x.f-t$ - f!~, ~5t!:H~~]í'lj1f:tftfp;J SRC/ ARCHIVE ~ 1í tp ~.js t~ *f ] -*rtf /fi, Pf~-fr ~ ifu IXXJ»f fjj~±i!!:l11:f1:j~~ js Jt ftf t:fj ~ ~ffsm -:111:. ~x.ta ~ ~ ~ ~MïÇJ~~ooMa~m~altlt js~#. :te HTML DOM r:p, H~ Wíhdow la Document M~a<J:ñtlt~~~*~lmli~~ M hlwl~~ 9:i1Iií.3:: 1* P-1 ~.!:.tltte:i1hf HTML DOM ~fil.; f;::l1!1. 1 ~It-t. ílj lv-~ :J(: ~ ~ t:fj ~ 11#11Ji'i:%l!fl.:ltB'&!t;h ~ (NULL). fe Window #I Document,M'j?.é<J151!~~ :p. t~jjth4it~ ~~~:lli:, ]lf003::-i*i*j~hi!!~b<j~~ïif~?t:1-jm~: ~-~!;A Window ~~B<J open /Jf1 :1-J 1~* open /Jf B<J~~:IikiÇJ~]Aïfir.fé<JiW4ii~~~tl!!.hl. ~J;j(~~:l! JavaScript m~ ~jf String ~~o :tei3 1tt;rH1Hl!~~1JY!ft-j, ï1ï lljl~:ñ~a<jff;i;j;iihe~fi.!!pmllit:t~~±t!!hl :i2iaf~.@.*:~j;f1ja<jfffa~ URL ~Aju cpo write :ñtlte<j~~(~ JavaScript tb-~rj;])i String ~~)~-~í'iij~~:iñ:wlt~ftfo.m=~[ J, Document Mfl.a<J write :ñtlt;.';jf\:* ~1!;.t=f'M'.W. B<JD~~~.~~;Jg~te1J~--a<J~~-X#r.f.& ~~±t!!~~moo.:e~~--~ ~U~ URL ~X*ffi,@.~:ñ~1H~itXA HTML t~ial=f:l.?fï:ii~h~~ 1 J3!~~1Jrt~..flity:j~ :ñ~tt-hf.1;j:ll1:~-~. &Pm.?. ~ynt~)l:1!f~ AW: -T;.tcit!!~~ a<j:>c1tfrp o EI3-T Rhino f!~~ 13 ~?f Java M~l!l JavaScript ;t-j~z..fbj:ttht.s um~~~-~'11" l'fjim9ltl~:e,w.-- X"-t.illZ. t!s\.tlt. ~ Rhino :(:Ei;I. Jr~;;$: Jt lmtf' e<j Window.open()!:j Document.write()R1. ~~jj-_:j!?-~ijif.j.ffl Rhino ft~rf:lj:jj:~~:ñi2;~~à9. Java E&~. tarre&fx#"p:"*-tiiei~rr:tja<j~lt. :te;ttfi.tg~-*j:fwt$.%~~ HTML DOM -*M~~~ g, tïij~ if.jffl Rhino ti!jtlt JavaScript i;ij.. '. '... ' '..... '... '... '... ' >>

202 !:f ftjmhj!* ~~:ffi!hl&:ïiuiñ.±-1* 1*1.W 7 o ~~$~)j!p ;;f;:jt f&!:f ffj HTML DOM r.j-, Rhino ;fih~bi j~è JJ!rtJ rij4s!iej~-í*!:fftjñ'/9wi~~ff,ffisyï;f;1~o ~~HTML DOM -*:tt!! È'JJ!~* Rhino ~JJ!11$J:H:9:!:f Window :t>f~1.ï~iy-j open ~f:i-f*~iy-jmhii1l~~f:t!!hl][~ *A~ * ~~~MR~un~ ~!:f.~~ï;t;~~wl*l*& rtj aa~~-o~x ~1!;.(. 1EY41-*Jtf9:!:f Document :t>tit:ñ*ff.j write ~~.wrmréj~fjj~~:w:~:2:14~a$:tt!!* 5:EftJ)t14!:f o te.út~litll.t, 1~JfJ1~~B<J HTML ;f;"f-idijh)ij1f*, gttj~$t:lt~f4h!jiy-1mt~~w Wi!X1tf!:fB<J.@ffiiffiWH~~~:!i!!.ill:: ~:ïih!if.:e-í*i*j$, re~ ~ * ~~-- ~~- ~ ~0042~~0 ~íw:tf:i2iañl@.*~~~ffj URL ~~1J, Ja va Sc ri p t;;l i\ti ))i ili URW!IIllt ff À URLI!o\~J HTML DOM~ilOl DOMJ!;f, ~T Rhino ~i!.i! JavaScript M151l'iifiiM.f!Ttll:li:H~ < Script Language = JavaScript > document. write ( dvbbs - top2ic - list ( TempStr, ' ', ' l ', 'fltf':ffl!hj:ij! Jg~~Jl.7f~1T?t., 'Susan 3 3 3', gun $ $ : 23 : 16 $ & ~!íi~~! $ $53304 $ $1', 'face1. gif', '68004', : o4 : 17', 1252, '88272 ', o : 21 :15','0',' 0 ', '0', ' 0','0',' 5', '1')) ; hiddentr( ' follow ') ; < I Scri pt > ~~r= < a href ="dispbbs. asp? boradid = l &ID = &page =1 "tilte = " (1it f':te@~f tbtmt.j;w Jl.7f~tr~.» & # 13 ; & # 10 d'f :;f: susan & # 13 ; & # 10 ; ~~ "f : : 04 : 17 & # 13 & # 10 ;m:ja~~:& ~~~~t'!!... "target = - blank"> f~p :ff@rrf$lmw~jl.1hf!1t~. < I a > ~WA 4.4~~2ftJ~-~ = Mfi~. ~-~~ :p~~~~ ~rt.j.±bi*j3, ~-~ < script language = javascript > document. write ( body ) ; < I script > < font color= 73A2A0 > < script l aguange =javascript >document. write (bodyl ) ; < I script > M:fJT J, 1:E~f~~!:fli~H~Htl~r 1*1 ~(..R 11lliltt 7 /Hlll7t): < b r > &nbsp ; &nbsp ; &nbsp ; ll'trbjhuii;r-?t- ~tll!tll!v!ij~. < br > &nbsp ; &nbsp i &nbsp ;'i!f~:f: I :, tt~:f.ïl. < br >&nbsp ; &nbsp ; &nbsp df.a.:ffi$íiii, ~~ll'.i!ft7.i8: << "

203 M 4 " ~JI" HTML Jiili * WA~~~~. WA-~a. R*Y **~ * A~~-~.~~-A~A~ <br> &nbsp ; &nbsp ; &nbsp ; ~:if f:eflt~:l:t!! IJX.A:$:"'f.íi;!f~~i.Jt:ií7E JavaScript cp1fb~ffi.ma<j:ii~. JavaScript 1fb~~lE)C:ffll~a<J-1- XiB, -tb~jj:a\. ffij!:ff7 Rhino a''.jf/htfj. mt~~mrm Jti:ñ ff:. -*]i'i:;l! Web p;j~1fb~a<j~-~.:t~i.jt:iít -~ ~ Web ffi,\mm~.f~~a<j~, :JJ%Ji.J;, 5zJJ 1EJi!IJ~J2i:j.\ f(j~; :Jj9 Q i,g, J;.LEdtffl Java ~ll1e!i!ú*:i3:j.\ a<j:ñf!. :jt;g, frtb 7 HtmlParser m. a<j["j~. :J:f:il~7l' 7 5z!JfilJ1fb!fX. I-iTML ]ilïii. ~frt737 JavaScripta''.l:fill!fX.:ñ~. -*"'fía<jp; ~ ~~~~. -~ ~~*-~ili ~ MT 'WIJ ' : >>

204

205 :1J 1 ~1t. :i:.t\.i/f-.ljit T *- fi-j j;_ #-, 1;.,l-.-re,j;_ #-#~A-ftt:+LJ1t.MJ 44:.4:1k*J A., ;f,b.ljit;f~:l!, fltfb]~*4t-:l--t-. '*-"*-ftj:~-~'t ftl~#-~#.tlf,~;t~~*

206 f!!!t9dt~~ :.t:\(pdf)~ Adobe 0aJ:tE 1993 ~:tftlt~eê.-tx1tf~:.t:\. ~#Jt 11f:!;J.~!k.TI:f ~~~~~~#~~~**~= ~Jt~~~.:tEW$~~*~r~fl00~~ey~~~~IT ;p PDF ~:t\é]jtlf, ~HLU Adobe 0W.l~ Acrobat Reader. PDF X ftf:~~~_jc~ijllj)jpj!2j.m ~ ]JfF tlt. PDF )tlf~it~mt~h: PostScript, ~fmïis~~;,- (Adobe 0aJ:Jil::.I:I~-~ITGJJ:m;(jt). PDF Jt flf~~i:f( <Jnf~~~ PostScript q= (fjflí~~,hw,, ~::&!:~1-t~:tEfru, ~1tf:1:fEJ ', ~:flq : t!ihl=~ 1 ~ff~ 2 ~tf~ 3... l!.'id-1=~ PDF ~J!ffiEj( T 5 #OO.*~f t: ~~m~~m~~ ' ~*~~~m~~~~m*.~~~~pj~ffl-~~$~~ ~~M. m*m~~«~ ~~ ~ ~~~~-~~~. x*m ~-~d$~ ~-~mm~.~-~~~~u. PJ~ffl-~d$~~ ~~* x*~~. m~~x*~ ~ ~~~~-~~ * m ~~~ * JE:Xé"JMa. ~ oomm~~:te~ ~ ~~~oomoo -#~~. ~-MaA-~~W%~. ~oo ~~%~~00Q ~*~-~ ~ -. 7F~ é"j~~~@fflf1'~h<3ma. ~~:flq, ~)t$~-~ftr.fffi7ç(f]!*f :~~ BT ~ET. BT *~ :tf~-~jt$~a, W ET *~~*-~Jt$Ma. fl~-~~~~#m~. ~ rof x#q= t'fj)t, ~~~~~~::fn ~ ~ ~~~ PDF Jt!f~~~PJ~Jll~ T. Jj~~i;;.Z..re PDF Jtféf r.p~~~!j!~tf.b1u ~mt~ft.ffj ~-~~ffh~~ IIlJ»!. PDFBox Wt~~nJlBié~ lff PDF Jttf ~ Java t9i êl. ~ PDFBox j ~, ~::ff-~.±.~.ffi T-M~! PDF JttffjJ ite~t ~ ( ~~~ ~3:1 PDFBox PDFBox ~-~7F~~f!:f:, iij 12J..M- -F~. PDFBox!ffi. El tf1lfw.i~.::.j9l êl : FontBox *ll JempBox. FontBox ~-~!Jl]::Jf PDF -*1-*a<J Java ~$. JempBox ;1! ~~~ XMP 7G~~~ Java ~W. ~#" PDFBox at*=jlfiiirtj~a a'-j~:ct;i!hbi~ PDF Jt~.M-~-jij)t ;l!e(t, PDF X~P3 ~fl<jb~ *~~~WB ~~~~.M~R~&* PN%ft~-~ ~ -~9~~$~ 1o/-.J~~ : ~~Jl, ;fjjj\~, ~~' fi-*, ~n$~= Jit%U1ñt. PDFBox :te org.pdfbox.cos 'El. r.p ~ 5(~ JJ:~l!;$:~-~~. PDFBox J:fa<J.3=.~-@.1)-tB:flll ~. org.apache.pdfbox: :l!~-@. *- ~± iijíj;l.~j'a'!j~, 91J:flll, ExtractText ~.IA PDF Jt ;f~ m~jt* org.apache.pdfbox.ant: tlt:ltm PDF Jt~!lU~Jt$ <<... '

207 org.apache.pdtbox.cos: m!ix PDF )C13!f.llifmx.t~. /fj-ff(f.j~~!f!~ COSBase 1i!I~~. ;(p~~(cosboolean), ~f* (COSName), ~~$ (COSString), *-:!J4(COSDictionary), ~!:j'!(cosnumber), )'.)~(COSObject)IY-J ~!Jl/.. COSNumber ft-j : r.!j!!~ ~(COSFioat)l'O ~~(COSinteger). COS Document ~ PDF )[~!f. rj;]~~lf. org.apache.pdfbox.encoding: Jf.Ff)C $!Ht.JWi ~~ ~~ ~JJY., ill'@.ni CMap ~~B<J ~!Jll.. org.apache.pdtbox.encryption:.m 7-)C.m!f.Jwm'n~~$. org.apache.pdtbox.examples: :ïj'ffi::floiijlff.l PDFBox rp (fl API A<J'f71JT-~ff. ;lt ep, org.apache.pdibox.examples.fdf )Ï'f~ll1EJ1tffl PDF H. org.apache.pdibox.examples.pdmodel )Ï:ïJ'Wl fai 'itffl pdmodel ~ r:p B<J~; org.apache.pdfbox.examples.persistence Jt:;J'Jm iij 'f!ffl PDFBox B<Jf#!A 11~~ : org.apache.pdtbox.examples.signature ~:ïj':tl!lfcj~~ PDF ffl?llfl:*-~~. org.apache.pdfbox.filter:.fiff~fjl PDF )C~r:p A<J-*1ltffi.. Filter ~ [J '@. 1\5.ffi~~.M Ei!:fií Wj 1'-1J ~ o org.apache.pdtbox.pdfparser: WHJT PDF )[~.fd)[~!:f A<JX1"~. org.apache.pdtbox.pdfviewer: 00%1t~7J' PDF :)et~. org.apache.pdtbox.pdmodel: ~~j~~tlkf'f PDF :)(t3 A<J ~.I~Hfl API. jtr:p, PD Document ti~7 COSDocument, f&~ POF x.mb<jp:i.ff~7j\. PDPage ~/J' PDF )(::;1:3r:j:li'f<J. -1'-]j.[ffiï. PDF Jt1lf-cj:lïiJ! ).@.ft.3(:.![jt*1f!, PDDocumentCatalog ~7J'Jt:f ropti<j * org.apache.pdtbox.searchengine.lucene: ~ pdfbox l'o Lucene ~pj(;~* LucenePDF ; Ht~. Docwnent ~te$1'- PDF )Cf!ftf!m~~~B<Jf U~.t&~JlltLucene cp 'f(j Docwnent ~<J"jl.. PDF!*J ~mt'fl ~i.t :$~~d'f 1'f(Ope rator). PDFBox ep ~- -'l'te Resources/PageDrawer. properties!'f ff ~fi : m~. :ff PDFBox "f, ~*'l'md'fr.f1l!lff~flxt& ti<j PDF )Cf!f~ 1±1 ~jiijt!l'.-it i!' O i'f<j@.fih'lff: PageDrawer. "Wil1zo, BT=org. apache.pdfbox.u t il.operator.begintext :!lbr- BT f*fl;:i'f<j~h..!~~ org.apache.pdfbox.util.operator.begintext. PageDrawer ::r:rié~ B<Jm -=f)(~ ~ 71'1riií 8\J?d:l!lt~f!~. 9tlW!1-J )(;;$:~ ~~7J'-ffi:lè~Wl~. ~*RA.Q)C#B<J~~t ). &ffi~~ m m~m. *~ re mf:)[#~~ *, mi. * ;I&~ 71' l:f:l ~/f'~ li:* 1!1! ffl PageDrawer, ~ t-1'-fjt PDFStreamEngine ê<j TextPageDrawer ~. TextPageDrawer *l-jj7 PageDrawer 8\1;9;;!]7, 8~fa31t 7 - Jl:l:!:j Path ;f'ij L~ ffi ~ B<J ~ ~B<J ~a. Rre~*B\J~-~-~ * ~ W* public TextPageDrawer(l throws IOException Properties properties = ResourceLoader. l oadproperties( "Resources/PageDrawer.properties, true ; >>

208 if( prop erties == null ) try throw new NullPointerException( properties cannot be null " ) ; Iterator keys = propert i es. keyset().iterator() ; whil e( keys. hasnext() J String operator = (String)keys.nex t(); if(operators. contains(operator)j continue; I I Itt~~ tf 1'1!f t J!Y. ê<.j ~ ~ String operatorclass = properties. getproperty ( o perator ); I I~Ht--t-~f:I<J~~J OperatorProcessor op = (OperatorProcessor)Class. forname( operatorclass ). newinscance(l; ) I I 11:Jllt ~ 9t.J ~ fjij Jij i!1"@ à<j ~ff~ registeroperatorprocessor(operator, op); catch( Exception e ) ) t h r ow new WrappediOException( e); ïlj! ).iii liti i org.pdfbox.util.pdftextstripper ~, i!~ processtextposition 1f~~I6QJ]j PD F )( 14 9=t tr.j TextPosition X'tfSt o ""f 00 ff.] ft W ïlj! ).fe].:ft!!. tjè Jf5(:Q:;k *i* Íl9 Jt ~o float currcntfontsize = t ext. getfontsizeinpt(); i f (currentfontsize < bigestfontsize) I 1~14>71' ilj(,!j!ij::f:ll!::f;j;rjs)c!l cons istent = false ; e l se if (currentfontsize > bigestfontsizej I 1~ -l*~::k. titleguess = text.getcharacter(); bigestfontsize = currentfontsize; consistent = true; else if(currentfontsize == bigestfontsize && consistent ~f J;Jt!i)C!j! <<

209 Ms $HTMliE~Ma &&! "". equals (text. getcharacter (). trirn()) ) I I!f14>:*U ~ li'u)( ~((.]~;: ~ i* - t:t;:, :IMf> i )e 'f ) titleguess += text. getcharacter( ) ; :X:*B<J~& -ïri.~tpf~ja)c:;f :~.j~][~~ftl, ffiïm1&~-f.r.-r)ca<j. PageDrawer ~-. ~ T X't 001f íf-h1$.. if( this. getgraphicsst a te ().g e ttextst ate(). get RenderingMode() - PDTextState.RENDERING_MODE_FILL_TEXT ) graphics. setcolor(this.getgraphicsstate(). getnonst~okingcolorspace ().createcolor() ) ; else if( this.getgraphicsstate( ). gettextstate ().getrenderingmode() == PDTextState. RENDERING_MODE_STROKE_TEX'I' ) :fti.~. graphics.setcolor( this. getgraphicsstate().getstrokingcolorspace( ).createcolor() ) ; -tffl PDFBox :ftll!), 9=' )( PDF X-4>: B J', ïi ti~~i/!nitj Unknown encoding for "GBK-EUC-H" zg;:9 PDFReader :I;J:gfi!J\iÀx:M'.ll:bcp)(*i.f>, f.ê. PDFBox :I; $!JJ~1fX'-t GBK-EUC-H ~ ité<jx~. CMap )t-ftf :j:l~.)( 7 CID!1&*~ ~1-T~~LfàJB'-J X'tJN*~ PDFReader fi~j~ cmap ~~'"FB<J CMap Jt14"Pé<J PDFBox "Pt.ll~~:a~. ï'iji,?j.~ PDFReader B<JJZ:~~tt"PlHtl PDF )C-ftf~~.fflftJ~~.t;. WJOO 5.1 nf7i'.l!l ~ 'JS;Il.G$ lrut:typc (CI). tllii : G411<IUC H ~~-u,tll ~,,...,,,., tr ~G82Jl.ttow ~ tru-dypc ((I) "" G4II< IUC H -!fo Adob<5o"95td U<J"'-Bold -~ Typtl ((JO) - 't. i* Jit!t : Truelype 1(10) illl5i : GBIC IUC H -1:1:!fo -,. ~ oid Jl[:l - T!'Vdypt f('i)j ~ Glk luc H ~~ lf..-ftpf' ~~- ~"'Jt:( lnc"f)pc jc~-- t1tí! IJ ep 1m.7Fatl~i* :(:E PDFBox 0.8 l;&;t$:9'oj, ~~~tlmt :~HjE;t$:~a<J!:f:-(.;t: ~ >>.

210 java.awt.font[j allfonts = j~va.awt.graphicsenvironment. getlocalgraphicsenvironment().getallfonts() ; \ int numberoffonts = allfonts. l ength; for (int i =O ;i<numberoffonts; i ++) j ava. awt.font font= allfonts[i) ; System.out.println(font); ftlj! GBK-EUC-H ~!J!Wff.ll:~Fo~~B<J -*1* ::-t':ft:~~~~1[ft:j j~b<j~jt(true type)!f.-f*, /YT!;J.l!~.1Jrtn~~::f GBK-EUC-H -*"* GBK-EUC-H ~'f*te Adobe 'l a=] B<J cmap -*-f*)tftft:fl J;EJ(. ; ~~.& Adobe 0 'iij rf.j~~j fò.jjm, PDFBox 0.8 ~ra rf.jwi:.$:::-f'~-@j.fi!i Adobe 0 B'J B<J cmap ~ f*)(: f!f. 1fffl ~ifia Ant ~~*~ êl ~ 13 ïç] JA Adobe % a'j r~ cmap *1* )(::#. l!~ ~T~~ s* )t~m *~~-~~ ftlf.l PDFBox j~jli7ff~~ ~ffl PDFTextStripper t%.jtt)tlfi:b<j1-\:~~ r: I ltlii.à pd f Jt#, jg(mfj!jfx!it~ public static String gettext(fil e file) throws Exception I I :ll 1!i' ~-(![~ ft ~ boolean sort = false ; I I 7f ~1J!Jj)(~ fx int startpage = 1 ; I I!ï5 ~IHR: JfX JJH& int endpage = 10; I I flilf cp1"ffi1jb<.j PDF Jtf-1 PDDocument document = null; try try I I.3f1:- 1- URL *~Ul.Jt-ftf document = PDDocument. load(file) ; catch (MalformedURLException e) I I ffl PDFTextStripper *1J!~)C:zt : PDFTextStripper stripper = new PDFTextStripper() ; I I i5t ~Jt :zt;: :ll: ~ tld:ll: 1t tt~ Jl: stripper. setsortbyposition(sort ) ; I I i5t~lti\~!jt stripper.setstartpage(startpage); I I i_i~t;!i*~ stripper.setendpage(endpage); 11 ~@Jt-* r eturn stripper. gettext(document); catc h (Exception e) <<

211 return ""; finally if (document l = null ( docurnent.close(); tfllli PDF 3t14Wmi ~lt~ PDF )( 1tf1fr ~ ~~fr!. h\x 1!ftfll ~ J::::;E~;'ê ili Jt14tF!M:i. f81jit, 11~ ;;J 1$1/H~~ 1J.~jltlJ.ltl:l PDF X14tffi~. Wil"9fl, M Google 1.!~ "filetype:pdf" i:ltbt~!tl Google ~J!Rú'~ PD F )( 11f ;fyjlll![ ~vt-mlfx~mliflmí.~1m oo s.2!yf7f. ~~~~HS~Illttf~ ~ AA~~.l.!~15m~ tr..~m $fh1j m~ IIJ 5.2 PDF!c1!fti>!l!!~l11U~IIl f~:q! umi1::~m~= ~Jt: ~tt~:l:l*(jq.lm -"fx-*tjh.rjvtjtthwfij~ o ~~x.tlira :f' ;J~~ ñjbtffl ~rpltífj/j~. M : -=fil F.Hf!za~it: tl!ñjbtffl 19i!PfF(f)1J~. Ma~ ~~~-x~~~-~~ ~ ~s ~»-.~~x ~ o ~**m m a ~J:::(f)ZM. x ~~-ff. ~A ~*~.~ê$-b. ~-~~ i:lt fi ~. ~~ # ~~~. ~~ -& ~m ~~~*x. ~x@m~" r:p ~ili JL 1-PS~~ tf.j f~~~jju~]i o ~~ w~ ~ = M~ 1- ~~ ~-~~1-~ tt-j s&~~ mtt~ ~)(JG~ttfl7to h\~)c (f]~:fïiw~~~. PJW.~ff«iiil~-):2!)C~~$~(TF IDF) ~ tt tt-j~~~~-~~ m ~~-~~ili 1- ~tt-jñjdtttt a.~~bt ~ ~~~r:p~a~x fi~~. ~tf ~~x~.x ~ft &~-~~a~x~x ~~ «tt. m ~ ~x~x (f)~ ~~.M~~tt~~)(~mtt:JJW~~. mbtm~-1-~bm~$. ~~ili~~ AA!li!IJ ~11= /9 ~ -m ffl ifl flh!u]!.uw, ta PJ bt xt.:k:l:fffi vil~ 1±1-1- HMM m ru. ~ ili ~ = ream *oo ~~ ili* m oo:jj~ ~ ~* P& ~~~~B~~~T)C~~~. ~W.ffl~~~~OO~~~~~~~~~~ : PDDocumentinformation info = docurnent.getdocument!nformation(); this. title = info.gettitle(); tim-~ rnf)c~~~~-~#~w~mffm~8. WA~ ~--~~--~ifl~ ~:ft~-~fft1ítò o PDF )C1tf?J-~p;!j~, -~t;í :W:~~IE)C, X-*b[:~j>. )C ~bt-~ffj$?1-ñjfi~:luff-.li >>

212 W--~ #~W~~~ -~~~tt~oo~m~fib~ &~ft~--fi~--~&w~~ ~m~~ ~~-fi~~~-~~~~ M~tt. ey~~m~*oo~ #~ffl m ~~~-0~~~-~~* *oo~~~~ :lê: PDF )C14~fïF. o 11~~~»2fPJ ~-'4i~!l! ~.l'l.r& - -t ~ ~ E;l(;:%ff[=f ---t ~I*Jal~o lilitl PDFDegger i5j~:tl ~tl PDF )C 14~;W~!! ~ffi.~ o l!l~;iijli:t PDFStreamEngine ~.\1!tl:i~)l:~*~=frajj~~ $)Hi1~ a. m~ ~-~~~~~- ~w.::k~ ~~~~~~~&x*~~-~~ PJ ~ffl* 2~- -t~~- ~*M ~w~ffi~ ~oo~~~ M oo~ o~~re PDFfítle fliit/vgi5j~fll?í:tfi7tl't-jil!~ o (I ) tih~ )C.Jf'.: 00 ;!lfi~i 1.\Z 'l!l ~ :tt ~f)t ~:li ::k ~ ][ l'fij ~ o (2) fthail~::k~1ïfbjiwitfi:7t PDFfítle. (3)!lll:5f!~~:J';UiJ-frittl't-J~ L ~J~tfi:1ttl:l*~flf~ PDFTítle l[ f~ffl (l), (2) j7!11, s~~m~ m?t :tt~~m~~-~~0 te PDFBox 0.8 J' [R.í.f;: f', i5j~iillcl TextPosition )('rj'~oo getfontsízelnpt 1J~:ll3.1EJ ~~~ 1*)\:'j'. ï:ij~1.tji~ SVN ~~p~i!iji)t TortoiseSVN JA SVN ~ th PDFBox0.8 7f1itl'i&A$:. ~ R10 ~ c:p tt: tt -E~.~-Jl:b.:E.lil2I iii1 f3t 1fJilJ ~ ~u m-=e N!ï ijij.e:~g tí-~lvi: 1*.1 ~ * f-tl ;F Riï o 1!t~iL-1'-tïF.~ ift*'h:4f. ~J!m~lt~;f f-jmtli~31hr ";;)Ç =f rt:~illl~" iè~tr.j%~. f!jjfjfi ~mmw-~~~~~~ili*~ï'f~tr.j~~tt. )C 1!f~.2tc f f & iïffihh5f~ o public static Stri ng getfirstname(string filenamel int pos = filename. i ndexof('. ' ) ; return filename. substring(q,pos); Ztll PDF ~~tt-j03t ~~a~m ~~*tr.j tt~x# ~~ ~o~!m. Q"1tXm*"~"~ J'CtJL:X:" ~& "~-%" lcj "~)Cff't 'BJ" ~o ~HIO /9T71'~1!fé<J "~)C!\iotr" ~ " '*te~ilfíff" I " ~%.. ;l! ".Jll,l-~~~ ( 2009) 1356 ~ ~~ I "~l&!f!.oftr" ~ "~:ttmk~_tflj~ilfíf.ij(~)" o r t1 r. ~ i!j! li......ltllci.i: Jtllll t ) ' ~~ ~ ltll l'lli ~~ "' ttitilh lt & ~. lla il ll ll~ -~ h~mil. ~ ~~ I!ISI!Ailll <JH.. A<J0. 7-ll ~1l4 ll ot U1 l!ut.l! ll;t;llclt>lllllo> t 4 ltii t>..,l f ) f$, 11 ;~ ~ -w ~ ~ ~ 9~ ~w. & ~~,.~ f IJl 5.3 PDF 03tlll G <-< o o o

213 ~--~~*~~~~~ ~~~~~~ : I I~ AJt,Jfl:fO Jt)jq ' <J ~'f* ejli fs jw *:f!.&jt $. 'Íii Jjllj is [El t ru e publie static bool ean i sprosendgovunit(stri ng s, Col or tcol or),.. double pro = O; I I!J'IJWT:.I~d!i';:i;J1i[Jt$il:W.l~* 1 1 :ttll.~ltll tt e.mu itl :boltlt ~ if(teolor. equals(color. RED)) pro+=0.2; I :!zthi~!!a :.t 1if!a ~JjliJ~ ;/) ~$ i f (s. endswith ( :,t# )) pro+=o0. 4; int numblank = O; I lit1t~.f*~:l: int numgovunit = O; I!iH11:4i-&:~~iEiftJ~:I: for(int i=o; i<s. lengt h() ; i ++) char e = s. e ha r At ( i J ; if(c. ) -- numblan k + + ; e l s e if (C -- ff' I I e == fit r r e -- 'ft#j ' I I e -- ~ t r e -- ~'l e -- fif ) numgovunit.++ ; if (numbl ank > 0) i pro + = 0. 6 * ((d ouble)numgovunit+double)numblank)/(double)numbl ank+s. lengt.h()) ; I I 1.i: Jt t i.l: WJ ;M $ :;k: T - "'Í" ~ R, J)l.IJi,\ ;:/;):f! J.tJt Jttiii if(pro > 0.6) return t r ue; return fal se ; >>

214 I I~ À)Df>:~ :lt* (1.) ~i* ~iii~, :!zil ~R::l&Jt Jtil'z:Y!IJ~ J t r ue publie s t ati e bool ean isproreeeigovunit (String s,color teolor) I I j ~.::f :J! ~ tg.ji!o 4' :li!: l&.:u'l.;x: if(!te olor.equals(color.black)) r e turn false ; float pro "O ; I I :lljl ~ 1;1. ~ I"Hfi ~ )jlú tij ~~;ll!;:i&)c:tj1.9ç if(s.endswith(": ") 11 s. endswith(" :")l ) pro += 0.5; for(int i =O; i<s. l ength(); i++) ehar e= s.eharat(il; I I :llll ~~:$" ~~~ ~9.lt* ~fi!-* Y!'JtiJ fit~ i& )Ctf].:;X;: i f (e "'"' ', ' I I e == ' ( ' I I e == ' ) ' I I e - - pro +=0.2 ; els e if (e === 'ff' I I e == '$ '11 e :::::: 'ffr' I I e -- ' fiu ' I I e :::: ~ ' l i e ::::- ~'li e == == ~'l i e -- 18:' l i e :::::: 'rp' I I e :::: ::: ' 1\f ) pro +=0.11 ; pro = pro/s.length (); ' l if (pro >= O. 07 ) ret urn true; return fal se; f.ijwt>:~<-f91jtm. J!g-3ê:moo (zoo9) ~)B<J1~~tmr: publie stati e bool ean i sfilenurn(string s,color teolor) if(!teolor.equals(color. BLACK) ) <<

215 M 5 t * HTML iexhia return false; boolean i ssymbol = false; I 1 Pil:!llittJtJ7f!ltl~ -\, j!ij!lj;~x'fliy(ij~tnihff~ for(int index=o; index<s. length(); index++) J i f( ' (' == s. charat(index)) issymbol = matchsym(s,index, ''); else if(' (' == s.charat( i ndex) ) issymbol = matchsym(s,index, ') '); else if (' ( == s.charat(index)) issymbol = matchsym(s,index, ) ') ; return issymbol ; I I PfüJ!:Jl!í IE... tjo' E~~ Pl~ ~~~~ tl:l public static boolean matchsym(string s,int i ndex, char endmark) boolean issymbol = false; boolean markbegsym = t r ue; boolean markendsym = false; f or(int i =index+l ; i <s. l ength() ; i++) i f (!markendsym) i f ( (s. charat (i)>:: ' O' && s.charat (i)<=' 9 ' ) li s. charat (i) == ' I I (s. charat (i) >:;: ' O ' && s. charat (i)<= ' 9 ' ) ) I I :9JJ.* [!; l'iè.l: 1JJ )E ri*.llle ~~.fi JJ!IjU;I6~ else if (s. charat(i) == endmark) ' els e markendsym "' true; i++; markbegsym = false; >>

216 ) rnarkendsym ~ false; if(markbegsyrn && markendsym) i f ( (s. charat (i>= ' O' && s. char At (i J <= '9 J 1 1 s. cha r At (i J == I I (s. e ha rat (i)>=' O ' && s. charat (i)<= 9 else if(s.charat(ij == ~ issyrnbol = true ; index = s. length(j ; return issyrnbol ; 5.2 ~fh~ Office 3tt~ ffl Apache aq POl J9l *~11 Office *~U)r~:Jik:Jl'V[ffé]/J ;r.l::. :R: POI ~Ff tf..net ;f$tq:~;ifl:. ~Jit!]H ~. bttp://code.google.com/p/t ext-miningi'@.~t 5H'~ -1'- W ord flf'hfï-1:~ ~:s:] POl Apache ( (] POl :0! ( l;lffl f te Windows :e)t Linux ~ 'ÉJ' ""F1.tlf>l. Word )(~. POl ~ éj ~:lli~jt~ttír'í:*~~fljl~f OOXML*O OLE2 W-J)(tf~:r.\ WJ Java API. ï'ifi2j.fffl POl *ii~ Excel, Word :ejt PowerPoint )(!f. :;k$~ Office )(#~;;! OLE2 ~;ct: (f], f91jy,u: XLS, DOC.fO PPT IJ..&~r MFC Ff~l).t API a<jjt lj::.ftt~. POIFS =f~ ffl Java ~JJ\l. TM OLE2 )rlj::~!jétf.jii ~.r;bij~. HPSF =f:!.w ~fj.\1. 7~~ OLE2 Xi'~)j! Jj; :9J~g. Office WJ OpenXML -Wt:à::/t Microsoft Office 2007/2008 tf:l BJ~.:Y XML )(t~mj:.\tf.jf/if f ft, - f5!].1. XLSX, DOCX ;fo PPTX ~J:JtJR45 WJ)[tf. POU9i j!ffl openxml4j ~ f;jt~~ ~ lj API ~!OtM'Jf /&:f't ~t] )E(OPC). )E~ _)( 7 - # i!j.:ítffiffe Zip )( ftf#fiti mffl ~* ~~.& ~ffi~~~tf. ~~~/J~. POI JYi F- :iilíu HSMF =fj.'ffi El ::RM' Outlook, illij.:í HDGF =f ~ 3tf.J Visi o, :lli u HPBF :Y. t;ï ::5[ * Publisher. O <<

217 M 5 :1~ HTML :iexftlllll iJf.l POl tib~ Word ;J'i9~ Word :JitmU~0~Jf~ftJ~~HJ..)Cf4~Jt. I;J. doc~~ docx f :/'v)(ftj:j ~~.? :~ltt:ñ~:lm~: ~:$:rt~~ public static String readdoc( J.nput St ream is) throws I OException( I 1 1jij! WordExtract o r WordExtractor extractor=new WordExtrac~or(is); I 1 M DOC :íc!filh'f!ih!x return extractor.gettext(l; - -t- Word )C1 'E!~- -t-~::tf$-i'" :~:l'ï (Section), ~-t-~liríii'ê!~--t-~1tf$-i'"~~ (Paragraph), 4ft-i'" f)l:~(paragrah)ríii'@. ~ ----t-9lir$ -i'" ~.f~$ (CharacterRun), :tzo Jifi7F. ( ) JJNIJJ Word )Cf~tM1Jfflf~ii!htlllr: Range r = doc. getrange(); Word?tl9~~1ï! for (int x =O ; x < r. numsections() ; X++) Section s = r. getsection(x) ; for (i nt y =O ; y < s.numparagraphs() ; y++) Pa ragraph p = s.getparagraph (y); for (int z =O; z < p.numcharacterruns(); Z++ l CbaracterRun run = p. getcharac terrun(z); - I 1*11-$)(;-11; String text= run. text() ; System.out.println(text); tf~tttl:.:.!i!::li!fcf!x<jj :R"J, PTlV.:iilíü Paragraph Él\1 getjustification :1:f~.f~f!JfJl:1Ea<JX1ft:ñ j.\. getjustification IY-Jjg@lil;f;J O ~7FlLX1* ' 1'J 1 ~7Fii!fcf!X1* ' 11 2 ~7F:t.ïX1.:tf, -jg ,,,., ~

218 PJ'I;li!n CharacterRun ~itltt~::q:~p3~- ~~jç/j,, ::Q:~j)j~~m.m.. run. tex t () ; r un. getfontsi ze () ; run. getcol o r () ; I I ::R: ~ :I;]~ I I ~ 1*::k+ I IJt-*Wi~ -~~~ ~-~~ rof ~#~ tttrtfi~~.m~ ~-~:X$* ~~--~ l±l~!f'iyíif, fmm, Word cfl~f:i:~!f'iytie~~~~. 13~Ail~fH~flJ PDF X-*~~~f&:fi~.@.,.IYT ~ ~ PDF ::íe11j:~jmi!~hi~fp]. mttt1~)2\ ;fjf~~!k!.w.~~ :wrf : public static ArrayLi st<titleinfo> getcandidates(stri ng filename, Str ingbuffer content) throws Exception ArrayList<Titleinfo> titles ~ new ArrayList<Titleinfo>(); InputStream is = new Fileinput Stream(fileName); HWPFDocument doc = new HWPFDocument(is); Range r = doc. getrange() ; Section s= r.getsection(o) ; int maxfontsize = O; for (int i~ O; i < s. numparagraphs() ; i++) maxfontsize = Math.max(s.getParagraph(i). getcharacterrun (O).getFontSize(), maxfontsize); int position = O; f or Cin t i= O; i< s.numparag r aphs(); i++) Paragraph para= s.get Paragraph(i); i nt maxfont = para.getcharacterrun(o l. getfontsize() ; int justification = para. getjustification() ; if (justifi cati on maxfont == maxfontsize) String t i t l e for (int j =O ; j < p a r a. numcharacterruns (); j ++l CharacterRun run = para.getcharacterrun (j; /.;' title = title + run. text(); maxfont = Math. max(maxfont, run.getfontsize()); if ( j == para.numcharacterruns() - 1) t itles.add(new Titleinfo(titl e, run.getcolor(), maxfont, position, run.getfontname())); els e I I.:E-f*yl:j~'l$?t ( ,..,~ ~ -..

219 M 5 * HTML.iEltMIJII for (int j =O; j < para.numcharacterruns(); j ++ ) ( content.append(para.getcharacterrun(j). text ().trim() +" "); position ++; is. close(); return titles; ~ffl POl t$~ PPT 7Ff lj PPT ;ttj:ei:! -1'~~ -'rícj 'IJ it(slide)ffi./v(;o ~ffl Apache é<j POI!/!lllt PPT cp(f.j;t :;4c(fl~.fil& :t fr : public static String readooc(inputst r eam is) throws IOException I / f!;.h! PowerPointExtractor PowerPointExtractor extractor=new PowerPointExtractor(is) ; 11M PPT x#ittrrtn~ return extractor.gettext() ; tem- * ícj 1J it (f.j ;fff-~ tf :k1 ;!YFJm: SlideShow ss = new SlideShow(new HSLFSlideShow(is)) ; 1 /is :.lè PPT X1'tl'f.J!ff1Ailit Slide () s li des = ss. getslides ( ; I ; Yt1J4iJ- ~UJ1J # return slides[oj.gettitle( ) ; fffl POl fll!iw. Excel 7FiJlJ m I 1~ft El3-1' ~~1'~ ~ 'M\(f.JI f1:;&(sheet) Excel )ttf El3- -'r I ff~(workbook) ~!I. /V(; o fflg o ~-i' I ff~ :R -E! 1f ~ ~.tf.i 7G #f (cell) o ~ 7 POI JYí 5'~, ie 1f:Jf ~ JYí jxl ( 12J. ilbi~~~ Excel :SC tf o if.ifij Apache é<j POI ~~ ;t;;fi: (t.jf~~1mt : public static String r eaddoc(inputstream is) t hrows IOException HSSFWorkbook wb = new HSSFWorkbook(new POIFSFileSystem(is)); Excelextractor e xtractor = new ExcelExtractor(wb); extractor. setformulasnotresults (true); extractor.setincludesheetnames(false); return extractor.gettext() ; :k17b fu~l:sc#é<j;fff-b,~~m~1'if'f ~~~iljftífl;!yfb,mm M~1'ItF* ep : : 7X ije J2t f!ill JH~ ' ] tff-b o -~~Rti& ~ ~tté<j~, ~R ~ft~~t, X~S~ffl** ;!YF -~ >>

220 public class Cellinfo 1 1.X::4cP'l~ 11~ -f*:k!j- 1 l~ :f'r:;lj~ 1 ~~~Jmi* 11 /ñ:ff~'ja<j&r 1 ~ ~~lt 1 l ~.ñ:r!grr public String. text ; public short fontsize ; public short alignment; public boolean boldness; pub li e int rowpos; pub li e double weight; public boolean isunique; public Cellinfo(String t, short fs, int rp, short align, boolean bold) tex t = t; fontsize = fs; rowpos = rp; a l ignment = align; boldness = bold; weight = 1. 0; isunique = false ; ) public class Title!nf. public Stri ng text; public double weight; public Title! n f (String t, double w) text = t ; weight = w; ~rl~mi~~~~~~~~m~~m*~*&~x~~~~@= private Ti t le!nf getsheetbesttitle(hssfsheet sheet, HSSFWorkbook wb) Iterat or<hssfrow> ri ter = sheet o rowiterator () ; I ;"JJ(fbliiJni t'f.~ ArrayList<Cellinfo> titles = new ArrayList<Cellinfo > () ; int maxfontsize = O; int rowcount = O; while (riterohasnext()) rowcount ++ ; int columncount = O; HSSFRow row = (HSSFRow) riter.next() ; o <<o o. o o o. o o o o o o. o o o o o o o. o o o. o o o o o o o. o o o o o o o o o. o o. o o. :o o o o o o o o. o o o. o o o. o. o o o o o o o o o o o o o. o o o o o o o o o o o. o o o o. o o o o o

221 M 5 * HTML.iE:i;ti. Iterator<HSSFCell> ci ter = row. celliterator (); 1 I ~IT~~JUi& JJ5 while (citer.hasnext()) HSSFCell cell = ci ter.next () ; int celltype = cell.getcelltype(); HSSFCellStyle cellstyle = cell.getcellstyle (); if (celltype! = HSSFCel l.cell_type_blank )// ~~~ columncount ++; if (celltype == HSSFCell. CELL_TYPE_STRI NG) // ~~~ I ~ ~~l' '!. ;n:;~p! tfj.)c.2f;: String cell String = cell.tostring(). trim() ; if (cellstring.length() >= 2) HSSFFont cellfont = cell. getcellstyl e). getfont (wb); short fontheight = cellfont. getfontheight () ; short al= cellstyle.getalignment(); short bol dness = cell Font.getBoldweight() ; maxfontsize = Math. max(rnaxfontsize, (int) fontheightl; Cellinfo ci = new Cellinfo(cellString, fonthe i ght, rowcount, al, bol dness - - HSSFFont. BOLDWEIGHT~BOLD) ; titles.add(cil ; if (columncount == 1) I l i!tr.r:ffl!-1' t i tles.get(tit les. size() - 1 ). isunique = true; if (titles. size() == 0) return new Titleinf ("",O); els e return s electbesttitle(titles, rnaxfontsize, rowcount); i5#'!\l1" Excel )Clfé9~./& : public String gettitle(string filenarne) t hrows Exception InputStrearn i s = new Fi leinputstrearn(filenarne ); >>

222 HSSFWor kbook wb = new HSSFWorkbook(new POIFSFileSystem(is)); ArrayList<Titleinf> candidate = new ArrayList<Titlei nf>(); int act i vesheetindex = wb. getactivesheet i ndex() ;/ / ~~ri!i~if'f~eí'-jf.lit% int sheetsnum = wb.getnumberofsheets( ) ; for (int i = O ; i < sheetsnum ; i++) //~~<fij-1'-itf!&iy-j:iifiijtj~f j;mi Titleinf bti = getsheetbestti tle(wb.getsheetat(i), wb); if (i == activesheetindex) bti.weight * = 3. 0 ; candida t e.add(bti ) ; I I( ~ i.lf ;t ~ il1!i i'f'.j f, ;~ :f;f ~ double maxweight = O; String best Title = null ; for (Title inf curtitle : candidate) if (curtitle. wei ght >= maxweightl maxweight = curtitle.weight; besttitle = curtitle. text; is.close (); return besttitle; 5.3 Jfb~ RTF tf ~, lflux 0 ffj ~ 7 ~ )( ím ff'- ~ *1t j;\ 1-t a<j Jt ; $:.llj ftx À~ 00 Jt i=i I À 7 tr Jt ; $:~i\: (RTF). ~ Jf~~:Jg 7 :tf.::f ~B]~f1:~!Jf(91J:tl0 MS-DOS, Windows.lO OS/2!V..& lp:*~ Maciotosh)..t. ~.::f ~ ~$f!hjll'fz ra:j~f$~~. ~JJ'i?:ïlJ!V.:tE RichTextBox ~tj: :j:i~$jl:. fj\i.fr-~#~ i\:e.~~ffl-t- Windows ~!Jf, RTF a<ji\&; $:h\ 1.0 ~IJ 1.9. RTF ~ 8 ÜL#t.i\a<J, :J-J 71.ítf1~~, t~?l~ RTF Jttj: 7!. El3 ASCII ~~~lifi)l c?jt~~ffl~)(~~e&7j'. 1ij1'- RTF Jt1!f~~~-1'-Jt; $:.)(1j:. X14.7ffla ~ ~ \rtf, 'i?; 'f: :;9 RTF Jt lf ~ fffi:t:;.!! ~ ::f ñj 1> (!<], RTF IJJ iia;fth.is 'i?; *~1 lfjt- 1-Jt #~ ~ :J-J RTF: ~i\:. r& Fa:liJt11f~~.íEJt, Jt11fR-E!.115~1*:&. )(1tf~, fm~:&~jl1-~1j;s!aft1,.íe)(~~~--- a<j~mb ~Jt#Ra<J ffl A* i\:1-t~. -1'-am -~*M~M* * ;lt9=1-e?.*~~$j:f.l~r.j "\" lf~~trj~. 2i51- ~í\'u~ ~ RTF.)(1lf:a<Jf91Jr, Jttj:cpf!.-e?.15 -rrjt~ : \rtflfoobar. Ff:IEJ~*1r.7f~1-Jttj:, ~ 71' : foobar. o <<

223 5. ~~HTML.iE::A:~IUI ff JJ. RTF 3'l:1tf=181HfiH ilf$7ftmï(lg RTF XflfD7f~ff~/F~~.iE1í!H.t~~* "'W~WI*J~, f9!j!m: javax.swing.text.rtf. ~~fhf>j~e!illfi~~h±~-e!* unicode ~~ag RTF J'(tj:, 71Jjzo RtfConverter(f~:l1!!.hl:li http :// /F tt l! 1'- :rffi êl :1! EE C# ~-;;- ~ JW. (fl, r"'w1r!ll!m1üjfl1 Java ttf-;;-~f)l!.--1' RTF )Cftj:M~ff~ ~!.!U-1'- RTF 3t#~*JTH RTF JtftfNHJT~~~JÈ!mriiit êl l~: PJ~:tE-tt-m(X _t1hjt RTF fits. :JE.m RTF ~~(flmfjt.lllmlf7bf. MM~M~M~ff(flPJ~Mtt. e RTF ~~@Jfj;ffil~~J)..t.~. e *J' tfflx~~tq, fi~~~~~~ )E)( RTF ~~ff. ~!Lt. iiitttmoo 5.5 Jfr/F(fl~1'-7fflX:&t~f'4J. RTF R.dParser Rtnnterpreter Text Image Xml Html 11! 5.5 RTF ttf~t~~~-i*~~lll RtfParser ~I'Wf~/i.X;~~'FI'fl~~Mtfi. llit i.ju51jt'f~(tag), stt#. RtfParser :JE. RTF ~$1Ht.lïJG:turF JL#~*(fl7G~ t.b5ú:j.91.-*f1~~~ Unicode >>.

224 RtfGroup: RTF 7G~a9-~ o Rtffag: -~ RTF ~~(IJ~~.fJ-mx-t. Rtffext: te~ B<J :SC-* pg~, :.li!:: :SC;;$: pg ~;f- j:f: ;li!;: ïij ~ ilfj o ~ RtiParser ~X>f Unicode ~ii.!hf.j5ttfmfç~:~urr: if ( t agname.equals (RtfSpec. TagUni codecode )) I!lJi;À Unicode iliê~tt-j *.r-t(flitt int unicodevalue = tag.getvalueasnumber(); char unicodechar = (char) unicodevalue; t:his. curte.xt.append(unicodechar); for (int i = O; i < this. unicodeskipcount; i++) 11 w~n:tl't~~ (lr:~:* char skipl = (char) reader.read( ) ; if (skipl == o o) char skip2 = (char) PeekNextChar(reader, f alse) ; i f (skip2.. = 0 \\ 0 ) reader. read ( ) ; c har skip3 = (char) PeekNex tchar(reader, false); while (skip3!= 0 \\ ' && skip3! = ' ) ' && skip3! = ' ' ) reader.read(); skip3 = (char) PeekNextChar(reader, fal se); else if (skipl == '\\ ' l reader. read() ; char skip3 = (char) PeekNextChar(reader, falsel; while (skip3! = \\ ' && skip3! = ' ) && skip3! = ' ( ') ( reader.read() ; skip3 = (char) PeekNextChar(reader, false) ; els e if ( skipl oo= o \r.) reader.read(); char skip2 = (char) PeekNext:Char(reader, false); if (skip2 == '\\') reader.read(l ; char skip3 = (charl PeekNextChar(reader o fa l sel; 0 0 while (skip3! = \\ && skip3 I = o o && s kip3! = 'o) ) reader.read ); 11 System.Console. Write((char)reader. Read()); skip3 = (char) PeekNextChar(reader, false); else if (tagname. equals(rtfspec. TagUnicodeSkipCount)) 1 ~ \. u n icodeskipcount tt-.ltiï <<

225 M 5. *HTML ie~hi. ) i n t newskipcount = tag. getva l ueasnurnber() ; if (newski pcount < O I I newskipcount > 10 ) throw new Exception ( "i nvalid unicode s ki p c ount: + tag); ) t his. unicodeski pcount = newskipcount; 1:1 :-9 RTF ~ 11 'f! ï=ij fi~'@. ~ ~ :>'~ - ff' + /d~ $tl ~ j\ ~ Urúcode, JYT!». ~ :tj!l 7 ~t TagUnicodeSkipCount (!IJ~JJ.o RTF ::t1!f:m~~(ll]r ~"9IJOO 5.6 fff71' ~~(!IJWH.JTrtmï=iJI».lili:i1 ParserListener.ll[ll.fio ParserListener Jl!li:t~~:tl ;flj\w~ 7 Xif~1'-$#&@.(!1Jm~#1Arr;ffl.@t(f)ï4Jff. ffi~bt~ ~JVG ~ M ffr ~.l.i~p!jf ~ RtfParserListenerFileLogger ï=ij!». J'IH!He RTF 7t ~(li]~~~ À B fit; Jt 1ti= (.:È ~ ffl.:(e 7f 1it llfr pj 89 iroiit). ~ l:l::l nt!»..lltit RtfParserLoggerSettings l!è.w. RTF, RTF fitlr~(parser) I M-tiT El:& I I tti ~ftlift~ I tl ;)(M*II:illiTIII I Bit tli.~-ltfjm e~)( El>t)(. I I R1"F ft~ $M ~~ 11 RTF ftj';ififji!.'! RTF WUJTB~~~~~ RtfParserListenerStructureBuilder.fi ti.smtfirt~ rp mjtl ft.j RTF jé-jt~~~ ;ftjt~l~ o ~ ïïj 1»-:illí.M RtfParserListener 1'-~~~~~*7t~*7I~~ lrtfgroup, Irtffag lfl Irtffext B<:l~-@IJ o StructureBuilder.StructureRoot ~ ~~ ~ ~~;ftj. ~ RTF Jtt~felftfillX:tM~1t~~V'. ff.tílfl»..imi:t RTF felf*f-ü*m~. RTF Jtf!fM-'f$(1<] ~~~005.7fff7J'.felf ~~~-- (I<J -1'-~~~~-Jt~ * ~~Jt~~--~(I<Jm~Ma. --tm (li]jtmc a~ ~T*md : JtMMA. ~~~,~~lfi~$w: ffl~ ~tt: ~êma: ~M A: Jt*a~: ~-~ma. a~. ~-~-A8M : Jt *ffi ~(li] #l j.\ t. flll\l.?t!wi~. :fm~, f)t14f, 11' :w. ~~~~. :tlnilitl~~, 13Hi7fMI1~3R, r~j~, 'JI%, i!~~1o >>.

226 I M~ E'I il'i I I )tg(~ïa~ lr EJ;,! ~ftlf.tf;.t!\t H JJ RTF Mff.ilt(lnterpreter) li / """\ )ttljjjt!t I I RTF )tl ~,)( I I l RTF PJtJHt u RTF i>j-\!lf.t iij (O)~(V isitor) I ~.)(M '-' ll:ii!it~ titll! l (;lli; ;i( )ttlf lli 5. 7 RTF tuf~~~~~ r W 8<J f7rj -T li 7r> 7 ~ PJ iiefilt)( t~ :tjhtufl ~ m API: I I~~ RTF )C Ijo~~ publ ic stat ic void RtfWriteDocume n tmodel (String rtfstr eam) throws Except ion Rtfin terpreterlist enerfilelogger logger = null; IRt f Document document = Rtfin t e rpr etertool. BuildDoc (rtfstre am, logger); RtfWriteDocument(document) ; 11 RtfWriteDocumentModel 1 ~~~x~~mi8jjï pub lic static void RtfWriteDocument(IRtfDocumen t d ocument) ( Syst em. out. println("rtf Versi on : + document.ge t RtfVersion()) ; I I ~7I'X~-m.~ System. out. pri ntln( "Ti tle: " + d ocument.getdocumentin f o ().ge tti tle ()) ; System.out. pri ntl n("subj ect : + document. getdocumenti n f o(). getsubject ) ) ; System.out. println ( "Author : " + documen t. getdocumenti nfo(). get Author()) ; I I.~71'~-1** o:p tt-j~ f* for (String f on tname : document. getfonttabl e().keyset( J ) System. out. prin t ln ( Font : " + f ontname) ; <<

227 I I Hï'I'fDj~~"PtflW!ê for ( I RtfColor color : document.getcolortable()) System.out.println("Color: + color.getasdrawingcolor()); 'WI)WO'ct'í "- 1)$~" JG;g- "lltfiïj" I IJ)(~)Ct!i~J.fflf'~ ~. for (IRtfDócumentProperty documentproperty : document.getuserproperties()) System.out.println( u ser property : " + documentproperty. getname O) ; I I ili!lffjl'i:ffilh'.mtjg~ for (IRtfVisual v isual : document. getvisualcontent()) RtfVisualKind v isualkind = visual.getkind(); if (visualkind == RtfVisualKind. Text) System.out.println("Text : " + ((IRtfVisualText) visual ). gettext()); ) else if (visualkind == RtfVisualKind.Breakl System.out.println("Tag: " + ((IRtfVisualBreak)visuall. getbreakkind().tostring()); else if (visual Ki nd == RtfVi sualkind.special ) System. out. println ("Text : " + ((IRtfVisualSpecialChar) visual ).getcharkind(). t ostring)); else if (visual Kind == RtfVisualKind.ImageJ IRtfVisualimage image = (IRtfVisual i mage) visual; System.out.println( Image : + image.getformat( J. tostring( ) + " " + image. getwidth() + "x " + image.getheight()); M-*IT RTF if19'1 r1id if.j ffl ft 1tJ 13 i3 ~~ 119 RTF )( 1tt~ ic WHfi ~ : URL u= new URL("d:ltexto_chino_7M.rtf "J; I I!)!- -i' URL :ltffit.ï!ut URLConnection uc = u.openconnection(); I I!1! I!J( fl3 ~ thlíii tl:l InputStream is = uc.getinputstream(); RtfExtractor extractor=new RtfExtractor (is) ; String text~ extractor.gettext(); is. clos e() ; System.out.println( "text: " +text); f,'t~ RTF )t14119fffi.ll2ir~h!je~~or o... ' >>

228 o J ~$; ~i2li ~Jt2! = re"*$ Rt.:k tj~~:t!:f:~ EJG:# m :p x-.t1'r t'j<j.::t!:f: ~ff~ f~~~.~m o (2) 1-.iffiS f~~~!l1i: tllh.i~f~~:f!fflil!jyt?:e t'j<j -lli:~~ f~~~.q2i ~ ie~ t'j<j :f1j f!>ujoih.!f? o 0) -~5~-~: --~-.:kt'j<j5~-~o tbt:~5u~~t~jm~. ~ii!?l~lfr : publ ic static class Titleinfo public String fontname ; public int fontsize; pub li e int posi tion; public int mergeto; public bool ean isbol d; publi c String text ; pub li e HashMap<String, public doubl e weight ; I ~-f*~ I I -*i-f> :;I\: 'J' I I ili~ ffi.(il. I l ~*~t; ~ I I :i!i!im-f* li ~~ Double> words; I ~~~i7hhí'iw: I t(ji public static String getrtftitle (String f i l enamej throws Exception StringBuffer content = new StringBuffer ( J ; I I JIBI~:ff fit~jt?i~ I I Jflt~~; ~~ ArrayList<Titleinfo> candidates=getcandidates(fi l ename,content); if (candidates == null I I candidates. siz e () == O J // N:fï~~;tij;A! return ""; e l se if (candidates. size (J == 1 ) return candidates.get (OJ.text ; el se II~~~~W 7.t ranktitle(candidates, content. tostring()) ; I I iew?h'l i\'li (Iq f~ilttf.r! ff~ ~I.! ~Jfl(!tHf~ return get BestTitle(candidates) ; JlR ~f~~ t~j]ï 8] íffl :W~~IJM~ ~~o "F : public s tatic ArrayList <Titleinfo> getcandidates (St ring fil ename, StringBuffer content) throws Exception IRtfGroup rtfstructure = ParseRtf(fileName) ;// ~~RTF~-f1:J Rtf!nterpreterListenerDocumentBuilder docbuilder = new RtflnterpreterListenerPocumentBuilder) ; // ~~t RtfinterpreterListenerLogger interpreterlogger = null; RtfinterpreterTool. Interpret(rtfStructure, interpreterlogger, docbuilder); e << :

229 RtfDocument doc = (RtfDocument) docbuilder.getdocument() ; if (doc == null) //~.fll~~ return null; ArrayList<IRtfVisual> rtfvisuals = doc.getvisual Content(J; int maxfontsize = O; int maxpositi on = O; f o r (IRtfVisual rtfv rtfvisuals) // 1'.!H\i::k~i* t ry maxfontsize = Math.max(((IRtfVisualTex t) (rtfv)).getformat().getfontsize(j, maxfontsize) ; catch (Exception e) ArrayList<Ti tleinfo> cand idates = new ArrayList<Titleinfo>(); for (int i= O; i< rtfvisuals.size(); i++) IRt fvisual rtfv = rtfvisuals.get(i) ; i f (RtfVisualKind. Tex t == r tfv. getkind ( J) //9.~ Text ;t:fïx*-jij tl: 11 ~rrntrt;~.m -#*~~.it-tt:~~qf.fht. M<T~~:k/J, String fontname = ((IRt fvisualtex t) (rtfv)). getformat(). get Font ( l. getname ( l ; li W*rtf*-1* Color te= ((IRtfVisualText) (rtf v)). getforma t (). getforegroundcolor(). getasdrawi ngcolor(); boolean isbold = (( I Rt fvisualtext) (rtfv)).getformat(). getisbold() ; RtfTex talignment a l ignment = ((IRtfVisualText) (rtfv)). getformat().getalignment(); int fontsize = ((IRtfVisualText) (rtfv)). getforma t().getfontsize(); String onerow = ""; while (RtfVisualKind.Break! = rtfv.getkind()) 11 ~ rr iltra<j ~il7r~ --t-mi* try // Special, I mage ~~~~!iht~~:f!kl& Text onerow += ((IRtfVisualText) (rtfv)).gettext(); catch (Exception e) i++; rtfv = rtfvisuals. g e t(i) ; ' '... ' ' ' ' '... '.. '. ' '. '... '. '.. '... >>

230 i f (isprosendgovunit(onerow, te) I I isproreceigovunit(onerow, te) 11 isfilenum(onerow)l rnaxposition++; continue; ) if (Rt ftextalignrnent.center == a lignrnent I I fontsize == rnaxfontsize) 11 _$!f:',.in:k~i*:jma~.ia;~jl! candidates.add( else rnaxposition++; new Ti tleinfo (onerow, fontsize, rnaxposition, fontname, isbol d) ) ; I I 7f:.F.!i ~I ' B<J~.iEJtP3 ~ content. append (onerow + " ) ; return candidates ; X1 f~~ ~i-~ W 7t l'rj 1-~Ç li! :91l F : public static void ranktitlecarraylist<titleinfo> titles, String content) HashSet<Str i ng> stopwords = StopSet. getinstance () ; ll~ffliiil;& HashMap<String, Double> contentwords = new HashMap<String, Doubl e>(); int. rnaxlength = O; int rnaxfontsize = O; int. width = 4 4 O; I I ie)[ ~ [Jt int firstposition = 9999 ; for (int i= O, j = 1 ; j < titles.size () ; i++, j++) I I m-~, ~;lf QJIJ~B<J ;t;j J!! Titlei nfo t i = titles.get(i); Titleinfo tj = titles.get(j) ; firstposition = Ma th.rnin( f irstposition, ti.position); if: ( ti. fonts ize == tj. fontsize I I ~-\':J :k+-fl: && ti.position + 1 == tj.position li..tr.it:v! && titles.get(i).tex t. ~ ength() > 1 && titles.get(j).text. l ength() > 1 /1 ~'%)(+-~ if (! (tj. text.startswith( "))) ( e <~

231 5. $HTML.iEXMa -.'.. =.. Titleinfo ttmp = t i ; tj.mergeto =i; while (ttmp.mergeto!= - 1) t j. rnergeto = ttrnp.rnergeto; ttrnp = titles.get(ttmp.rner geto) ; I I t. li'o" - t ;l:j:;f j;cj [i:ij 111" Jllj ~ titles.get(tj.rnergeto). text = titles.get(tj. rnergeto). text. trim () + tj. tex t. t rim () ; tj. t!'!x t =.. ' if (ti. fontsize * ti. t ext. length() > widthl < I I f!l ::.9 ~ rq: :M ~ ïfii ~~T B9 wi dth * = 2; titles.get(tj. mergeto).weight += 0. 2 ; for Title info m : titles) I I m= t!7: 7~, if (m. text. tri m (). length () >= 2) I I ~~3!~1l!7t~ lft~~~~.q!( (j~fjt.!;j-ül:~ m!jll rnaxleng th = Math.max(rnaxLength, m. t ext. length()); maxfontsize = Math.rna x (rnaxfontsize, m. fontsize); I I ;f-)f~~ ifij, ij;i; Jfi ArrayList<CnToken> taggedtitle = Tagger.getFormatSegResult(m. tex t) ; m. words = new HashMap<St r ing, Double>(); for (CnToken et : taggedtitle ) if ( m. equals (et. t ype ()) I I t. equals (et. type () l 1 I stopwords.contai ns(ct. termtext())) continue ; I I ~fltf,ifil Double val = m.words.get(ct. termtex t ()); if (val! = null ) m.words. put(ct. termtext(), new Double(val + 1.0)); else m.words. put(ct.termtext(), new Double(l. O)); m.position - = f i rstpositi on; I I ïejt jtiii), ~(nj ïl >>

232 ArrayList<CnToken> taggedcontent = Tagger. getformatsegresul t (content) ; for (CnToken et : taggedcontent) if ( w. equals (et. type () l I I "m". equals (et. type ()) 11 " t ".equal s(et. type()) I I stopwords.eontains(et.termtext())) conti nue; I I Z:l!idll'.fflif\1 if (contentwords.containskey(ct. termtext())) ( contentwords.put(ct.termtext( ), newooubl e(contentwords.get(ct. termtext () ) + 1) ) ; else contentwords.put(ct.termtext(), new Oouble(l.O)); double contentnorm = calculatenorm(contentwords); for (int i "' O; i < titles.size(); i++)// ~ = p, r;ihlfiir~;i$\;w;~~llï Titleinfo t = titles.get(i); if (t.text.trim().length(j >= 2) double lengthweight = getlengthwei ght(t.tex t.length()); doubl e fontsizeweight = getfontsizeweight(t.fontsize, maxfontsize); double positionweight = getpositionweight(t.position); 1 Ji.t nïit ~ $i;jf!i! ~:SC tr.:j ;f;!h!i.t~, ffl ~im ~ ~t *~:fi ffl1w.l double semanticweight = getsimilarity(t.words, contentwords, contentnorm ; l I!it~~-% tj(1t Double eompositiveweight = new Double(t.weight * Math. pow( l engthweight * fontsizewe ight * positionweight, 1.0 I 3) * semanticweight) ; 1 I~ 1±1-fffi m tr.:j ituh1(.1j! if (t.isbold) t.weight = compositiveweight * 1.1; els e t.weight = compositiveweight; public static String getbesttitl e(arraylist<titlei n fo> titles ) ( double max = O; String besttitl e = null; I /ig:lr::iij!hif.r2l for (Titleinfo t titles) if (t. weight >max && t.text.t r i~().length() >= 2) max = t.weight ; I /ig:!rlti::k:?hl <<

233 besttitle = t.te xt ; return besttitle; -* jj:fr~ à9ja~~~w*~j)!!~~;: 11/SJf:a<J~.@., ~IN:!.!i!.7f:bt9" 11t~ñü.litttl:~~~ ~tlib1jl~ht-jtm5t. ~~x~m 3':\B<:J~3m1Jit~J.\!.~ 5.1. * s.1 *~Jtt.lHbt.!:;J1.et~t.~ t~rs $ it M.*fi Microsoft Offi,ce OLE2 Compound Document Format Apache POl (Excel, Word, PowerPoint, Visio,Outlool1. Microsoft Office 2007 OOXML Adobe Portable Document Format~DD_ Rich Text Format (RTfl ~xx* HTML ApachePOl PDFBox RtfParser ICU4J NekoHTML XML Java ~J javax.xml ~ ZIP Archives Java PH'I~S"J ZIP ~ TAR Archives GZIP com_j)fession BZIP2 compression ~acheant Java Pi fill (f. GZIPlnputStrearn ApacheAnt Image formats (metadata only) Java (t!j javax.imageio ~ Java class files Java JAR files MP3 audio Open Document Microsoft Office 2007 XML MIDI X1 1 F ASM library (JCR-152~ ZIP + Java Class files org.famg.mp3 1f.tl~tfi XML Apache POl Java Pi tflll'f.l javax.sound.midi >>

234

235 ~ ~ ~* ~ 'f, ~;, 1 -.t~-t :l~jht.t ~.#7~-ti!..Jt.J:t.-~ ~_..~'*I:$:., ~ll~ _...t. -t!iu& ~~ f1-~~~-t~1t.t~.#7.~.r..+t. ll51~. ~.t~~.:tmi~.hr~itjèl~l!.j:'*i. -t~l!f év~~tt""--t.a. ~ ~1fl~# 1lt-~~1-f.Hr~+r-i":MI*J:$:..

236 RM~~---~~~8*: M.m~.ffl~8~~. M~M~. ~X~* M~M ~-~-~~~w~ m.~~~~ooo~e~ M ~. mw*ttm~~a. ~~ftmp~ -~~ ~m 6. t ~~. I!IJII lt!!: U U ü IH U U Google ~!UI ~ftft 3. 1 ~ 11 ~,. l'i mmtl ~ ~ 4::?0ft!t?Oftltlilt. ot )!: ~ - 11! 6.1 ~~~-!Ni ~~~W~T~~@toom~ ~ -~~~~~~~~~~. ~~-*~~~*~~$~ 8a5$~. ~~~~ ~ oo~ - ~~~~oo ~ xw~~~-~~~~~mtt. ~* ~-~~- ~ ~M ~. x ~~nsm~ M -~~-$ft%~~b~a ~ ~M~4 ~-~rt~. ~--~~ --~A-8A A 8~~Z~~ *-~A~-#~~-~~~~~. ffl~-~a tt ~ ~urm. tül ~J~ * m~~~rn-~ 1tJ~m:km~. # J3..~~m~- :ie$-flt:~i1:~. ~~El:! --~-~-~.~-~~~~-~~ ~JlX~~;I iit~.d! ~~ tfl:~&i!:~~ Jmit~M * ~Ji. W~m 6.1 m~. ~7Ht$JiiiiJ~fF~ -~!lfhfj URL fiinl*ff$9ifijt~~-:k. t 1l. ~FI~. 111\F.:(ilï* t$9t00jt~,!tiij~ :i!í rt:ix-1' URL f;!~~tl*ü*~~ fb'jjjt, :ix~*tl!*jil~~~~~~:ta' mií~~f;!tt~ ~~ * M ~ ~w 4~$#. ~~ iij ~~m~~ ~ ~.a~ ~m ~~ s* M~~-~~. *~~m~-#*mm~ ~ ~rm~~~~~~* M~ m~. ~r~~~~-~~*@m~-~~3~ ~: l) 12:1l- ~:lfi:;jç~ :;)Ç tfljlgifi M. (2) 4ij~flt~ ~~F ~~rtitl!r ~.m-mfiffl~~ :;)ÇflM. Q) ~ffl~fa:;jçffi~~~-~-~~~ ~8:;JçM#** ~~~ar ~~~~ ~ :;)Çt@$Jil1J!~. ~~ 3~W~~.-=$ ~ ~-~~~~. _$m.7m ~Fm*M~M~.~ MïW~#aií:iX~~I'PJl2[ <<

237 .6 ili. ~ili ~~~~~~ey~m~ ~~~~~*~~ oa~~~~~~~=~ = ~.:r~jjj& ~m~~~:t&3@01j~. ~.:rm~~ -~~~.fft~~~~ o ~.: -~ J;J ír.jfi~~jf.~ij!o~l* o ~=#~~ :p, ír.j~at-mm~~~~~#momm. -~-~---~~00 ír.j&«. ~ey~-~&m~~~~-@& o~ ~ír.j-~. ~~-~~ &M~OO~ft. ~MMM~-~ ow~~ ~~-~~00. ~-~~- ~ MMR~-~.W-~ír.J~Jf.. JJJ J;*ffll::t ~ n ~!li~~ n+ l $.dl~j!itfi ~/H!Ctk:~ Java ~!ijj ~t ~~~ ~Kffi Java ~-f*~~~~(jmf)ey~f,l l ~l:ffjjjjg~.!ft~~~-~ ~!P~.I' *'L'~~!!~ ~~tw.itll'ilo JMF API ~if.ffhf'!~~t! liij.ijí, ffi:.liuvy.fie~7i..li.nf1f~~~f,lij tf:jf16-* *~ ~m~~mm~tt:~ft~~~~~ ~ ~o JMF êl Htrtf:J:Qif~;;jl:~ 2.1. Sun 0 ï:ihilírl~ (<i] Java 9='51À~~~~~tf:Jf:1~.7J. "fmi~ JMF )75 :~JJfj~~JI. ;ift~:. e ey ~te Java Applet l!j.@ffl.li!9=j 11$:~~~-f*)C11f, 'W!Hlll AU,A VI,MJDI,MPEG, QuickTime *' W A V ~)e -f4: o ey~:tlflih\.![~!xigj:~~~jil~mt. ey~~m ~~*' m-~tt:~ 4 ~ *' *~~d~ Jt#. ~~~--)(#. M&Jt#* ~o (<;J li~~ J: 1~f~l!J~~~UI.ii Vft. f:e.![~~j:tl~~~l!j~~~~. :::*J71!~:tl!!i$t~ JMF ~~~. ij::f]tffjffl~-f*jsli ~-1$--"ríVïllP-~~P ju. ~ CD.mt~ñt CD P~ Jt ír.j 111 f'*, CD ~ Jt (.i]~ ~tjhjhf *ffi ~. ià j!;b~=!.f!i ~te *i tim :p.ffl ~ ~)Xl.~;Jtft!!~ M~ ~ ír.l o co m~m ** ~~ ~ ~ of:e~--r~~cr.~~~ ~- 1-1r;Ji!1H ~t~w:4. CD P~)t~?$1!~. 'ffiïifm~fltiliw:~o IMF ír.j~t'iii*'.ji: 'f*jfi-ïqíij~tjf ~t:'m' :f!h ;.t, ~ E8.( ~ JL1' ~ 4mfiXo 1. l'i-~ 'Íl CD P~):l-'flf*.fHfXiltJ-~, ~*lm'fl-el*7~-f*lt1!svfto :te JMF tf, DataSource ~~~~~*~ ~~~~-1-~~~~#. ~m~~m~~~l"f~~~1!s~o 2. -~i.i* a~ 4m~ ey~~~ * ~~~#. ~liiltw.~~à Player ~~q: ilhf~:ij.o ~-~~, ~~ ~ 3. fl~ft :te JMF l:j=l~j~ ~~~tr.j~qr; Playero Player X'.t~~~~Ji/1!M!Ji~!@i~1'F~~À, ~~~~mt~tl:l~ij~~~hf ~.l:o Player m~~~#~~. JMF!fl)E)(. 7 Player (f) 6 f!p.ljj( ~, >>?i.\fo

238 ie 'M'f~(lr, Player ~~i'it~~jjj4ïj"i' :f:jt~, ~ JI ';;t1t~11 $:$9.l-flt>o r iid~~~j!!;:f:t~ífl ;;ta_ij Unrealized: te~~~t'\~""f. Player ~~ Bf&it~~~Ht, ffl :.lvf~~iltb~lhhi$ ( ] $1tliHï.$: (!<]f fiif t'f,@ o Realizing: ifoj.ffl realize()1jy!il'1, Player ~~IY-Jt'\~ JA Unrealized ~~:::.9 Realizingo tf i!~:f:jt~ r, Player M le.t~:fdú~b~~ ~.ffl ~ J!!;~~. Realized: te~~ :f:jt~r. Player M~ B~M!~7Bí'fs~llJI~ ~~. #J3.t!!~i1!7 ~ ~11~ (!<] $!llli* (!<] ~!tl. Prefetching: ij.1 ffl prefec tch()1j~il'1, Player ~~ífl:f:jt~ja Realized ~~ Prefetchingo :f~:f:jt~rífl Player M #.IE:tf;9 tl$:$!/l#ft«-~?i1l I ft, Jt r:f- 1i51m~$~ #~m. ~~~HlS~~~íJ<J~~~o ~-ti:t~t!i~~bí!ij( (Prefetch)o e Prefetched: Player mfl.% fi.l(;billc!jjk1t. Hft:i!A ~:f:t~. e Started: lr..!jf.l start()1:fi'i;f, Player m :t&uit:itta ~ :t-j\~# tltdl$!/liï.$:. 40 ~~H Processor ~ 1:1 ~-#11 :QU~, te JMF API r:f:1, Processor ~ 1:1 ~~ T Player ~ 1:1 Processor xt~ a y ~f.y: Player 5<;f ~~M'íJ<JWflfJJíJ~9~. ~ iijï2,fl1j IJ~r~À ífl$~1*~w Vit :ltt ffpjfhtll~.&ll!í:i:1.fi~~ IPJ ;lt1!bífl Player M#~ Processor ~ ~ili fitl. ~7 te 11$:~9='~$0rf.l 6 ~:f:jt~9~. Processor ~jl.~'@!. f.!ii'li~ffi(fl:f t i\, ~~~:!*~ :f Unrealized :f:jt~z.j ', Realizing :fit~z.ïitr o Configuring: ir.l.ffl configure()1ji'ti;fc, Processor ~ft:izi:a ~~~ o :f~:f:jt~r, Processor X<tf~j!fl 3ïlj~~~#~ Il(!i!fuA~W íj<j*&~ «,~. Configured: E3.ieñlt~m~~ ~. ~~QHíftrA~im f*~ íj<j ffi,~ f, Processor xt#jlft~ r Configured tt~. 5o J.1l!I!" s:t Format ~~ r:p-f*ff: 7 $~i*íj<j~:i\ 111L~ o ~~~*~~:ff1èjr$!/li*~ ~íj<j;ti ;)Çffi,@_, ÉL~B-f*f+T~~ (fli;~. Fonnat íj<j -7-~-E!i\5 AudioFonnat lo VideoForrnat ~. ViedeoFornat,Xf 6 -t:r:~ : H261Format, H263Forrnat, IndexedColorForrnat, JPEGForrnat, RGBFormat ~ll YUVForrnat ~. 60 W!IH.IMF ~~7 4 ~ tr~~o e Manager: Manager ;fl:l ~+ ~-t~z.fbj (jj*q o ~Jjzo~11 :fi!- "i' DataSource ~~. DJ~JJBli:t~ ffl Manager x-..tfk 'Jl- -í'- Player X1j'l.*~ ñlt o 'f!ffl Manager );j"~oj ~ -B~ lt Player, Processor, DataSource lo DataSink ~it o PackageManager: ~~J!I!.~ 'P *f'f 7 JMF ~ r:f ;lli 'Ïff,@!. o CaptureDeviceManager: ~ ~ ~ ~ r:f i*~ T ivlr '0i..( ] tl.:jlfl,[i. o PluglnManager: ~1t- Jt~!:f f*.ff 7 JMF taj!f rf.ji±flltm,@!. << o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o.

239 ~m ~ *rr~~~m. ~~~~~ Thff.~~~~#~~~-~~* rnr*~~ ft~~tf Windows xp r~jf~ -l!l~~ut~, j2 JJ!tj(.q:ttf~tl~~11:~tfém~~~1tt$fi!: ~ Windows ~; c~. lil~ Java JMF ~1-19tlY' I- T JMF. r0071'19tl;fuffl ~ 1í*, :te- 1- Applet q:t][~:j J\J&fll1t~~~1~ : import java. awt. Border Layout; import java.awt.choice; import java.awt.component; import java.util.vector; I I JMF ;ffi;lf (l<j~ i mport javax. media.captur e Deviceinfo; import javax.media.capturedevicemanager; import javax.media. Format ; import javax. media. Manager; i mport javax. media.medialocator; import javax.media.player; import javax.media.forrnat.videoformat; import javax.swing.jpanel; impor t javax.swing. JApplet; #~:I!JYT~~~ÉI J:$?kJJY. p ubl ic class VApplet extends JApplet l private JPanel jcontentpane ~ null; private Choice choice = null ; public VApplet() super(); public void init() this.setsize(320, 240); this. setcontentpane(getjcontentpane(); this.setnarne("vapplet"); 1 1 lluu1t/9t:ffi'ij :*~(I<) ~J.!flf i.st ~.?U~ private CaptureDeviceinfo ( J getdevices() Vector devices = CaptureDeviceManager.getDeviceList(null); CaptureDeviceinfo [ ] i n fo = new CaptureDeviceinfo[devices.size() ] ; for (int i= O; i< devices. size(); i++) in fo (i) = (CaptureDeviceinfol devices. get (i) ; return info; I 1 JA B~llW:"~'fl.!OC/YrW~Iti.Ji lli$é'~,u~ private CaptureDeviceinfo(J getvideodevices() CaptureDeviceinfo [ l info ~ getdevices(l; CaptureDeviceinfo[J v ideodevinfo; Vector ve = new Vector(); >>

240 for (inc i = O; i < info.length; i++) I I ~ ~.ft 1i:t#A9~ i:\, :!211 *li ---'r:lh!u.!ii~ 5l:, JllJ\A..* Jlt~ ~.* t!i!.~ li 4- Format[J fmt = info (i). getformats(); for (int j = O; j < fmt.length; j++) if (fmt[j] instanceof VideoFormat) vc. add(info (i)) ; break; videodevinfo = new CaptureDeviceinfo[vc.size()]; for (int i= O; i< vc. size(); i++ ) videodevinfo (i) = (CaptureDeviceinfo) vc.get(i) ; return videodevinfo; private JPanel getjcontentpane if (jcontentpane == null) BorderLayout borderlayout = new BorderLayout(); jcontentpane = new J Panel(); jcontentpane.setlayout(borderlayout); MediaLocator ml = null; Player player = null; try ~~~~R~---'r~~~~.n~~~--1' I I ~~~ílirli~a<j MediaLocator ml= getvideodevices() [Oj.getLocator(); I I ffl B~~~ó<J MediaLocator ~JIJ--1' Player player = Manager.createRealizedPlayer(ml); player.start(); 1 I ~~I Player A"1 AWT Component Component comp = player. getvisualcomponent(); 1 I :!lll!l~1hjiili..,.i!--'rñr!~:is@l null. ~!!~~~J!fi-l'X if (comp!= null) li :l'ff Component :JmjíJtJJll> jcontentpane.add (comp, BorderLayout. EAST); catch (Exception e) e.printstacktrace); return jcontentpane; <<... o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o

241 s Java t~~flil~il'i~..t. Tiñ ijf~ T JMF B<J i!!.:<t.: llíh.m ~ ~ fjc~1~hj<j7j'í91j, 33:-~, ~ ffj ~ffl JMF it!tr~tj:jj!m ~, ~~~~~~tj:jj~~m~b<j~.* ~~~r : import java.io. * ; import java. awt.*; import javax.media.*; i mport javax.media.control. *; import javax.media.format. * ; import javax.medía. protocol. * ; import j ava.awt.image.* ; import com.sun.media.codec.video.jpeg.nativeencoder; import com.sun. ímage.codec.jpeg.*; : publíc class vid2jpg extends Frarne implements ControllerListener. Processor p; Objec t waitobj = new Object(); boolean stateok = true; DataSourceHandl er handler; imgpanel currpanel ; í nt imgwidth; int imgheight; D~rectCo lormodel dem = new DirectCol ormodel ( 32, OxOOFFOOOO, OxOOOOFFOO, OxOOOOOOFF); MemoryimageSource sourcer mage;image outputimage; Stríng sep = System.getProperty("file.separator ); Nati veencoder e; int(] outvid; ínt startfr = l; i n t endfr = looo;int countfr = O; boolean sunjava=true; publ ic stati c void main(string [ ] args) if(args.length == 0) els e ( System.out.println("No media address. "); new vid2jpg("file:testcarn04.avi" ; /I or alternative "vfw://0" String path =.args[oj.trim(); System.out.println(path); new vid2jpg(path); public v i d2jpg(string path) >>

242 MediaLocator ml;string args = path; if((ml = new Medi alocator(args)) == nulll System.out.println( cannot build media locator from: + args); ) i f (! open (ml) ) System.out.println( "Failed to open media source") ; / * * ~WftAMea.~&~B~#H~~- ; private boolean open(medialocator ml) System. out.println ( "Creat e processor for: + ml); try p = Manager.createProcessor(ml); catch (Exception el System. out.print.ln ( "Failed to creat e a processor f rom the gi ven media source: +e); return false; p.addcontrollerlistener(thisl; p. configure(); if(!waitforstate (p.configured)) System.out.println("Failed to configure the processor. "); return fal se; p.setcontentdescriptor(new Cont entdescriptor(contentdescriptor.raw)); TrackControl te] = p.gettrackcontrols(); if(tc == nulll <<

243 Sy.stem.out.printl n( "Failed to obtain track controls from the processar. " ) ; r eturn fal se; TrackControl videotrack = null; for(int i = O; i < tc.length; i ++) if(tc(i).getformat() instanceof VideoFormatl e l s e tc ( i ]. setformat(new RGBFormat(null, - 1. OF, 2 4, 3, 2, 1) ) ; v i deotrack = tc ( i]; tc (i]. setenabled(falsel; if(videotrack ="' null ) - 1, Format.byteArray, System.out.pri ntln( "The i nput media does not contain a video track." ) ; return false ; System.out. println("video format : " + videotrack.get Format()); p.real ize(); if(!waitforstatelp.realized)) System.out. println( "Fail ed to realize the processar. "); return false; I I ~:ltff~~~~ Datasource ods = p. getoataoutput() ; handler"' new DataSourceHandlerl); try handler.setsource(ods); catch (IncompatibleSourceException e) system. out. println ( "Cannot handle the output DataSource f rom the processar: " + ods); ret urn false; setlayout(new FlowLayout(FlowLayout. LEFT)); >>

244 cur rpanel = new imgpanel(new Dimension(imgWidth,imgHeight )); add(currpanel); pack(); set Visibl e(true); ha ndler. start() ; I I Hi.lfR p. prefetch(); if(!waitforstate(p.prefetched)) System.out.pri ntl n("failed to prefetch the proce ssor." ); return false; I I!li: Il.~ 7Hfl I t'f p. start () ; return true;! ** * ~Ji00 -lj;:/j\ *I private void imageprofile(videoformat vidformat) Sy stem. out. pri ntln( " Push Fo rmat "+vidformat) ; Dimension d = (vidformat). getsize(); Sy stem.out.println("video f rame size: "+ d.width+ "x" +d.height); imgwidth=d. wi dth; imgheight =d. hei ght; private void useframedata(buffer inbuffer) countfr++; i f (countfr<startfr I I c ountfr>endfr)return; try printdatainfo(inbuffer); if(inbuffer.getdata()! =null ) if(outvi d ==null)outvid = new i n t [imgwidth*imgheight]; outdatabuffer(outvi d, (byte [J ) i nbuffer. get oata () ; <<

245 setimage(outvid); String paddedname = " inbuffer.gettimestamp(); String sizedname = paddedname.substring (paddedname.length() - 20); if(sunjava) ( savejpeg(outputimage, image_"+s i zedname+ ".jpg"); els e ( if(e==null)initjpeg((rgbformat)inbuffer.getformat()); byte(] b = fetchjpeg(inbuffer) ; string filename = "image_ "+sizedname+ ". jpg"; makefile(filename, b); catch(excepti on el System. out.println(e) ; / ** * 7Ç~ * I public void tidyclose() handler.close(); p.close (); if(e! =null)e. close(); //dispose() ; // frame System.out. println("sources closed" ); / ** * ~ilítlll!tl * I private void seti mage(int(] outpixl i f (sourceimage:=null) sourceimage = new MemoryimageSource ( imgwidth, imghei ght, dem, outpix, O, imgwidth); outputimage = create!mage(sourceimage); currpanel.setimage(outputimagel;... >>

246 * te~~ft*ilta:tr?.ttlitrm.m *I private boolean waitforstate(int state) synchronized(waitobj ) try while(p.getstate() < state && stateok) waitobj.wait(); catch iexception e) return stateok ; / ** * ~PJT~ * I public void controllerupdate(controllerevent evt) if(evt instanceof ConfigureCompleteEvent I levt instanceof RealizeCompleteEvent I I evt instanceof PrefetchCompleteEvent) synchronized (wai tobj) stateok = true; waitobj.notifyall(); els e if(evt instanceof ResourceUnavailableEvent) synchronized(waitobj) stateok = false; waitobj.notifyall () ; el se i f (evt instanceof EndOfMediaEvent I I evt instanceof StopAtTimeEvent) tidyclose(); <<

247 6... ~ w / / ** * tre!j * I private void printdat ainfo(buffer buffer) ( Sy stem.out.println( Time s t amp: + buffer. gettimes tamp()) ; System.out.println( " Time: + (buffer.gettimestamp()/ )/loof+"secs ); System.out.println( " Sequence #: + buffer.getsequencenumber()); System.out.println(" Data l e n g th: + b u ffer.getlength()) ; System.out.println( " Key Frame: + (buffer.getflags() ==Buffer. FLAG_ KEY_ FRAME)+" "+buffe r.getflags ()) ; /** * refil!l P-J tr-j~!t;hht:h1~ ~ *I public void outdatabuffer(int[ ] outpix, byte[] indata) // could use JavaRGBConverter ( boolean flip=false; int srcptr = O; int d stptr : O; int dstinc = O; if(flip) ( dstptr = imgwidth * (imgheight- 1); dstinc = -2 * imgwidth; for(in t y = O; y < imgheight; y++ ) f or(int x = byte red = O; x < imgwidth; X++) indata[srcptr + 2] ; byte green = indata(srcptr + 1 J ; byte blue = indata ( s r cptr] ; int pixel = (red & Oxff) << 16 I (green & Oxff) << B I (blue & Ox ff ) << O; outpix(dst Ptr] = pixel ; srcptr += 3; dstptr += 1 ; >>.

248 ) dstptr += dstinc; Thread.yield(); / ** * JPEG~~ * I public void savejpeg(i mage img, String filename) Bufferedimage b i = new Bufferedi mage(img.getwidth(null), img.getheight(null), Bufferedimage.TYPE_INT_RGB); Graphics g = bi.getgraphics(); g. drawimage(img, O, O, this) ; BufferedOutputStream fw=null; try fw = new BufferedOutputStream(new FileOutputStream( images +sep+filename)); catch(ioexcepti?n e) System. out. println( makefile "+e); JPEGI~ageEncoder encoder = JPEGCodec.createJPEGEncoder(fw); JPEGEncodeParam param= e ncoder.getdefaultjpegencodeparam(bi); pararn.setquality(0.6f,false) ; encoder.setjpegencodepararn(param); try encoder. encode(bi) ; fw.close () ; System.out.pri ntln( *** Created file "+filenarne) ; ) catch (java. io.ioexception io) Systern.out.println("I OException"); / ** * WJ~t JPEG f;ij~ * I <<

249 Ms SWi*jta private void iriitjpeg(rgbformat vfin) throws Exception float val=o o6f; int widpx=imgwidth; int hgtpx =imgheight ; if(widpx % 8! = O I I hgtpx % 8! = Ol Systemooutoprintl n("wi dth = "+imgwidth+" "+"Height = +imgheight) ; throw new Exception( "Image sizes not /8") ; VideoFormat vfout = new VideoFormat("jpeg, new Dimension(widpx,hgtpx), widpx hgtpx 3, Format obytearray, -lf); e = new NativeEncoder(): eosetinputformat(vfin) ; e osetoutputformat(vfout); e o open ( l ; Control es[] = (Control [ ])eogetcontrols(); for (int i = O; i < cs olength; i++) if (cs (i] instanceof QualityControl ) QualityControl qc = (QualityControl)cs(i]; qc osetquality(val) ; b r eak; / **.. MJ.i? I 11 'P~1~ JPEG ~~ * I private byte[] fetchjpeg(buffer inbufferl throws Exception Buffer outbuffer=new Buffer() ; int result = e oprocess(inbuf fer, outbufferl ; int lengthf = outbufferogetlength( ) ; byte[ ] b = new byte[lengthf]; Systemoarraycopy(outBufferogetData(), O, b, O, lengthf); return b; o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o>>

250 I ** *! ',f *I publi c void makefile(string filename, byte() b) Buff e r edoutputstream f w=null; try fw = new BufferedOUtputStream(new FileOutputStream("irnages +sep+filename)); fw.write(b, O, b. l ength) ;fw.close () ; System. out.println(" *** created file "+filename); cat ch (I OException e ) System. out.printl n( ma kefil e "+e) ; / * ********* ***** ******** *** ************************* ************ ****************** ************ ***** **** / / * * * llim i!ul:jlll~, ffl '=f~ lllt l'i~~# li~'l' ~ ~ tr.:j 4i~ l'fj -m-, ;. *I class DataSourceHandler implements BufferTransferHandler Da tasource source; PullBufferStrearn pullstrms[) = null ; PushBufferStream pushstrms[) = null ; Buffer readbuffer; I ** *I pri vate void setsource(datasource source) throws IncompatibleSourceException i f(source instanceof PushBuf ferdatasource) pushstrms = ((PushBufferDataSource) source ). getstreams(); pushstrms[oj.settransf erhandler(this); imageprofile((videoformat)pushstrrns[o].getforrnat()); <<

251 els e if(source instanceof PullBufferDataSource) System. out. println ( "Pul lbufferdatasource! -; throw new IncompatibleSourceException(); this. source ~ source; readbuffer = new Buffer(); / ** * I public void t r ansferdata(pushbufferstr eam stream) try s tream.read (readbuffer); ) catch(excepti on e) System.out.println(e); return; ) Buffer i nbuff er = (Buffer) (readbuffer. c l one ()) ; if(readbuffer.iseom()) Syst em. out. print ln( " End of stream") ; ret urn; useframedata(inbuf fer); public void start() ( t rysource. s t art();) catch(exception e)system.out.println(e) ; ) pub l ic void stop() ( t rysource. stop();catch(excepti on e) Sy stem.out. println(e ) ;) >>

252 public void close ( )stop( ) ; publi c Object[) g e tcontr ols() r eturn n e w Ob ject [ OJ ; public Obj ect getcontrol(string n ame) r eturn null ; / ** * Panel ítli * I class imgpanel e x tends Panel Oimensio n s i ze ; public Image myimg = null; public imgpanel(dimension size) s up e r( ) ; t his. size = size; public Dimension getprefer redsize() r eturn size; public void update(graphic s g) pai n t (g ) ; public void p aint(gr a phi cs g ) if (myi mg! = null) g.drawi mage(myimg, O, O, t h i s) ; <<

253 s f* a public void set!mage (I mage i mg ) ) i f ( img! =null l ) this.myimg = img ; updat e(g elgraph i c s ()l ; te ~JJ( ~ ~ q:, a<j ~ Sf-3t # R1 i8í ~ ~ -#íthi:l ti 5T-3t # t:f B<J lifx =f~, lltt!lm~~ :TG~,@ ~ll:~t~,\j!,l!jif Jf-3t14-Je~J:Vt T :R1illft~ó<J~iM*4C!.tll JiJT7J\). 3mfiiJ JA~!:). f MP3 ~l'fj *x#~mjj(ili ~~.-~~~:TG &~?*~~-~~~~~w. ÜTOf&OO bft tt~ TOP200 J..,f ±lm ll _~e ~ ]: S.H.E 11~ ' Ü ~ u.!3jt Ei! ~ 12Iili: - : l i J'i.HJ(! ~ 13 ~. ru iu ) ~l ç!í 13~ ~ ~ In 1~ lt="flif~ )ijw:h ~n 14~ j.. ~ ~ 1) ~!ta wm 5 ~!S mm 6 U ~ 16! ' JUl!!D 6eyonó 6~ 16 ii:s Iii i! 7 l! a1.l' ~ 17! tt um 1 ~ 17~ 8 M U& u 18~ 9 l' llfti! ~ 19. u UI! ~ 9 BY2 19~. 10 ; i! ~ 20 -!.~ H.&!OH 2aü_! i!f \llfih!!~>» t'f~«6.2.1 jft!~ fi~ all~ MP3 li~ ~~.f1;j~ ii"~~~êji$, ii;.~;fjl;j~mm:~;rp if >f-jt14=(!g:tgm.m. bt1m"il *~, 11:~-~. ~ :TG1~.m;ijJi ~$~m:tt:>c14 t:p. -Fi1ñfltfi'J12J. MP3 )Ctf.)991J, *~~ MP3 3t14 fi':jg l ~. MP3 )Cft~ :;kf*?t::-9 - SMt: TAG_ V2(JD3V2), Frame.fO TAG_ V l(id3vl). jtq:,, ID3V2 -E!f&"T11::1tf, 1'\:rtlJ, ~ $4~;~.~ *. -~ &7f' JJE, 1tlfl:T ID3Vl 8~1~L@.it. Frame ;Hir-* ~ú l'f] Ji, -t'fi EE )C# :\: 1j' ~ $!)~ tjc ~, 4it1' F ram e l'f] i'ç]fiij ím ~ l~luè ~1' F ram e )(. 7t 7-J '~ffi#-l!j~~~1*~tfij7t,!pffi~-d2j]7 MP3 l'fjil$, *=f:f$, Jt&.:$:~1~.@, t1-$jiiz.fbj.f ]i ~!lt :fz:. ID3Vl 'Éb*7f1=*, f-t ltt!. ~~~ffi.m., * lfi7-j 128 ~ il -nm, tt.7t*;i - 'F ~idl ag MP3 (lf]~!fi~ P:, -t:bmt~ _t ootl!jtl ~ Frame l'fjtt j\. 4it:1-*!1l fiw- 1-@*~*I'fJM#-. m '"f*~ W~-t- ~agc~ ~-.~~~ ~M~~I'fJA~~ >>

254 ~o ~fi'ff,ft;.li!:~9!a<j~1*~~. ie.ffjt~ MAIN_DATA 7 o (1) ~.W3.1éa~o üz:~ *li m~ (BIT) (BITS) Frame sync(oxfff) 18/ I Layer, 00-reserved, Ol- Layer III 10 - Layer Il, 11 - Layer I protection_bit, O ~'**~ CRC 1*1fl, ~9í~J iidha 16 -ilia<j CRC bitrate index, H:~$ sampling_frequency, 00-44oikHz, O I - 48kHz 10-32kHz, 11-1*00 padding_bit,l ~'*;f~j)l~~~ padding ft, fx.~*;ff~$ oikHz B1~~ private_bit mode, 00- stereo, 01-joint stereo(intensity stereo and/or ms_stereo) 11- dual_channel, li - single_channel mode_extension,.r± Layer lli 9='~ 7Jç-ft:ffl 71! ~;fljl joint stereo t!iu~:ñ~o lntensity_stereo ms_stereo 00 off off Ol on o ff on o ff on on 3 I copyright, 1 7&zy;~J'i&:& ilil:íp o 2 1 original, O 7&7!'~ bitstream ;.li!:---t- copy, 1 7&7J\:Il original emphasis, 7&zy;~1%ffl!YJ~;fljl de-emphasis 00 no emphasis, microseco Emphasis 10 - reserved, li - CCIJT Jol7 <D xit~~*~~~j>. 1ïH9!a<Jti$B1fEiJ~:J! 26mso!~tWi$lñ7c/l\: FrameSize = 144 * Bitrate I SamplingRate + PaddingBit ~ 144 * Bitrate I SamplingRate ::fíie:~ 8ft~, ij!rj:!jijj:~ga<j paddingbito (2) MAIN_DATAo MP3 a<j granu1e '@. * 18 x 321- subband *=:ff o P3~jl1fr : - main_data_ end pointer - side i nfo for both granules (scfsi) - side inf o granule 1 4Ii:1-~!!i!l $!ñ~wwí1- granule a<j~~ ;lt <<o o o o o o o o o o. o o o o.. o o.. o o o o o o o o o o o o o o o o o... o. o o.. o. o o o o o o. o o o o.. o o o. o.... o o o.

255 s M - side info granule 2 - scalefactors and Huffman code data granule 1 - scalefactors and Hu ffman code data granule 2 ±Jt:~m!I!-@* 7 ff M~Jjr(scalefactors), *1d!~~ a<j JJl:~(Huffrnan encoded data).fll ~~ )tffi.@.(ancillary information).;lt Pi ~.ïff!i>itf~. ïl]ï,l~;;.ty MP3 SPEC-ISO 1II72-3 AUDIO PART. l~.:tf-~fflé<jtfsij:1i:f*ff, scfsi (ll]*fjtj.! 32-1-*il ~lil~mf.j(ll]-/ '--~ïm: :l! ÜL VIi: bitstream. :li!'iit ~ 1fJ ~fe!! ~IJ B<J ~ m ~~H~:~?J&, :11/HI<J 1ft üt:jmj! byte. li?&fi 1t:t ili~ ffl ~- a<j~~q* ~~~a<j~~. ffi~mfib~a<jft:tm~~re~~* ~ W *. ~m MP3~# -~~** ~-ff~ :ii!a<j. ~M~ft:t. ~~ m Aa~--1'- -1' lt*fifti ;lt rf - / '-.9XJL/ '-!:tt.f~*~ ~ YJG=M-ffi,@J,(ij~. *:~~~/f._~/ '- MAIN _DAT A I i'u~pjt~~a<ji;l. bit 1.J iftüla<j~$:.fij$ta#r ~. M ñ'ff:i1hfm~. ~llt*:~ft:ttr.;~-1-f-~ff, getbit(n), /f._~'tlcpm:l; Wf~~~ÜL. *~nx:- 1-~tl<J~?l&:. *.1.1*J~tl<Jtttl:i. T~ T?l& 1f.ll ~JfB<J ~:r.t. r ooltf.u~ TM- "F MP3 é<j ID3V2.fO ID3VJ -íti.~. ;t:t.~. tjt M MA1.JT~W~M MP3tl<JM~~-1'-~~Mtl<JTM. -ma** m3 * ~w~m a<j~ s. MP3 IJ!!A~'fl ~ T ff:fil-@fj~f.k(private), m~m *~ ~~-~-!'&;&(copyright). L~j!~ffi,@. (originai)~rfjw ift ~ Sf,*llJH~ J~.w.;.~. 1.2:1f~~ff:$:~~. ff~. ~ ~~. ~~~~Hil;#!,@., ïfñ~.~tt.ffi,@.~ MP3 JiYm :p~~ ~~-~ ~. FricKemp i:e Studio 3.~ El "F~tl:itE MP3 )CftfM!J.t;IJo- * FH.Y?f:~Ux tt (l<]*ajj~,@.. r!pj~p.lgt 103 ~j]çfi, ~~BI!iiJ;Etl:i ID3 Vl.O. Vl.l. V2.0, V2.3 ~ V2.4 ~fie. J%&-*~il$. i.g~tf.j t1l:* 1~L~~,.t~W'~. ID3 VI.O ~fi:tf.ïf'm~. ff:$:tl<j1~l~&. xi2h+1iuf.: iiij, xr!)ra ~ ft!-tüü. OOJt~. ~.o A 1'ffi ~~ 4rr.J 5~. ~- ~ ~#~~m. P.lG~~~tJ<JAm$. ~-# ~ J:lE~l.W.tJ<J!P:m &. ~::k~b MP3 7Ht!H ID3 Vl.O ~~~.lltfj~;lf :fff MP3 ~#~(l<]ja.ji i 128-1'-~ï~iffl*.fl.ñt ID3 m.~. ~ 1 281'~ 1Hf.J~fij~ IJ,g:Ua 6.1 JYT7J\. *6.1 ID3V1. 03t#~iltPil * ~ * 1l ilt Pl :(.J-/IJ. TA G!ij!f!f, ~lf' 103 VI. O fif 1ft, ~~ t lis' B1J ;/! lifk fill ;i!,@, x~ =* "'V$lS f-( Ç(!Yí 128 I MP3 ~~~~J(~ 147 ~l ID3 V2 ~U~tE-~ff 4 1-Mi:<$:, BV!i:fftl<J:jjf&t\:1tf- Jllt.R3tf#Jg 3 Mi, ret ID3 V2.3. Eb r 103 VI igjjtf: MP3 )C14tf.J*~ 103 V2 int..r~ig~te MP3 Jt#tl<Jtf$7 t!?.le Ji1:~1' mi~.x-1 ID3 V2 tj<jthht 103 VI ~1!. ïfiiil 103 V2 ~ f1j~ ID3 VI tl<j!a :f'4~ >>

256 ~:(..$, ili!::t rro :%f~ íid J])iJ);J-1$!11i lf.j :jj RL rtiimt:frm- -r ID3 v2.3. &1- JD3 V2.3 ;fff-~1$e13-1-:fjf~~lf.j;fi'=f1-~11h~hï~-1-:jj~~~~tll$;. *T-I!B ê! ~--~~-.~ - fi~-~~~~~~m~. :jt&~~~lf.l~-m*~~~-~. ili &1-ti%:~&~1f- -t-:fff-~$.r!l. tjf~~.fllti%:$.rñ-.i@j 1~ff: f:e MP3 x #ff.jt~rm. 1. ~~~ f:exf4~ f.f~jll9tffia!k 10 -t-~11ff.j m3 v2.3 ff.j~m. ~i1lltat4j:!m r : I l:ff.:iv(t (]m_ :.r,~_m;kj char Reader[3 ) ; "ID3", ~li!ul..\.1<jf;f~~fhe char Ver ; li~*~ char Revision; I IIMII&*~ char Flag; I lt.~$=f:ti char Size[4]; f;f~:;k+, -E!.WW-~~t'J(] 10 1-!:j1:1J.fOM'1fé<J**~~Jl!t'J(J:;J.;:+ 1) tïf$*11 tff-~ ~"Ti-M.:fl abcooooo a *:ïl'~i!f!l!j'h o. ~;x.:tlllr: Unsynchronisation. b *:ïl'~~lf:jj&~$. - ~~1f, ~~-~ili~~~. e *7l'fiki!f:fJ~~iï\:tlF~(99.99%~;fff-~~1PGJ'll=f~~lt. JiJT~-~tE.~~~). 2) :fi-1fe:k+ -~~~*1l ~ ~*1JRffl7~. ~~~m~:~go.m~~r: Oxxxxxxx Oxxxxxxx Oxxxxxxx Oxxxxxxx 2. *i-~~ -t-~~ - ~ -~ 10~ ~~ ~~~:J.>-1-*1lff.J~~~*~~~--~ ~m ~~~-f:e)c~~. lf.j~~~&~~~~~m~w~r~ *~*~-~~ -1-~m ~~.!il~ l*l* R::tf 5t.JA$JJ1~ ~~~~J!*J t:'j', Z.J?tíi~Ji ro!f'f.i~ 1:11. ~Jlï ~ ~ J:E ;x. Y.o r : char FrameiD [ 4) ; I I 1!!11-~ttB'~l!Jl)i~_;j;l,.q char Size [4] : I ri!)i :l;j~(!<j:;k+, :ï!''e!ffiljlr)i9::, :ïf'~ Jv'f 1 char Flags[2]:.ff:!$(~ :, fuext 61:iL 1 ) ~Jt! ;j:ff, ij~ m~-t- ~ m--t-m, ~~-1-M~!*Jw~;x.. ~m~~a~ r= TIT2 : ;t;ï;~ TPEl ='\::t - TALB=4t~ TRCK= -~*il (#f):\: N/M: :J'ttp N.1;J~ mcp J'f.J~ N ~, 'f) f,(!:j1: ) e <<..., ~ M ~1t~o:jl;J:!: M 3, N $11M.1<J ASC!I ~~71' ~......

257 TYER= ~fe (:Mffl ASCII i.q!ij(7r-(fj~*) TCON=~tfl OJJ~ffl~~ $~ 71') COMM= ~ii c.m-:ï.\: "eng\0 ~ii:p3~", ;lt:rp e ng ó&7j'~ itjlf~,ffj tj9 ~!H&11il 2) :;ka, ~~~~ ~ 4~ ~m ~. ~ ~~ s Q~m~m. -~~~ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx ~~ : i n t FSi z e ; FSize = Size [O) *OxlO OO OOOOO +Size[l *Ox loooo +Size(2 ] *0x100 +Size [3 ] ; 3) ~;!; JUÈ 5< ft, :~Hr~ LO üciij o, tei ::k$?h~m.r, 16 -&m~ o ~ïljj». 7 o :r:\ abcooooo ijkooooo a: ~--~~;!;, ~-~~~~M~- o b: X#~~fi~, --~~~~M~ C: R-- ~. ~-~~~~M~- ~ i:.ffimí~; l;;, ~f(u1-1- :f:"rf fffd(pj ~ BCD ~~7.T' fx:~o -h :ll;o ±- %- J:.tJJ:h:r; 'f.ij\,~ o k: m~~. ~-"~~~-~~~~~M~mo ~ ~ fifi 7 ~3 ~#~ ~ ~ a ~ ~#A~~~m~u~~#. &~ J». ~x 1t!: l'f.j tii:f'0:i11trnhjt o fl.a!l± =!t~ i3 1 ~::k~ ~~1Hl. 3t~ MP3 a :r:t)c'f ~:tl~. :ter - -;;, -!lt1f1 ljtm:é: Java cp"lzdfujmtñ MP3 ~.*)('fo ~SJ Java m~iffi~ti* Sourceforge tji!: fjt 7 -~/JI!! ~fi;fjt MP3 )t 1tfHtJH vao~ : org.farng.mp3o B ïij J». *i~ l:t!! fljijt_ ~ ()~~ MP3 'P é<j%f'p1~.~ o ~l!m : import j ava.io. IOException; i mport org.farng. mp3. MP3File; import org. farng.mp3. TagException; i mport org.farng.mp3. i à 3. AbstractiD3v2; import org. f a rng. mp3. id3. ID3vl ; import org.fa rng.mp 3. lyrics3. AbstractLyric s 3 ; public class TestMP3 / ** args " I '... : >>

258 publie statie void main(string[] args) try IIMP3Fil e file = new MP3Fi l e ("e:\ \ TDDOWNLOAD\ \sh uangjiegun. mp3 " ) ; I 11,2 MP3File file = new MP3File ("e:\ \TDDOWNLOAD\ \l.mp3"); I 11, lyries 11 MP3File fil e = new MP3File(" /home/zhubin/musie/l.mp3") ;//l,lyries Abst raetid3v2 id3v2 = file.get i D3v2Tag(); I D3vl id3vl = f i le.getid3vltag(); if (id3v2! = null) System. out. println ( " i d 3v2") ; ) ~:±1~5fl:~o-r, id3vl mrma*fím O )f~ li!! aq 'Ï! f&.j*fê [ti : UJT~~ífJ'Ï!J System. out.pr in tl n(id3v2.geta lbumt it le()); 'l/~~~ Syst em. o ut. println ( id3v2. getsongtitle () ) ; l!liixiii!~ System.ou t.pr in t ln(id3v2.get LeadArtist());//mx~ else System. out.prin t ln ( "id3vl ") ; System.out.pri ntl n(id3vl. getal bumti tle()); System. out.println(id3vl.getsongtitle()); System.out.printl n( i d3vl. getleadar t i st()); AbstractLyrics3 lre3tag = fil e. getlyries3tag(); if (lre3tag!= nul l) St ring lyries = lre3tag.getsonglyrie(); System.out. println(lyries); eateh ( I OExeepti on e) 11 TODO Auto-generated eateh bloek e.printstaektraee(); cateh (TagExeeption e) 11 TODO Auto-generated eateh b l oek e. printstaektraee(); System. out.println( over"); [ar:~~fêl [a l :m~ffit.f[lï J [by : Hybert Kwok ] o o : o o YT :Wl~-~ ff'., it [OO:Ol.ll]liil: fl'i:l*fê IÜI: ~~fe ~,,. //..

259 c o o, o2. u 11;lii dl!, :f*ilfilf mtim lllljtf.a,.m~ :~; ~ [00 : JfiiH I;Iii~: ~ :1; -fç; fll.f: ~~~ [ o o : o J jjif.'m:!iïjj ~j]!:~ : : ( 00: J /!fki.ï.j:i'ka : Hybert Kwok [00 : ]! o o: !8~ ~;lf~ fhit$ fli.l~ [ 00: ] t.jft" ~ jj~j.a :B::~ii ~ll!ll c o o, a~te*'ll!lj ii!ij ~i~<w~~ i#;iis C 00 : ] jjlj.a.tem~~ f.!gtij3tj::i ~~~tt:j ABC ro o, J al:!l~~- #~~tt:jls.m [ 00 : J ffit; J~J!J-ti' IB IEJjJ!:f.jn l 00:25. ~8 J ~ft"-i.~pjfw~íf.jij [ 00:28.29 J *:;J;:Fo-11Ftt~7f ~f:l Jm7 :i2\:ibi:it W1 [03 : ) [00 : J*:k.Fc ~7fM:ifjijè (03 : !00 : J:h ft"-i.~ i'th~htjjjj.a.~ (03 : ] [00 :35. 14]li~lt jj4j.ailr. (o 3 : 21. o 6] [ 00 : ) ~**~~~Jftll:li':; ftll!lli!<.j ~i!id [03:24.02] [00 :38.73 ]::1\: *~é'-j~:.lè JJG~ l'f.j~ 103 :26. 81] [00 :41.4S J.!I~~íf.J*'i:S 'f'ij:iat~.ml..! 03 : C o o: J 7!ll!l(lg1titttf.l!t!J t'lufií f 03 : J f 00 : 4 6. s 1 J ~~ f.,\;$ ~t\!1!itltl!fi<jf. co3, 34. 6s 1 e o o, He.:P.~lf- lt!l~iih (03 :36.84] [02:55. 18] [02: ] [ 00 : J ilfr~~(j(jit ~!JiJ:~~-f1] (03 :47.22] [03 :05. 96] [02 : [Ol : ):1!-ltiètR*~ ;;f~~jp.l!t!j!03: !02 : !Ol : ll.43jj!x!wé"jè:tt $m"p:&3f!04 : !02:45 *. 161!01 : J 7ê~éMir! iall.i"p ~~ [ o 1 : ).:tefj; íf.j -t'f.ffi:fidfl' Jr.J :E Ml [Ol : ] * 3:.~~1'-~~ Ol : J ~! ~~~!He!i1r-ti~ o 1 : :ias;lil;ivt ~~fi<ji.tub [Ol : JI!!f!t.\1f~jlfl!!l c o1, 4o. 461 ~milifh\**tt.;~ [o 1 : i!=t~l:t~2!7ih't o 1: J ffl;<fhf~~jt-fd!faj~ (Ol : Fé: ~'@.'i'!i*~idí (o 1 : 4 6. o o J g'ftti)b'lifltl'j j :qw! (Ol : I ~~Jll)jj~ ts Ol : f1hbi3<~ ro 1, 5o. a4j ;;r-~ f;t~ m [Ol : ) ~!J,~!J ~~~I'J<J ~:OC ~.Z..~M.~~E iltili ~;J.~u41fl;ffl:cJ~~ f ;~ t f~j-iy-jl&31 01 : ~~H:~~i!JB<Jil1'f~ft~it VF111\!'ile.:kfli!;ti COl : I XV7! ~~.i!j!j7 Jl.li!JJ~ [ 02 : f~i1f;iíJ~~ti!~~~- ( 02 :o I Mflll**:.lï!1fl\?$~ [02 : J :&:tlt'f~!j li::lf.:~(!c]'!w~ l 02 : os. 44] i1r-1i1t'f'jlui.a ( 02 : ) ~:AJJ1~7Ç~~tE~~J:~j!IJ co2, 01. 9o 1 fh\~h~11-aa:..t.m1r~ c o2, ~~ *~1i7f~?'l~Pm (ilfil. JtJ» :... >>

260 (04: ] END over * ~m T ~~M~~* ~8 ffi~ -~~~ ~ -~~ m7~mm M~~ ~~x#~~~~~m ~*~T~~ ~~~. ~~~ffi~x~. 8 << "

261 Ia~M~~-~~~~~-À~~~. ~~-"~fo"~&~~~~~t ~~~ ~~*;t-:1!~1tj1~~i-~a~:t3lt1t.té?~--.$".

262 7.1 ilf$ xi1]1jrcpti)- 15 ~3::~1*J~x~ ~t-j x;;t;:, iii~~ I!IJ:f,fll Flash i;t.ll!i!j~. :tzo fïl 7.1!7T71' 119-~ xi(jljr~x*~*~b*bey~~~~ ~..,, I <UPALJ~.-.-.Jta.."-.4!fl&lJi;ll!;l;d:. Jt~Jit--:.P!:ac:ft2 ~~ ~"~'"*7G. -- a ~~e. -..~..._,_,!',,..."tk JUI.U14t.-. e c.a... a~fll!'lt J~._.,.,.... -~~2Y'I'fJI(. ~~-~ - ~ - ~-.~..,.- ~~- ~-~~- ~~r.~4*. ~.. E4~.. ~,.. _._.._..r.21'rtjptcil lil ""'.,.** ft*t '"li!* uq_r.,...._. x~~ r.~---.(~~2-~ l. -.~~ A~!&&P ~~-~ AS.~ ~ ~~r.~ ~ ~s.:~a r.j"-..~~rt:-.:11. C!f-JUrO. ~ --; ~ tt~h'fiil. 2!!.U, na " ía"j' z ''i ' *~--a rtc-rí ~ LMc- n,.,.... e xn s'-"+dt-v ' x.~o-o:-r 1:~ # t);~ ~t_,.th P*'A*.. ~~-=--::.-:.~: ;.. =I!EP!*F!,._ a=u ~= -.o.atd tt1í"%1!hjmi...,ttnil-"4 ~.. o,! 1: ::-:f. f"lf-!!.1.,...,..,.,."*.:"m'-~! t J(=:!" '7r.ê""'" 1!: 1..tt!t ~~ ~ ~, ~a s«r: r,r., r :ttlsta ]l~ A - *""'~~tr-'\ t~r=:~ h v.r., e ne ~_, :t~ *a :L!:tffs 1 --:-.!I>!... "!aft.c....,..~... 'l:l-u;.k mouj e..,, n =:st...ca s;.u~ 'X~eo"'"_. C"'!":ill:t ''!II«Bi: ' 'f&!i&-g... y.,..,.,,.,.,..,...,..,.. -~ -~w... ~~... e...-~-x Zl4Jl.C~...,..~....._- "":'li...,._!''\ 1!St'l i" ,.~ ~;~. & tt ~ c ~..e s:poot.~.u,... -.;1/t'<lf'!; <<>

263 OJ!:.LtrmiEJt~:fE~-i'~]J cp.r;f - ~f1h7i'qi~/j'-=f 20%, Jt~$*E13 '5-, )HJtl*, - m*~ ama ~ te::eri!uls ii/jt, ::k~:a<j ).gr, ~AA*~ Ili?i~W~ -~~±NM~~(topic drift). i!\!íuj.i 1~tlé à9 ::En! tf 11: tt "P I:.L ~ 9t~Jg ~l.l~j l11i B9 Web 00 :ïf ~?tlhi.fú, &:'~~A fu fx1:1jíi ;fi ffl:m ~ J1 ~a<j&~ ~ ' ~ ~8*fi~mMtt. te Web f~,@~-~~. f.&~~*é9.ffl~tf~~fia<jll! ll:jh.ifffl" Web ~ *~11<JiW1' tli ;tij\ o 1!n * :ïf-* ~ J~Jtrs ~ Yi rr tfj 1\i Pf ~ ~, ~ ~ tlf~lli ;s ~ ~ t!?.lliz: s ï31. JA i7íï ~fi-&-& ~~~ ~~tl:~*~ ~a<j~~~~c:p~~.w~~~ ]J ~~~*~@. W~]j a<j~~~~ i~tjrn.fu i! 1' 1t ifij itil.]ï; ~ x :X: o.13.;ijs iit ~:.L :if :±1 lli Jfï P3 ~::f-&~ Vl! ~ ï31 ~ ~J é91;1à!#fl~ *. w ~ ~-~~tta<j~~ o~~]j ~ *~ ~=1'-~A~P~e. ~~w~ ma<j~ ~ ~P38 m. &:if' ~~~~wiliat~:~~~ c:p J'JT *~~1. follf!a<j /J~:lê:fl~~ - ~A'f*J»g -:Yi, AI!I ltt~~~ji:a<ji*i m~m ~.Mm. m ~m ~ ~M~-T ~~a<j~ Jtc:pm~ffiml*! w.~~.#~ ~ ~~a<j~~--é9~yi~ ~~~ a<j ~-tea#~-~. iif!:.l~-~~ Ji:~~-~. --~:r. ~~~*~~---~o@~~~~~-~~~~ ~tl.mmr::r.ai ~*~ m ~.i!~-ti*iw&~~$a<j ~&~*~B ftffl"~~.~~t~:m$m~c:p. -~*~ ~a<j~~~ ~ma<j~yi*~ *'Jitll~ Ji:c:p a<jjt111i~ "PjJD " ker:: ":ft~m-~ ", ~:.lik-1'wi~?t~ fpj.!. :te~&v l l ~ ~~. ~~:.L*m~~~ ~*~~. re~~--~~~a<j ~~~.~-~ v11 or~:.l*ffl~~~ m** ME~~. m~~~~~g~~~. -~~-~~~mm~~ ~?t~~~-~~m ~ ~~~~~ f,. iit 1:.L fil.!f!!!!! re~ Ji:?t ~ el )R ~ AA:à:9Hiï < :th~?ú ~ Jll List Page), Wffil Ji: ïjñ (Detail Page) ~*~~~(Unknow Page)Yiifii o ~~~, ~~!:.L7 lli 00 it ]j ifí, ~±ï;guflï, M~ Jfïfii;tJ -f;ll 11.u. oo 1.1.IYT7Fa<JJriiímt~-1'w!tl!Jr. -1'~ú*Yittttm~~1-w ffi!jr, :tmoo 1.2 J'fi71' >>.

264 ~-'/ -- - ~:W ïç_ )t~(jj~: llj~~ffi:ffi1~lll-tjt:. ~ffi:: fs:f$1 ï'lj ~Ml~!&*Htfll!!.?:~:# DOM ;p,j o ~~$ff~~. ~~*ffls*0~~ *~tffi~~ o ~fwmm~. ey~~n-8 1~1t*~it 1XXJ ghtj1wji. HtmlParser( Html :)e f4nhjt~j:y:, tjt '** ff tfl "1) #.-o 11 5tre ~ J-( 7t '* é:j HTML t;ji~ m'* tfl 15 ffi. o ~Hlll : <font c lass= nrbt >/1Y~*</font> ey ~re~ ]A~ EB - 1-iJf.:i:ffi.ñlt: "<font class="nrbt">" ~ f/f~.. i)f.:\(tagnode), " )'à-~~ " ~X-*ïlr.?: (Text Node), " </font>" -t!?.~t;ji~;l.t;?:(tagnode ), ~N ~( J isendtagq:ñi*~!el:lt o ey~:iiltl Firefox ixtjw:~b<j " DOM ~~H" :ft~~]a DOM ~. ~ nflf\. - I!TIL K!J.D -BODY l\thxl - DIV l\tlext =: SCIIIfT -A -ltext BR BR I text B& llhxt tt.ext - C!NT!R liiil9i DOM~ ï'lj ~ ñt!ffl NekoHTML( HTML :)(~ #% mttf?1f. tr-.l DOM ;p,j o NekoHTML 1,15tlinttE~~re HTML ~ ~PX:$ Wétl XNI(Xerces Native Interface) )C~ ":(Jfl~", fi.\!i5 JfJ~"F~:fH~:H(tag balancer):lzi:-$:bijiillz:ji:~ DOM f:t Q tr-.j;p,j o roo1'r-mffl:m*0~.::fff?u(longest 1JW;;.. ~f~-mf!llwi-1-ff.~íj sl lo s2 étl~c~0jt:-tjf.?tl. s l ~ a, b, e, b, d, a. b J s2 = b, d. e, a, b, a l Common Subsequence, LCS)*jj1i~JiH1H~J.lB''1 Wl sl lo s2 ~:l!!i*0;1:tt-fr.?ij)j LSC(sl, s2) = b, e, b, a ~fflïç_ $~Je:!GiJtf'-J,\M.fHt-t'/: LCS ír.j:ñ$2;: '!ill!- 1-.= tttflm nurno[], ffj nurn[i][j]iê.~ sl (J<]Rti i 1-*ltB'-.J.Y* ~ s2 Btl1ÏIT j 1- -~~B''.l =f *tfl LCS tr-.l -~[Jt. ~~lnjj::i1hr:im1fhi ~. J~.Z,.:(f:l-tjf nurn[i]o]zflíj, nurn [i- l)[j- 1], num [i- l ][j) 1-J num [i][j-l):i$.jb1-tft:tti*!ltll'tfll~~ sl[i-1 ]~ s2u- l]~~8~. tñj~l-tff.!:tl nurn[i]u] <<

265 e ~~ ~.. =e public static ~E > List~E> longestcommonsubsequence E[I s l, E[ s2) I I =~~ ffi.. ~JJttflt~ O int [ ] [] num == new int(sl. l ength+l) (s2.length+l l ; I I r;ij ~ ~:l()hf f for (int i = l ; i ~= sl.length; i++) for (int j = l ; j ~ = s2.length; j++l if sl(i- 1.equals(s2[ j-1))) num [i 1 [ j 1 = 1 + num [i-11 [ j -1] ; els e num(i1 (j] = Mat h. max ( num [i-1 ) [ j l, num [ i l ( j -1] ) ; Syst em.out.println ("length of LCS = + num[s1.length1 [s2. lengthl); int s 1position = sl.length, s2position = s2.length; List~E> result = new LinkedLi.st<E> ( l ; whil e (slposition!= O && s2position! = 0) if sl[slposition - 1].equals(s2[s2position- 1])) ) result.add(sl[slposition - 1)) ; slposition--; s2position--; else if(num(slposition [s2position - 1 >= num[slposition- 1) (s2position] ) ) els e ( ) s2position- - ; slposition--; ) Collections.reverse(result ) ; return result; l:t~ l~'f!>hí!i ~-lfh!2lt 89 ~-*ml.fm :W:l "F: 1E! lijj [Mi~ pjg - --t "'fi (;;i;~ ffi. it~m --t ~ gr~ J Z :f] ~\1!Al: ~~.ll a<j :tlif 0Jt-f' ~~H~:!Jt LCS-Length. >>S

266 ~:J: jjij 1'- ~ Jlr ~rf!h~;j.j.!t sim=(2 LCS- Length)/(Length l+length2) o ~À~"'Í" URL t!btl 1-tn"ilij-1'-Jijj:!ilzfiïJ étlffi1wl, ~J.W. 1-'è~:tzur : public static double getpagesim(string urlstrl,string ur1str2) ArrayList<Node> pagenodesl = new ArrayList<Node>(); URL url = new URL(urlStrl); Node node; Lexer lexer = new Lexer (url. openconnection ()) ; lexer.setnodefactory(new PrototypicalNodeFactory ()) ; while (null!= (node= lexer.nex tnode ())) pagenodesl. add(node) ; ArrayList<Node> pagenodes2 = new ArrayList<Node>() ; URL url2 = new URL(ur1Str2) ; lexer = new Lexer (url2. openconnection ()); lexer.setnodefactoryn ew PrototypicalNodeFactory ()) ; while (null!= (node= 'lexer.nextnode ())) ) pagenodes2. add(node); double distance = PageDistance. longestcommonsubsequence (pagenodesl, pagenodes2) ; return (2.0*distance)/ ((double)pagenodesl.size() + (double)pagenodes2.size()); ~~~illff#~~~~m~~mw: ~ wm.w ê ~ ~Moo~~~~. lfl-jsj iij r *l!l /f' =li: im A\~ a<j X: ~o fj.i] ~~!Xlt&m ~~X'1 DoM tt! étlw-1'15 R.-#~. ~-ft T " ~ ;=tr 15.è: ".fu "ff%(~~ " o ffff/g~x<tïj.~r:a<j -1'-~:ilrm:~#~. ~JJJ :if!t ïïjtm -* ïj 11-m.ffl IXXJ M JXI.#H!I~ ~ :ñi* 1rJ DOM ;fj;j rj:l atj &1'-"fi.è: ii!ilt5cilï~m o &1'- HTML :!ilffñx-1 El.Y- 1'- DOM ~, 11; rp ~~~ DOM :ftj p;j tliht<j15 R.i:, iñj 1-lf:lll ~ J'C * 001!t~~hl!Itii.YllJ;Ii!:ll"f1.i 1.'5. o Jilf\ T HTML atj- tm# ~.& ~Èffi~a<J DOM ~ o =li: DOM :ftjcp, *1.f~.f!ft~~~15,9;, w:l~1.f~ft~15.è: atj?k~p;j~, f9~jzu : ~~ IMG fi9~~:fp3~~ ''src=image.gif" ~1'-Tï R.i:tf.l- 'ttt lt~wi~~hlf\~f!o tt;ft, j!.!j!ft JA BODY f;f~jf tlffí]f1; HTML ~il. ~;k nfff(f] Iif~mr#mtE BODY j[!j JI*J o :tlff-=f7t.fjt, te~ DOM pjp;j, ~:bot - 1'-.Km~HtHl ~15 R.i:(root), ~ 1ï R.i:iiffffilJJjlf!, ft::-9 BODY <<

267 <T ABLE ~<ídlh 800 height-200 > <111\BLE> <IMO sro-''itn.tge.gif' "idlh=soo> <I' ABLE bgcolofr.eo>... : roct <ITAllLE> <!BODY> idth=soo TABLE TABlE lll 7.4 M.f.iiil9r!IJ DOM ~ li~ DOM ~ B~.@!». 41 it HTML ~iii!fj ;fj ~.fu!jj(l.)xi.#f, 8 ;lt:: 11l~~f!f!-"!'-(fl HTML Jftiií 7 M- ~li HTML :m: iii HT ff!fj ~JJíl.JX\.#Ht.EI.X1B ill ~ 1* ~~ o f.8l!lt, i± f.ij IJ1;1J t' ~JJ!l.JX\. -.fu~ ~~~~~WIJ't. DOM WB~ R~ffl f~.i~ o ~!lt.ft«l~w--t-e ~~!fl M~ o~-1'-m~~~ ~ ~-ill~-~--m--t-m~~-n ~~~!t-jjx\.8, ~~m ~"*p; o JJJ!:tf~IA -#f4\ ~JX\.~W(Style Tree)!fJjTB'-J~ ~:f'qo ~~ M~tJ~ffi~-~ll t1t:yd1xjjft rfj ~ 151 _j!jj\l.)x\,. o ~t:i:\7--1'-.jx\*lt~hf-j~ff, ~-1'-JX\~W~#7 DOM W d 1.fU d2 ~7;l -T ~J~Hf.J lm-1'-~ffi~(p, IMG, P *IJ A), d, t:f!fl!yt 1H>~~:tE d2 4' ft1i1j ;ff:j Zrfj:fff,~. ~!lt. d,.%fl d 2 ;.l!:pj!». t&: EE!1i:i é<j o PT~»-~m* * ~r±jx~.awrfl--t-m~m~~w-~~~jx~. oo~:mw ~ o r± r:p, íltifl~~~ijwj-1'-~ft~w, BODY ff~, # Jl ~llt BODY (IJitB~ 2o BODY 1! /.!. 1í "F, W3 1'- ~ ft -:ff~~ (0 TABLE-IMG-TABLE.IX\.. o m Jl! -1' TABLE-IMG-TABLE f;f~rf.~tlli411&jx\*&ïl g, ~1'-~ g'@.feii:oo 7.6 Fff 7r; é\jj;t~ K:1Jm~ P3 o!ltlt-t, Bf~7&-#! t.tf~a<j~!jtjxi.#t o ~.tlt. :te DOM ~4', -1'-JXI.#fï! 1.!.1!:--t-:+tF~ï! g ff ~tl. íltfhe:i! Jl.t ;f,j~11 t.\~~ 7Gf 11 F.f.,!. ~:te DOM W ~ 1E 'e: frj M. ~~ 1l,è:.:P ~ ~1 ili *o 'W!J:9Jl, TABLE-IMG-TABLE JX\. ï! AU~*lf - 1'-7G~llB : TABLE, IMG.fU TABLEo 7G ~ l1 F, í, ~ * 13 DOM ~1 é\1 t~~ ï1 g ::f f.ij, Jti iii~~ 1:11 7G ~ 1l R.H:t\1 ~ )( o :ft!jj 7.6 ep, PJI».~i1Jtí9[tl(0 TABLE ~~ ""F, d, ~ d2?1-ft, tf.jxi.*if~rp EE!Wi-1'-/F151JXI. #f(0~.f.\*&~ o ~-1'-JXI.f1íj,è:?j-. i!j~ P-IMG-P-A.ff~ P-BR-Po :iè~'*.tf:ft;tjj!~ TABLE ij F.f. r, f;f~#::f 151 (0 ~J~.JX1.*& o ~Wi-1'-.IXI.m ~F.f. I'J<J ~ 9Ht-BBW~ I o JXI.*!~ ~ Wi 1- DOM... ' '... ' ' '... '.. ' '... '.. '... '. ' ' ' '. ' '... '... '... >>

268 li!ij a<j o lli!luu!a!.% ~ o :ilfí J1 Jxl~ W, ïïj ~;). ~ ~J DOM ~H!<J IIJftl $ 7t ~ ;ffi f<il ~, PI! Jl;b $ 7t ;lè :if: BODY BODY TABI..E IMG TASLE p root I I 1 ' EIOOY I ~ \ I '---,----' I : : :,, :. : wk:tth=800, ha~nht._ry =200 :.....,. '.... : 2 : f..,...r. (- ~-~ ~ \ ~:~ ) G TAPU ' dt I., , , 8 B c;j 8, _,, ; 1. JXI.~t~(Style Tree) am ~~X~RMM~M~#~m~~A. ~~-~~8~-~ 0 --tr.ftttí.è:-lç~t - 1-;fffiíi~~l.W.JX\..ftt, '8fïW91-giJVi:SMt, ~t'f(es, n), ;ltrp ES 8 <<.. ~ - : - ".. ~~

269 .l!~:l ' 1' B 1'19-1'-ff:?tJ, n ~ff ~)Xl-f* 1'191»9 Y[ 1'19 ~I!: o :tf OO 7.6 9=', Jxl.:mïl #(te-!1~ /J«!P'l) é<j P-IMG-P-A 1f 41'- ~~"P.é:: p, IMG, p.f:ll A, #il. n= l o ~!JlJXl:mïJ i.\:rf.jjx~~~yn"f : public c l ass Styl enode ( private int count; pri vate ArrayLi st<elementnode> nodelis t = new ArrayList<ElementNode> ( ); -1'-7G~íJ.é: E ::tr=1'-ffi/j~$jt, iaf'f(tag, Attr, Ss), 1t!fl: e TAG :lh;:f-~~:gf/j\, 'i9tl'9fl, TABLE.f:ll IMGo e Attr ~ TAG A<J-~liï.'l'JMfE, ~!JWJ, bgcolor RED, weidth= IOO ~o Ss~ E rïfiïé<j -i;jxl#pp.~o -~-.~ DOM ~ ~. -~ft ïj~~s-1'-~-~. ~-M~ -ma~jxl.m l' Ss(J.\! )o.tJT/J~; J\1,!ltif]Jl',!j;fflt~~a<J~~t"Fla.7G~ltfi:, ffi)t~lt.éï:ff:?u~; JXl~1t.e:.~~7G~l'#~~~~~'tmr: p ublic c lass ElementNode ( priva te Stri ng tagname; private Stri ng nodeval ue; private bool ean istext; pri v a te HashMap<String,String> atrrmap = new Has~2p<String, S tring>(); pri vate Arra y List<St y l enode> childr en = new ArrayList<Styl enode> (l : privat e ArrayList<String> c ontents= new Arr aylist<string>(); ~~)ili!f! ~~:W: 'ftlj ij!-~.ix\..fm~!(~y.; ~~ JXl.nlW~ SST)~F'if;mïlJf!. 1ï ~~1f1~4ij~I#J YC E 1 J :t:ejx\m:f~ht-j-1'-~ ~ 7G ~ ïj R.: E Ptl:<E ll!- 1'- DOM :fil ~,s ~.1: rro r -&r tt mc.;.x~*1t$1~. :(f DOM ~tf1~1mz~~~lt,b~ T), ~ii-"f DOM ~9=' T é<j=ft"f~ï1.~*?u ~!;.Jf[f- E ~1'!9JX\ ~~ s ~~~-~,B~~~ffi~.~-ffi~. ~R~*~T.IX\.-~R.\SA<J~~~ fi~~. ~Fo~#JX\~~~ DOM ~!:f:l~j "Fó<riJF.?:. 1lll*/!'11HiiJ, JX\ :f/h~:tcpa<j~~;l,b E l'f.l ~ 1J 1;Jt PI l2j. 'ftlj it - 1'tfr (IJ.IX\.. ï1 F.?:. te~~ ::Al.IX\.~~ a<j J)d... ~-.Q;fa.7G ~~i ñ Fo, -le DOM ~ ~~-il~ T a<j~mu~~jxlnlw n ~. 2. -ttjxi. f!m P3 Mf! ~ ~~t J5 ~~ a<j %~~fl2j.r~~ = ~- 1'-7G~ï1~~~rt-J~~.IX\.~~~.~7G~~B~ m~. ~-tjg~,~-f.?:a<j~g.r- I*J~A.:~n'tiJ?t?i~~. ~7G~lt.B~:JI~. ~~~Jt~f.í ft~ 11Lfflf w~jg ïir.:a<ja~tt. ~ ~.fxl.mm &a<j a<j~m ~J)d..Ma<J~ * ~~~. ~ ~~~. ##I*JWK~ttl'f.l a<j~~m~ ~~~B<J~aj)d.. ~~a<j~yf a<j ~ I*J ~. e ~. -~~~ tt~i*j3m tt. lli7gail~oo tt. -1'-ftW~~ ~-~ ~tt~* ~~~~~~±~a<j~~~~ o >>.

270 .teoo 9='. SST ÉfJ~J.I~$7HIFiJflU!~J5, ~:1\1--eiflÉfJ~lJ». JXI.*it( li 1RJ Jt ~ ll-'f ~f@:. ~ =' *~7l' tfj )..I~HlU!ilJi!O.W ml JÈ íñ ~.tlt:lf' ~~~ :m:~ o =' a<j~~ Table 7G~"'iJ r.'h'fil:f$7- JXl.~"'iJ ff_, 1B~::liH~. ~;c~ïl ffi~&ïit~l?:~!i!~hf.j o 1B~ ~i#.. ~~ Table J!ïiT~H~!;~-jj~J:~:B<J±:~~~o t:i='éfj~~ Text 1BW~)(. 1:51:1\IÈtr-:l~ JJ».JXt.aM~~~ ~OO. ~;lt~~~ntr-:l~~~~osstïit~m-~nm(ifj@tfl~~~tfj*o 100 ~ Root body I I I I \ 100, \ Tabk: t--- Tabk: I I I I 100, r- -- l r;-u-;1 : l r;ll"";ll ~-, I L..IL.J I l L...SL.:J I I! --~---- ~ :, _I ïit~mma~~~(ifjm*~~~~jxt.m(ifj:b!.tto "'W.Q.Jftt#./it!: te SST t:p,,x1-f-;l I ~7G~ïltf. E, 1i m ~-E!.~ E ÉfJ~].[~. :lik E ftl "TJXt.Mïl R.i:ffl~:it(fl.P 1- IE.Ssl), E a<jïl #,1:][- tieéj Nodelmp(E)~7l'. ~5(1-J I --Í,p,logmp, Nd o.e Im p (E) I, m>i, m=l ;ltçp, p; ;&7I':ff. E.Ss t:p, ~JJH.t.Fl'Bl~ i 1'-JX\.~ïl.Ba<J'Pffi~tt. if._%_t, :9-ll.* 1 1 1', E.tE/f' ~ a<jjxt.m "P~. il».tfj*ft-jïlj~gtt,59t'h (1) ~.tlt Nodelmp(E)íñ1li 1 J'o :tm* # ~J!),!Xl. ]~~ Nodelmp(E)a<Jlï~:ko ~tl"flil, teoo 7.7 ~7J'a<J SST~, :n;~ïl.è'. BODY 8"!11:. tt~ O(llog 100 /=0), ~ 11 I =lo fl.p, te BODY r. f3l_ :ff- # Ml!Jl.JXt.m Table-lmg-Table-Tableo ~~ Table éq ]t~ ti;!!: Nodeimp(TABLE) = -0.35logl x0. 25l og logl = > O << '... '

271 publi c double getnodeimpor tance() double sum = 0.0; int numüfst y l es = O; for (StyleNode child : t his.children) n umofsty l es += child. getcount() ; for (StyleNod e c hild : t h i s.children ) s um += chi l d.get impo r t ance (numofstyles); retur n sum; ;!ttf;l child.getlmportance!r-j~jjr.~r: public double getimp o r t ance(int numofst y l es) if (numofstyles == l) return 1. 0 ; d ouble r a tio = (double )count 1 numofst y les ; r eturn - ratio * Math. l og (ratio) I Math. log(numofstyl e s ); ~ïfü, 'f'fitr~.ffll) R.i: JI~tt. ~,7;] BODY ~-/ '-~pr, JYTI».~~iif~~;t l f~tl911! tt.ftffj~m~ tt~ M- -t-n ~~~;n:m~a<jm tt. -~tlfftit-jj:mj; : ~llt-t ïj AH:fl ~~ llt r ïj,t;t;i:( t!?.it;lè ~ a!lïl R.i:) ïij ~ Jfl ijj3 #:f l'iij rt-.1 1J ~* ~m ~~~~-~~~* 1ï~. m~~~~r1l~~* 1ï~. te SST r*j, x-.j"t---1-r*jtmn~ i'i Ai: E. 11! I= je.ssj. E (19~~1E~ttffi Complmp(E)~ 7J'.~5(Jij f Complmp(E) =(I-y)Nodelmp(E)+ y_l(p 1 Compimp(s;)) (2) i l ;n:r:p p ~71'tE E.Ss "PE :fl..iifm i -1'-rJX\~ïJ.~rt-.JïiJ~~ tt. tt...t~~~ rp. complrnp(s,) ;li!:- / '- ~f! f f A, S 1 ( E E.Ss)l'fl~~ ll!~fí, Compimp(S 1 ) =..:... '/f!.5(:jj L Complmp(E ) 1 =- --- ;n:rp~-~erpl'fl--1'-~sïl~. *Rk = ~&j,. 71'tf~rp~ ïj4rt-.je. te0:a:(2)"f, -rjb)t~t1i!t, 11!W;I;; 0.9. ~ I $5t;J;:a1, B ill/jut Nodelmp(E)é<J;&Ji;; ~I $5t/J,a1, B~d>T Nodelmp(E) I'fl~1f!. ~~'*~ --1'-.JXt~ïJ ~ Aiifl'flT-~~ïJA:~ $. ;n:*~a<j~~~~tt~;j;:. ma~i'flrjx\~;; ~ ~~:1>. ;n:.:.~a<j~*~ tt~;j;:. * ~ ïj~a<j~~m Httl'fl~-~~~r : public doub l e getcompositeimporta n c e ( ) >> k (3)

272 double sum = 0. 0 ; ) for (StyleNode child : this.chi ldren) sum += chil d.getcompositeimportance(); ) return (1 - ElementNod e.ramda) * this.getnodeimpor tance() + ElementNode. RAMDA * sum; ~7-"'P #..!:ij 1*1 ~ r=p-#..:if:; 16.1, ~ 1il't::fi'l.R *ï~~j P3 ~Y.5!1ftffi~. lltffli!l!if:jt~~,; ;J;J ~(~P ~$. 00~-~MW)~~fflA~X-~~~-~~ ~ * tt. f:e SST ep B<J -~P j JG~iJ ~E,!ili ;&:±lfjil,.tf E ep B<J~hl:~~ ( ~P~!f:. 00 ~~14. ~M > #~~ m ~~~E ~~ ~~, l Complmp(E) = I "L.H(a ) -.:.::. 1='-- t -- Eê<J *m tt~~~ m=l m>l (4), m=l, m=l (5) Xcp, ~-~~J~~-~~ E cpl±\~ a 1 B<Jeyatt. t!d* m=l. Ji!IJ~"*~,R::(f-1-~~i.t::ff E, Jl~,Z. E ;!!-~~F ; ni~b<j1),r;'.i:, #Jl't::~ *1!t~tt Complmp :1J:: l(complmp (JI,Jft[l)3-1tJ& 0~ 1 Zi'Blê<J~i[). f9tl:!loit1f:oo 7.8 ~~JXt:m~ep-=p R-E B<l *lf~ li= I ~ I 9 Ep I.- ~... I IMG I I 3 ~ " TABLE I E tl: t2: tl: o, * *il. lliflllll A~M a:rv ~ << '.. ' '.. '.. '. '... ' ' ~

273 ~~E (IJ~]UHI m = IE.TAGsl = 3 ~~1'-~N=I*~ ii$~. A~~.CCTV I= 4 :~!~fiti HE(*im) = -3 * (1/3log 3 1/3) = I m1jl HE( í"$~)= Hs (A~~)= HE (CCTV) = -(O+O+l logj 1) = o 'L,H(a 1 ) 1 Nodelmp(E) = l - -'-- =-'-- / I 1 - = I -(HE(*~) + Hs (if$jxxj) + HE (A ~~)+ HE (CCTV)) /4 =I- (I+ O+ O +O) /4 = (( I- 1) + 3*(1-0))/4 = 0.75 ~n~~-~~ ~ ~tt(ij~w~~ = public double getleafcompositeimpor t ance () boolean i stext = true; for (Eleme n t Node stylenode : node List) i f (! stylenode. is Lea f () ) istext = false; break; if (! is Tex t) r eturn O; int m = count ; if (m==o) return O; HashMap<String,Integer> atts = new HashMap<St ring,integer>() ; for ElementNode stylenode : nodelist) ArrayList<String> contents = stylenode.getcontents(); for(string a : contents) Int eger count = atts.get(a); if(count == null) e ls e atts.put (a, 1) ; ++count; atts.put(a, count) ; >>

274 _/, double sumhe =0 ; for (Entry<String, Integer> attentry : atts.entryset()) String att = a t tentry.getkey(j; for (ElementNode s tylenode : node List) ArrayList<String> contents= stylenode.getcontents( ); double heatt = O; if(contents. contains(att)) double p = 1/(doubl e) (attentry.get Val ue( )); heatt = p * ( Math. log(p ) I Math. log(m) ); surnhe += heatt; int n = atts. size(); if(n==o) return O; return (l+surnhe/nj ; m~~~~~~~~~~~~~*~~tt~~~ ey ~~rt~m sst *~~. ~m~~ ~m~~ moo~~~~.ey ~re~ ~ ~ ~~~~~-*~ " ~ ~ "w -~- ~~~~~. S~ fi~t -~M~m ~ ~fi-~~~m ~. ~ftoo~~-a~ ~moo~.m~~~~~~~~~~w~ ~~~it~~lli " Java ~I.!\! m~~~~~~ *~~~~~~~m~~ r. (I) ffltj~jw f~ : ~ ~~ ~~:<$:~~.Ji!:~Jffl ~f'lliifijf.!oo*f~ie* IR*:ff~ ~ ~~5fO~ ) j, ]jïh14j, )'i!f~;lt: ftkftl~. ;M;cp - ~ m-jf.!~rzy~:j!fiu~jc~l:t$. -ey~fe! HTML $'4'~d OOMW. M-~~~ ~-~-~*!:t.~--~~~@ ~~l:t -~lm~~. ~re )g~ij ~ -:t;~. (2) :lw~ft;t~. üj ~:If. HTML 9tOOiiJ~d:$~ft;t~(Paragraph). 1\ll.t)ta<J~JJ\t!.r~:Jl, *tii1as<td><p><br><d iv><tab le>~~f,j~*t11~f&:~. 0) W~&~. -~--~B<J~ ~~~-~~-~-~~~M~~~ fi~ft~wa ~~~~~--~~~--~--~~ ~~~. roo~m~~ ma<j~~~ -~il~r~~ ~ / ** i.t~--t-~srrt-j tii~fl inode * *~;.tna<rw li. iii~~ * I <<

275 public static int getnumlinks(final Node i Node) int links = O; if (inode.haschildnodes()l Node next = i Node.getFi rstchild() ; while (next!= nul l) Node current = next ; next = current.getnextsibling(); 11 Jt!B i,ll3 m iuhiuu!o't-j ::tj~ links += getnumlinks(current) ; if (islink(inodej) links++ ; ) return l i nks ; / * * * ~t ~ - 1' 11 R.CF tt-1!'(!. iiij lll: i Node * 7f P:&it ~ A<ri1 g itt'iiil~ * I private int getnumwords(final Node inode) i n t words = O; if (inode. haschildnodes( ) Node next = inode.getfirstchild(); while (next! = nulll Node current = next; next ~ current.getnextsibling(); IJ!U:;r-tt-rmu... 11:lm~~ïlb 11 t\~-1'~* if (!islink(current)) words += getnumwords(current); 1 ;;!>&' 11 g~)c; j;:i'i g~f.!::rdn'fg int type = inode.getnodetype(); I )l.:;$;11g i f (type == Node. TEXT_NODE) String content= i No d e.getnodevalue() ; >>

276 ) words += gethtmllen(conten t ) ; return words; ~ ïii~* -~~*~ *M~m~ ttx*~~ñ~ : private i n t gethtmllen(string text) int len = O; for(int i=o;i<text.length();++i) i f (text. charat (i ) == o o e l se if (text. charat(i )== 0 o else if(text. charat(il==o o else if(text. charat(i)== 0 & 0 ) ( e l s e i +=S ; ++len; if( len<lo) len = O; return len; M~~ ~ ~-~-~~ ~tta~~, ( l) 1-t"-T.ï A'Cf~llif:tl:~. (2) i-t~t.ï,è:~ (J<] )(~~. (3) it1fil E.~tli~Jt!j!lt = 1J ffi ""f~ tit~ l!r/ll E.""frf.JJt~ tt. ~ ~ T.ïr.?:o<J x tt*~~-~ ~ -~~-~~1Jr.?:. -roo ~;~t!k~~~,! ** * :1m ;lli! tiut lt~ 1:-ilt9!U IJJ!Jf!JS'dil: 1' * i(l JG <<

277 i Node * ~lf'.;rc;"il R.\ * I publ ic void test RemoveCell(final Node inode I I '!zil :l'f!: i$; 1'-~i(i. 7G& :f.f~ =f"il ~!íltl.:;:f'; tr,g ~ 1'~ i(. 7G if (! inode. haschildnodes()) return; ' : :u ":.; double link s ; doubl e words; I I i.h'hiuh~l $ tij f& link s = getnumlinks(inode) ; words = getnumwords(inode) ; I I it~iii:mx ~Ht fhft ~ :lí1:: i!'f.fjt o ~ double ratio = O; if (words == O) ratio = setti ngs. linktextratio + 1 ; e l s e ratio = links I words; if (rat io > settin g s. l inktextratio) inode.get ParentNcde().removeChild(iNode); ~~~~~~~g~*~m~~~~~~ ~~~M~. Wr~ttttfflÈ~~ -~~~~ *'FJ!~iJtJi:ii H7FT*WFf~$~~Wi-í' JñiiiZ..fa:J ~~S~ffi. ~~!!tt!'!lto ll'lfti'il\j ilii*!i!i;e\lfllt " 24j!i!!'!K1fsmw, ' " llfililli!epjjjel JEe.tl$ 1 21!4 Bol! (lè~hf~) #11'0) (~~ s ~ ~~U~!'.J) M*Vlfftl-tt~~4 Bttf!AU1>\Jt, ~"i~~*ft.t. ~a~ :~tjl<~'lil*=>~ij!.w.t"ïn~ ftlli)1joj.u#tl<ffi*fltl~~fl'~fòl d. Jl;l<i!r:lll13~1!9 "~ " >>.

278 public stat i c double getsi miarity(string t i tle,strin g b o dy) int matchnum = O; for (int i =O;i<titl e. l engt h ( ) ;++i) i f body. i ndexoftitle.charat(i))>=o) ++matchnum; double score = (double) matchnum/( (double) title. l ength( ) ); return s core; ~~R ~~~~--~~ ~* ~ ~m ~ ~~~-~~~ ~ m ~*~~~ w* NOO~~ -M~. w ~m - ~-- ~ ~~--~ê. ~~~ê~~~* ~-~-. ~-~~~-~~~~ oo ~*mm ~oo~ ~-~~-~~~-~~~m w~~w~& ffi,@., ~~AD!hJU~tl ib-~#t~rlf L ~!fi 1?i DOM :W~t4;J7HJT :i~hrjjhfñ 7J-~,.!l!tliiJ!;J. ~JiJ!l! ~~~* -~ ~~ ~ - ~ A ~ 2~ - ~ W7 ~ ~-~ ~~ M *~ -~ VIPS(Vision-based page segmentation). VIPS ~ i'! ft:?t:fljm 7 Web ~ïlñ~:ffl,rnj~~(~lll 7.1 0), 1?:~= ~3::~ 2;17!11 : 11Jt;:tf DOM W9=rt».t!uJ'~*':i.Jl[t.%~W3ff-ïPJat~~~. ~.ll~ í:ïJ&ff,~~ l-trh:l -~ " -~ ti~~ " (Degree of Coherence, DoC)fli*Wi J.t ~~ ;I;J-tfn ~ -~~ ~tt. ~c~ * ~-~ ~~ ;~;] ~ ~~~ ~ ~ &~ ~e. = i!7~ffl-~ïij8~-~~m~&-~~~~- ~W'i?:ID~ ~3TW~~~ ~ * ~--~~. am~~~~*m*.~ffle$ ~ ~ ê ~ ~ w~ :ïlfiii ~Ji3~~~(.!A!.~ 7.11 ). ~s#~ ~«~+~~ -. 'i?:~?t~m 7 ~ M ~a ~ ~ ~~~* ~ ~fmj U ~~~-~*~ ** a 7~*~8-&.~-- N ~8~fflA~~~--. l!l~ HTML m * -;;- ;;te~ # ~f-ï 1~!:~ JÈ fj$~t!l!:!.ltm.@., JYT IV.~ ~Jt ieh71' lfl ~ ~-~ 81 ~J JA! - ~~ ~~-#~~ -~Y~~~~~~ a ~-%~~~~~~m *(f] CSS )(14. JavaScript )(14, I~LJt)C1lf~, ~ Ji ii3 Jf.I~J~:Ili ~~~~~~ jg.ll:~~ M)t#, :timb\~jw:~ Ii3 **ft~ ~HH.:J ~~JIJ ~ HTML ~~~-t!.l!.-ffi.m o II~il7~~15t$ 111. ïfü.. +~~~ -=fixjj.oo:~~~~~. ~~ VIPS -~~~7 i/al ffl IE ~J.OO:~~ COM *D.:(E Linux ~;1/UJI!f.f:it ~, í:ïj 12).~J ffl l'f19. li Mozilla ft~ 1ttiW~t A (< , ~..

279 ~~~-~~ ~- * w ~~~~mgc, R ~x ~~~~M* :lttf~.m o..._.[ ,.,.s... -'rft... f --.,._ ~ ,_...,.., _..._._ ''t --- C....._ _ ' -Qt H 11 *P'ef - --c-.. KCW'tt' --..., -.,,..., _.....,..... (~1--..._ol...,,...,_ -... w!".""_"!'_~-=...,.,..., -li!iml~... _.,...,.. -r... n _..,... _...,_...,.. - ~ "Htt) a,..._aa... 1I I I I I I I I I, ~ ~ ~ , I I I I I I I I I I I I I I I I I 1'----'!D l nj I ' ' 11 li!o i 1 I I I I!! i I I I I u 1: : : I I I I I I ' 1 'f'2 I I I I I I fp? : rp Cobra(http ://lobobrowser.o rg/cobra)tjll:~ 7 ~ Java ~:E.W.~l*lJJl!:f,i o ~ 9HfH(gl$, :st~ CCS2 ~ JavaScripto Cobra (!J-JI:hf!ffíYur: e ~J.W, 7 W3C írtj HTML DOM Level 2 ~ C o Cobra :Jr::- 1'- HTML fl~j'!t!~-f*m.fjtj.w.~ífl~ji o >>

280 PI ~»-a&~ ~(body)~mi-t!f! 1tm. ~ Wff.ITX~It-t, m~ DOM ~~é<j:!tl"lt(i(jjffi~. PT!;J.illlü~if:n:;~~ innerhtml!íltt~:ñy*:te:ll~ W!i~t& DOM. ~ijurj JavaScript. fll'h.itü~cpx>f DOM B<l~c&(i91Jj :,.lijíü document.write ~èj(~ ]J.(I*J~)~~:a;~J!: DOM cp&_bjèt!h~. J3j~-t!?.ï3J!;).~ffl JavaScript. jjfi:èt DocumentBuilderlmpl ~*Mtff~]J.(, j : "'ft/.1\: URL urlob j = new URL (urll; //~À url ttkhl URLConnection connection = urlob j. openconnection() ; InputStream in= connection.getinputstream() ; UserAgentContext contex t = new SimpleUserAgentContext(l; Document Builderimpl dbi = new DocumentBuilderimpl (contextl; Document document= dbi.parse (new I nputsourceimpl(in, url, "UTF-8")); Element ex= document.getdocumentelement(); dotree (ex J ; I I.!>\~ ij A.ia1Ji3i91Jï DOM tal public void dotree(node node) if (node instanceof Element) (//~j~ Element e l ement = (El ement) node; i:q "ol;t,j~ doelement(element); I I ijj l'dl~ 1-tf-~ r ffrlí lr-j f1< r, "I:B8,\';;I!~ 1-tf-~Jrr~ ~ l'l'-j:i'i~~ NodeList n l = element. getchildnodes(); if (nl == null) return; int num = nl.getlength(j ; for (int i = O; i < num; i++) dotree(nl.item(i)j ; I l f,j~~jr dotagend(eleme n t ); else //~);~~ij JA\, i?tjpijjt;;j : System.out. println(node.getnodename()+": "+node.getnodeval ue()) ; tf~ 71 d:t~cp, DOM ~ep é<j~?fe~"'yl ;~!f~,6j(; 7 ModelNode X>f~. Htm.IBlockPanel ~ paint(graphics g):ljy*ïjj!;).q~jij i:ii~a(]t.i Aí_j;::;j' <<

281 Rectangle clipbounds = g.getclipbounds() ; g.setcolor(this.getbackground()) ; g. fillrect(clipbounds.x, clipbounds.y, clipbounds. width, clipbounds.height); public enum BlockType Link, t tt ~~ Text, 1/X:$:~~ Irng, IIOOJ:t~~ Form I t:&t(j.~~ public class Splitter l/ fêi~~fri!/ij4ihf.j :ct::lffl (!g~.tf public long left; public long top ; public long right ; public long buttom; public long width; public long height; 1/~R~~ ~ ~* ~-~~M ~~-tt& ~* public int weight; I l:l!; j(~~~h!t E5:1!~1íf;!llj~. true *7J'~H: false ~7J'*Sfpublic boolean vertical; public NodePool leftupblock; /1~-'f *Sf'-~'!ltl*- li í< t 'f@ :fl:$~1 ~, iwt:ffii(71!i~l*-..tíiiï~ block public NodePool rightbuttomblock; li )( j -'f*~:$\!ltl *, ~; 'fii(7~l*:olll~ * block I I ~r'lltí'j. ~ W~~, f!hf :$-j!q* r -ool é<j block llihf-~7t~j~li:~9'-j block ) li ~~ièjf'j"'f~tjitfr ~ ltr7t!wi*-;!è:;r,;ll!b,~~~* I I bt:iw HR, ~(t'~ Table. TR, TD ljj..&~.!t~i!1ïi[jiil1ïif 20 8<J :M,tq*~~ T~.i.\7t!Wi* I I Jt fk 09 7t ~ ~ )/.1! f lr.l ~ :5 1ft\!~ public boolean isexplicit; VisionBlock ~ VIPS rj:)<tt:j:l:lfk]1~ít-j~. B.ffi\:J.Wii~Jfk~ít-J~:W:P'J?J~. :ibtljjít-jif1- ~W~f-1-*~ ~~ ff -~~ft~, ~-~~-~~~~~-~~,~-~~. ~Jl:ti!..)(~~~~@ ~~ -@.~( <Jo public class VisionBlock 11.f!l w fil@. public long blockleft; I I ~íitrr~~ B<Jll:ü.l~-(tlJl >>

282 public long blocktop; pub li e long blockright; public long blockbuttom; public long b l ockwi dth; public long blockheight; li ~litr?i~:l!lètl<j..t~~~-- li ~iiür~~tl<j:t:jjij. -.-ttl11 li ~ïltr?i~:l!lèé<j r;tj.~-i:!l~ li ~ i-ïú R ~~ é<j ~f.!t li ~flür~:!eièé<jit'íf.!t I I?1-l\'U*-ffi.@_ public Splitter upsplitter; pub li e Splitter downsplitter; public Splitter leftspli tter; pub li e Splitter rightsplitter; 11 ~mr.tm private ArrayList<CHTMLNode> leftvisionblock; private ArrayList<CHTMLNode> topvisionblock; private Array!JiSt<CHTMLNode> rightvisionblock; private ArrayList<CHTMLNode> buttomvisionblock; I I ~ïl1j R~ti.!Ailta~i'.5(* I I ~ïlur~*é<j -t'oojilt.5(~ 11 ~ nu~~t~.taij;(:ï~~*)(* I I ~ ïitr R ~ :feiè é<j '"'f 1)Jtl tl:t.5( ~ I I ta f4 'Ïff.lli. I I~ Block ~$t~1:~(1).block privat:e ArrayList<VisionBlock> cont ainedvisionblocklist; I!~WI R~~M''@"*ffJ -\H\ private ArrayList<CHTMLNode> containedhtmlnodelist; I I ~ï\tr Block cp-@*(11]1!1 ~'lhu>, public ArrayList<CHTMLNode> nodesplitterlist; 1 1 ~ 1Ïti :i!~lh~ é<j doc tri, l'fl ). Wi ~ ~ ~:!Eiè R íl\l é<j ;)Ç ~~& (11] ij!itïj~ /I DOC J'lFffi~~iltr VisionBl ock Jjg$é<J;)ÇJI*'fi, DOC ffj«l~;;k. ;ê.!!*'fi~;;)ç /I DOC (l'.]il~lj', ~~f.f~1j ' DOC ffjifi:fl--'f 1-10 ZJàJ public int DOC; private String blockname; I I ~nu Block fl<j~~ public BlockType blocktype; I I ~ ïlü itt..$(. ±;lç WJ :si:~ ~1.)(i;R public v isionblock parentblock; I I R'iffll'f.J Block ~~:a Block xhi'o j(.ijz Block publ ic int blockdirection; public int dividedirection; I I ~liu block f?tf1j( (J1f P] I 1 ~lwí~íiíti'f.j Block :12!1!i'Jil!Jt; f;.: Block public int istextblock; I I =f illihjijj.hj public int childnums; <<

283 * ~~~~7~~"~* "~~~~~-~~~~*-~ M ~~~M ~~ ~. ~* ~~~H~R~ M. ~ey~~~mh~--~ ~~-. --~~~M~K a~ fl~ffi.~ >>.

284

285

286

287 ~~ 1 :f:"~1fl:m.ijt-ii,. :.*p rf],4;.$,t -~~1i14t*JAúfJ Web 1!1.,(!.ftl )a]~jft!ki!..ff-. M:JJrúfJat1~, 1;-:l-~~1'-l!l~tA I;]4'f. 1;t,~, ~ :~HJí"1i14túfJB<t1~. ~1f11;~~il*1'- URL J'Jí"*-.-FúfJ )a] Jr 'f ;tr J jt'1i14t. ~~"ft~jtt.1i14t. A::t, #.lt.t-1'-~hl -li"-"~4lt4t:.ot:..t~fo7'è úfj.trtfo1 )a]~ úfjtt4t#jj ~ ~ úfj "AJ.t", ~ 1*1 ~.tltfo1 ~f;ul('p ~ Jr 'f ~_.Ut-ll-)#J!J ~:Pi úfj "~J.t" 1!.-i:~i!ii?r# Web 1!1~4-fil, ~~" JM Web ll),l!f~!ui:it..ft~#fo] :t...

288 8.1 ff fi! Web "[I] " <<... o M~~,m~oo~mm. ~ ~ ~~~~~ ~~re~ffj~~~~~.w~~~ moo~w~. ~~~~ ~~~~~~~~~- ~~. ""fiid ~-fj ~ pg.ff~ #f* Web I~Ur<J 1-1;; ~. public class WebGraphMemory I l re!fi-1- URL l!!km:!*j--1-lut, :ff:~ff: Web OO "f=l private Map<Integer,String> IdentifyerToURL; I ** :ff:i\t Web 00 ~ Hash ~ *I priva te Map<String. Map<String. Integer>> URLToidenti.fyer; I ** * 1'fi\tA.IJ!:, ;Jitrfl!U!ra<Jm- -1-~$:;li!; URL é'~ ID, m=-t-~$:1'f/d(fs'jíij) -1- URL ~~~J Map, * Double ~71''&1li: *I private Map< Integer,Map<Integer,Doubl e>> InLinks; I ** = *.ffflt tll /Jt, ;Jit c:f ~--'Í" ~«RJè URL (Iq ID, 1,g * tl(:!l: *I private Map<Integer,Map<Integer,Double>> OutLinks; I ** OO<P'\7t.ï.a<Jll ~ l private int nodecount ; I ** * ft;jji[~j!(. o -1- '17 ~~~~lfí~f ( *I ) -'Í" ~ l!(.ff$: fii:l9j: tp tj9 ;th iif~, Do ub l e *'l' public WebGraphMemory ( ) I dentifyertourl = new HashMap<Integer,String>(); URLToidentifyer = new HashMap<String, Map<String, I nteger>> () ; InLi nks = new HashMap<Integer,Map<Integer,Double>>(); OutLinks = new HashMap<Integer,Map<Integer,Double>>( ) ; nodecount = O; I ** *»---t-x*xf!fc:t>~f.j'w t.ï.l'fl~lrtoo l[. fitri~!:~--t-mlnl~~. ~J:tzn, * com -> http : //ur l2. com 1. 0 * ::&71' "http : 1 lurll. com" i!!,~-1'-m!~tt "http: I l url2. com" :Jt ll.i&-1-mlfil~!'!ij. tl(')t~ 1. o *I public webgraphmemory (File file) throws IOException. FileNotFoundException o o o o o o o o o

289 M 8 ~tlf Web 11 ) thi s () ; BufferedReader r eader = new BufferedReader(new FileReader(fíle)); String l íne; whíle((line=reader.readlíne())!=nul l) ínt índexl = l ine. índexof("->"); íf(indexl==-1) addlink(líne.trím()); else St ríng url l = line.substring(o, indexl ).trim() ; String url2 = line.substring(index1+2). t rim(); Doubl e strength = new Double(l.O) ; índexl = url2.índex0f (" "); if(indexl! =- 1) try strength = new Ooubl e (url 2. substring ( indexl+l ). trim () ) ; url2 = url2.substring(o,indexl).trim(); catch ( Exception e ) ) addlink (urll,url2,strength); / ** " ~1!ij URL $~ ij'i?;! (!D * I publ ic I nteger URLToldentifyer ( String URL ) ) Stríng host ; Stríng name; ínt índex = O, índex2 = O; íf(url.st artswi th( "h ttp : //")) índex= 7; else íf(url.startswith("ftp://")) í ndex = 6; índex2 = URL. substríng(index).indexof( " / "); if ( index2! =-1 ) narne = URL. substring(índex+índex2+1 ; host = URL.subst ring(o,index+i ndex2); ) els e host = URL; name = 1 " Map<Stri ng, I nteger> map = (URLTo!dentifyer. get (host)); if (map==null) return null ; return (map. get(name)); / ** * :m_1!ij ID ~fi URL * I o. o o..... o o ,, ~

290 public String IdentifyerToURL ( Inceger id ) return (Identify e r ToURL oget(id)); / ** * i:e 00 9=' :!1.1Jo -1'"P %.\ * / public Integer addlink (String link) I nteger id = URLToidentifyer(link); if ( id==null) id = new Integer(++nodeCounc) ; String host; String name ; int index = O, index 2 = O; if(link ostartswith( " index = 7; else i f( link ostartswith("ftp : //"l) index = 6 ; index2 = link osubstring ( i ndex) oindexof("/") ; if (index2l =-1) ( name = link. substring(index+index2+1); host= link osubstring(o,index +index2 ) ; else host = link; name = ""; Map<String, Integer> map= (URLToidentifyeroget(host) ) ; i f (map==null ) map = new HashMap<String, I n teger>() ; URLToidentifyer oput(host,map) ; map oput(name,id); IdentifyerToURL oput(id, l inkl ; InLinks oput(id,new Has~ap<Integer,Doub l e>()); OutLinksoput(id,new HashMap<Integer, Double>( )) ; ret urn id; / ** ~M-1'~~~~~--tu&~ -~ ~~~~a.a ag"p~ * I public Double addlink (String fromlink, St ring tolink, Double weight) Integer idl = addl~nk( fromlink ) ; I nteger id2 = addlink(tolink); return addlink(idl,id2,weight); <<o o.... o o o o o o o o... o o o o o o o o o o o o... o o o o... o o o o o o o o o o o. o o o o o o o o o o o o o o o o o. o o o o o o o o o o o 0 0 o o o o o o o o o o o o o o o.

291 M 8 * ~ff Web 11!** * ti:w3-t-~ a<pttt:jm--t-~~:;tçf;.. ~!it!ij a::f:f-he. ~gij-b~~ij a * I private Double addlink ( Integer fromli.nk, I nteger tolink, Double weight ) Double aux; Map<Integer,Double> mapl = (InLi n k s. get(tolink)); Map<Integer, Double> map2 = (OutLinks.get(fromLink)); aux = (Double) (mapl.get(fromlink)); if(aux ==null) mapl.put(fromlink,weight); else if(aux.doublevalue()<weight.doublevalue()) ma pl. put ( fromlínk, weight l ; else weíght = new Doublelaux.doubleValue()) ; aux = (map2.get(tolink)); if (aux==nulll map2.put(tolink,wei ght); else if(aux. doublevalue()<weight.doublevalue()) map2.put(tolink,weight); else weight = new Doubl e(aux.doublevalue()); mapl.put(fromlink,weight) ; InLinks. put(tolink,~apl) ; OutLinks. put(fromlink,map2) ; return weight ; / ** * <ftx'fmj:ebij URL ~ J~"@;'t;BIJP.lffl" AJlf!ítBIJ Map *I public Map inlinks ( String URL l I nteger i d = URLToidentifyer(URL); return i nli nksl i d) ; / ** *ti"mm"i:eé<j URL ~ 11Q.~'t:B'.Jffrfl" All~tlBI.l Map * / public Map<Integer,Double> inlinks (!nteger l ink l if(link=~nulll return null; Map<Integer.Double> aux = (InLinks.get(link)) ; J / * * return aux; >>

292 * -tt~hi:f~fñ URL ~'i?;fñ:±l& fñ~f.lfñ Map *I public Map<In teger,double> o u t Links ( String URL ) Integer i d = URLToidentifyer(URL); return outlinks(id); / ** * #~t1'f~ (fj URL ~@ ' ~'t:;(!<j:±l /Jt1.)t~ a<j Map *I public Map<Int eger, Double> out Links ( Integer link ) if(link==null) return null ; Map<Integer, Double> aux = OUtLinks.get(link); return aux ; I ** * ~@l~~~~ ~~fr.lam. *I public Double inlink ( String fromlink, String tolink ) Integer idl = URLToidentif yer(fromlink); Integer id2 = URLToidentifyer(toLink); return i nlink(idl, id2) ; / ** * ~@l~~~ ~~a<.~a~. ~ * *I public Double outlink ( Strin g fromlink, String tolink ) Integer idl = URLToidenti f y e r (fromlink) ; Integer id2 = URLToiden t i f yer(tolink) ; return outli nk (idl, id2) ; / * * ~~~~~ ~ ~~am. ~~~ &~~ ~@o *I public Double inlink ( Integ e r f romlink, I n t eger t o Li nk ) Map<Int eger,double> aux = inlinks(tolink) ; if(aux==null) return new Double(O) ; Oouble wei ght = (aux.get(fromlink)) ; return (weight == null)? new Double(O) we ight ; / ** *-~~~~~~ ~a<j«m. ~-~ &~~ ~o *I <<

293 M 8 ~*~f Web 111 public Double outlink ( Integer fromlink, Integer tolink ) Map<Integer,Doubl e> aux = outli nks(fromlink); i f(aux ==null) r eturn new Doubl elo); Double weight = (aux.get(tolinkl); return (weight == nul l )? new Double(O) weight; ' / * * * :le ::ff f';] 00 5t: jg x li] 00 * I public voi d transformunidirectional () ( Iterat or it = OutLinks. keyset().iterator (); whil e ( i t. hasnext ()) I nteger linkl = ( I nteger) (it. next()) ; Map auxmap = (Map) (OutLinks.get( l inkll); Iterator i t2 = auxmap.keyset( ). iterator(; while (it2. hasnext()) I nteger l i nk2 = ( I nteger) (it. next()); Double weight = (Doublel (auxmap. get(link2)) ; addlink(link2, l i nkl, weight) ;!** *~ ~ ~ 4ma~-~m~~ * I publ ic void removeinternallinks ) i n t indexl; I terator it = OutLinks.keySet(. iterator(); while (it.hasnext ()). I nteger linkl = (Integer (it. next()) ; Map<Integer,Double> auxmap = (OutLinks.get(linkl)); Iterator i t 2 = auxmap.keyset(). i terator(); i f (it2.hasnext ( l l String URLl = (String) (IdentifyerToURL.get(l inkl)); indexl = URLl. indexof( "://"); if(index1! =-1) URL1=URL1.substring(index1+3) ; index1 = URLl. indexof(" / " ); if (indexl!=- 1) URLl=URL1.substring(O,indexl); while (it2.hasnext()) I nteger link2 = (Integer) (it. next(); String URL2 = (String) (IdentifyerToURL.get(link2)); index1 = URL2. indexof("://"); if(index1!=- ll URL2=URL1. substring(index1 +3) ; i ndexl = URL2.indexOf( "/"); i f(index1! =-ll URL2 =URL1. substring(o, i ndex1); >>

294 if(url1. equals (URL2)) auxmap.remove(link2); OutLinks.put (linkl,auxmap); auxmap = (InLinks.get(link2)) ; auxmap. r emove(linkll ; InLinks.put(link 2,auxMap); " I publ ic void r e movenepotistic() removeinternalli nks( ) ; I ** *!Jtlj~~ J.t URL * I public void r emovestoplinks(string stopurls[)) HashMap aux = new HashMap(); for (int i=o; i<stopurls. length; i++ ) aux.put(stopurls[i],null) ; removestoplinks(aux) ; l "" * SM~~.U: URL * I public void removestopli nks(map s topurls) int indexl ; I t erator it = OutLinks. keyset( J. i terator() ; while (it.hasnext!ll Integer link l = (Integer) (it.next()) ; String URLl = (Stringl (IdentifyerToURL.get(linkl)); indexl = URLl. indexof(": l/"); if(indexl! =-1 ) URLl =URLl. substring(index1+3) ; indexl = URLl. indexof( " / " ) ; if(index l! =-1) URLl=URLl.substring(O,indexl) ; if(stopurls.containskey(urll ) ) Out Links.put(link l,new HashMap()) ; InLinks.put(linkl,new Ha shmap()) ; <<>

295 as ~~Wabll public int numnodes() return nodecount ; ~OO~M. ~-~~-HS OO. ~~M~M~H800~?~*Mmm~. ~ffl~w ~~~~*~~*~ ~~~~~~~rr~. ~~.~~~~m~~~~~~~~w~~ ~.ie!r.j~ílli.$c:j:r. ""f ïfó1~1i!;;t!~ Berkeley DB ~~~íllip-!i*.fh~ Web 00 lt-1, ~~ Berkeley DB ~~iq, ill~ja!.~ 1 ít!r.j~~publ ic c l ass WebGraph I I I±IJJt privat e Primaryindex<St ring,link> outlinkindex; I I,A jt privat e Secondaryindex<St r i ng,string,link> inlinkindex; private Enti tystore s t o r e ; I ** * fi, ili fil~ *I publ ic WebGraph (String dbdir) throws DatabaseException F i le envdir = n ew Fil e(dbdir); EnvironmentConfi g envconfig = new EnvironmentConfi g(); envconfi g.settr ansacti onal(fal se); envconf ig.setall owcreate(true); Environment e nv = n ew Environment(envDir, envconfig); StoreConfig storeconfig = new StoreConfig(); storeconfig. setallowcreate (true) ; s t oreconfig.settransactional (false); s t ore = new Ent i t yst ore(env, "cl assdb'', storeconfig); out Li nkindex = s t ore. getpri maryindex(st ring.cl ass, Link.class); inlin k i ndex = store.get Secondaryi ndex (outli nki ndex, String.cl ass, "tourl ") ; I ** ~~Web 00, JA)C!fi*J~A. 4Ij:-~y,;k)-1'M fi\z~~. 'W1J:Wl * http :llur ll.com - > http : llurl 2. com 1.0 * ~:ïj'teili* h t t p: 1 lurl l. com Etf~7J'Bg~J/t..ti1ii. ~-1'-M!~~ http : I lurl 2. com * ;Jt J,l. t; 1i'JZ. fai (IJtt:!E,;k) 1. o *I pub l ic voi d load ( Fi l e f i l e ) throws IOException, FileNotFoundException, DatabaseException Buffered Reader reader = new Buf feredreader(new FileReader(file)) ; ' >>

296 String line; while ((li ne=reader. readli ne())! =null ) int indexl = line.indexof ("->") ; if (ind exl=::-1) continue; else String urll = line. substring (O,indexl). crim ( ) ; String url2 = line.substring(index1+2). trim( ); indexl = url2.index0f( " " ); if(indexl!=-1) try url2 = url2. substring(o,index l). trim(); catch ( Exception e ) addlink (urll,url2) ; / ** ~A~d~~~~~~~. ~~~~~-~~. *e~w~~~~ ~ *I public void addlink (String fromlink, String tolink) throws DatabaseException ( Link outlinks = new Link() ; outlinks. fromurl = fromlink; outlinks. tourl = new HashSe t<string>() ; outlinks. tourl. add(tolink ) ; boolean inserted = outlinkindex. putnooverwri te(outlinks) ; if (! i nserted) outlinks = outlinkindex.get(fromlink); outlinks.tourl.add(tolink) ; outlinklndex.put(outlinks); 1 ;J'fHilllM~~ url. ~:IH~:f"&rñ.J"e~AJJHi~~ public String ( ) inlinks ( String URL ) t hrows DatabaseException Entityi ndex<string,link> s ubi ndex = inlinkind ex. s ubindex(url); String[J linklist = new String[(int)subindex. count()) ; int i=o ; EntityCursor<Link> cursor= sublndex.entities () ; try for Li nk entity : cursor) linklist(i++] = ent ity. fromurl ; finally << '

297 8 ~fi Web 111 -~ _.!... _..!I!I ~ i. cursor. c l ose() ; return l inklis t ; 8.2 ~IJffl Web " 111 " ~ffitii1l ~_)(") ;? _t-1)~~t:tlo'fiij?ff~1l ll*~)~j* ê:* ~ Web 00. JJ~~.ff~r*~ Web OO~U*1ffiï1 M~~-~~w~. # ~ w~oo~~m *~ ~ ~~~~A&~ o~m~~,@. íi~~ )1Hrbt~7Hfi o 1t 1!11, m ithïhl ê<j :±1 JJt ~ A/Jt ê<j *13;X:ffi,@., ïjj t;j. X" Hit~11Hr fl~ ~o.. :tefo WH!~ ê<jf/11~, íij t;j.1e1z#t=l~~ A<Jtfi :!ifh'f :tjtit~tili ~JHH!.N.~.ffl f' o fiu~?f :fjt ~ ñ lt1=hih~.fit, tl:!g mi ñ' ~ tb :lihi: ~ ~ 1ilfl ~ ~ ~ 1J 12; :.!ik PageRank ji lt ~ HITS.lto :t:e TUfia<J~l'Jcp, :f!tifj~7t~tj1)-~b~vj3#ff.lto 8.3 Google ~"i'w--pagerank ;(fj::t. ;;JL::=r:"P, Google nl(;:tj~t!:!:~'ff.ffl~$~5!~5 1~. ~;JtftM!~5 1 $ *13bl:, W ~ tt ~~ am ttt;j.~~. -~ ~tta<j~ ~~ê<j«~~~ ~ ~~ B<J *~ =f PageRank ~~ffi~ ~t=ifff~jít~ftt~~~:ñ:à: ~ÀJIN"l PageRank JJ 5j :./A7H~ ~ ~.!fl M, ~ ~ ~ I $ m1:e 7f ~ ~ li'fj ~ t=l~ J:Y. ~ 9-Ut-J :iïlt o 1fl.!k!w-J:, ][ $tj <t 7è, m 1f 1! 51$x-t JijJ IJ'i 8<1 fif w: I ~ n: ~ m ~ ~ ê<j ib1 ~lufï i~he ]):t iii 'i" lf1.f.ll ~ ex #i I ;n:.m IJ'i oo * ll*u html ~~aq!t~tt:i:jl!7.f'~:it!:tr;& 1lH~l.T o 1-J T ~~~u!i!!:!thtj 1!. ~a W:, ::Jt;t;t; ~ f.il! 1!. ~ I $ ê ï;fj 1\\ ft~j J1~ JJ:~~.:f~ -~~~~:fffi ft JA TEi 0C~)~8ê<J?tfiW ~ ~~dê<jjijj~, ~-T--A~mê<JM* ~~ * -~~ IJ'i~#aqA~ ~* *~~#aq ~o~~. - aoo. ~*M;Jt~~fi~~-- ~~~aq ~~$~ w~~~~~~~m~. *ffl~~a~~aq~~~~~ t;j.~~~w~ ~M.l~~~l$1-t Ji&:ff1f:f5J?kllt'F~:X.ary~Jñ~i'J ~J~~~o?liffií, if :$ ~Mtfllll9Uv Tm ffjj~m1:e~f ~ lff t:f 001:1~~ I ~ M:1t~~fi~:X.I'fJ!Xjj ~ fw~:kital'lj!if;!~. Mrmf3l!~Ma<J tlu&a ~7l itli. ~t.!~ A '=t11i:ffi tl:~, PageRank n~jt~~fü).$ :ltmlhf6aïl~bt~~ -~-~~tht-fl:t.n:~. PageRank ~ *a<jjjfif11b~. - ~~Jffê<J 1lt!~~ll1«~ ~ BAfJÀ. :till~~~~ ê<j~14bt~ iu ~ ffi:, ]~ ~ ~ ~ PageRank é'>1 ~_m Jj!lj, ll:'g ~ ~ aq ~ ~ -tl?. ~ :lm~, te PageRank -~ c:f, )( flf IY-J ~ ~HB!::j B ~~ é'>1 )C f-4 é<j ~ ~).H)ç ~ ïfü /Yffii~ rt:j ~ Jl:l:;~ftf:a<:J~t& lll é3 ~~fl~tl)l:#a<:j~~-~. ~~~ - ~)l:ftf:ê<j PageRank ffi:jt~jtf'f(y.j , " " >>

298 ,~j3w3z..fi.j~~o.ta ~z.. PageRank ;Èo!if&~~ :ñi't[:j.jf Page ~ Brin Bf&íi~~:i!:ü--l'~i4ia<JXE.i't9!U~'ï?.:1-tlt~ lat o :l!hjj a<j PageRank f,ti't:lh PR(A) = (1-d) + d (PR(Tl)/C(Tl) PR(Tn)/C(Tn)) ~.:P :. PR(A) ~]it A a<j PageRank ffi: PR(Ti) ~~ 3 1j A a<j Ti ~Jia<J PageRank U1i: C(Ti) ~Ji Ti ú<jtl:lm@~~ iit : d-~iije~lt. O<d<l. Pf JÀ!., 1Bt: PageRank: #~ ~1vm-i'~Mt41:ti, ïtií~ ~!ti-1' 9fòñit~a<J o t:(?\, 9Hm A a<j PageRank 'iil~vè T- JJ~I.I:tt;!*JIJ A Iñ:ifña<J p;ij Jña<J PageRank a<j~!eiiïo PR(Ti)iill#~ ~:ltg~~ opj])i ï1fi PR(A)ê<J. :A: PageRank a<jltfi:0~jt!., T X'.J-=f A a<j~~ ibt T ll<j:±lmti!*~ C(T)ê<J~Ilf~. ~Wt~1>L T l'fj:±lmtit~~$. A~ T ~-i'ti!~tf..hl~ ~~ ~~~. PR(A)à:IJYflfBa<JA.tiitf(( J PR(Ti)!::i C(Ti)~J&;. Z.JYT~~~ P(T i)/c(ti) ít''.j%~. ix~ ;9 T f~ihif. i!~ a<j lfj9 9i ( tb Wt~ PageRank 11ï ~t~ ~, ê_~ ili M lihl ;f1! ~~ ~ a<j JijJ Jít)FJfm rñj a<j :Yriïü, ~t~fll:~ a<j jij]jjiflírí:ij fji,j:yfiii PageRank tt1ï~ t.fi.»t-t 9lïii A ïtií~, 4ij$m1Jo- -1' À fit :Jt~Hl~m1Jo PR(A)a<JJl. ~;9 43Jt1Jo-1' A.ilt~. PR(A)ê<Jm.sX;J:j:l~~~1Jil-J.Ji PR(Ti)/C(Ti). ffií f~a<j~-1' A.lit~a<J PangRank.iU2Jij\:, PR(A)!I:boll<Jtkt\;~$. te PR(A)a<J~~ cp, FJflf PR(Ti)/C(Ti)Z..l!J:i35~*~--l'~i!.JB~~ d. Ba<JfliE O ~!j 1 z.oo.mm~ (f]~m. PageRank a<.jw= tí~o Jí!flJ'. PJ~~~x ~JtOO~ n~m Aa<Jm~~ -~--i'~jij]i'$~ =-i'jjiooa. B.cm~. B.fi.JC,B~M~C. CM~~ A. Page ~ Brin ~~..t ~~Ii.IÈ~~ d 1St;!lg Tí'iH!Ht- ~~;K1i:R 0.5 o g~~i!.je~ ~ d ll<jlíh4fliiï7é~~~o tj ~lj PageRank fil, ilj ;li;b#~~u íij Pa~eRank itjf.a<jbi J!. ~.llt. :flt1fl~~!j ~ '"F ittí PageRank 1.ia<J/Jfjl: PR(A) = PR(C) PR(B) = (PR( A) / 2) PR(C) = (PR(A) / 2 + PR(B)) 11!8.1 PageRank é9~ti <<

299 Mst ~-Webll *M~ Jtt: 1f.fj, *'fú<fi-1' JñïiDa<J PageRank fi~: PR(A) = 14/13 = PR(B) = 10/ 13 = PR(C) = 15/13 = i!ïaj~,pfr:(f]jí ij( <J PageRank 1l.a<J~~ 3; ~-'f~ji( <J,ffi_~. ~fjj;j...tjilftfta<j, Jit~* MT-~1'~$é<na~J*i$í:J.t/FW=~. ~-T~-1-fHf -1'-:iñïiDa<J~iflmi1tl*vL iiu1j~mi~ï$~~~ PageRank li&~~~-j;j.~*a<j~a. ~/F~RM1fftma<J. ffi-t~ii-t>a<j1í&~ l~fií:f.:ilê:*, Ooogle tf~ 51$ 1~ffl T - 1'-jfifi;J.ff.J, ~1-t(I<Jit~Ji ~1-t PageRank 1l.. ~;,1!.7t;~4i1'~:iñ - 1'-f]]~H. ~,tg~jffl...tï:úi(!<j0~. ~lf~trfliw?x jg-~jijjftf!;.a<j PageRank 1l.. ~ifjflj:~'tffl " =»Hfií" (I<Jffi191J*i#.IY'I~~*fl:, ~ll ~ &1'-~ Ifií ( <J 1JJ ~tl1í ~ 1 ' ~1 ~ 8.1 fjt 7]'\ ~i~~ft PR(A) PR(B) PR(C) o I I I I I ~~f-1-t#.:p, ii-m- m~...t s~1-tn7.1- fiih~fij PageRank a<j-m:. ~!I!JU~tl:l7 12 ~~~ * f1=.1-j7fi91j. ~f PageRank (!<J~gQ.. IIJ~a<J~~g!(I<J PageRank 1ft~~~ Ooogle ~~ ~S~I*JI.J]ñ(I<J :fl~f'f. -MfJJ, Google W~ '5 1 $X>tr~9ra<J:flF~IÍI-1'-~~t/èí:E : ]ñïjü ( <J~J:Ett9~ Àt!ttàa<J~!1PH~ PageRank JffiDa<J~~~~ua :lhllir : I#.J]!I*J~, ~ ïi*j~~)cf~tfj URL. ~if S Page ~ Brin %3f. ~~l'f])(~, ti'ff1ijf ~~:(f~~ a<jtt5lfg~~ Google (J<Jf.II'~1J5t. 8 :l!~.!l~ f 'J/Fi'1itJ~.Il:~ ; ~. Ooogle m_ ~ Jfi:iiñ é<j~ )E!; ~~ À tium tfj )C ~ ~ ~*~ tl:l f.i;ij ) tfj IR 1«, T4fl ~ )C~ ~ 11~ >>

300 a<j *1t~tl. f& J5 Jlt 1R i!! -&l- PageRank i;& ;;r- jijj )ï ((.] :m: ~ ~J.t jj 7!! * IR i~ PageRank fil l! im i'11htll ffi ~o ~ f!jl H ;;f' ï'ij f!~:.llk ffi :bo a<j, :S!JW a<j i~i. ~o ~:jfiiihffl f.r---ni f,\) ( ] PageRank fi, llp i!~ 1! ~ ift tij 5'C 1, 1!?. ~ ]: 1! ~ ~i!hij! ep f.ll= E 1ltr im ~f~* ~~~~~~m~ ~~ *~. m m~~w 5~a<J~~~~ ;ffi&~. PageRank.:E ~~m-ffl -t-iiijf1= ;lg~ ~~it't rf.jvfi&i1t~~~a<j~~ o $~.A.~ii!tl Google I J'l.~ 7f.M!TM PageRank a<j. Google IA~~ Microsoft Internet Explorer ~ - 1'-i~J~~tffftj:, PJI;I.:tE Google ~M~~o ~ Google ~~IA~a<J PageRank úï Google IA~è(J PageRank I Google IA~ ~o- I o a<j~ijjt~ :;)' PageRank i. h\j.bj\~cp~f!!.tmjta<j t.e:llïij l;l.fí!ïl.t :±1 ~ iw ie te ijj lllj ((.] ]í.t im ((.] PageRank 11[ ~ll * ffl f' :f4j ~;l'ff- tij. T ~ 7J\ ~ J:, it ~ ~ 7J\ PageRank it. tt~: ~71' PageRank i[)! Google I)l~((.J-1'-itfi.JJ~ o j o* ~tbt:ijj#ft:~1t7f, Google 3~~i&~fflf' ~.ê-o ~:91-. Tit ~~HZ9J7HJL fflf';jc~;r,~,. m~. Google ï'ij~ijí lllj JfJ f'a<j~1l o ~ll*f.!g1fj:izht--nt~. PageRank Ji~J:-.:ml*.fi'ckfi(Nd+( I -d)), i3:!l!a<j N :AJE:~~ ~ ]fi.~.~. d.ii~ ~ii:kj 0.85, it~:±la<j PageRank R~JI,Il\.t~~/J\LI:IB<l~«<nlt- ~a<j bti91j. 'WiSiA~AfJ:.Ilk, ~1nzraHru~ti91J #=I~ f3gtta<j, rm~-~~*~ jzo*\2:~11/b~~ d :Jv o.s5, fflj PageRank (IJfla;ij;J 0.1 5, #.ft.x;ffi( J~~JJ 6, Jj!IJPJ!?J.~~Ijj o;& 8.2 ffr ~ a<j ~ti?ij~~ o * 8.2 ti~i.jt~m.;r-è(j PageRank ii -!; ~1!,-F PageRank t~t~ IA~ PageRank ~JÈ~ft' r.fè(j~siil ~1\j PageRank Ol io / / / << o o o o o o o o o o

301 8 it~web 111 IA~ PageRank l!llje~~'-' o.1s~t-taqn&il ~IJ,l; PageRank / / /10 I 51 I / x N + 0.\ PageRank lf)!èj Java ~Im ;$:~((.] PageRank.ft-tf.fflT-#;$3~ BigMatrix (t-)~1/.hi!i~*jtfff~~~t~o public class Google private double[] rank; Hashtabl e<string,integer> hashedpages; String ( J sortedrank; public Google() private void rankfilter(bigmatrix datamatrix) String[ ] temprank = new String (sortedrank.length]; Boolean isequal = true; I 1~-Rit ][3itl ~!rllilii&~~:tt!xll!:~ju 50?j;: for (int i = O; i < 50; i ++) rank = datamatrix. multiply( rank) ; I I ~Ji!~iltr99~iji'(giljli 1Jt~& for ( i nt j = O; j < sortedrank.length; j++l t emprank[j] = sortedrank(j ]; ff.~ Arrays.sort(sortedRank, new comparebyrank(); 11 ih~~~i&~ for (int j = O; j < sortedrank.length; j++ ) if (sortedrank( j ].compar eto(temprank ( jj)!= 0) isequal = false; break; if (isequal :::= = true) break; els e isequal : true; ' ' >>

302 class comparebyrank implementa Comparator<Str i n g> public i nt compare(string a, String b) ( int indexa ~ hashedpages.get(a); ~nt indexb = h a shedpages. get(b) ; if (rank[indexaj.=; rank[indexb)j return( O) ; else if (rank [indexaj > rank[indexbjj r eturo ( - 1); e l se return ( 1); public j ava. lang.string[) pagerank(java.lang.string(] s) 11 height of data int thesize = Math.max(4 * s. length13 + 1, 16); I I :t9.j flt1 i.t hashedpages = new Hashtable<String,Integer> (thesize J ; String[) pages ; new String[s. length) ; int(] nlinks ~ new int[s.length); rank = new double [ s.length] ; sortedrank = new String[s. length]; String(] dataentry = new String[ s. length) ; I I ~JUfi~ for (int i = O; i < s.length; i++) ( String[J temp = s[i].split( "); pages[i] = temp[o); nlinks[i) = temp.length.- 1; sortedrank[i) = temp[o] ; rank[i ) = l ; dataentry[i) = ; hashedpages. put(pages[i), i); int trow, tcol; I I ~J] ~ -f.t~ ll!'f for Cint i ; O; i < s.length ; i ++) ( String [] temp = s[i].spl it(" "); for (int j = 1 ; j < temp.length; j++l tcol = hashedpages.get (temp[o]) ; trow = hashedpages. get(temp[j)) ; dataentry[tr.c::m) += "" + tcol + "," + 1 I (double)nlink.s(i]) + " ; " ; <<

303 s ittiweb s ) ) BigMatrix datamatrix = new SigMatrix(dataEntry); I I f.if~ r ankfilter(datamatrix ) ; I l is.@lf.ll:~, (1<] URL :Vú* return(sortedrank); /I~~ class BigMatrix public int ncols, nrows; EntryList[] therows; I I #Jii!H~~*.If:i String a<jfdll ff,.j~à. '!fhlll C 1, 1) ; C 4, 3 l ; C s, 8 l, C 2, sl ; //(3,4), (3,8) ; (4,5)") 1 ;<tij-f-*~~'~'~ ~~Wtzt~1t-rr~~. 'lfljj([j. < 2. s, *7l'tl:~= rra<js:n=:y~jffi~ s public BigMatrix( java. lang. String[] x) nrows = x. length; ncols = O; therows = new EntryList[nRows] ; for (int i = O; i < nrows ; i ++ ) therows[i = new EntryList() ; if (x [i]!= null) String[) temparr = x [i]. split(";"); if (temparr[o]!= nu11) ) ) for (int j = O; j < temparr. length; j++) Entry instance = new Entry(tempArr[ j] ) ; therows[i).add(instance); if (ncols <= instance.col l ncols = instance.col + l ; I I ~ 1;1. 1 ~l flij fi: public double [ ] multiply(double[j x ) double [] result = new double [nrows); int count; for Cint i = O; i < nrows; i++) EntryList temp = therows (il ; count = O; while ((temp!= null) && (temp. data! = nullll result(i]+= (temp.data.value * x [temp.data.col]) ; temp = temp. next; count++; >>

304 return(resul t ) ; I l~fl!fê<j.ftit class Ent ry int col; I I JC~nftE!d doubl e value; I 1/C~m public Entry(java. lang.string x l String(J temp = x.split( "," ) ; if ( t emp [O J compareto ( " " )! = O) col= Integer.parseint(temp[O]. trim().substr ing(1)) ; val ue = Double.parseDouble(ternp[l). trim(. substr ing(o, ternp 1]. trim (). l e ngth () - 1) l ; J I I ; it ~tl *, X<Hf :itl:.ff ijh'~ class EntryList Entry da ta; Ent ryli st next, tail; public EntryList(l! I l ~:lmlfl~ nex t = null; tail = null ; data = null; void add(entry x) if ltail == null) data = x ; tail = this ; els e tail. next = new EntryList( l; tail. nex t.data = x; tail = tail.next; lffl PageRank i1hï-titl~#t teidj:.ícc:p!!trji,fj:felfjtl!~7hjt(flm~. ~~7HJTJA;.l>:mt...t.Vt, ~~te Web IJl'fl, it1f: ~).tfr-j:!k~tt. ~llt. fl~~~~1t~lli ~ ~lí~tt fr-jf(#, :a!ift~vj$~fflrt!tt;ttjt. PageRank t!?)f~j ~~. :fe~q- PageRank ;W~B"JB't~. ~~J=fUU~JHf.Jilill~Af.t~&IXX.I]'tZ..fa]fr-J :~a~. ~ ~W~~~-~B"J~?A~R~--~ MB1, re~~~~ili~~-~* #A~EE~~ ~ m~~~a~. ~llt. ~~~Y-J **rta~oo s.3~~. <<

305 .8. 分析 Web 圃 获得阿页 记录网页的出度, 以及出链接 更新网页出链接所对应的页丽的入 JJr. 使用 PageRank 或者其他锥按分析算法进行分析 图 8.3 链接分析过程 前面讲的 PageRank 算法, 是理论上的算法, 也是比较基础的算法 但是, 由于互联网 的复杂性, 往往要对 pagerank 做一定的修改才能用于链接分析 8.4 PageRank 的兄弟 HITS HITS 算法是由康奈尔大学 (Comell University) 的 Jon Kleinberg 博士于 1998 年首先提出 的, HITS 的英文全称为 Hypertext-Induced Topic Search. 目前, 它是 IBM 公司阿尔马登研 究中心 (IBM Almaden Research Center) 的名为 CLEVER 的研究项目中的一部分 深入理解 HITS 算法 向量 Kleinberg 将每个网页赋两个值, 即 hub 和 au 出 ority 网页的集合组成 hubs 和 authorities HπS 算法的目标就是通过一定的计算方法 ( 迭代计算 ) 以得到针对某个检索提问的最 有价值的网页, 即排名最高的倒也 orities 为便于理解, Kleinberg 用图来表示链接关系, 他认为超链页面的集合 V 为一个有向图 GE(V, 酌, 图中的每个节点对应一个网页, 有向边 (p, q) EE 表示网页 p 链接指向网页 q, 节 点 p 的出度 (out-degree) 指节点 p 链出的网页数量, 而节点 p 的入度 (in-degree) 则指的是指向 节点 p 的链接数量 如果集合 W 是 V 的一个子集 ( 即 W 包含 V 中的一部分节点 ), 则用 G[W] 来表示自 W 中的节点组成的有向圈, 而有向图边对应于 W 中的所有链接 现在假设 给定一个泛指主题检索提问 σ, 需要通过链接分析确定该提间的权威页 最先是确定 HITS 算法作用的 www 子集 理想地, Kleinberg 希望得到的集合 So 具有以下特点 : sσ 相对较小 ; sσ 中相关网页丰富 : sσ 包含多数最有价值的 authorities 页面 针对具体的检索提问, 构建关于该提问的 www 聚集子图的具体做法如下 : 6

306 J (1) 用基于文本的搜索引擎如 AltaVista 或 Hotbot 来得到 σ 的查询结果集, 取排名最高 的前 t(t 值通常设为 200) 个结果的结果集 ~ 称为 RootSet), Kleinberg 认为凡满足特点 a 和 b, 但远不能满足 因此需要扩充儿 (2) 扩充 R., 分为两个方面, 一是将所有 Ro 中页面所指向的页面扩克进去, 该扩充在数 量上没有限制 : 二是将指向 R 中的每一页面的链接页面取其中任意 d(d 值通常设定为 50, 如果 d 不大于 50, 则取其所有页面 ) 个页面扩充到原来的 R 中形成 So ( 称为 BaseS et) 通过 实验表明, 这样的集合 Sa 能够较好地满足上述三个特点, Sa 的数量范围一般为 1000~5000 (3) 为了排除干扰, 提高计算效果, Kleinberg 还将 Sa 作了进一步的处理, 他将链接分 为两种情况 : 一是指有链接关系的两个页面处在不同域名之间, 这样的链接称为横向链接 : 还有一种情况是指有链接关系的两个页面处于同一域名之下, 这样的链接称为内在链接 Kleinberg 认为内在链接只具有网站内部的导航功能, 它几乎不能传递网页间的 authority, 因此需要将这种内在链接从 Sa 中删去, 形成仇 Kleìnberg 认为 bubs 和 authority 是相互增强的关系 一个具有较高 hub 值的页指向许多 具有较高 au 由 ority 值的页, 在许多实例中, 它们之间是一种环状的关系, 因此需要一定的 计算方法来打破这种环状结构 对于每一个页面 p, 用 x<p> 表示页面 p 的权威权重 (authority weight), 用 y<p> 表示页面 p 的中心权重 (hub weigl 吟满足规范化条件 : ~> Esa(x<P>)2EI 且立 pεsσ(y<p>)2el. Kleinberg 将网页权重的传递分为两种方式, &p 1 操作和 0 操作 I 操 作为 hub 到 autho rity 的传递, 表示为 x<p> L q:(q,p)εey<q>; 0 操作为 authority 到 hub 的传递, 表示为 y<p> L q:(q,p) E Ex<q> 预先设定法代次数 k, 算法表示如下 : Iterate (G. k) G: a collection of n linked- pages k: a natural number Let z denot:e the vector (1.1, 1...., 1) Rn. Set xo, Ez. Set yo: Ez. For 1 El. 2,.. k App ly 巳 he 工 pera t: ion to(xi. l.yi. l). obtaining new x-weights x-i. Apply the 0 operation to(x-i,yi.l).obtaining new y -weights y. Normalize x-i, obtaining xi. Normalize y-i.obtaining yi. End Return(xk.yk). 根据矩阵计算知识容易证明, 对于给定一个初始向量 xo 和 yo, 法代过程收敛, 结果川为 ATA 的主特征向量, y. 为 AAT 的主特征向慧 其最终 HITS 算法的 J ava 实现 根据以上的分析, 本小节我们给出 HITS 的算法代码 public class HITS 1 ** 存储 Web 图的数据结构 * / 5 氧

307 篇 8 章份析 Web 圄 private WebMemGraph graph: 1** 包含每个网页的评分 *1 private Map< 工 nteger, Double> hubscores: II<id, value> 1** 包含每个网页的 Authori 叮叮 private Map<Integer, Double> authorityscores ; ll<id, value> 1** * 构造函数 * 1 publ ic HITS ( WebGraph graph ) ( this. graph = graph: this.hubscores = new HashMap< 工 nteger, Double>(): this.authorityscores = new HashMap<Integer, Double>(); int numlinks = graph. numnodes() : for(int i~l: i<=numlinks: i++) ( hubscores.put(new Integer(i), new Doublel)): authorityscores.put(new I n teger(i), new Double(l)); computehits( : 1** * 计算网页的 Hub 和 Authority 的分值 *1 publi c void computeh 工 TS () ( computehits(2s); / ** 叶 t 算网页 ' 的 Hub 和 Authority scores * */ publ ic voi d computehi TS (int numlt erations) while(numlterations-- >O ) for (int i = 1; 土 < = graph. numnodes() ; i++) ( Map< I nteger, Double> i n l i nks = graph.inlinks(new Integer(i)); Map<Integer, Doubl 台 > outlinks = graph.outlinks(new 工 nteger(i)) : double authorityscor e = 0; double hubscore = 0: for ( 工 nteger id:inlinks.keyset()) authorityscore += (hubscores.get(id)).doublevalue(; >>e

308 j for (Integer id:outlinks.keyset()) ( hubscore += (authorityscores.get(id)).doublevalue() ; authorityscores.pu t (new 工 nteger (i), new Double (authorityscore)) ; hubscores. put(new Integer(i ), new Double(hubScor e)); normalize(authorityscores); normalize(hubscores); public void computeweight e dhi TS(int numlterations) ( while(numlterations-->o ) for ( i nt i = 1 ; i <= graph.numnodes (); 主 ++ ) ( Map<Integer, Double > inlinks = graph. inlinks(new Integer(i)) ; Map<Integer, Double> outlinks = graph. outlinks(new Integer(i)); double authorityscor e = 0; double hubscore = 0 ; for (Entry< 工 nteger, Double> in:inlinks,entryset()) authorityscore += (hubscores.get (in. getkey()) ). doublevalue() << in.getvalue(); for (Entry<Integer, Double> out :outli nks.entryset()) hubscore += (authorityscores.get (out.getkey())). doublevalue() * out.getvalue(); authorityscores.put (new Integer(i), new Double(authorityScore)); hubscores. put(new Integer(i), new Double(hubScore)); normalize(authorityscores) ; normalize (hubscores); / ** * 归一化数据集 * / private void normalize(map<integer,double> scoreset 工 terator<integer> iter = scoreset. keyset(. i terator(; 10 氧

309 篇 8. 分析 Web 圄 double sumrnati on = 0.0 ; while (iter.hasnext()) S 田 mation +~ ((scoreset.get((int eger) (iter.next())))).doublevalue(); iter ~ scoreset.keyset().iterator(); while (iter. hasnext() I nteger id ~ iter.next() ; scoreset.put(id, (scoreset.get(id).doublevalue()/sumrnation); / * 由返回与给定链接关联的 Hub 评分 食 / public Double hubscore(string linkl return hubscore(graph. U 缸,Toldent ifyer (linkl ) ; i * 返回与给定链接关联的 Hub 评分 l private Double hubscore(integer id) return (Double) (hubscores.get(id)) ; / * 禽 ' 初始化与给定链接关联的 Hub 评分 合 / public void initializehubscor e(string link, double val ue (!nteger id ~ graph.urltoldentifyer(link) ; if( idl ~nulll hubscores.put(id, new Double(value)) ; / ** * 初始化与给定链接关联的 Hub 评分 */ public void initializehubscore(integer id, double val ue) if (idl =null hubscores.put(id, new Doubl e(value) ; / ** * 运回与给定链接关联的 Authority 评分 食 / public Double authorityscore(string link return authorityscore(graph.urltoldentifyer(link);...>>5

310 J / 食 * * 返回与给定链接关联的 Au thority 评分 * / private Double authorityscore( I nteger id) ( r eturn (Double) (authoritysco r es. get( i d)); / ** * 初始化与给定链接关联的 Authority 评分 食 / public void initializeauthorityscore(string l ink, double value) ( 1nteger id = graph.urlto1dentifyer(link); i f (idl =null) authorityscores.put(id, new Double(value)) ; / ** * 初始化与给定链接关联的 Authority 评分 食 / publi c void initializeauthor i tyscore (1nteger id, double value) ( if(idl=null) authorityscores.put(id, new Double(value)); 以下是存储 Web 图的数据结构 public class WebGraphMernory // 把每个 URL 映射为一个整敛, 存储在 Web 阁中 private Map<1nteger, String> IdentifyerToURL ; / ** 存储 Web 回中 URL 之间关系的 MAP * / private Map<String, Map<String, 1nteger>> URLTOldentifyer; / ** * 存储入度, 其中第一个参数是 URL 的 1D. 第二个参数存放指向这个 URL 链接的 Map. * Doubl e 表示权篮 * / private Map<Integer, Map<1nteger, Double>> 1nLinks; 命 存储出度, 其中第一个参数是 URL 的凹, 第二个参数存放网页巾的超链续. Double 表示 * 权重 * 1 pri vate M.ap<Integer, Map< 工 nteger, Doub l e>> OutLinks; / ** 图中节点的数目忖 private int nodecount;.< 泣..

311 .8. 分析 Web 圄 / ** * 构造函数, 0 个节点的构造函数 */ public WebGraphMernory () ( IdentifyerToURL = new HashMap< 工 nteger, String>( ) ; URLToldentifyer = new HashMap<Stri ng, Map<Stri ng, Integer>>(); InLinks = new HashMap<Integer, Map< 工 nteger, Double>>() ; OutLinks = new HashMap< 工 nteger, Map<Integer, Double>>(); nodecount = 0; / ** * 从一个文本文件中取得节点的构造函数 每行包含一个指向关系 例如 2 * http : //url1.com -> http : //ur12.com 1.0 * 表示 '' l l.com'' 包含一个超链接 并且这个超链接 * 的权重是 1. 0 */ public WebGraphMemory (Fi le f ile) throws IOException, FileNotFoundException this () ; BufferedReader reader = new BufferedReader(new FileReader(fil e)); String l ine; while((line=reader.readline())!=nul l) ( int indexl = line.indexof(" - >"); if(indexl==- l) addlink(line.trim()); else ( String urll = line.substring(o, indexl ). trim(); String ur12 = l ine. substring (index l +2).trim() ; Doubl e strength = new Double(1.0); indexl = ur12. indexof(" '); if(indexl!=-l) try strength = new Double (ur12. substring (indexl+l). trim () ) ; ur12 = ur12.substring(o, indexl).trim(); catch ( Exception e ) addlink (urll, ur12, strength); / * * * 根据 URL 制订它的 1D * / public Integer URLTo 工 dent i fyer ( String URL ) ( String host ; String name ; int index = 0, index2 = 0; if(url. startswith( ''http : // ")) index = 7 ; else i f(url. startswith( " ftp://")) index = 6; 5

312 j index2 = URL.substring(index). indexof("i"); i f(index2!=-1 ) ( name = URL.substring(index+index2+1); host = URL.substring(0, index+index2); else ( host = URL ; name = "'"; Map<String, Integer> map = (URLToldentifyer. get(host)); if(map==null) return null; return (map. get(n 缸 ne) ) ; / " " 禽 * / 根据工 D 获得 URL public String IdentifyerToURL ( 工 nteger id ) return (Id entifyertourl.get(id)); / "* * 在阁中娴加一个节点 * / public Integer addlink (String link) Integer id = URLTo 工 dentifyer(link) ; if (id==null) ( id = new Integer (++nodecount ) ; s 巳 ri ng host; String name; int index = 0, index 2 = 0 ; if(link. startswith(" index = 7; else if(link.startswith( "ftp : // " )) index = 6 ; index2 = link. substring(index). i nde xof("i"); if(index2!=-1) ( name = link.substring(index+index2+1) ; host = link. substring(o, index+index2 ) ; else ( host = l ink; name = ; ) /ISys tem. out.prin tln( " HOST :" +host 令 " name: "+name); Map<String, 工 nteger> map = (URLTo 工 dentifyer.get(host)) ; if (map==null).< 民..

313 篝 8 章分析 Web 圃 map = new HashMap<String, Integer>() ; URLToIdentifyer.put(host, map); map. put(name, id); IdentifyerToURL.put( i d, l ink) ; I nlinks.put(id, n ew HashMap<lnteger, Double>()); OutLinks.put(id, new HashMap<lnteger, Double>()); return id; 1** * 在两个节点中增加一个对应关系. 如果节点不存在, 就新建节点 * / public Double addlink (String fromlink, String tolink, Double weightl ( Integer idl = addlink( fromlink); Integer i d2 = addlink(tolink ) ; return addlink(idl, id2, weight ); 1** * 在两个节点中增加一个对应关系. 如果节点不存在, 就新建节点 * 1 private Double addlink ( Integer frα 咀,ink, 1nteger tolink, Double weight ) Double aux; Map<Integer, Double> mapl = ( 工 nlinks.get(tolink )) ; Map<Integer, Double> map2 = (OutLinks. get(fromlink)); aux = (Double) (mapl.get(froml.ink)); if(aux==null) mapl.put(fromlink,weight); else if(aux.doublevalue()<weight.doublevalue() ) mapl.put (fromlink, weight); else weight = new Double(aux.doubleValue()) ; aux = (map2.get(tolink)); if(aux ==null) map2.put(tolink, weight) ; else if(aux.doublevalue()<weight.doublevalue()) map2. put (tolink, weight) ; else ( weight = new Double(aux.doubleValue()); mapl.put(fromlink, weight); InLinks.putftoLink, mapl); OutLinks.put (fromlink, map2) ; return weight ; >>.

314 J * 针对指定的 URL 返回包含宙的入度的链接的 Map */ public Map inlinks ( String URL ) ( Integer id = URLToldentifye r(url); return inlinks(id); /.... 吨 + 对指定的 URL 返回包含它的入度的链接的 Map i public Map<Integer, Double> i nlinks ( Integer link ) ( i f(link==nul1) return nul1; Map<Integer, Double> aux = ( 工 nlinks. ret urn aux: get(link)): / ** * 针对指定的 URL 返回包含它的出度的链接的 Map * / public Map< 工 nteger, Double> outlinks ( String URL ) ( 工 nteger id = URLToldentifyer (URL): re 巳 urn outlinks(id) ; j *" * 针对指定的 URL 返回包含宫的出度的链接的 Map.. / public Map< 工 nteger, Double>' outlinks ( Integer link ) if(link==null) return null; Map<Integer, Double> aux = OutLinks.get(link ); return a ux; j "* * 返回两个节点之间的权茧, 如果节点没有链接, 就返回 o * / public Double i n Link ( String fromlink, String tolink ) ( Integer idl = URLToldentifyer (fromlink) ; Integer id2 = URLTo 工 dentifyer(tolink ); return inlink(idl, id2); / ** * 返回两个哨点之间的权篮, 如果节点没有链接, 就返回 o * / public Double outlink ( Strlng fromlink, Strin g tolink ).< 在..

315 .8 章分析 Web 圃 Integer idl = URLToldentifyer(fromLink); Integer id2 = URLToldentifyer(toLink): return outlink(idl, id2); / ** * 返回两个节点之间的权童, 如果节点没有链攘, 就返回 食 / public Doub le inlink ( Integer fromlink, 工 nteger t OLin k ) Map<Integer, Double> aux = ínlínks(tolink) ; if(aux==null) return new Double(O); Double weight = (aux.get(fromlínjc)); return (weight == null)? new Double (O) weight: /..* * 返回两个节点之间的权重, 如果节点没有链接, 就返回 * / public Double outlínk ( Integer fromlink, Integer tolink ) Map<Integer, Double> aux = outlinks(fromlink); if(aux==null) return new Double(O); Double weight = (aux.get(tolínjc)); return (weígh t == null)? new Double(O) weight ;.. t 巴有向阁变为无向民 * / publíc void transformunidirectional () 工 terator it = OutLínks.keySet().iterator() ; while (it. hasnext()) Integer linkl ~ (Integer) (ìt.next()); Map auxmap = (Map) (OutLinks.get(linkl)) : Iterator it2 = a uxmap.keyset(). iterator(); while (it2.hasnext ()) Integer l ink2 = (Integer) ( 主 t. next ()) ; Oouble weight = (Double) (auxmap.get ( l ink2)); a d dlink( l ink2, linkl, weight) ; / ** * 删除内部链接, 内部链接就越指在向一主机上的链援 " / public void removelnternallinks () ( ínt ìndexl; >>.

316 / 工 terator it ~ OutLinks.keySet ().iterator() ; while (it.hasnext()) Integer l inkl = ( 工 nteger) (it. next () ) ; Map<Integer. Doubl e> auxmap ~ (OutLi nks.get(linkl)); Itera tor it2 ~ auxmap.keyset().iterator() ; if (it 2. hasnext ()) ( string URLl = (String) (Ident ifyertourl.get(l inkl)); i ndexl ~ URL1. indexof(" : I/"); if (index l l =-l) URL1: URL1.substring(indexl+3) ; indexl ~ URL1. indexof("/") ; if(index l l ~-l) URL1=URLl.substring(0.indexl); while (it 2.hasNext()) I nteger link2 = ( I nteger ) (it. next ()) ; String URL2 = (Strin g) ( 工 dentifyertourl.get(l ink2)); indexl = URL2. indexof( 刨 :/1"); if(indexll : - l ) URL2~URL1.subs t ring(indexl + 3); indexl ~ URL2.indexOf("I"); if(indexll =-l) URL2~URL 1.substr ing(0.indexl) ; if(urll.equal s(url2)) auxmap.reroove( l i nk2); OutLinks.put(linkl.auxMap) ; auxmap = (InLinks.get(link2)); a uxmap.reroove( l inkl ); InLinks. put (link2, auxmap); * 删除内部导航链接 *1 pub l ic void reroovenepotistic() ( reroovel nternallinks(); / * * *.ljij 除 stop URL * / public void reroovestoplinks(string stopurls[ ] ) HashMap aux ~ new HashMap() ; for (int i =O; i <stopurls. length; i++ ) aux.put(stopurls[i), null ; reroovestoplinks(aux); /**.< 在..

317 第 8 章分析 Web 固 * 删除 stop URL * / public void removestoplinks(map s topurls) ( i nt index l ; 工 terator it = OutLinks.keySet). iterator(); _ while (it. hasnext ()) 工 nteger l inkl = (Integer) it.nextll: String URLl = (String) ( I dentifyertourl.get(linkl)); indexl. = URLl. i ndexof ( : / / " ) i if (indexll=-l) URLl=URLl.substring (indexl+3); indexl = URLl.indexOf("/"); ifindexll=-l) URLl=URLl.substring(O, inde xl); if(stopurls. containskey(urll)) ( OutLinks.put( l inkl, new HashMap()); InLinks.put(linkl, new HashMap()) ; public int n umnodes() return nodecount: 应用 HITS 进行链接分析 与 PageRank 一样, Hηs 也可用于链接分析 但是 HηS 用于链接分析, 不能够结合该 链接所代表的网页的文本内容 因此, 针对口 ns 有一些变种的算法, 很值得我们学习 1.ARC 算法 (Automatic Resource Compilation) IBMAlmad en 研究中心的 Clever 工程组提出了 ARC 算沽, 对原始的 H ITS 做了改进, 在赋予网页集对应的链接矩阵初值时, 结合了链接的锚 (anchor) 文本, 适应不同的链接具有 不同的权值的情况. ARC 算法与 HITS 的不同之处主要有以下 3 点 : (1) 由根集 S 扩展为 T 时, HITS 只扩展与根集中网页链接路径长度为 l 的网页, 也就 是只扩展与 S 相邻的网页, 而 ARC 中把扩展的链接氏度增加到 2, 扩展后的网页集称为增 集 (Augment Set) ο) 在 HITS 算法中, 将每个链接对应的矩阵值设为 1, 但实际上每个链接的重要性是 不同的, ARC 算法可以根据链接周围的文本来确定链接的重要性 在 ARC 算法中考虑链 接 p q, p 中有若干链接标记, P 的格式是 " 文本 1 <a href="q"> 锚文本 <Ia> 文本 2 ", 设查 询项 t 在 " 文本 1 " 处, " 锚文本 " 处," 文本 2 " 处, 出现的次数为 n(t), 则 w (p, ω = 1In(t) 文本 l 和文本 2 的氏度经过试验设为 50 字节 构造矩阵 W, 如果有网页 i j, WiJ=w(i, j). 否则 W ij =O, H 值设为 1, Z 为 W 的转置矩阵, 送代执行下面 3 个操作 : 1'A = WH : 2H=ZA ; 3 规范化 A. 6

318 J 以上三个步骤都是针对矩阵的操作 ARC 算法的目标是找到前 15 个最重要的网页, 只 需要 AIH 的前 15 个值相对大小保持稳定即可, 不需要 AIH 整个收敛, 这样 2 中选代次数 很小就能满足, 实验表明远代 5 次就可以, 所以 ARC 算法有很高的计算效率, 开销主要是 在扩展根集上 2. H ub 平均算法 (Hub Averaging Kleinberg) 科学家 Allan Borodin 等指出了一种现象, 设有 M+ l 个 Hub 网页. M+ l 个权威网页, 前 M 个 Hub 指向第一个权威网页, 第 M+l 个 Hub 网页指向所有 M+1 个权威网页 显然根据 HITS 算法, 第一个权威网页最重要, 有最高的 Authority 值, 这是我们希望的 但是, 根据 HITS 算法, 第 M+l 个 Hub 网页有最高的 Hub 值, 事实上, 第 M+ l 个 Hub 网页既指向了权威值很高的第一个权威网页, 同时也指向了其他权威值不高的网页, 它的 Hub 值不 应该比前 M 个网页的 Hub 值高 因此. AUan Borodin 修改了回 TS 的算法, 使得仅指向权 威值高的网页的 Hub 值比既指向极戚值高又指向权威值低的网页的 Hub 值高, 此算法称为 Hub 平均算法 3. 阔值算法 (Threshhold Kleinbe 咱 ) AUan Borod in 等同时提出了 3 种阑值控制的算法, 分别是 Hub 阑值算法, Autbority 闽值算法, 以及二者结合的全阑值算法 计算网页 ω) 的 Authority 时, 不考虑指向它的所有网页的 Hub 值对它的贡献, 只考虑 Hub 值超过平均值的网页的贡献, 这就是 Hub 阐值方法 Au 伽 ority 阀值算法和 Hub 阀值算法类似, 不考虑所有 p 指向的网页的 Authority 对 p 的 Hub 值贡献, 只计算前 K 个权威网页对 Hub 值的贡献. 在做链接分析的时候, 使用 HITS 的改进算法, 往往能取得更好的效果. 8.5 PageRank 与 HITS 的比较 PageBank 和田 TS 均是基于链接分析的搜索引擎排序算法, 并且在算法中二者均利用 特征向量作为理论基础和收敛性依据. 但这两种算法的不同点也非常明显, 下面就主要谈 谈其不同点 : 从算法思想上看, 虽然间为链接分析算法, 但二者之间还是有一定区别的 HlTS 的原理如前所述, 其 authority 值只是相对于某个检索主题的极重, 因此 HITS 算法也常被称为 query dependent 算法 而 PageR 缸 lk 算法独立子检索主题, 因此也常被称为 query independent 算法 PageRank 的发明者 (page 和 Brin) 和网络文档重要性的计算中借鉴了引文分析思想, 利用网络向身的超链接结构给所有的网页确定一个重要性的等级数. 当然 PageRank 并不是引文分析的完全翻版, 根据因特网自身的性质等, 它不仅考虑了网页引用的数量, 还特别考虑了网页本身的重要性 从权量的传播模型上来看. HITS 首先通过基于文本的搜索引擎米获得最初的处理数据, 网页重要性的传播是通过 hub 页向仙也 ority 页传递 而且 Kleinberg 认为, 8< 氧

319 篝 8 章分析 Web 圄一端hub 与 authority 之间是相互增强的关系 ; 而 PageRank 基于随机冲浪 (random s 旧 fer) 模型, 可以认为它将网页的重要性从一个 authority 页传递给另一个 authority 页 从处理的数据量及用户端等待时间来分析 表面上看, 当需排序的网页数量较小时, 采用 HITS 算法, 所计算的网页数量一般为 1000 至 5000 个, 因为四 TS 算法需要从基于内容分析的搜索引擎中提取根集并扩充基本集, 这个过程需要耗费相当长的时间 而 PageRank 算法从表面上看, 处理的数据量远远超过了 HITS 算法 据 Google 介绍, 目前收录的中文网页已达 33 亿以上, 但由于其计算量在用户查询时己由服务器端独立完成, 不需要用户端等待, 基于该原因, 从用户端等待时间来看, PageRank 算法应该比 HITS 要短 8.6 本章小结 本章详细介绍了 Web 图的存储, 包括内存存储和内存数据库存储两种情况 之后, 还阐述了如何利用 PageRank 算法和 HITS 算法来分析 Web 图, 并对两种算法进行了简单的比较 Web 图在网络爬虫中是非常重要的数据结构, 它直接决定了搜索引擎中检索的质量, 比如检索结果的排序等 有关 PageRank 和 HITS 的相关算法, 如果读者还有不明白的, 可以参考相关资料..>>8

320

321 去? 尊重复的! 文挡 " 互联网存在大量的重复内容, 有研究显示, 其中有 30% 的网页内容重复. 重复的网页 降低了爬虫抓取有效信息的速度. 与文本去重类似的技术还可以用在抄最栓量 "J 主.

322 一, 9.1 何为 " 重复 " 的文档 有些网页是完全一样的, 例如镜像网站和原网站中的内容 还有些文本大部分内容是 一样的, 而只有少许差别, 例如 : 广告 计数器 时间戳等 9.2 去除 " 重复 " 文档一一排重 时间. 为什么需要去除重复文挡? 因为这样可以节省空间, 提高搜索质量, 从而节省用户的 可以用比较 checkswn 值的方法来判断完全相同的文档 checksum 是一个代表文档内容 的值 如果两个文档的 checksum 值不匹配, 则认为这两个文挡不相同 当然, 事实并不一 定完全如此, 但如果选择合适的 checksum 计算过程, 则不同的文档产生相同的 checksum 值的概率很小 checksum 计算过程叫做 checkswn 算法 最简单的算法是把文本中的每个字符按编码求 和. MD5 算法是一种流行的 checksum 算法, 文本的 MD5 值 返回 128 位的字节数组 下面的代码返回给定 public static byte[] getmds(string text) MessageDigest md = null; byte[] encryptmsg = nul1; try ( md = MessageDigest.getlnstance("MDS"); 11 encryptmsg = md.digest (text.getbytes ()); 11 catch (NoSuchAlgorithmException el ( System.out.println("No Such Algorithm Exception!"); return encryptmsg; 取得 MDS-Instance 求 MDS-Hash 9.3 利用 " 语义指纹 " 排重 检测近似重复的文档很困难, 因为很难对近似文档给出一个确切的定义 检测转载文章也与此类似 可以用衡量两个网页的相似度的阙值来定义近似文档 例如向量余弦夹角大于 0.9 的两篇文档算作相似文档. 有两种常见的检测重复文档的方式 : 在一个给定的文档集合内部检测, 叫做自查重 : 将某一给定文档和一个文档集合比较, 叫做单条查重. 自查重最容易想到的方法是集合内部的文档两两计算相似度, 但采用这种方法计算的时间复杂度是 O( n 1 ). 太高 为了提高比较效率, 不直接比较原文, 而比较文挡的缩略表示. 文档的语义缩略表示叫做语义指纹 σingerp 由 1t)..< 氧

323 .9 章去掉重量的 " 文挡 " 也可以把文档提取成结构化的形式, 然后再生成语义指纹 为了提高准确性, 需要考虑 到同义词, 例如 It 北京华联 " 和 " 华联商厦 " 可以看成相同意义的词 最简单的, 可以 做同义词替换, 即把 " 开业之初, 比这还要多的质疑的声音环绕在北京华联决策者的周围 " 替换为 " 开业之初, 比 i 这茬还要多的质疑的声音环绕在华联商厦决策者的周围 设计同义词词典的格式是 : 每行一个义项, 前面是基本词, 后面是 ~ 个或多个被替换 的同义词 例如 : 华联商厦北京华联华联超市 对指定文本, 要从前往后查找同义词词库中每个要替换的词, 然后实施替换 同义词 替换的实现代码分为两步 首先是查找 Trie 树结构的词典过程 : public void checkprefix(string sentence, int offset, Pr e f ixret ret) if (sentence == null 11 root == nul l 11.".equals(sentence)) ret. value = Prefix.MisMatch; ret.data " null; ret. next = off set; return; ret. value =: Prefix.MisMatch;// 初始返回值设为没匹配上任何要替换的词 TSTNode currentnode " root; i nt charlndex " offset; while (true) ( if ' (currentnode,, = null) return; int charcornp = sentence.charat(charindex) - currentnode.spli t char; if (charcomp == O) charlndex++; if (currentnode.data!= null) ret.data = currentnode.data;// 候选最长匹配词 ret. value = preflx.match; ret.next = charindex; if (char 工 ndex == sentence.length() return; // 已经匹配完 currentnode = currentnode. eqkid; else if (charcomp < 0) currentnode = currentnode. lokid; else ( currentnode = cur rentnode.hiki D; >>G

324 / 然后是同义词替换过程 : public s t atic String replace(stri ng con t ent) throws Ex ception int len = con t e nt. l ength(); StringBuil der ret = new StringBuilder(len); Synony mdic. PrefixRet matchret = new Sy nonymdic. PrefixRet(nuIl, null) ; for(in t i=o ; i<len;) 1/ 检查结否存在从当前位置开始的同义词 synonymdic. checkprefi x(content, i, matchret); if(matchret. value == Synony mdic. Prefix.Match) ret. append(matchret.data); i=matc hret.next ; l/ 下一个匹配位置 else 1/ 从下一个字符开始匹配 ret.append(content.charat(i)) ; ++1 ; return ret. t ost r 主 ng () ; 理解 " 语义指纹 " 网络一度出现过很多篇关于 u 罗玉凤征婚 " 的新闻报道. 其中的两篇新闻对比如下 : 文铛 10 文档 1 文档 2 标题 北大消华硕士不嫁的 " 是牛征婚女 " 1 米 4 专科女征婚求 1 米 8 硕士男应征者如云 内容...24 岁的罗玉凤, 在上海街头发放了 岁的罗玉凤, 在上海街头发放了 1300 份征婚传单. 传单上写了近乎苟刻的条件, 份征始传单. 传单上写了近亭苛刻的条 ~ 求男方北大或消华硕士, 身高 1 米 76 至 1 件, 要求男方北大或清华硕士, 身高 1 米 米 83 之间, 东部沿海户籍. 而罗玉凤本人, 乃至 1 米 83 之闭, 东部沿海户籍. 而罗 只有 l 米 46. 中文大专学历. 1It 庆幸 E 江人.. 玉凤本人, 只有 l 米 46. 中文大专学历, 此事经网络曝 - 光后, 引起了很多人的兴趣. 重庆蒸江人 o... 此事经网络曦光后, 引起 " 每天都有打电话 发短信求证, 或者是应 了很多人的兴趣. " 每天都有打电话 发 征." 罗玉凤说, 她觉得满意的却寥寥无几, 短信求证, 或者是应征 " 罗玉凤说, 她 " 至 IJ 目前为此只有 2 个, 都还不是特别满 觉得满意的却寥寥无几, " 到目前为 J t 只 怠 '\ 有 2 个, 都还不是特别满意 " 挺....

325 篝 9 窜去掉重复的 " 文挡 " 对于这两篇内容相同的新阔, 有可能提取出同样的关键词 : " 罗玉凤 " " 征婚 " " 北大 " " 清华 " " 硕士 " 这就表示这两篇文挡的语义指纹也相同 u 语义指纹 " 排重的 Java 实现 语义指纹生成算法如下 : (1) 将每个文挡分词表示成基于词的特征向量, 使用 TF.IDF 作为每个特征项的权值 地名 专有名词等名词性的词汇往往有更高的语义权重 (2) 将特征项按照词权值排序. (3) 选取前 5 个特征项, 然后重新按照字符排序. (4) 调用 MD5 算法, 将每个特征项 E 扭转化为一个 128 位的亭, 作为该文挡的指纹 输入文档的标题和内容, 返回代表语义指纹的字符串 : public sta t ic String getfingerprint(string name, String conten t ) ( int min = Math.mi n(content. 1ength (), 20000); content = concent. substring(o, mi n ) ; // 取正文的前 个字 StringBuilder EingerPrint = new StringBuilder (); // 键取 FEATURE_NUM 个关键词 String [J ret " gettag(name, content, FEATURE...NUM) i for (int k = 0 ; k < ret. 1engthi k ++) fingerprint.append(ret[k]); return fingerprint. tostring(); 9.4 SimHash 排重 如果两个相似文挡的语义指纹只相差几个位 (bi t) 或更少, SimHash o 这样的语义指纹叫做 可以用海明距离来衡量近似的语义指纹 海明距离是针对长度相同的字符串或二 进制数组而言的 对于二进制数组 s 和 t. H(s, t) 是两个数组对应位有差别的位数 例如 和 的海明距离是 2 0 下面的方法可以按位比较计算两个 64 位的长整型之 间的海明距离 pub1ic stati c int hamming (10ng 11, 10ng 12) i nt coun ter = 0; for (int c =O; c<64; c++) counter += (11 << (1~<< c)) == (12 & (1L << c))? 0 : 1 ; return counter; 这种按位比较的方法比较慢, 可以把两个长整型按位异或 (XOR). 然后计算结果中 1 的个数, 结果就是海明距离 例如计算 A 和 B 两数的海明距离 : A -= 1110,>>.

326 / B = 0100 AXORB = 1010 计算 中 l 的个数 =2 0 实现代码如下 z pub1ic stat ic i nt harnmingxor (1ong 11, 10ng 12) ( 10ng 1xor = 11 ^ 12; // 按位异或 return Bituti1. pop(1xor); // 计算 1 的个数 理解 SimHash 文档向量是高维的, 把维度削减技术用在文挡排重上, 就产生了 SimHash 己有的输入是文挡的一系列特征, 每个特征有不同的重要度 为了实现快速排重, 需要计算文档对应的 SimHash 值 ( 假定 SimHash 的长度为 64 位 ) 计算文档的 S imhash 值的方法是把每个特征的 Hash 值叠加到一起形成一个 SimHash 计算过程如图 9.1 所示 可以把特征权重看成特征在 Hash 结果的每一位上的投票权 权重大的特征的投票权更大, 权重小的特征投票权小 所以权重大的特征更有可能影响文档的 SimHash 值中的更多位, 而权重小的特征影响文档的 SimHash 值位数要少一些 文档的 SimH ash 值计算过程 : (1) 初始化长度为 64 位的向盘, 该向量的每个维度都是 0 (2) 对于特征列表循环做如下处理 : 1 取得每个特征的 64 位的 hash 值 2 如果这个 hash 值的第 i 位是 1. 则将向量的第 i 个数加上特征的权重 ; 反之, 如果 hash 值的第 i 位是 0, 则将向量的第 i 个数减去特征的权重 (3) 完成所有特征的处理, 向盘中的某些数为正, 某些数为负 SimHash 值的每一位与向量中的每个数对应, 将正数对应的位设为, 负数对应的位设为 0, 就得到了 64 位的 0/ 1 值的位数组, 即最终的 S imhash 特征. 权 íl! h 甜飞权 :ffi 叫 -== 晴 W1... 畸 W, -W 1 叫 w, w, -W1 W2 0:::: 畸 11 则 W2 == 畸 WZW2 叫叫叫叫 巴 -1: Wn a:=ii 圃, 1 1 Wn 0:::== 司 ~ -wn-w" w(1"w,,-w(i wn nh符号函敬 H圃'仇M11α) , fin 伊 rprint 图 9.1 语义指纹计算过程 (<<

327 第 9 章去掉 ' 氧的 " 支挡 " SimHash 排重的 Java 实现 设计排重接口 : 首先生成 - 个语义指纹的集合 (FingerPrint Set), 然后通过一个 SimHash 的 key 查找近似 的文档集合 fingerprintset. getsimset(key, k) 返回和 key 相似的数据集合 // 返回一条记录的 si mhash long simhashkey = poisimhash.gethash(poì. address.tel) ; // 根据一条记录的 sìmhash 返回相似的数据 HashSet<SimHashData> r et = fingerprint Set. getsimset(sìmhashkey. k) ; 计算 S imhash 的代码如下 : publi c static long s ì mhash(string[ ] features.int[] wei ghts) int[] hist = new int[641;// 创建直方图 for(int i=o;i<features.length;++i) long addresshash = GeneralHashFunctionLibrary.DJBHash(features[i)); int weight = weights(i); / * 更新直方图 */ for ( i nt c =O; c<64; c ++) hist [c l += (addr esshash & (1 << c)) == 0? -weight : weight; / * 从直方图计算位向量 */ long simhash=o; for (int c=o; c<64; C + +) long t= ((hist[c1>=o)?1 : OI; t <<= c; simhash 1= t ; return simhash; SimHash 的海明距离计算问题描述如下 : 给出一个 f 位的语义指纹集合 F 和一个语义指 纹 fg, 在 F 中寻找与龟只有 k 位以内差异的语义指纹 - 最基本的一种方法是逐次探查法, 先把所有和龟差 k 位的指纹找出来, 然后用折半查 找法查找排好序的指纹集合 F 首先借助组合数生成器 Comb i nationgenerator 来生成组合数 : long fingerprint = l L; // 语义指纹 int [ ] indi ces; // 组合数生成的一种组合结果 // 生成差 2 位的语义指纹 combinationgenerator x = new CombinationGenerator(64. 2); >>8

328 / i nt count =0 ; 11 计数器 whi1e (x. hasmore()) indices = x.getnex t( ); 11 取得组合数生成结果 10ng simfp = fingerprint; for (int i = 0; i < indices. l ength; i++) simfp = simfp ^ ll << ind ice s[ij;11 翻转对应的位 Sys tem. out. print1 n(long.tob inaryst ring(simfp)) ; I/ 打印刷似语义指纹 ++count ; 这里运行的结果是 count= 因为是从 64 位中选有差别的 2 位, 所以计算公式是公司 完整的查找过程如下 : 11 输入要资找的话义指纹和 k 值, 如主快找到相似的话义指纹则返回真, 否则返闯假 pub1ic boo1ean contai nsim(1ong fingerprint.int k) ( /1 首先用二分法直接查找语义指纹 if(contains(fingerprint)) return true; 11 然后用逐次探查法查找 i nt[] ind ices; for(int ki=1; k i<=k;++ki) 1/ 瓷找去主 1 位直到差 k 位的 CornbinationGenerator x = new CombinationGenerator(64. ki); while (x.hasmore ( )) ind i ces = x.getnext( ); 10ng simfp = fingerprint; for (int i = 0 ; i < ind ices.length; i ++) ( simfp = simfp, ll << indices[i); 1/ ; 在找相似语义指纹 i f (contains (sirnfp)) r e turn true; return false; 在 k 值很小, 而要找的语义指纹集合 S 中的元素不太多的情况下, 可以用比逐次探查 e< 氧

329 第 91 院去掉置蟹的 " 文槽 " 法更快的方法查找. 例如 k= ] 时, 可以结成指纹集合 S 中每个元素的所有可能差 1 位的元素, 然后再把所有这些新生成的元素排序, 如图 9.2 所示 e 例如, 对于长整型的元素, 差别 在 1 位以内的元素只有 65 种可能 语义指纹织合 S 回 9.2 查钱语义指纹 对给定 S imhash 值生成差 1 位的 Hash 值的代码如下 z for(int j=o;j<6 4;j+ 叫 long newsimrash=n^ll<<j;!! 生成和 n 差 1 位的 Hash 值 当 k 值较大时, 会导致新集合过大而不容易存储. 这时候可以考虑压缩存储语义编码 在实际系统中.S imhash 数组是一个很大的数组, 写入到文件中也会是 - 个很大的文件, 比如 100 万的记录保存成 500MB 左右大小的文件 对于记录编号也会有类似压缩存储的需要 数据压缩, 通俗地说, 就是用最少的字节来表示大量的数据 其作用是 : 把写入数据 的文件减小, 减少文件在硬盘上占用的存储空间, 同时在读取数据时, 减少对内存的占用. 在 Java 中, 1 个长整型数据占 8 个字节, 但是可以使出现较多的数字使用较短的编码, 如数字 "1" 的有用编码在最低的字节上, 也就是说 1 个字节就能存, 可是因为 "1" 是 民整型, 所以占了 8 个字节 如果把 " 1" 用 1 个字节存, 就能节约 7 个字节的空间 所以说如果较小的数值用较短的编码可以取得不错的压缩效果 取出一个已经从小到大排好的数组的第一个数字, 通过 longtobytes 方法把此数值转换成一个字节数组, 周 BufferedOutputS 位 eam 实例把它写入文件, 然后通过一个循环产生 - 个 压缩的数组, 用一个变量记录数组中后一个数减前一个数的差, 如压缩数组中的第二个数 等于原数组的第二个数减去原数组的第一个数, 再通过 writevlong 方法将这个变量写入文件中 这种压缩方式叫做差分编码 (Differential Encoding). 或者增量编码 差分编码转换示例如下 : 矶 ' X2 ', X. -> X"X 2 -X",X. - X._1 用 DatalnputStream 实例的 readlong 方法读出文件的第一个数字写入数组, 再通过 readvlong 方法把该文件剩下的数字读取出来, 同时把此数组剩下的数字都变成后一个数字和前一个数字的和, 还原成原来的数据. 还原数据的过程叫做解码, 解码转换示例如下 : 罚, ζ, 1 = 巧, 乓 + 罚,.ζ+ι.>>e

330 j 写入数据并用差分编码压缩的实现代码如下 : // 把 - 个长整型的数据变成二选制 public static byte[) longtobytes(long n) ( byte[) buf=new byte(8);// 新建一个字节数组 for(int i=buf. length- l ; i>=o ; i --) buf [i) = (byte) (n&oxooooooooooooooff) ; / / 取低 8 位的值 n>>>=8; / / ; 在移 8 位 return buf; / / 压缩一个长整型的数据 public static void writevlong (long i.bufferedoutputstream dos) throws IOException while ((i & -Ox7F)!= 0) ( dos.write((byte)( ( i & Ox7f) I Ox80)); // 写入低位字节 l. >> 月 7; / / 右移 7 位 j dos.write((byte)i); / 11 已长整型数组 s i!tihashset 写入 fi lename 指定的文件中 private static int write(l ong[) s imhashset.string filename ) int j=o; try ( BufferedOutputStrearn dos = new BufferedOutputStream(ne w FileOutputStream(fi lename)); // 妃敛组中的第一个数字转换成二进制表示 byte [ ) b = longtobytes(simhashset[o )) ; dos. write(b);// 把毛 ' 写到文件中 for (int i = 1 ; i < simha shset.length; i++l ( 1/ 数组中后一个数减前一个数的差 10ng deta=simhashset[i)-simhashset[i-l ); writevlong( 指出. dos); // 把这个差值写入文件 dos.close(); j:si!tihashset.length; catch (File NotFoundException e) ( e. printstacktrace() ; ) catch (IOExcepti on e) ( e.printsta cktrace(); return j; 读出数据并解压缩的实现代码如下 : // 把一个压缩后的长 ' 应型的做掘读取出来 0< 氧..

331 第 9 章去掉 ' 蟹的 " 立挡 " private static 10ng readvlong(data lnputstream dis) throws 工 OException ( byte b = dis.readbyte(); // 读入 - 个字节 int i = b & Ox7F; // 取低 7 位的值 /1 每个高位的字节多乘个 2 的 7 次方, 也就是 128 for (int shift = 7; (b & Ox80)! : 0; shi ft += 7 ) if (dis. a vailab1e ()! =0) b = dis.readbyte(); i 1= (b & Ox 7Fl << shift ; /1 当前字节表示的位乘 2 的 'Sh ift 次为 return i;l/ 返回最终结果 i 11 从 filename 指定的文件中把氏整型数组读出来 pri vate static void read(int 1en. String fi1ename) try DatalnputStream dis = new Dat alnputstream(new BufferedlnputStream( new Fi l e 工 nputstream(fi1ename))1 ; long[] simhashset = ~ew l ong[lenj ; simhasl 恒的 [ 0] = dis. readlong() ; /1 从文件读取第一个长整型数字放入数组 for (int i = 1 ; i < len; i ++) ( simhashset ( i] = r eadvlong(dis) ; 11 谍 ' 取文件剩下的元素 11 将元素都变成数组中后一个数字和前一个数字的和 s i mhashset[i l = simhashset[i J + simha s hset [i - 1]; dis. close () ; f or(int i =O;i<len;i++)( 11 将长整型数组的数据显示出来 System. out.println(simhashset [ i)); ) cat ch (Fi1eNot FoundException e) e.printstacktrace(); ) catch ( I OExcepti on e) ( e.printstacktrace(); 假设我们有一个己经排序的容量为 2 d 的 f 位指纹集合 看每个指纹的高 d 位 该高 d 位具有以下性质 : 因为指纹集合中有很多的位组合存在, 所以高 d 位中只有少量重复 S imhash 排重的假设 : 整个表中排列组合的部分很少, 不太可能出现, 如 : 一批 8 位 SimHash, 前 4 位 都一样, 但后 4 位出现 16 种 0-1 组合的情况 整个表在前 d 位 0-1 分布不会有很多的重复 这两个假设得到排重的基础 : 前 d 位上的 0-1 分布足以当成一个指针 有能力快速搜索前 d 位.. ->>5

332 / 现在找一个接近于 d 的数字 d', 由于整个表是排好序的, 所以一趟搜索就能找出高 d' 位与目标指纹 F 相同的指纹集合 F 因为 d' 和 d 很接近, 所以找出的集合 F 也不会很大 Ifl = Is1/2 d ' 最后在集合 f 中查找和 F 之间海明距离为 k 的指纹也就很快了 海明距离的比较在 f- d' 位上进行 要确保海明位不同的几位都被限制在 f- d' 上就需要考虑 f 上不同位的组合可能, 即让海明位不会出现在前 d' 上, 每种 f 位上的组合就需要复制一次 T 总的思想 : 先把要检索的集合缩小, 然后在小集合中检索 f-d' 位的海明距离 例如, SimHash 长度是 64 位, 按 16 位拆分, 复制 4 份, 分别是町 T2 T3 T4 这 里, T i 在 T, _, 的基础上左移 16 位 精确匹配每个复制袤的高 16 位 然后在精确匹配出来的 结果中找差 3 位以内的 SimHash 按 16 位拆分的查找方法如图 9.3 所示 精确 m 是前 16 位 16 位 A,-l 飞 A B C D "'rs 一囚 己甸的指纹库 指纹 F C 因 9, 3 分 4 块查找语义指纹 比较用长整型表示的无符号 64 位语义指纹的代码如下 z public stat ic boolean i slessthanunsigned( l ong n1, long n2) ( return (n1 < n2) ^ ( ( 口 1 < 0)! = (n2 < 0)); static Comparator<SimHashData> comp = new Comparator<Si mhashdata>() ( 3 氧

333 .9 窜去掉置氧的 " 文帽 " public int compare(simhashdata 01, SimHashData 02) ìf(01.q==02.q) return 0 ; return (ìslessthanunsìgned(01.q, 02.q))? 1: -1; ; / / 比较无符号 64 位 statìc Comparator<Long> comphìgh = new Comparator<Long>( pub1ic int compare(long 01, Long 02) 1 1= OxFFFFFFFFFFFFL; 2 1= OxFFFFFFFFFFFFL; if(01. equals(02)) return 0; return (ìslessthanunsìgned (01, 02~)? 1 : -1; ); / / 比钱无符号 64 位中的高 16 位 对表 T. T2 T3 T4 按高位排序的代码如下 : publìc void sort() t2.clear(); t3. clear () ; t 4.cl ear(); for(simhashdata simhash:t1) 10ng t = Long. rotateleft(simhash.q, 16); t2.add(new SimHashData(t,siffiHash.no)); t = Long.rotateLeft(t. 16); t3.add(new SìmHashData(t.siffiHash.no)); t = Long.rotateL.eft(t, 16) ; t4.add(new Si mhashdata(t, simhash.no)); Collections.sort(t1. comp); COllections.sort(t2, comp); Collections.sort(t3. comp); Collections. sort(t4. comp); 下面介绍两种更复杂的分块方法 : 第一种策略 : 语义指纹 f 分为 6 块, 分别是 位, 最坏的可能 是其中 3 块里各出现 l 位不同, 把这三块限制到低位, 换言之, 把精确匹配的主块选到高 位, 有 c;=20 种选法, 因此需要复制 20 个语义指纹集合 T 衰 对每个表高位做精确匹配, 需要匹配 位 (1 1 *3 =33 11 *2+1 0= *2 31), 那么 T 的个数大概是 jsj/ 2 J1 = 2,34-31,. ~>.

334 j, 即精确匹配一次, 产生大约 8 个需要算海明距离的 SimHash. 第二种策略 : f 先分为 4 块, 各 16 位, 选 1 个精确匹配块到高位的可能有 c~ =4 种遥 法, 再对剩下 3 块 48 位切分, 分成 4 块, 各 12 位, 选 1 个精确匹配块到高位的可能有 d=4 种. 4x4 = 16. 一共要复制 16 次 T 衰 那么高位就有 = 28 位 每次精确匹配 28 位 后, 产生 叫 海明距离算法步骤 : = 64 个需要算海明距离的 S imhash. (1) 先复制原表 T 为 t 份 : T"T2,Tt ο) 每个 Ti 都关联一个 Pi 和一个饵, 其中 Pi 是一个整数, 概是一个置换函数, 负责把 P i 的个位换到高位上. (3) 把置换函数 πi 应用到相应的表 Ti 上, 然后对 Ti 排序 (4) 对每一个 Ti 和要匹配的指纹 F 海明距离 k 做如下运算 : 使用 F' 的高 P i 位检索, 找出 T; 中与高 pj 位相同的集合, 在检索出的集合中比较剩下的 f-pi 位, 找出海明距离小子或等于 k 的指纹 (5) 合并 t 个表检索出的结果 举个例子, 假设有 100 亿左右 ( 2 勺的语义指纹 SimHash 有 64 位, 所以 f = 64, d 言 34. 海明距离是 3. 算法的本质就是采用分治法, 降低问题规模 ~. 利用内存空间的增加和并行计算换取查 找时间的减少 当然这个算法即使不并行也比逐次探查法快. 9.5 分布式文档排重 在批量版本的海明距离问题中, 有一批查询语义指纹, 而不是一个查询语义指纹 假设己有的语义指纹库存储在文件 F 中, 批盘查询语义指纹存储在文件 Q 中 80 亿个 64 位的语义指纹文件 F 大小是 64GB, 压缩后小子 32GB 假设有一批 l r-. 但大小的语义指纹, 因此文件 Q 的大小是 8MB. Google 把文件 F 和 Q 存放在 GFS 分布式文件系统中 文件分成多个 64MB 的组块, 每个组块复制到一个集群中的 3 个随机选择的机器上 并且每个组块在本地系统存储成文件 使用 MapReduce 框架, 整个计算可以分成两个阶段 在第一阶段, 有和 F 的组块数量一样多的计算任务 ( 在 MapReduce 术语中, 这样的任务叫做 mapper). 每个任务以整个文件 Q 作为输入在某个 64 岛侣的组块上求解海明距离 一个任务以它发现的一个近似重复的语义指纹列表作为输出 在第二阶段. MapReduce 收集所有任务的输出, 删除重复发现的语义指纹, 产生一个唯一的 排好序的文件 Google 用 200 个任务 (mapper), 扫描组块的合并速度在每秒 1GB 以上 压缩版本的文 件 Q 大小约为 32GB ( 压缩前是 64GB) 因此总的计算时间少于 100 秒 压缩对于速度的提 升起了重要作用, 因为对于固定数量的任务 (mapper). 时间大致正比于文件 Q 的大小 8< 缸

335 铜第 9 章去掉重复的 " 文挡 " 9. 6 本章小结 Broder 提出 shingling 算法用于文档内容相似性检测 Charikar 在论文 Similarity Estimation Techniques 企 om Rounding AIgori 出 ms 中提出了用 SimHash 实现维度约减 Google 公司的论文 Detecting NearDuplicates for Web Crawling 介绍了把 SimHash 用于爬虫抓取过程中的网页去重 本章介绍了用语义指纹排重的基本方法, 以及一种特殊的语义指纹 SimHash 详解在 Java 中实现 SimHash 与 SimHash 的压缩存储方法 川>>e

336

337 份类与聚类的应用 抓取的网页或文档需要区分出类别以方便浏览或检索. 处理互联网上的海量信息需要 能够自动分类或聚类. 分类和聚类有很多技术基础是相同的, 例如分类和聚类的依据一般 是从文本中提取的特征. 这两个任务都是经典的机器学习问题.

338 j 10.1 网页分类 可以把网页归为 - 类, 或者分为多个类别 例如可以把新闰分成 " 国内新阔 " 或 " 国际新闻 " 为了理解机器学习的算法是如何对网页进行分类的, 首先看一下人是如何对事物进行分类的 为了判断食物是否为健康食品, 可参考食品巾的饱和脂肪 胆圄醇 糖和铀的含量 如果这些值超过一个阑值就认为该食品是" 不健康的 ", 否则是 " 健康的 " 首先从整个项目集合中找出一些重要的特征, 然后从每个待分类的项目中寻找特征, 从抽取出的特征中组合证据 (combine evidence), 最后根据组合证据按照某种决策机制对项目进行分类. 在食品分类的例子中, 特征是饱和脂肪 胆固醇 糖和铀的含量, 可以通过食品包装上的营养成分表来取得 为了量化食物的健康程度 ( 记做酌, 有很多方法来组合证据, 最简单的方法是按权重求和 H ( 食物 )= Wn 肺脂肪 ( 食物 )+ W~g Illl ØJ 胆固醇 ( 食物 ) +W, 帽糖 食物 )+W 铀铀 ( 食物 ) 这里 W 黯防. WII!!WØJ 等是和每个特征关联的重要度 在这个公式里, 这些值可能是负数. 对网页中的文本进行分类是网页分类的重要基础 除文本外, 还可以利用网页的 URL 地址和网页输入链接的铺点文字等信息来对网页分类. 图 10.1 展示了文本分类的框架. 训练话料 特征 ~ 成 里 特征选择 ' 选择和训练分类指 测试语料 E 文挡分主题 图 文本分类的步骤 收集语料库 对语料库进行人工分类很费时. 例如要取得分好类的 " 国际新闻 " 和 " 国内新闻 " 可以利用爬虫抓取新闻网础, 比如 htφ://news.sina. m.cq, 这个网站上有国际新闻和国内 新闻的栏目 如果索引页能分成 " 国际新闻 " 或 " 国内新闻 ", " 详细页 " 的类别参考 " 索 8 氧

339 篇 10. 分集与黯集的应用 M 1/.". H hi.... 叫但 ::::t 引页 " 的分类, 那么抓取下来的语料就可以分别归在 " 国际新闻 " 或 " 国内新闻 " 类别下 选取网页的 " 特征 " 对于文本分类来说, 可以选择文本中出现的字 词或者词组作为分类特征. 根据实验结果, 普遍认为选取词作为特征优于选取字或者词组作为特征 所以实际的中文文本分类系统中, 特征一般是对文本分词后得到的词 词的数量可能很多, 例如中文分词词表可能包括几十万个词 特征空间维度太大会影响分类器的执行效率和有效性, 因此得要降低特征空间维度, 这个技术叫做特征选择 可以按词性过滤, 只选择某些词性作为分类特征, 比如, 只选择名词和动词作为分类特征词 文本分类的精度随分类特征词的个数持续提高. 一般至少可以到 2000 个词 特征选择的常用方法还有 :CHI 方法和信息增益 (Information Gain) 方法等. 下面介绍特征选择的 CH I 方法 利用 CHI 方法来涟行特征抽取是基于如下假设 : 在指定类别文本中出现频率高的词条与在其他类别文本中出现频率比较高的词条, 对判定文挡是否属于该类别都是很有帮助的 用 C 皿方法衡量单词 tenn 和类别 c lass 之间的依赖关系 如果 term 和 class 是互相独立的, 则该值接近于 o. 一个单词的 CHI 统计通过表 计算. 表 CHI 统计变量定义亵 属于 c l ass 炎不属于 c l ass 炎合计 包含单词 tenn a b a+b 不含单词 tenn c d c+d. 合计 a+c b+d a+b+ c+d=n 这里, a 代表属于类别 class 的文档集合中出现单词 tenn 的文档数 ; b 代表不属于类别 c lass 的文档集合中出现单词 tenn 的文挡数 ; c 代表属于类别 cjass 的文档集合中未出现单词 tenn 的文档数 ; d 代表不属于类别 class 的文档集合中没有出现单词 tenn 的文档数 ; n 代表 文挡总数 表 中的单词 term 的 CHI 统计公式如下 : chi_statistics(term. class) = 俨 (ad-cb) 吃 /(a+c) 咐 +d)*(a+b) 气 c+d) 类别 class 越依赖单词 term, 则 CHI 统计值越大 以表 10.2 中的垃圾邮件分类为例, 在这个例子中有 1 0 个文档 ( 每个文档有一个唯一的白编号 ), 要把文档分到两个类另 IJ(spam 和 not spam) 中的一个, 对文档分类的候选分类特征词是 cheap b 町 banking dinner 和出 e. 骂 >.

340 J 褒 10.2 垃极邮件分类型睡 "the " document id cheap buy banking dinner the class notspam spam not spam 1 sp 部 n spam notsp 缸 n notsp 缸 n notsp 缸白 1 not spam not spam 对于 chi_statistics(dinner, spam) 来说, a = 0, b= 1,c 斗,d=6, n =10 chi_statistics(dinner,spam) = 1 0 气 0 *6-3 叫 )^2 1 (0+3) 气 1 +6) 气 + 1 )*(3+6) = 10 吨 13 *7 叫叼 =0.11 对于 chi_statistics(the, spam ) 来说, a = 3, b=7,c=o,d=o, n =10 chi_statistics(the,spam) = 10 *(3 *0 _ 0 叮 ) 吃 1(3+0) 气 7+0)*(3+7 沪 (0+0), 不作为分类特征 对于 chi_statistics(cbeap, spam) 来说, a = 3, b=l,c=o,d 司, n = 10. 除零溢出, 因此 chi_ statistics( dinner 呻 am) = 10 *(3*6 _ 0 * 1 )^2/(3+O) 气 1 + 印 (3+1 沪 (0 吨 ) = 1 0*324/3 叩 4*6 =6.43 信息增益 (Inform.ation Gain) 是广泛使用的特征远择方法 在信息论中, 信息增益的概念 是 : 某个特征的值对分类结果的确定程度增加了多少 信息增益的计算方法是 : 把文档集合 D 看成一个符合某种概率分布的信息源, 依靠文档集合的信息娟和文档中词语的条件精之间信息量的增益关系确定该词语在文本分类中所 能提供的信息量 词语 w 的信息量的计算, 公式为 : IG(w) H(D)-H(Dlw) = _ L di@o P(d;)Xlog 1 P( 矶 )+ 艺刚川 P(W)L d,ω P( 矶 I w)xlog2 P( 圳 w) 这里 H(D) 是 P( C) 的 ; 楠, 而 H(Dlw) 是条件 ; 恼 因为涉及到取对数, 因为这里的 IG 只有相对大小的意义 为了在 Java 中方便计算, 可以简单地以 e 为底计算 计算 cheap 的信息 增益 : IG(cheap)= -P(spam)logP(spam )- P ( spam )logp( spam )+ P( cheap )P(spamlcheap )logp( spamlcheap)+ P( 伽 ap)p( 石 am I cheap )10 的 spam Icheap)+ 3 氧

341 .'0. 分费与襄樊的应用 P( cheap )P(spaml cheap )logp(spaml cheap )+ P( cheap )P( spam I cheap )logp( spam I cheap ) = 町百丁百 10 町百叮百 * :4 lo~ 而 * 4 lo~ + 百事 6 lo~ + 百 * 6 lo~ = 计算程序如下 : double i g ~ -(3. 0 / ) * (Math.log(3.0 / 1 0.0) ) - (7. 0 / 10.0 ) * (Mat h. l og7. 0 / 10.0)) + (4.0 / 10.0) * (3.0 / 4. 0) * (Mat h. l og ( 3. 0 / 4.0)) + (4. 0 / 10.0 ) * (1. 0 / 4. 0) * (Mat h. l og1. 0 / 4. 0)) + (6.0 / 10) * (0. 0 / 6) * (O. O) + ( 6. 0 / 10. 0) * ( 6. 0 / 6. 0) * (Ma th. l og ( 6. 0 / 6. 0) ) ; System. out.println(ig); 分类器采用特征选择来降维的过程是 : 先计算所有候选特征的类别区分度, 分度最大的 5000 个, 然后重新计算特征词的权重 再选出区 1. 计算候选特征集巾每个特征的类别区分度 wordnode.m _ dweight = 各类别下文本区分度之和 概率统计有基于词的统计和基于文档的统计两种方法 以基于词的统计方法为例, 对 每个文本类别, 令 : P c 表示类别中的词占所有类别的词的比率 P ft 表示含 f t 的词占词总数的比率 p c 武表示类别中含 f t 的词占词数的比率 P_n_c ~ 1 - P_c; P_n_ ft ~ 1 - P_ f t ; P_ n _c_ft ~ P_ ft - P_c_ ft ; p_c_n_ft = P_C - P_ c_ft; P_n_c_n_ft = P_n_ft - P_c_n_ft; 可以用不同的方法计算区分度 : 方法一 : 右半信息增益 (Right half of 10) ( p c, n ft 1 Weigbt + = P c, n ft * logl _ -. ~.' _-- -- ~ 1 ", , Ql P c * P n ft ) 方法 2: 信息增益 (10) ( p c. n fì 1 Weight+= P c, n ft* logl _ - : _- ^ I c; _. - - \ P _c, * P _:n_ 忧 j Wei 民 t+ = 川叫 05(JLf 负 ) ->>e

342 J 方法 3: 互信息仙 11) _( P _cl_ft ) Weight+:::; P _ C 1.logl -=- 一一一一 -_.>l P_C i.p_ft) 方法 4: X2 统计量 (CHI) Weight+ = P _cj_ft.p_n_cl_n_ft 唰 P_o_ci_ft P _cl_o_ft 方法 5: I P Cj ft * P 0 c 1 0 ft - P _n_ci_ft P _cl_o_ft P ft * P n c, * P n ft 期望交叉娟 (ECE) Wei 院 hh- 企 * 吨!;1a) 方法 6 : 文本证据权重 (WET) 默认的概率统计是基于词的统计, 特征选择算法用 X2 统计量 2. 生成新的特征词和权量 特征词的权重有两种计算方法 TF*IDF: 特征区分度 log( 文档数 f 特征的文档频率 ) TF _IDF _DIFF : log( 文档数 f 特征的文挡频率 ) 使用支持向量机进行网页分类 支持向量机 (SVM) 是 Vipnik 于 1995 年根据统计学习理论 (Statistical 出的一种机器学习方法 支持向量机的最初思想是对于线性可分问题如何寻求最优分类面. 图 Learning Theory) 提 显示了一个 两类分类问题, 其中 ((+" 是特征空间的一类点, 而 1I _ " 是特征空间中的另一类点 图 中左边的点是线性可分的, 右边的点是线性不可分的. 对于特征空间中线性可分问题, 最 优分类面就是归 j 隔 y 最大的分界面 线性判别函数 (discriminant 数 : g(x)=w 气 +b 缸 nction) 是指由分类向盘 x 的各个分量的线性组合而成的函 分成两类的情况 : 把分类向盘 x 分成 Cl 类或 C2 类, 对于两类问题的决策规则如图 10.2 所示 如果 g(x) 注 0, 则判定 x 属于 Cl 类 : 如果 g(x)<ο, 则判定 x 属于 C2 类 E _+ _ ++ + 皿 回 _ 图 10.2 两类分类问题.< 怎

343 霸, 章分费与襄樊的应用 当 g(x) 是线性函数时, 这个平面被称为 " 超平面 " (hyperplane) 在一维空间中, 任何一个线性函数能不能解决图 10.3 所示的划分问题 ( 粗线和细线各代表一类数据 ), 可见线性判别函数有一定的局限性 a b 图 10.3 线性函数不能分类的问题 如果建立一个二次判别函数 g(x)-(x-a)(x-b) 如图 10.4 所示, 则可以很好地解决图 10.3 所示的分类问题 决策规则仍是 : 如果 g(x) 注 0, 则判定 x 属于 C1 ; 如果 g(x) 吨, 则判定 x 属于 C20 自 6 2 a.2 4-A 图 10.4 二次判别函数 SVM 是从线性可分情况下的最优分类面发展而来的 基本思想可用图 10.5 所示的两维 情况说明 + / 龟 / ^-,/ J 也.JtFf/.~ / 斗峙, - 图 10.5 分类阎隔.. >>8

344 j 最优分类超平面是指该分类面不但能正确分类而且使各类别的分类间隔最大 SVM 文档分类实现流程如下 : (1) 生成所有候选特征项, 将其保存在 candidatew ordli st 中 WordList candidatewordlist = gendic(); (2) 为候选特征项列表 candidatew ordl ist 中的每个特征设置权重 featherweight(candidatewordlist) ; (3) 从特征项列表 candidatewordl íst 中选出最优特征 WordList trainwordlist = featherselection(candidatewordlist); (4) 生成文档向量, 将其保存在 traínwordl i st 中 genmodel(trainwordlist) ; (5) 训练 SVM 分类器 trai nsvm() ; (6) 保存分类模型 巳 rainwordlist. writ emodel(m_paramclassifier. m_txtresultd ir + '\\model.prj"); 利用 URL 地址进行网页分类 从 h 即 :/lbj.58.comljob.shtm1 网址就可以知道它可能是和招聘相关的网页, 而且可能是北京地区的网页 因此, 使用 URL 地址可以快速分类同页 每次搜索 gle 都会看见一个编码后的 URL 地址, 例如 :h1 即 :// clíent=opera&rls=en&q=%22rabbits%22%2beaster+eggs&sourceid=opera&ie=utf-8&oe=utf-8 实际的查询词是 : "rabbits"+easter eggs 使用下面的 Java 语句对编码后的 URL 地址解码 : String reconstituted ; URLDecoder.decode( received, 'UTF-S. ) ; 为了提取 " 衔 j.58.comljob 血 trnl" 的特征词 " 均 " "58" "job", 首先按 U.", / " " " 切分, 然后通过词干化和小写化处理, 去除停用词 " h 邸 ", " com", " sh 由让 ", 最后剩下 " 均 " "58" " job " 三个分类特征词 使用 AdaBoost 进行网页分类 Boosting 是近年来发现的最强大的机器学习方法之一 它的基本原理就是 " 三个具皮匠, 顶个诸葛亮 " 可以将弱的分类器组合成为高精度的分类器 用来组合的弱分类器必须在 3 氧

345 .1 章给羹与. 提前应用 训练实例上的错误少于 50% 假设有 t 个弱分类器, 分类结果是代 ( 巧, 可以采用如下的公 式线性组合强分类器 z KX)=b AU) 'aj AdaBoost 使用投票的方法来综合 r 个弱分类器的分类结果. 根据 MALLET(h ttp ://mal let. cs.umass.edu) 中的 AdaBoost 分类过程实现代码说明如下 : FeatureVector fv = (FeatureVector) inst.ge tdata() ; // 取得特征向窒 int numclasses = getlabelalphabet). doublec1 scores = new double[numclasses] ; int bestlndex; double sum = 0 ; /1 收集所有弱分类器的分值 size();// 取得类别数量 for (int round '" 0 ; round < numweakclassifierstouse; round+-t) ( bestlndex '" weakclas s ifiers[round).classìfy(inst ).getlabeling().getbestlndex() ; scores [bestlndex) += alphas[round); sum +'" scores[bestlndex ) ; /1 归一化分值 for (ìnt i = 0; i < scores. length; i+-t) scores[i] 1= sum; return newcl assification (inst, this, new LabelVector (getlabelalphabet(), scores) ) ; AdaBoost 的训练过程是 : 首先, 使用原始的训练集生成一个分类器 这时候每个弱分 类器的投票权 - 样 然后, 给被错误分类的实例赋予较大的权重 ( 把这些实例错误分类会使 得错误更大 ) 接下来, 通过使用上一步带权重的训练 集取得新的分类器 计算训练错误, 根据弱分类器在训练集上的表现给这个弱分类器分配权重 因此, 新的分类器在错误的实 例上应该有更好的表现 这个过程可以重复多砍, 最终, 分类器 在最后的分类器中组合若干个弱 FeatureSelection selecte dfeatures = traininglist. getfeatureselection() ; java. util.randorn random = new java. util.randorn(); // 设置训练实例的初始权重为均匀分布 double w '" traininglist. size() ; 工 nstancelist traini nglnsts = new InstanceList(tra iningli st.getpipe( ), traininglist. size( )) ; for (int i = 0 ; i < traininglist. size(); i++) traininglnsts.add(traininglist.get( i ), w); boolean[] correct '" new boolean[traininglnsts.size )] ; Classifier!j weaklearners ~ new Classifier[numRounds) ; 1/ 弱分类器 double[] alphas '" new double[numrounds);...>>5

346 J Instance List roundtraininglnsts := new I nstancelist(traininglnsts. getpipe()) ; // Boos ting 叠迭, 每轮叠返学习出一个弱分类器 for (int round = 0 ; round < n umrounds; round++) ( // 保持重新采样训练实例 ( 使用实例权重的分布 ). 在这些实例上训练弱分类钱 // 直到达到预定的放大叠起草次数, 或者弱分类榕在训练集中学习到非零的错误 // 这样可以保证至少来样到一些难的实例 int resampl ing 工 terations = 0 ; // 重复来样坐迭计数量普 double er 古 ; do ( err = 0; roundtraininglnsts = traininglnsts. samplewithlnstanceweights(random) ; weaklearners(round] = weaklearner. train (roundtraininglnsts); // 计算错误 for (int i = 0 ; i < trainingl nsts. size() ; i ++) Instance inst = traininglnsts.get(i); if (weaklearners[round ].classify(inst). bestlabe l IsCorrect()) correct[il = true; else correct[i] := false; err += training 工 nsts. getlnstanceweight(i) ; resamplinglterations++ ; while (Maths.almostEquals(err, 0) && resampling 工 terations < MAX~ 肌 JM_RESAMPL 工 NG_ITERATIONS) ; // 计算分配给弱分类糠的权重 alphas[round] = 巴 ath. log((l - e rr) / err); doub1e reweightfactor = err / (1 - err); doub1e s um = 0; 1/ 降低正确分类的实例的权喷 for ( i nt i = 0 ; i < training 工 nsts.size(); i++ ) ( w := trainingl n sts.ge tlnstanceweight( i ); if (correct (i) ) W 我 = rewe 立 ghtfactor ; traininglnsts. setlnstanceweight (i, w) ; sum += w; // 归一化训练实例的权 ji for (int i := 0 ; i < traininglnsts. size( ) ; i ++ ) t rain inglnsts. setlnsta ncewei ght (i, traininglnsts. getlnstanceweight(i) / SW 咀 ) ; G< 氧

347 .ι.. -. 今 厂-I第 10 章 铺分提与. 巍的应用 logger.info( "======== === AdaBoostTrainer round " + (round+l) + ' finished, weak classifier t raining error = " + err); // 打印训练出来的 a lpha 值 for (int i = 0; i < a l phas. length; i ++) logger. info("adaboosttrainer weight (weaklearner(" + i + ") ) =' + alphas(i) ); t his.classifier = new AdaBoost (roundtraininginsts.getpipe(), weaklearners, alphas); 10.2 网 页聚类 分类算法需要训练数据, 也就是分好类的数据 和分类算法不同, 聚类是无监督的学习 正如其他一切无监督学习问题一样, 处理过程要在没有标记的数据中寻找相应的结构 聚类就是 " 把相似的物体组织到一起 " 相似的物体分在一起, 叫做 " 旗 ", 因此同簇中的对象相似皮大, 不同簇的对象相似度小 图 10.6 是一个聚类简单的图形化例子.:i::_, - l e v~jj 平 i 斗 川 H 1 J i..; :.. ".ι.' '.. '-. 二 ". o...,_ - ' 一.. 一吃 o..:. " 三巳.,...-. 一.~. 怠 '. o......,.咱 Ị 1 川.I J冯. 叫.l.. 图 10.6 聚类 卡耐基 - 梅隆大学的研究人员把聚类技术和网页搜索相结合, 创建了 聚类搜索技术商业公司 vivisimo 并不是开源的, 而 ect.caπ'ot2.orgl 则是一个开源的聚类搜索引擎 深入理解 DBScan 算法 DBScan 是 Martin Ester 等人在 年提出的一个数据聚樊算法 DB Scan 算法是基于 密度的方法 它从对应点的估计的密度分布发现一些簇, 能够发现任意形状的簇并且可以 有效地处理噪声 DBScan 基于密度可达性的概念来定义旗 如果满足两个条件就认为从一个点 p 直接密 度可达点 q: 如果它们之间的距离在一个给定的距离 E 内 ( 也就是说 q 是 p 的一个 ε 邻居 ), 并且如果 p 周围围绕了足够多的点 这样就可以认为 p 和 q 是属于同一个族中的点 如果存在一个点的序列 PI', 孔, 其中 P1 p, Pn 吁, p, 直接密度可达 Pi+ 1 ' 则认为从 >>3

348 / e<< 度可达 q 注意, 度司达 p, 密度可达关系不是对称的, 也就是说 p 密度可达 q 并不 - 定保证 q 密 因为 q 可能位于簇的边界, 没有足够多的邻居点来让它真正成为簇元素 因此引 入了密度连通的概念 : 如果存在一个中间点, 与 'P 是密度可达的, 同时 o 与 q 也是密度 可达的, 则点 p 和 q 是密度连通的 一个族是文档库中的点的子集, 簇满足两个属性 : 簇 中的所有点是互相密度连通的 : 如果某个点和簇中的任意某个点是密度连通的, 则这个点 也是簇的一部分 在一个簇中有两种不同的点 : i> 邻居数量多于指定阑值 MinPt s 的称为核心点, 否则为 非核心点 核心点可以把非核心点 " 拉 " 到簇里面 DBScan 需要两个参数 : Eps 和 MinPts 来形成一个簇 从任意的未访问过的点开始, 如 果它包含足够多的 ε 邻居, 则形成一个簇, 否则把这个点标示成噪声 也意, 这个点后来可 能在另外某个点的 ε 环境中, 如果一个点是簇的一部分, 因此成为一个簇中的一部分 有点都加入到簇中 重复这个扩展点的过程, 的点同样处理, 从而发现另外一个簇或者噪音 则它的 E 邻居也是簇的一部分 因此, 把它的 E 邻居中的所 直到完全发现旗 然后取 - 个新的没访问过 发现一个簇的步骤基于这样一个原理 : 一个族能够由它的任一核心对象唯一地确定 伪代码如下 : DBSCAN(D, eps, MinPts) c = 0 X'Í 数据集 D 中每个未访问过的点 P 把 P 标志成已经访问过 N = getneighbors (P, eps ) i f sizeof (N) < MinPts else 把 P 标志成噪音 C 乞下一个簇 expandcluster(p, N, C, ep s, MinPt s) expandclust er(p, N, C, eps, MinPt s) 把 p 加到簇 C 对 N 中的每个点 P' if 没有访问过 P' 把 P ' 标志成己访问 N' = getneighbors(p ', eps) i f s i zeof (N') >= MinPts N = N 与 N ' 合并 if P' 不是任何簇的成员 把 P' 加到簇 C 使用 DBScan 算法聚类实例 Weka r.þ 有个 DBScan 算法的实现 源代码在 weka.clusterers 包中, 文件名为 DBScan.java 其中 buildclus terer 和 expandc luster 这两个方法是最核心的方法 buildclusterec 是所有聚类方法实现的接口, 而 expandcluster 用于扩展样本对象集合的高密度连接区域 另外还有一

349 篇 10.. 分羡与黯缉的应用 个用于查询指定点的 E 邻居的方法, 叫 epsilonrangequery, 这个方法在 Database 类中, 调 用例子如下 : seedlist = database.epsilonrangequery(getepsilon(), dataobject); 在 buildclusterer 方法中, 通过对每个未聚类的样本对象调用 expandcluster 方法, 查找 由这个对象开始的密度连通的最大样本对象集合 在这个方法中处理的主要代码如下 : whîle (iterator.hasnext( )) ( DataObject dataobject = (DataObject) iterator.next(); if (dataobject.getclusterlabel() == DataObject.UNCLASS!FIED) if (expandcl uster(dataobject)) (// -- 个簇已经形成 cluster 工 D+ +;// 取下一个聚类标号 numberof GeneratedCl usters++; 下面再来看 expandcluster 方法, 这个方法的输入参数是样本对象 dataobject 这个方法 首先判断样本对象是不是核心对象, 如果是核心对象再判断这个样本对象的 ε 邻域中的每一 个对象, 检查它们是不是核心对象, 如果是核心对象则将其合并到当前的聚类中 源代码 分析如下 : // 查找输入的 dataobject 样本对象的 ε 邻域中的所有样本对象 List seedlist = database.epsilonrangequery(getepsilon(), dataobject) ; // 判断 í dataobject 是不是核心对象 if (seedlist.size() < getminpoints()) ( // 如果不是核心对象则将其设登为噪声点 dataobject.setclusterlabel(dataobject.noise); // 没有发现新的簇, 所以返回 false return false; // 如果样本 ; 对象 dataob j ect 是核心对象, 则对其邻域中的每一个对象进行处理 for (int i = 0 ; i < seedlisc. size(); i++l DataObject seedlistdataobject = (DataObject) seedlist.get(i); // 设置 dataobjec t 专 ll 域中的每个样本对象的聚类标识, 将其归为一个簇 seedlistdataobject.setclusterlabel(clusterid) ; // 如果邻域中的样本对象与当前 dataobject 是同一个对象, 那么将其删除 if (seedlistdataobject. equals(dataobject)) seedlist.remove(i); ~ -- ; // 对 dataobj ec t 的 e 专 II 域中的每一个样本对象进行处理, 二一 ~ ~..... ~ 二二 办 3

350 J for (int j = 0; j < seedlist.size( ); j++) ( // 从邻域中取出 个样本对象 seedlistdataob ject DataObject seedlistdataobj ect = (DataObject) seedlist.get(jl; // 查找 seedli st DataObject 的 epsi lon 邻域并取得其中所有的样本对象 List seedlistda.taobj ect_neighbourhood = dàtabase.epsil o nrangequery(getepsilon(), seedlistdataobject); // 判断 seedlistdataobject 是不是核心对象 i f (seedlistdataobj ect_neighbourhood. size () >= getminpoints ()) for (int i = 0 ; i < seedlistdataobject_neighbourhood.size(); i++) DataObject p = (DataObject) seedlistdataobject_neighbourhood.get(i); // 如果 seedlistdataob j ect 样本对象是一个核心对象 // 则将这个样本对象邻域中的所有未被聚类的对象添加到 seedlis t 中 /1 并且设置其中来聚类对象或噪声对象的聚类标号为当前聚类标号 if (p.getcl usterlabel() == DataObject.UNCLASS 工 FIED p.getclusterlabel() == DataObject.NOISE) if (p.get Cl usterlabel () == DataObject. UNCLASSIFIED) ( // 在这里将样本对象 p 添加到 seedlist 列表中 seedlist. add(p) ; I I p. setclusterlabel(clusterid) ; // 去除当前处理过的样本点 seedlist.remove(j); J -- ; 11 发现新的簇, 所以返回 true return true; 10.3 本章小结 本章介绍了网页的特征选择方法和常用的几种分类 聚类算法 分类算法除了已经介绍的 SVM 和 AdaBoost 算法, 还有朴素贝叶斯和 K 近邻算法 EM 算法 最大情等 聚类算法除了已经介绍的 DBScan, 还有层次聚类和 KMeans 聚类方法 数据流聚类算法等 3 民..

351 性飘鄙锺 ~ 您好! 感渝您购买本书, 请您抽出宝贵的时间模写这份回执伞, 并将此页剪 r 寄回 我们会在以后的工作中充分考怠您的意见和建议, 并将您的馆息加入公司的客户档案中 ;,,~ << 的一体化服务. 您享有的权益 : 食免费获得我公司的新书资料, 食寻求解答阅读中遇到的问题, 食免费参加费公 食可参加不定期的促销活动, 电司 S E.mall 遭慨地址 "ιjjl 嗡.át. 认可 ~ 打 I (6.t 10 " 甲 T 多这 ) 1. 您购买的图书名称是什么 z 2. 您在何处购买的此书 : 3. 您对电脑的掌握程度 2 4 您学习此书的主要目的是 E 5. 您 ' 昏. 通过学习达到何种程度 3 6. 您想学习的其他电脑知识有 2 口不懂 口工作需要 口基本.. 口电脑入门 口编程知识 口基本 ' 撞撞口个人爱好口 ' 在练应用口镰作系统口图像设计 口熟练应用口驳得证书口专业水平口办公软件口网页设计 口精通某一领. 口多 ' 醒体设计口互联网知识 8 您比较喜欢哪些形式的学习方式 2 9. 您可以镰受的图书的价格是 : 10. 您从何处在夏知本公司产品倍息 g 利. 您对本书的满意度 您对钱们的建议 : 情剪下本页填写清楚'放入信封寄回7. I 匠 " 句您购买图书的阂. : 口书名 口内容简介 口封面. 口看图书 口 20 元以内 口报纸 杂志 口很满. 缅固及版式 团圆圆圆圆圆 读者服务部 巳作者 口出版机构 口网络宣传 口图书定价 口知名作家 学禽 的锥荐 11 书评 口上网学习 口用数学先. 口 30 元以内 口 50 元以内 口广捕 电视 口罔事戒朋友 ' 障糖 口较满窟 口 -A 量 收 贝占 -处亩北京 倍箱 m口印刷 辈革锁.. 口书店宣传口其他口参加精训班口 1 元以内口网魅口不满. 邮 邮政编码 : 口口口口口口 '谢谢!

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 odps-sdk 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基 开放数据处理服务 ODPS SDK SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基础功能的主体接口, 搜索关键词 "odpssdk-core" 一些

More information

Guava学习之Resources

Guava学习之Resources Resources 提供提供操作 classpath 路径下所有资源的方法 除非另有说明, 否则类中所有方法的参数都不能为 null 虽然有些方法的参数是 URL 类型的, 但是这些方法实现通常不是以 HTTP 完成的 ; 同时这些资源也非 classpath 路径下的 下面两个函数都是根据资源的名称得到其绝对路径, 从函数里面可以看出,Resources 类中的 getresource 函数都是基于

More information

Microsoft Word - 01.DOC

Microsoft Word - 01.DOC 第 1 章 JavaScript 简 介 JavaScript 是 NetScape 公 司 为 Navigator 浏 览 器 开 发 的, 是 写 在 HTML 文 件 中 的 一 种 脚 本 语 言, 能 实 现 网 页 内 容 的 交 互 显 示 当 用 户 在 客 户 端 显 示 该 网 页 时, 浏 览 器 就 会 执 行 JavaScript 程 序, 用 户 通 过 交 互 式 的

More information

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double

More information

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double x) { d

More information

1 4 1.1 4 1.2..4 2..4 2.1..4 3.4 3.1 Java.5 3.1.1..5 3.1.2 5 3.1.3 6 4.6 4.1 6 4.2.6 5 7 5.1..8 5.1.1 8 5.1.2..8 5.1.3..8 5.1.4..9 5.2..9 6.10 6.1.10

1 4 1.1 4 1.2..4 2..4 2.1..4 3.4 3.1 Java.5 3.1.1..5 3.1.2 5 3.1.3 6 4.6 4.1 6 4.2.6 5 7 5.1..8 5.1.1 8 5.1.2..8 5.1.3..8 5.1.4..9 5.2..9 6.10 6.1.10 Java V1.0.1 2007 4 10 1 4 1.1 4 1.2..4 2..4 2.1..4 3.4 3.1 Java.5 3.1.1..5 3.1.2 5 3.1.3 6 4.6 4.1 6 4.2.6 5 7 5.1..8 5.1.1 8 5.1.2..8 5.1.3..8 5.1.4..9 5.2..9 6.10 6.1.10 6.2.10 6.3..10 6.4 11 7.12 7.1

More information

chp6.ppt

chp6.ppt Java 软 件 设 计 基 础 6. 异 常 处 理 编 程 时 会 遇 到 如 下 三 种 错 误 : 语 法 错 误 (syntax error) 没 有 遵 循 语 言 的 规 则, 出 现 语 法 格 式 上 的 错 误, 可 被 编 译 器 发 现 并 易 于 纠 正 ; 逻 辑 错 误 (logic error) 即 我 们 常 说 的 bug, 意 指 编 写 的 代 码 在 执 行

More information

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例 帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例 这篇文章主要介绍了帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例, 本文还详细介绍了帝国 CMS 数据库类中的一些常用方法, 需要的朋友可以参考下 例 1: 连接 MYSQL 数据库例子 (a.php)

More information

EJB-Programming-4-cn.doc

EJB-Programming-4-cn.doc EJB (4) : (Entity Bean Value Object ) JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Session Bean J2EE Session Façade Design Pattern Session Bean Session

More information

untitled

untitled JavaEE+Android - 6 1.5-2 JavaEE web MIS OA ERP BOSS Android Android Google Map office HTML CSS,java Android + SQL Sever JavaWeb JavaScript/AJAX jquery Java Oracle SSH SSH EJB+JBOSS Android + 1. 2. IDE

More information

JavaIO.PDF

JavaIO.PDF O u t p u t S t ream j a v a. i o. O u t p u t S t r e a m w r i t e () f l u s h () c l o s e () public abstract void write(int b) throws IOException public void write(byte[] data) throws IOException

More information

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6

1.JasperReport ireport JasperReport ireport JDK JDK JDK JDK ant ant...6 www.brainysoft.net 1.JasperReport ireport...4 1.1 JasperReport...4 1.2 ireport...4 2....4 2.1 JDK...4 2.1.1 JDK...4 2.1.2 JDK...5 2.1.3 JDK...5 2.2 ant...6 2.2.1 ant...6 2.2.2 ant...6 2.3 JasperReport...7

More information

untitled

untitled 1 Outline 數 料 數 數 列 亂數 練 數 數 數 來 數 數 來 數 料 利 料 來 數 A-Z a-z _ () 不 數 0-9 數 不 數 SCHOOL School school 數 讀 school_name schoolname 易 不 C# my name 7_eleven B&Q new C# (1) public protected private params override

More information

水晶分析师

水晶分析师 大数据时代的挑战 产品定位 体系架构 功能特点 大数据处理平台 行业大数据应用 IT 基础设施 数据源 Hadoop Yarn 终端 统一管理和监控中心(Deploy,Configure,monitor,Manage) Master Servers TRS CRYSTAL MPP Flat Files Applications&DBs ETL&DI Products 技术指标 1 TRS

More information

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO

雲端 Cloud Computing 技術指南 運算 應用 平台與架構 10/04/15 11:55:46 INFO 10/04/15 11:55:53 INFO 10/04/15 11:55:56 INFO 10/04/15 11:56:05 INFO 10/04/15 11:56:07 INFO CHAPTER 使用 Hadoop 打造自己的雲 8 8.3 測試 Hadoop 雲端系統 4 Nodes Hadoop Map Reduce Hadoop WordCount 4 Nodes Hadoop Map/Reduce $HADOOP_HOME /home/ hadoop/hadoop-0.20.2 wordcount echo $ mkdir wordcount $ cd wordcount

More information

1 1 大概思路 创建 WebAPI 创建 CrossMainController 并编写 Nuget 安装 microsoft.aspnet.webapi.cors 跨域设置路由 编写 Jquery EasyUI 界面 运行效果 2 创建 WebAPI 创建 WebAPI, 新建 -> 项目 ->

1 1 大概思路 创建 WebAPI 创建 CrossMainController 并编写 Nuget 安装 microsoft.aspnet.webapi.cors 跨域设置路由 编写 Jquery EasyUI 界面 运行效果 2 创建 WebAPI 创建 WebAPI, 新建 -> 项目 -> 目录 1 大概思路... 1 2 创建 WebAPI... 1 3 创建 CrossMainController 并编写... 1 4 Nuget 安装 microsoft.aspnet.webapi.cors... 4 5 跨域设置路由... 4 6 编写 Jquery EasyUI 界面... 5 7 运行效果... 7 8 总结... 7 1 1 大概思路 创建 WebAPI 创建 CrossMainController

More information

Java Access 5-1 Server Client Client Server Server Client 5-2 DataInputStream Class java.io.datainptstream (extends) FilterInputStream InputStream Obj

Java Access 5-1 Server Client Client Server Server Client 5-2 DataInputStream Class java.io.datainptstream (extends) FilterInputStream InputStream Obj Message Transition 5-1 5-2 DataInputStream Class 5-3 DataOutputStream Class 5-4 PrintStream Class 5-5 (Message Transition) (Exercises) Java Access 5-1 Server Client Client Server Server Client 5-2 DataInputStream

More information

(TestFailure) JUnit Framework AssertionFailedError JUnit Composite TestSuite Test TestSuite run() run() JUnit

(TestFailure) JUnit Framework AssertionFailedError JUnit Composite TestSuite Test TestSuite run() run() JUnit Tomcat Web JUnit Cactus JUnit Java Cactus JUnit 26.1 JUnit Java JUnit JUnit Java JSP Servlet JUnit Java Erich Gamma Kent Beck xunit JUnit boolean JUnit Java JUnit Java JUnit Java 26.1.1 JUnit JUnit How

More information

Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7.

Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7. Kubernetes 包管理理 工具 Helm 蔺礼强 Kubenetes 系列列公开课 2 每周四晚 8 点档 1. Kubernetes 初探 2. 上 手 Kubernetes 3. Kubernetes 的资源调度 4. Kubernetes 的运 行行时 5. Kubernetes 的 网络管理理 6. Kubernetes 的存储管理理 7. Kubernetes

More information

长 安 大 学 硕 士 学 位 论 文 基 于 数 据 仓 库 和 数 据 挖 掘 的 行 为 分 析 研 究 姓 名 : 杨 雅 薇 申 请 学 位 级 别 : 硕 士 专 业 : 计 算 机 软 件 与 理 论 指 导 教 师 : 张 卫 钢 20100530 长安大学硕士学位论文 3 1 3系统架构设计 行为分析数据仓库的应用模型由四部分组成 如图3 3所示

More information

Microsoft PowerPoint - 05-Status-Codes-Chinese.ppt

Microsoft PowerPoint - 05-Status-Codes-Chinese.ppt 2004 Marty Hall 服务器响应的生成 : HTTP 状态代码 JSP, Servlet, & Struts Training Courses: http://courses.coreservlets.com Available in US, China, Taiwan, HK, and Worldwide 2 JSP and Servlet Books from Sun Press: http://www.coreservlets.com

More information

使用MapReduce读取XML文件

使用MapReduce读取XML文件 使用 MapReduce 读取 XML 文件 XML( 可扩展标记语言, 英语 :extensible Markup Language, 简称 : XML) 是一种标记语言, 也是行业标准数据交换交换格式, 它很适合在系统之间进行数据存储和交换 ( 话说 Hadoop H ive 等的配置文件就是 XML 格式的 ) 本文将介绍如何使用 MapReduce 来读取 XML 文件 但是 Had oop

More information

第四章 102 图 4唱16 基于图像渲染的理论基础 三张拍摄图像以及它们投影到球面上生成的球面图像 拼图的圆心是相同的 而拼图是由球面图像上的弧线图像组成的 因此我 们称之为同心球拼图 如图 4唱18 所示 这些拼图中半径最大的是圆 Ck 最小的是圆 C0 设圆 Ck 的半径为 r 虚拟相机水平视域为 θ 有 r R sin θ 2 4畅11 由此可见 构造同心球拼图的过程实际上就是对投影图像中的弧线图像

More information

Chapter 9: Objects and Classes

Chapter 9: Objects and Classes Java application Java main applet Web applet Runnable Thread CPU Thread 1 Thread 2 Thread 3 CUP Thread 1 Thread 2 Thread 3 ,,. (new) Thread (runnable) start( ) CPU (running) run ( ) blocked CPU sleep(

More information

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点

OOP with Java 通知 Project 4: 5 月 2 日晚 9 点 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 5 月 2 日晚 9 点 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double x) { d =

More information

(HMI) IO A

(HMI) IO A 6.5 6.5 (HMI) IO 6.52 6.52 6.5 2007 113 A 602 100086 010 82616619 010 62638166 www.kingview.com 4 7 25 38 43 52 63 68 86 SQL 95 99 WEB 105 Web Web Web I/O Microsoft Windows XP/NT/2000 I/O PLC PLC PLC PLC

More information

javaexample-02.pdf

javaexample-02.pdf n e w. s t a t i c s t a t i c 3 1 3 2 p u b l i c p r i v a t e p r o t e c t e d j a v a. l a n g. O b j e c t O b j e c t Rect R e c t x 1 y 1 x 2 y 2 R e c t t o S t r i n g ( ) j a v a. l a n g. O

More information

《大话设计模式》第一章

《大话设计模式》第一章 第 1 章 代 码 无 错 就 是 优? 简 单 工 厂 模 式 1.1 面 试 受 挫 小 菜 今 年 计 算 机 专 业 大 四 了, 学 了 不 少 软 件 开 发 方 面 的 东 西, 也 学 着 编 了 些 小 程 序, 踌 躇 满 志, 一 心 要 找 一 个 好 单 位 当 投 递 了 无 数 份 简 历 后, 终 于 收 到 了 一 个 单 位 的 面 试 通 知, 小 菜 欣 喜

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 49 [P.51] C/C++ [P.52] [P.53] [P.55] (int) [P.57] (float/double) [P.58] printf scanf [P.59] [P.61] ( / ) [P.62] (char) [P.65] : +-*/% [P.67] : = [P.68] : ,

More information

1: public class MyOutputStream implements AutoCloseable { 3: public void close() throws IOException { 4: throw new IOException(); 5: } 6:

1: public class MyOutputStream implements AutoCloseable { 3: public void close() throws IOException { 4: throw new IOException(); 5: } 6: Chapter 15. Suppressed Exception CH14 Finally Block Java SE 7 try-with-resources JVM cleanup try-with-resources JVM cleanup cleanup Java SE 7 Throwable getsuppressed Throwable[] getsuppressed() Suppressed

More information

提问袁小兵:

提问袁小兵: C++ 面 试 试 题 汇 总 柯 贤 富 管 理 软 件 需 求 分 析 篇 1. STL 类 模 板 标 准 库 中 容 器 和 算 法 这 部 分 一 般 称 为 标 准 模 板 库 2. 为 什 么 定 义 虚 的 析 构 函 数? 避 免 内 存 问 题, 当 你 可 能 通 过 基 类 指 针 删 除 派 生 类 对 象 时 必 须 保 证 基 类 析 构 函 数 为 虚 函 数 3.

More information

res/layout 目录下的 main.xml 源码 : <?xml version="1.0" encoding="utf 8"?> <TabHost android:layout_height="fill_parent" xml

res/layout 目录下的 main.xml 源码 : <?xml version=1.0 encoding=utf 8?> <TabHost android:layout_height=fill_parent xml 拓展训练 1- 界面布局 1. 界面布局的重要性做应用程序, 界面是最基本的 Andorid 的界面, 需要写在 res/layout 的 xml 里面, 一般情况下一个 xml 对应一个界面 Android 界面布局有点像写 html( 连注释代码的方式都一样 ), 要先给 Android 定框架, 然后再在框架里面放控件,Android 提供了几种框架,AbsoluteLayout,LinearLayout,

More information

Chapter #

Chapter # 第三章 TCP/IP 协议栈 本章目标 通过本章的学习, 您应该掌握以下内容 : 掌握 TCP/IP 分层模型 掌握 IP 协议原理 理解 OSI 和 TCP/IP 模型的区别和联系 TCP/IP 介绍 主机 主机 Internet TCP/IP 早期的协议族 全球范围 TCP/IP 协议栈 7 6 5 4 3 应用层表示层会话层传输层网络层 应用层 主机到主机层 Internet 层 2 1 数据链路层

More information

无类继承.key

无类继承.key 无类继承 JavaScript 面向对象的根基 周爱 民 / aimingoo aiming@gmail.com https://aimingoo.github.io https://github.com/aimingoo rand = new Person("Rand McKinnon",... https://docs.oracle.com/cd/e19957-01/816-6408-10/object.htm#1193255

More information

本章学习目标 小风 Java 实战系列教程 SpringMVC 简介 SpringMVC 的入门案例 SpringMVC 流程分析 配置注解映射器和适配器 注解的使用 使用不同方式的跳转页面 1. SpringMVC 简介 Spring web mvc

本章学习目标 小风 Java 实战系列教程 SpringMVC 简介 SpringMVC 的入门案例 SpringMVC 流程分析 配置注解映射器和适配器 注解的使用 使用不同方式的跳转页面 1. SpringMVC 简介 Spring web mvc 本章学习目标 SpringMVC 简介 SpringMVC 的入门案例 SpringMVC 流程分析 配置注解映射器和适配器 配置视图解析器 @RequestMapping 注解的使用 使用不同方式的跳转页面 1. SpringMVC 简介 Spring web mvc 和 Struts2 都属于表现层的框架, 它是 Spring 框架的一部分, 我们可 以从 Spring 的整体结构中看得出来 :

More information

RUN_PC連載_12_.doc

RUN_PC連載_12_.doc PowerBuilder 8 (12) PowerBuilder 8.0 PowerBuilder PowerBuilder 8 PowerBuilder 8 / IDE PowerBuilder PowerBuilder 8.0 PowerBuilder PowerBuilder PowerBuilder PowerBuilder 8.0 PowerBuilder 6 PowerBuilder 7

More information

ChinaBI企业会员服务- BI企业

ChinaBI企业会员服务- BI企业 商业智能 (BI) 开源工具 Pentaho BisDemo 介绍及操作说明 联系人 : 杜号权苏州百咨信息技术有限公司电话 : 0512-62861389 手机 :18616571230 QQ:37971343 E-mail:du.haoquan@bizintelsolutions.com 权限控制管理 : 权限控制管理包括 : 浏览权限和数据权限 ( 权限部分两个角色 :ceo,usa; 两个用户

More information

拦截器(Interceptor)的学习

拦截器(Interceptor)的学习 二 拦截器 (Interceptor) 的学习 拦截器可以监听程序的一个或所有方法 拦截器对方法调用流提供了细粒度控制 可以在无状态会话 bean 有状态会话 bean 和消息驱动 bean 上使用它们 拦截器可以是同一 bean 类中的方法或是一个外部类 下面介绍如何在 Session Bean 类中使用外部拦截器类 @Interceptors 注释指定一个或多个在外部类中定义的拦截器 下面拦截器

More information

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课 复习 Java 包 创建包 : package 语句, 包结构与目录结构一致 使用包 : import restaurant/ - people/ - Cook.class - Waiter.class - tools/ - Fork.class

More information

MASQUERADE # iptables -t nat -A POSTROUTING -s / o eth0 -j # sysctl net.ipv4.ip_forward=1 # iptables -P FORWARD DROP #

MASQUERADE # iptables -t nat -A POSTROUTING -s / o eth0 -j # sysctl net.ipv4.ip_forward=1 # iptables -P FORWARD DROP # iptables 默认安全规则脚本 一 #nat 路由器 ( 一 ) 允许路由 # iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT ( 二 ) DNAT 与端口转发 1 启用 DNAT 转发 # iptables -t nat -A PREROUTING -p tcp -d 192.168.102.37 dprot 422 -j DNAT to-destination

More information

99 cjt h 7. 0 (8 ) 0 () abc a b c abc0 aaa 0 a () bca abc0 aa0 a0 0 a0 abc a789 a b c (8 ) 9!

99 cjt h 7. 0 (8 ) 0 () abc a b c abc0 aaa 0 a () bca abc0 aa0 a0 0 a0 abc a789 a b c (8 ) 9! 99 cjt h. 4 (79 ) 4 88 88. 0 0 7 7 7 ( ) (80 ). ( ) (8 ) 4! ( ) 0 4 0 4. n (x)(x) (x) n x an bn cnd abcd (8 ) () adbc () acbd () ac (4) db0 () abcd (x)(x) (x) n n ( x)[ ( x) ] ( x) ( x) ( x) x) ( x) n

More information

一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页

一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页 第 1 页共 32 页 crm Mobile V1.0 for IOS 用户手册 一 登录 crm Mobile 系统 : 输入 ShijiCare 用户名和密码, 登录系统, 如图所示 : 第 2 页共 32 页 二 crm Mobile 界面介绍 : 第 3 页共 32 页 三 新建 (New) 功能使用说明 1 选择产品 第 4 页共 32 页 2 填写问题的简要描述和详细描述 第 5 页共

More information

IoC容器和Dependency Injection模式.doc

IoC容器和Dependency Injection模式.doc IoC Dependency Injection /Martin Fowler / Java Inversion of Control IoC Dependency Injection Service Locator Java J2EE open source J2EE J2EE web PicoContainer Spring Java Java OO.NET service component

More information

FileMaker 16 ODBC 和 JDBC 指南

FileMaker 16 ODBC 和 JDBC 指南 FileMaker 16 ODBC JDBC 2004-2017 FileMaker, Inc. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, California 95054 FileMaker FileMaker Go FileMaker, Inc. FileMaker WebDirect FileMaker Cloud FileMaker,

More information

13 根 据 各 种 网 络 商 务 信 息 对 不 同 用 户 所 产 生 的 使 用 效 用, 网 络 商 务 信 息 大 致 可 分 为 四 级, 其 中 占 比 重 最 大 的 是 ( A ) A 第 一 级 免 费 信 息 B 第 二 级 低 收 费 信 息 C 第 三 级 标 准 收 费

13 根 据 各 种 网 络 商 务 信 息 对 不 同 用 户 所 产 生 的 使 用 效 用, 网 络 商 务 信 息 大 致 可 分 为 四 级, 其 中 占 比 重 最 大 的 是 ( A ) A 第 一 级 免 费 信 息 B 第 二 级 低 收 费 信 息 C 第 三 级 标 准 收 费 助 理 电 子 商 务 考 试 真 题 试 题 第 一 部 分 : 理 论 部 分 一 单 项 选 择 题 1 ( B ) 是 信 息 系 统 的 核 心 组 成 部 分 A 逻 辑 模 型 B 数 据 库 C 概 念 模 型 D 以 上 全 部 2 ping www.163.com -t 中 参 数 t 的 作 用 是 :( A ) A 进 行 连 续 测 试 B 在 新 窗 口 中 显 示 C

More information

中 文 摘 要 智 慧 型 手 機 由 於 有 強 大 的 功 能, 以 及 優 渥 的 便 利 性, 還 能 與 網 路 保 持 隨 時 的 鏈 結 與 同 步 更 新, 因 此 深 受 廣 大 消 費 者 喜 愛, 當 然, 手 機 遊 戲 也 成 為 現 代 人 不 可 或 缺 的 娛 樂 之

中 文 摘 要 智 慧 型 手 機 由 於 有 強 大 的 功 能, 以 及 優 渥 的 便 利 性, 還 能 與 網 路 保 持 隨 時 的 鏈 結 與 同 步 更 新, 因 此 深 受 廣 大 消 費 者 喜 愛, 當 然, 手 機 遊 戲 也 成 為 現 代 人 不 可 或 缺 的 娛 樂 之 臺 北 市 大 安 高 級 工 業 職 業 學 校 資 訊 科 一 百 零 一 學 年 度 專 題 製 作 報 告 ------ 以 Android 製 作 ------ ----- 連 線 塔 防 遊 戲 ------ Tower defense game using Internet technology 班 級 : 資 訊 三 甲 組 別 : A9 組 組 員 : 葉 冠 麟 (9906129)

More information

全国计算机技术与软件专业技术资格(水平)考试

全国计算机技术与软件专业技术资格(水平)考试 全 国 计 算 机 技 术 与 软 件 专 业 技 术 资 格 ( 水 平 ) 考 试 2008 年 上 半 年 程 序 员 下 午 试 卷 ( 考 试 时 间 14:00~16:30 共 150 分 钟 ) 试 题 一 ( 共 15 分 ) 阅 读 以 下 说 明 和 流 程 图, 填 补 流 程 图 中 的 空 缺 (1)~(9), 将 解 答 填 入 答 题 纸 的 对 应 栏 内 [ 说 明

More information

Converting image (bmp/jpg) file into binary format

Converting image (bmp/jpg) file into binary format RAiO Image Tool 操作说明 Version 1.0 July 26, 2016 RAiO Technology Inc. Copyright RAiO Technology Inc. 2013 RAiO TECHNOLOGY INC. www.raio.com.tw Revise History Version Date Description 0.1 September 01, 2014

More information

第3章.doc

第3章.doc 3 3 3 3.1 3 IT Trend C++ Java SAP Advantech ERPCRM C++ C++ Synopsys C++ NEC C C++PHP C++Java C++Java VIA C++ 3COM C++ SPSS C++ Sybase C++LinuxUNIX Motorola C++ IBM C++Java Oracle Java HP C++ C++ Yahoo

More information

基于CDIO一体化理念的课程教学大纲设计

基于CDIO一体化理念的课程教学大纲设计 Java 语 言 程 序 设 计 课 程 教 学 大 纲 Java 语 言 程 序 设 计 课 程 教 学 大 纲 一 课 程 基 本 信 息 1. 课 程 代 码 :52001CC022 2. 课 程 名 称 :Java 语 言 程 序 设 计 3. 课 程 英 文 名 称 :Java Programming 4. 课 程 类 别 : 理 论 课 ( 含 实 验 上 机 或 实 践 ) 5. 授

More information

胡 鑫 陈兴蜀 王海舟 刘 磊 利用基于协议分析和逆向工程的主动测量方法对 点播系统进行了研究 通过对 点播协议进行分析 获悉该协议的通信格式和语义信息 总结出了 点播系统的工作原理 在此基础上设计并实现了基于分布式网络爬虫的 点播系统主动测量平台 并对该平台获取的用户数据进行统计分析 获得了 点播系统部分用户行为特征 研究结果对 点播系统的监控及优化提供了研究方法 点播 协议分析 爬虫 主动测量

More information

untitled

untitled 1 行 行 行 行.NET 行 行 類 來 行 行 Thread 類 行 System.Threading 來 類 Thread 類 (1) public Thread(ThreadStart start ); Name 行 IsAlive 行 行狀 Start 行 行 Suspend 行 Resume 行 行 Thread 類 (2) Sleep 行 CurrentThread 行 ThreadStart

More information

Microsoft PowerPoint - string_kruse [兼容模式]

Microsoft PowerPoint - string_kruse [兼容模式] Strings Strings in C not encapsulated Every C-string has type char *. Hence, a C-string references an address in memory, the first of a contiguous set of bytes that store the characters making up the string.

More information

Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0,

Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0, http://debut.cis.nctu.edu.tw/~chi Java java.lang.math Java Java.util.Random : ArithmeticException int zero = 0; try { int i= 72 / zero ; }catch (ArithmeticException e ) { // } 0, : POSITIVE_INFINITY NEGATIVE_INFINITY

More information

EJB-Programming-3.PDF

EJB-Programming-3.PDF :, JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Value Object Design Pattern J2EE Design Patterns Value Object Value Object Factory J2EE EJB Test Client

More information

FY.DOC

FY.DOC 高 职 高 专 21 世 纪 规 划 教 材 C++ 程 序 设 计 邓 振 杰 主 编 贾 振 华 孟 庆 敏 副 主 编 人 民 邮 电 出 版 社 内 容 提 要 本 书 系 统 地 介 绍 C++ 语 言 的 基 本 概 念 基 本 语 法 和 编 程 方 法, 深 入 浅 出 地 讲 述 C++ 语 言 面 向 对 象 的 重 要 特 征 : 类 和 对 象 抽 象 封 装 继 承 等 主

More information

(Microsoft Word - \272\364\263q\245|\244A_49636107_\304\254\253\330\336\263__\272\353\302\262\263\370\247i.doc)

(Microsoft Word - \272\364\263q\245|\244A_49636107_\304\254\253\330\336\263__\272\353\302\262\263\370\247i.doc) SCJP (Oracle Certified Professional, Java SE5/6 Programmer) 學 制 / 班 級 : 四 年 制 / 網 通 四 乙 指 導 老 師 : 方 信 普 老 師 學 生 學 號 / 姓 名 : 49636107 蘇 建 瑋 繳 交 年 份 : 100 年 6 月 一 SCJP 介 紹 SCJP 是 Sun Certified Java Programmer

More information

概述

概述 OPC Version 1.6 build 0910 KOSRDK Knight OPC Server Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOS_Init...5 2.2.2 KOS_InitB...5 2.2.3

More information

3.1 num = 3 ch = 'C' 2

3.1 num = 3 ch = 'C' 2 Java 1 3.1 num = 3 ch = 'C' 2 final 3.1 final : final final double PI=3.1415926; 3 3.2 4 int 3.2 (long int) (int) (short int) (byte) short sum; // sum 5 3.2 Java int long num=32967359818l; C:\java\app3_2.java:6:

More information

Spark读取Hbase中的数据

Spark读取Hbase中的数据 Spark 读取 Hbase 中的数据 Spark 和 Flume-ng 整合, 可以参见本博客 : Spark 和 Flume-ng 整合 使用 Spark 读取 HBase 中的数据 如果想及时了解 Spark Hadoop 或者 Hbase 相关的文章, 欢迎关注微信公共帐号 :iteblog_hadoop 大家可能都知道很熟悉 Spark 的两种常见的数据读取方式 ( 存放到 RDD 中 ):(1)

More information

Office Office Office Microsoft Word Office Office Azure Office One Drive 2 app 3 : [5] 3, :, [6]; [5], ; [8], [1], ICTCLAS(Institute of Computing Tech

Office Office Office Microsoft Word Office Office Azure Office One Drive 2 app 3 : [5] 3, :, [6]; [5], ; [8], [1], ICTCLAS(Institute of Computing Tech - OfficeCoder 1 2 3 4 1,2,3,4 xingjiarong@mail.sdu.edu.cn 1 xuchongyang@mail.sdu.edu.cn 2 sun.mc@outlook.com 3 luoyuanhang@mail.sdu.edu.cn 4 Abstract. Microsoft Word 2013 Word 2013 Office Keywords:,, HTML5,

More information

校友会系统白皮书feb_08

校友会系统白皮书feb_08 硕 士 研 究 生 招 生 管 理 系 统 1 产 品 白 皮 书 希 尔 数 字 校 园 硕 士 研 究 生 招 生 管 理 系 统 白 皮 书 目 录 1 产 品 概 述... 1 1.1 产 品 简 介... 1 1.2 应 用 范 围... 1 2 产 品 功 能 结 构 图... 2 3 产 品 功 能... 3 3.1 系 统 设 置... 3 3.2 信 息 发 布... 3 3.3

More information

untitled

untitled 1 Outline 料 類 說 Tang, Shih-Hsuan 2006/07/26 ~ 2006/09/02 六 PM 7:00 ~ 9:30 聯 ives.net@gmail.com www.csie.ntu.edu.tw/~r93057/aspnet134 度 C# 力 度 C# Web SQL 料 DataGrid DataList 參 ASP.NET 1.0 C# 例 ASP.NET 立

More information

山东2014第四季新教材《会计基础》冲刺卷第二套

山东2014第四季新教材《会计基础》冲刺卷第二套 2016 年 会 计 从 业 考 试 会 计 基 础 冲 刺 卷 2 一 单 项 选 择 题 ( 本 题 共 20 小 题, 每 小 题 1 分, 共 20 分 在 下 列 每 小 题 的 备 选 项 中, 有 且 只 有 一 个 选 项 是 最 符 合 题 目 要 求 的, 请 将 正 确 答 案 前 的 英 文 字 母 填 入 题 后 的 括 号 内, 不 选 错 选 均 不 得 分 ) 1.

More information

. (A) (B) (C) A (D) (E). (A)(B)(C)(D)(E) A

. (A) (B) (C) A (D) (E). (A)(B)(C)(D)(E) A . () () () () () (A) (B) (C) B (D) (E). (A) (B) (C) E (D) (E) (A) (B) (C) (D). () () () () E (A) (B) (C) (D) (E). C (A) (B) (C) (D) (E). (A) (B) (C) (D) D (E). () - () - () - () - () - D (A) (B) (C) (D)

More information

通过Hive将数据写入到ElasticSearch

通过Hive将数据写入到ElasticSearch 我在 使用 Hive 读取 ElasticSearch 中的数据 文章中介绍了如何使用 Hive 读取 ElasticSearch 中的数据, 本文将接着上文继续介绍如何使用 Hive 将数据写入到 ElasticSearch 中 在使用前同样需要加入 elasticsearch-hadoop-2.3.4.jar 依赖, 具体请参见前文介绍 我们先在 Hive 里面建个名为 iteblog 的表,

More information

Learning Java

Learning Java Java Introduction to Java Programming (Third Edition) Prentice-Hall,Inc. Y.Daniel Liang 2001 Java 2002.2 Java2 2001.10 Java2 Philip Heller & Simon Roberts 1999.4 Java2 2001.3 Java2 21 2002.4 Java UML 2002.10

More information

epub 61-2

epub 61-2 2 Web Dreamweaver UltraDev Dreamweaver 3 We b We b We Dreamweaver UltraDev We b Dreamweaver UltraDev We b We b 2.1 Web We b We b D r e a m w e a v e r J a v a S c r i p t We b We b 2.1.1 Web We b C C +

More information

FileMaker 15 ODBC 和 JDBC 指南

FileMaker 15 ODBC 和 JDBC 指南 FileMaker 15 ODBC JDBC 2004-2016 FileMaker, Inc. FileMaker, Inc. 5201 Patrick Henry Drive Santa Clara, California 95054 FileMaker FileMaker Go FileMaker, Inc. / FileMaker WebDirect FileMaker, Inc. FileMaker

More information

三种方法实现Hadoop(MapReduce)全局排序(1)

三种方法实现Hadoop(MapReduce)全局排序(1) 三种方法实现 Hadoop(MapReduce) 全局排序 () 三种方法实现 Hadoop(MapReduce) 全局排序 () 我们可能会有些需求要求 MapReduce 的输出全局有序, 这里说的有序是指 Key 全局有序 但是我们知道,MapReduce 默认只是保证同一个分区内的 Key 是有序的, 但是不保证全局有序 基于此, 本文提供三种方法来对 MapReduce 的输出进行全局排序

More information

/ / (FC 3)...

/ / (FC 3)... Modbus/TCP 1.0 1999 3 29 Andy Swales Schneider aswales@modicon.com ... 2 1.... 3 2.... 3 2.1.. 3 2.2..4 2.3..4 2.4... 5 3.... 5 3.1 0... 5 3.2 1... 5 3.3 2... 6 3.4 / /... 7 4.... 7 5.... 8 5.1 0... 9

More information

sql> startup mount 改变数据库的归档模式 sql> alter database archivelog # 打开数据库 sql> alter database open 禁止归档模式 sql> shutdown immediate sql>startup mount sql> al

sql> startup mount 改变数据库的归档模式 sql> alter database archivelog # 打开数据库 sql> alter database open 禁止归档模式 sql> shutdown immediate sql>startup mount sql> al RMAN sql> sqlplus / as sysdba 查看数据库版本 sql> select * from v$version; 查看数据库名称 sql> show parameter db_name; 一 使用 RMAN 时, 需要将数据库设置成归档模式 sql> conn / as sysdba; sql> show user 查看数据库是否为归档模式 sql> archive log list

More information

untitled

untitled ArcGIS Server Web services Web services Application Web services Web Catalog ArcGIS Server Web services 6-2 Web services? Internet (SOAP) :, : Credit card authentication, shopping carts GIS:, locator services,

More information

软件工程文档编制

软件工程文档编制 实训抽象类 一 实训目标 掌握抽象类的定义 使用 掌握运行时多态 二 知识点 抽象类的语法格式如下 : public abstract class ClassName abstract void 方法名称 ( 参数 ); // 非抽象方法的实现代码 在使用抽象类时需要注意如下几点 : 1 抽象类不能被实例化, 实例化的工作应该交由它的子类来完成 2 抽象方法必须由子类来进行重写 3 只要包含一个抽象方法的抽象类,

More information

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式] Arrays and Strings 存储同类型的多个元素 Store multi elements of the same type 数组 (array) 存储固定数目的同类型元素 如整型数组存储的是一组整数, 字符数组存储的是一组字符 数组的大小称为数组的尺度 (dimension). 定义格式 : type arrayname[dimension]; 如声明 4 个元素的整型数组 :intarr[4];

More information

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション Perl CGI 1 Perl CGI 2 Perl CGI 3 Perl CGI 4 1. 2. 1. #!/usr/local/bin/perl 2. print "Content-type: text/html n n"; 3. print " n"; 4. print " n"; 3. 4.

More information

關於本書 Part 3 CSS XHTML Ajax Part 4 HTML 5 API JavaScript HTML 5 API Canvas API ( ) Video/Audio API ( ) Drag and Drop API ( ) Geolocation API ( ) Part 5

關於本書 Part 3 CSS XHTML Ajax Part 4 HTML 5 API JavaScript HTML 5 API Canvas API ( ) Video/Audio API ( ) Drag and Drop API ( ) Geolocation API ( ) Part 5 網頁程式設計 HTML JavaScript CSS HTML JavaScript CSS HTML 5 JavaScript JavaScript HTML 5 API CSS CSS Part 1 HTML HTML 5 API HTML 5 Apple QuickTime Adobe Flash RealPlayer Ajax XMLHttpRequest HTML 4.01 HTML 5

More information

温州市政府分散采购

温州市政府分散采购 温 州 市 政 府 分 散 采 购 招 标 文 件 招 标 编 号 :F - G B 2 0 1 6 0 3 1 4 0 0 4 7 招 标 项 目 : 温 州 市 人 民 政 府 办 公 室 政 务 云 平 台 ( 重 ) 招 标 方 式 : 公 开 招 标 招 标 人 : 温 州 市 人 民 政 府 办 公 室 招 标 代 理 : 二 〇 一 六 年 三 月 目 录 投 标 保 证 金 办 理

More information

上海市教育考试院关于印发新修订的

上海市教育考试院关于印发新修订的 沪 教 考 院 社 考 2012 7 号 上 海 市 教 育 考 试 院 关 于 印 发 上 海 市 高 等 学 校 计 算 机 等 级 考 试 大 纲 (2012 年 修 订 ) 的 通 知 各 有 关 高 校 : 为 进 一 步 加 强 本 市 高 校 计 算 机 基 础 教 学 工 作, 推 进 学 校 更 加 科 学 合 理 地 设 置 计 算 机 基 础 课 程 及 安 排 教 学 内 容,

More information

untitled

untitled 4.1AOP AOP Aspect-oriented programming AOP 來說 AOP 令 理 Cross-cutting concerns Aspect Weave 理 Spring AOP 來 AOP 念 4.1.1 理 AOP AOP 見 例 來 例 錄 Logging 錄 便 來 例 行 留 錄 import java.util.logging.*; public class HelloSpeaker

More information

ebook39-6

ebook39-6 6 first-in-first-out, FIFO L i n e a r L i s t 3-1 C h a i n 3-8 5. 5. 3 F I F O L I F O 5. 5. 6 5. 5. 6.1 [ ] q u e n e ( r e a r ) ( f r o n t 6-1a A 6-1b 6-1b D C D 6-1c a) b) c) 6-1 F I F O L I F ADT

More information

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO Car DVD New GUI IR Flow User Manual V0.1 Jan 25, 2008 19, Innovation First Road Science Park Hsin-Chu Taiwan 300 R.O.C. Tel: 886-3-578-6005 Fax: 886-3-578-4418 Web: www.sunplus.com Important Notice SUNPLUS

More information

C 1

C 1 C homepage: xpzhangme 2018 5 30 C 1 C min(x, y) double C // min c # include # include double min ( double x, double y); int main ( int argc, char * argv []) { double x, y; if( argc!=

More information

2005 Sun Microsystems, Inc Network Circle, Santa Clara, CA U.S.A. Sun Sun Berkeley BSD UNIX X/Open Company, Ltd. / Sun Sun Microsystems Su

2005 Sun Microsystems, Inc Network Circle, Santa Clara, CA U.S.A. Sun Sun Berkeley BSD UNIX X/Open Company, Ltd. / Sun Sun Microsystems Su Java Desktop System Sun Microsystems, Inc. 4150 Network Circle Santa Clara, CA 95054 U.S.A. : 819 0675 10 2005 2 2005 Sun Microsystems, Inc. 4150 Network Circle, Santa Clara, CA 95054 U.S.A. Sun Sun Berkeley

More information

提纲 1 2 OS Examples for 3

提纲 1 2 OS Examples for 3 第 4 章 Threads2( 线程 2) 中国科学技术大学计算机学院 October 28, 2009 提纲 1 2 OS Examples for 3 Outline 1 2 OS Examples for 3 Windows XP Threads I An Windows XP application runs as a seperate process, and each process may

More information

1. 访 问 最 新 发 行 公 告 信 息 jconnect for JDBC 7.0 1. 访 问 最 新 发 行 公 告 信 息 最 新 版 本 的 发 行 公 告 可 以 从 网 上 获 得 若 要 查 找 在 本 产 品 发 布 后 增 加 的 重 要 产 品 或 文 档 信 息, 请 访

1. 访 问 最 新 发 行 公 告 信 息 jconnect for JDBC 7.0 1. 访 问 最 新 发 行 公 告 信 息 最 新 版 本 的 发 行 公 告 可 以 从 网 上 获 得 若 要 查 找 在 本 产 品 发 布 后 增 加 的 重 要 产 品 或 文 档 信 息, 请 访 发 行 公 告 jconnect for JDBC 7.0 文 档 ID:DC74874-01-0700-01 最 后 修 订 日 期 :2010 年 3 月 2 日 主 题 页 码 1. 访 问 最 新 发 行 公 告 信 息 2 2. 产 品 摘 要 2 3. 特 殊 安 装 说 明 2 3.1 查 看 您 的 jconnect 版 本 3 4. 特 殊 升 级 指 导 3 4.1 迁 移 3

More information

KillTest 质量更高 服务更好 学习资料 半年免费更新服务

KillTest 质量更高 服务更好 学习资料   半年免费更新服务 KillTest 质量更高 服务更好 学习资料 http://www.killtest.cn 半年免费更新服务 Exam : 310-055Big5 Title : Sun Certified Programmer for the Java 2 Platform.SE 5.0 Version : Demo 1 / 22 1. 11. public static void parse(string str)

More information

TwinCAT 1. TwinCAT TwinCAT PLC PLC IEC TwinCAT TwinCAT Masc

TwinCAT 1. TwinCAT TwinCAT PLC PLC IEC TwinCAT TwinCAT Masc TwinCAT 2001.12.11 TwinCAT 1. TwinCAT... 3 2.... 4... 4...11 3. TwinCAT PLC... 13... 13 PLC IEC 61131-3... 14 4. TwinCAT... 17... 17 5. TwinCAT... 18... 18 6.... 19 Maschine.pro... 19... 27 7.... 31...

More information

深入理解otter

深入理解otter 深 入 理 解 otter 七 锋 2013-07-04 Agenda 1. 中 美 同 步 需 求 2. otter 架 构 & 设 计 o o o o o o o o 如 何 解 决 " 差 " 网 络 如 何 避 免 双 向 回 环 如 何 处 理 数 据 一 致 性 如 何 高 效 同 步 数 据 如 何 高 效 同 步 文 件 如 何 支 持 系 统 HA 如 何 处 理 特 殊 业 务

More information

目 录(目录名)

目  录(目录名) 目录 目录...1-1 1.1 域名解析配置命令... 1-1 1.1.1 display dns domain... 1-1 1.1.2 display dns dynamic-host... 1-1 1.1.3 display dns server... 1-2 1.1.4 display ip host... 1-3 1.1.5 dns domain... 1-4 1.1.6 dns resolve...

More information

XXXXXXXX http://cdls.nstl.gov.cn 2 26

XXXXXXXX http://cdls.nstl.gov.cn 2 26 [ ] [ ] 2003-7-18 1 26 XXXXXXXX http://cdls.nstl.gov.cn 2 26 (2003-7-18) 1...5 1.1...5 1.2...5 1.3...5 2...6 2.1...6 2.2...6 2.3...6 3...7 3.1...7 3.1.1...7 3.1.2...7 3.1.2.1...7 3.1.2.1.1...8 3.1.2.1.2...10

More information

2 Java 语 言 程 序 设 计 教 程 1.2.1 简 单 性 Java 语 言 的 语 法 与 C 语 言 和 C++ 语 言 很 接 近, 使 得 大 多 数 程 序 员 很 容 易 学 习 和 使 用 Java 另 一 方 面,Java 丢 弃 了 C++ 中 很 少 使 用 的 很 难

2 Java 语 言 程 序 设 计 教 程 1.2.1 简 单 性 Java 语 言 的 语 法 与 C 语 言 和 C++ 语 言 很 接 近, 使 得 大 多 数 程 序 员 很 容 易 学 习 和 使 用 Java 另 一 方 面,Java 丢 弃 了 C++ 中 很 少 使 用 的 很 难 第 1 章 Java 概 述 Java 的 诞 生 Java 的 特 点 Java 开 发 环 境 安 装 与 配 置 创 建 并 运 行 一 个 简 单 的 Java 程 序 Java 语 言 是 当 今 计 算 机 软 件 行 业 中 最 热 门 的 网 络 编 程 语 言, 以 Java 为 核 心 的 芯 片 技 术 编 译 技 术 数 据 库 连 接 技 术, 以 及 基 于 企 业 级

More information

内 容 协 作 平 台 TRS WCM 6.5 北 京 拓 尔 思 信 息 技 术 股 份 有 限 公 司 Beijing TRS Information Technology Co., Ltd 版 权 说 明 本 手 册 由 北 京 拓 尔 思 信 息 技 术 股 份 有 限 公 司 ( 以 下 简 称 TRS 公 司 ) 出 版, 版 权 属 TRS 公 司 所 有 未 经 出 版 者 正 式

More information

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1 21 , 7, Windows,,,, : 010-62782989 13501256678 13801310933,,,, ;,, ( CIP) /,,. : ;, 2005. 11 ( 21 ) ISBN 7-81082 - 634-4... - : -. TP316-44 CIP ( 2005) 123583 : : : : 100084 : 010-62776969 : 100044 : 010-51686414

More information

使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款

使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款 JAVA 程 序 设 计 ( 肆 ) 徐 东 / 数 学 系 使 用 Java 语 言 模 拟 保 险 箱 容 量 门 板 厚 度 箱 体 厚 度 属 性 锁 具 类 型 开 保 险 箱 关 保 险 箱 动 作 存 取 款 使 用 Java class 代 表 保 险 箱 public class SaveBox 类 名 类 类 体 实 现 封 装 性 使 用 class SaveBox 代 表 保

More information

p.2 1 <HTML> 2 3 <HEAD> 4 <TITLE> </TITLE> 5 </HEAD> 6 7 <BODY> 8 <H3><B> </B></H3> 9 <H4><I> </I></H4> 10 </BODY> </HTML> 1. HTML 1. 2.

p.2 1 <HTML> 2 3 <HEAD> 4 <TITLE> </TITLE> 5 </HEAD> 6 7 <BODY> 8 <H3><B> </B></H3> 9 <H4><I> </I></H4> 10 </BODY> </HTML> 1. HTML 1. 2. 2005-06 p.1 HTML HyperText Mark-up Language 1. HTML Logo, Pascal, C++, Java HTML 2. HTML (tag) 3. HTML 4. HTML 1. HTML 2. 3. FTP HTML HTML html 1. html html html cutehtmleasyhtml 2. wyswyg (What you see

More information

Microsoft Word - json入门.doc

Microsoft Word - json入门.doc Json 入门 送给亲爱的女朋友, 祝她天天快乐 作者 :hlz QQ:81452743 MSN/Email:hulizhong2008@163.com json 入门 (1) json 是 JavaScript Object Notation 的简称 ; 在 web 系统开发中与 AJAX 相结合用的比较多 在 ajax 中数据传输有 2 中方式 : 文本类型, 常用 responsetext 属性类获取

More information

詞 彙 表 編 號 詞 彙 描 述 1 預 約 人 資 料 中 文 姓 名 英 文 姓 名 身 份 證 字 號 預 約 人 電 話 性 別 2 付 款 資 料 信 用 卡 別 信 用 卡 號 信 用 卡 有 效 日 期 3 住 房 條 件 入 住 日 期 退 房 日 期 人 數 房 間 數 量 入

詞 彙 表 編 號 詞 彙 描 述 1 預 約 人 資 料 中 文 姓 名 英 文 姓 名 身 份 證 字 號 預 約 人 電 話 性 別 2 付 款 資 料 信 用 卡 別 信 用 卡 號 信 用 卡 有 效 日 期 3 住 房 條 件 入 住 日 期 退 房 日 期 人 數 房 間 數 量 入 100 年 特 種 考 試 地 方 政 府 公 務 人 員 考 試 試 題 等 別 : 三 等 考 試 類 科 : 資 訊 處 理 科 目 : 系 統 分 析 與 設 計 一 請 參 考 下 列 旅 館 管 理 系 統 的 使 用 案 例 圖 (Use Case Diagram) 撰 寫 預 約 房 間 的 使 用 案 例 規 格 書 (Use Case Specification), 繪 出 入

More information

chap07.key

chap07.key #include void two(); void three(); int main() printf("i'm in main.\n"); two(); return 0; void two() printf("i'm in two.\n"); three(); void three() printf("i'm in three.\n"); void, int 标识符逗号分隔,

More information

新版 明解C++入門編

新版 明解C++入門編 511!... 43, 85!=... 42 "... 118 " "... 337 " "... 8, 290 #... 71 #... 413 #define... 128, 236, 413 #endif... 412 #ifndef... 412 #if... 412 #include... 6, 337 #undef... 413 %... 23, 27 %=... 97 &... 243,

More information