Hadoop-MapReduce-v2-秘籍-三-

70 阅读48分钟

Hadoop MapReduce v2 秘籍(三)

原文:Hadoop MapReduce v2 Cookbook

协议:CC BY-NC-SA 4.0

七、Hadoop 生态系统 II——Pig、HBase、Mahout 和 Sqoop

在本章中,我们将介绍以下主题:

  • Apache Pig 入门
  • 使用 Pig 连接两个数据集
  • 使用 HCatalog 访问 Pig 中的 Hive 表数据
  • Apache HBase 入门
  • 使用 Java 客户端 API 随机访问数据
  • 在 HBase 上运行 MapReduce 作业
  • 使用配置单元将数据插入 HBase 表
  • Apache Mahout 入门
  • 使用 Mahout 运行 K-Means
  • 使用 Apache Sqoop 将数据从关系数据库导入 HDFS
  • 使用 Apache Sqoop 将数据从 HDFS 导出到关系数据库

简介

Hadoop 生态系统有一系列项目,这些项目要么构建在 Hadoop 之上,要么与 Hadoop 紧密合作。 这些项目催生了一个专注于大规模数据处理的生态系统,用户往往可以组合使用几个这样的项目来解决他们的大数据问题。

本章介绍 Hadoop 生态系统中的几个关键项目,并展示如何开始使用每个项目。

重点抓好以下四个项目:

  • Pig:一种数据流样式的数据处理语言,用于大规模处理存储在 HDFS 中的数据
  • HBase:一种 NoSQL 风格的高度可扩展的数据存储,它在 HDFS 之上提供低延迟、随机访问和高度可扩展的数据存储
  • Mahout:机器学习和数据挖掘工具的工具包
  • Sqoop:用于在 Apache Hadoop 生态系统和关系数据库之间高效传输批量数据的数据移动工具

备注

本章的一些 HBase 和 Mahout 配方基于本书前一版Hadoop MapReduce Cookbook章 5Hadoop 生态系统章。 这些食谱最初是由斯里纳特·佩雷拉(Srinath Perera)撰写的。

备注

示例代码

本书的示例代码和数据文件可以在 giHub 的github.com/thilg/hcb-v…中找到。 代码库的chapter7文件夹包含本章的示例代码。

通过在代码库的chapter7文件夹中发出 Gradle build 命令,可以编译和构建示例代码。 Eclipse IDE 和 IntelliJ IDEA IDE 的项目文件可以通过分别在代码存储库的主文件夹中运行gradle eclipsegradle idea命令来生成。

本章的一些食谱使用图书交叉数据集作为样本数据。 该数据集由 Cai-Nicolas Ziegler 编制,包括图书列表、用户列表和评级列表。 源资料库的chapter6文件夹包含此数据集的清理样本。 您可以从www2.informatik.uni-freiburg.de/~cziegler/B…获取完整的数据集。

Apache Pig 入门

Apache Pig 是 Hadoop 的高级语言框架,它使得分析存储在 HDFS 中的非常大的数据集变得容易,而不必实现复杂的 Java MapReduce 应用。 Pig 的语言被称为 Pig 拉丁语,是一种数据流语言。 虽然 Pig 和 Have 框架的目标相似,但这两个框架的语言层通过分别提供过程性语言和声明性语言来相辅相成。

PIG 在后台将 Pig 拉丁语查询转换为一系列一个或多个 MapReduce 作业。

为了安装 Pig,我们建议您使用免费提供的商业 Hadoop 发行版之一,如第 1 章,*《Hadoop v2 入门》中所述。 另一种选择是使用 Apache bigtop 安装 Pig。 有关使用 Apache bigtop 发行版安装 Pig 的步骤,请参阅第 1 章《Hadoop v2 入门》*中与 bigtop 相关的食谱。

备注

如果您没有正在运行的 Pig 和 Hadoop 安装,下面的步骤将向您展示如何使用本地文件系统作为数据存储,在 MapReduce 本地模式下安装 Pig。 建议仅用于学习和测试目的。

下载并从pig.apache.org/releases.ht…解压最新版本的 PIG。 将解压缩文件夹的bin目录添加到 PATH 环境变量,如下所示:

$ export PATH=pig-0.13.0/bin:$PATH

使用带有local标志的pig命令启动 Grunt shell,如下所示:

$ pig -x local
grunt>

这个配方演示了如何使用 Pig 查询来处理 HDFS 中的数据。 我们将在这个食谱中使用图书交叉数据集。 本食谱将使用 Pig 处理图书交叉用户数据集,并选择年龄在 18 岁到 34 岁之间的用户列表(按年龄排序)。

做好准备

此配方需要将 Working Pig 安装与 Hadoop Yarn 集群集成。 您也可以使用 Pig 本地模式运行这些示例。 但是,在这种情况下,您必须使用本地文件系统而不是 HDFS 来加载数据。

怎么做……

本节介绍如何使用 Pig 拉丁语查询从图书交叉用户数据集中查找按年龄排序的 18 到 34 岁之间的用户。 继续执行以下步骤:

  1. 从代码存储库的chapter6文件夹复制并解压缩 BookCross 示例数据集(chapter6-bookcrossing-data.tar.gz)。

  2. 在 HDFS 中创建一个目录,并将书签用户数据集复制到该目录中,如下所示:

    $ hdfs dfs –mkdir book-crossing
    $ hdfs dfs -copyFromLocal \
    chapter6/data/BX-Users-Prepro.txt book-crossing
    
    
  3. 启动 Pig Grunt shell 并发出以下 Pig 命令:

    $ pig
    grunt> A = LOAD 'book-crossing/BX-Users-Prepro.txt' USING PigStorage(';')  AS (userid:int, location:chararray, age:int);
    grunt> B = FILTER A BY age > 18 AND age < 34 ;
    grunt> C = ORDER B BY age;
    
    
  4. Print the output of the processing flow by using the DUMP operator in the same grunt shell. The queries we issued in step 3 get executed only after we issue the following command (or any other data output command). You should notice a series of MapReduce jobs after issuing the following two commands:

    grunt> D = LIMIT C 10;
    grunt> DUMP D;
    
    

    前面命令的输出如下所示:

    How to do it...

  5. You can also use the ILLUSTRATE operator to test your queries. The Illustrate operator retrieves a small sample of data from your input data and runs your queries on that data, giving faster turnaround times to review and test the Pig queries:

    grunt> ILLUSTRATE B;
    
    

    前面命令的输出如下所示:

    How to do it...

它是如何工作的.

当我们发出 Pig 查询时,Pig 会在内部将它们转换为一组 MapReduce 作业,并在 Hadoop 集群中执行它们以获得所需的结果。 对于几乎所有的数据查询,Pig 查询比 MapReduce 应用更容易编写和管理。

以下行指示 Pig 将数据文件加载到名为A的关系中。 我们可以为load命令提供单个文件或目录。 使用PigStorage(';')指示 Pig 使用默认加载函数加载数据,并以;作为分隔符。 在执行 MapReduce 作业时,Pig 的 Load 函数解析输入数据,并将其分配给 AS 子句中描述的模式的字段。 任何不符合给定模式的数据点都会在执行时导致错误或NULL值:

grunt> A = LOAD 'book-crossing/BX-Users-Prepro.txt' USING PigStorage(';')  AS (userid:int, location:chararray, age:int);

FILTER运算符根据给定条件从关系中选择数据。 在下面的代码行中,我们选择数据点,其中用户的年龄在 18 到 34 岁之间:

grunt> B = FILTER A BY age > 18 AND age < 34;

ORDER BY运算符根据一个或多个数据字段对关系中的数据进行排序。 在下面的查询中,我们按用户的年龄对关系 B 进行排序:

grunt> C = ORDER B BY age;

运算符LIMIT使用给定的数量限制关系中的数据点(元组)的数量。 在下面的查询中,我们将关系 C 限制为只有 10 个元组。 以下步骤使使用DUMP运算符检查数据变得容易:

grunt> D = LIMIT C 10;

还有更多...

PIG 拉丁语还包含大量内置函数,这些函数提供数学、字符串处理、数据时间处理、基本统计、数据加载和存储等领域的功能。 PIG 的内置函数列表可以在pig.apache.org/docs/r0.13.…中找到。 您还可以实现 PigUser Defined Functions来执行所需的任何自定义处理。

另请参阅

使用 Pig 连接两个数据集

本食谱解释了如何使用 Pig 连接两个数据集。 我们将使用本食谱的书本交叉数据集。 本食谱将使用 Pig 将 Books 数据集与 Book-Ratings 数据集连接起来,并查找关于作者的高评级(评级为>3)的分布。

怎么做……

本节介绍如何使用 Pig 拉丁文脚本通过将图书数据集与评级数据集相结合来查找作者的评论评级分布:

  1. 从代码存储库的chapter6文件夹中提取书签示例数据集(chapter6-bookcrossing-data.tar.gz)。

  2. 在 HDFS 中创建一个目录,并将 BookCrossBooks 数据集和 Book-Ratings 数据集复制到该目录,如下所示:

    $ hdfs dfs –mkdir book-crossing
    $ hdfs dfs -copyFromLocal \
    chapter6/data/BX-Books-Prepro.txt book-crossing
    $ hdfs dfs -copyFromLocal \
    BX-Book-Ratings-Prepro.txt book-crossing
    
    
  3. 查看chapter7/pig-scripts/book-ratings-join.pig脚本。

  4. Execute the preceding Pig Latin script using the following command:

    $ pig –f pig-scripts/book-ratings-join.pig
    
    

    前面命令的输出如下所示:

    How to do it...

它是如何工作的.

下面的 Pig 命令将数据加载到 Books 和 BookRatings 关系中。 如前面的配方中所述,PigStorage(';')指示 Pig 使用';'作为字段分隔符:

Books = LOAD 'book-crossing/BX-Books-Prepro.txt' 
USING PigStorage(';')  AS (
    isbn:chararray, 
    title:chararray, 
    author:chararray, 
    year:int, 
    publisher:chararray, 
    image_s:chararray, 
    image_m:chararray, 
    image_l:chararray);
Ratings = LOAD 'book-crossing/BX-Book-Ratings-Prepro.txt' 
  USING PigStorage(';')  AS (
    userid:int, 
    isbn:chararray, 
    ratings:int);

我们使用以下FILTER操作只选择评分良好的评论:

GoodRatings = FILTER R BY ratings > 3;

然后,我们使用 ISBN 作为公共标准加入图书和好评关系。 这是个内部等值连接,并生成由连接条件过滤的所有记录的笛卡尔乘积。 换句话说,结果关系包含每个匹配图书的记录和图书评级(匹配图书的数量 X 良好评级的数量):

J = JOIN Books BY isbn, GoodRatings by isbn;

下面的语句按作者对联接结果进行分组。 现在,每个组都包含属于某个作者的所有记录。 假设我们对每个好的评价都有一本相匹配的书,那么一个组中的记录数就是该组的作者收到的好评数。

JA = GROUP J BY author;

下面的语句统计每组关系 JA 中的记录数,并输出作者姓名和该作者所写书籍的好评计数:

JB = FORACH JA GENERATE group, COUNT(J);
OA = LIMIT JB 100;
DUMP OA;

您可以在 Pig Grunt shell 中手动发出上述命令,以更详细地了解数据流。 在执行此操作时,您可以使用LIMITDUMP运算符来了解每一步的结果。

还有更多...

PIG 也支持外连接。 然而,目前 Pig 只支持 equi 联接,其中联接条件必须基于相等。

使用 HCatalog 访问 Pig 中的 Hive 表数据

可能会有希望从 Hive 和 Pig 访问同一数据集的情况。 还可能存在这样的场景,我们希望处理使用 Pig 映射到配置单元表的配置单元查询的结果。 在这种情况下,我们可以利用 Pig 中的 HCatalog 集成从 Pig 访问 HCatalog 管理的 Hive 表,而不必担心数据定义、数据存储格式或存储位置。

做好准备

遵循配置单元批处理模式-使用第 6 章Hadoop 生态系统中的查询文件配方-Apache 配置单元创建我们将在本配方中使用的配置单元表。

怎么做……

本部分演示如何从 Pig 访问 Hive 表。 继续执行以下步骤:

  1. 使用-useHCatalog标志启动 Pig‘s Grunt 外壳,如下所示。 这将加载访问配置单元

    $ pig -useHCatalog
    
    

    中的 HCatalog 托管表所需的 HCatalog JAR

  2. 在 Grunt shell 中使用以下命令将users表从bookcrossing配置单元数据库加载到名为users的 Pig 关系中。 HCatLoader 便于从 HCatalog 管理表读取数据:

    grunt> users = LOAD 'bookcrossing.users' USING org.apache.hive.hcatalog.pig.HCatLoader();
    
    
  3. 按如下方式使用describe运算符检查users关系的架构:

    grunt> DESCRIBE users;
    users: {user_id: int,location: chararray,age: int}
    
    
  4. Inspect the data of the users relation by issuing the following command in the Pig Grunt shell. The relations loaded through Hive can be used similarly to any other relation in Pig:

    grunt> ILLUSTRATE users; 
    
    

    前面命令的输出如下所示:

    How to do it...

还有更多...

您还可以使用 HCatStorer 接口将数据写入 HCatalog 管理的表格中,如所示,从 Pig 将数据存储在配置单元表格中:

grunt> STORE r INTO 'database.table' 
 USING org.apache.hcatalog.pig.HCatStorer();

另请参阅

HCatalog-在映射到配置单元表的数据上执行 Java MapReduce 计算-从 Java MapReduce 计算配方将数据写入配置单元表第 6 章Hadoop 生态系统-Apache 配置单元

Apache HBase 入门

HBase 是一个高度可伸缩的分布式 NoSQL 数据存储,支持列式数据存储。 HBase 是以谷歌的 Bigtable 为蓝本的。 HBase 使用 HDFS 进行数据存储,并允许随机访问数据,这在 HDFS 中是不可能的。

可以将 HBase 表数据模型可视化为一个非常大的多维排序映射。 HBase 表由行组成,每个行都有一个唯一的行键,后跟一个列列表。 每行可以有任意数量的列,并且不必遵循固定的架构。 每个数据单元格(特定行中的列)可以基于时间戳具有多个值,从而形成三维表(行、列、时间戳)。 HBase 按排序顺序存储所有行和列,从而可以随机访问数据。

尽管数据模型与关系数据模型有一些相似之处,但与关系表不同,HBase 数据模型中的不同行可能有不同的列。 例如,第二行可能包含与第一行完全不同的名称-值对。 HBase 也不支持跨行的事务或原子性。 您可以在 Google 的 Bigtable 文章research.google.com/archive/big…中找到有关该数据模型的更多详细信息。

HBase 支持超大型数据集的存储,并提供低延迟、高吞吐量的读写。 HBase 为一些要求非常苛刻的实时数据处理系统提供动力,比如在线广告代理;它也为 Facebook Messenger 提供动力。 存储在 HBase 中的数据也可以使用 MapReduce 进行处理。

HBase 集群架构由一个或多个主节点和一组区域服务器组成。 HBase 表水平拆分成区域,由区域服务器提供服务和管理。 面域按柱族进一步垂直细分,并作为文件保存在 HDFS 中。 列族是表中列的逻辑分组,其结果是在存储层对列进行物理分组。

要获得 HBase 的最高性能,需要仔细设计表,同时考虑到它的分布式特性。 RowKey 在性能中起着重要作用,因为区域分布和任何查询都是基于 RowKey 的。 本书中的食谱并没有关注这样的优化。

为了安装 HBase,我们建议您使用免费提供的商业 Hadoop 发行版之一,如第 1 章,*《Hadoop v2 入门》*中所述。 另一种选择是在 Amazon 云环境中使用 HBase 集群,如第 2 章云部署中所述--在云环境上使用 Hadoop Yarn

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

本节演示如何开始使用 Apache HBase。 我们将创建一个简单的 HBase 表,并使用 HBase shell 向该表插入一行数据。 继续执行以下步骤:

  1. 通过执行以下命令启动 HBase shell:

    $ hbase shell
    ……
    hbase(main):001:0> 
    
    
  2. 在 HBase shell 中发出以下命令以检查版本:

    hbase(main):002:0> version
    0.98.4.2.2.0.0-2041-hadoop2, r18e3e58ae6ca5ef5e9c60e3129a1089a8656f91d, Wed Nov 19 15:10:28 EST 2014
    
    
  3. 创建名为testtable 的 HBase 表。 列出所有表以验证test表的创建,如下所示:

    hbase(main):003:0> create 'test', 'cf'
    0 row(s) in 0.4210 seconds
    => Hbase::Table - test 
    
    hbase(main):004:0> list
    TABLE 
    SYSTEM.CATALOG 
    SYSTEM.SEQUENCE 
    SYSTEM.STATS 
    test
    4 row(s) in 0.0360 seconds
    
    
  4. 现在,使用 HBaseput命令向test表插入一行,如下所示。 使用row1作为行键,使用cf:a作为列名,使用 10 作为值

    hbase(main):005:0> put 'test', 'row1', 'cf:a', '10'
    0 row(s) in 0.0080 seconds
    
    
  5. 使用以下命令扫描test表,该命令将打印表中的所有数据:

    hbase(main):006:0> scan 'test'
    ROW      COLUMN+CELL 
    row1column=cf:a, timestamp=1338485017447, value=10 
    1 row(s) in 0.0320 seconds
    
    
  6. 使用以下命令从表中检索值,方法是将test指定为表名,将row1指定为 RowKey:

    hbase(main):007:0> get 'test', 'row1'
    COLUMN    CELL 
    cf:atimestamp=1338485017447, value=10 
    1 row(s) in 0.0130 seconds
    
    
  7. 使用disabledrop命令禁用并删除测试表,如下所示:

    hbase(main):014:0> disable 'test'
    0 row(s) in 11.3290 seconds
    
    hbase(main):015:0> drop 'test'
    0 row(s) in 0.4500 seconds
    
    

还有更多...

除了本章接下来的几个菜谱外,本书中的以下菜谱也使用了 HBase,并提供了更多关于 HBase 的用例:

  • 将大型数据集加载到 Apache HBase 数据存储--第 10 章海量文本数据处理中的 Importtsv 和 BulkLoad配方
  • 创建用于第 10 章海量文本数据处理的文本数据配方的 TF 和 TF-IDF 向量
  • 生成第 8 章搜索和索引的爬行网页的内链图配方
  • 在 Amazon EC2 上使用云部署的 EMR配方部署 Apache HBase 集群-在云环境上使用 Hadoop Yarn

另请参阅

使用 Java 客户端 API 随机访问数据

前面的配方引入了 HBase 的命令行界面。 本食谱演示了如何使用 Java API 与 HBase 交互。

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

以下步骤执行 HBase Java 客户端以从 HBase 表存储和检索数据。

通过从示例源资料库的chapter 7文件夹运行以下命令来运行HBaseClientJava 程序:

$ gradle execute HBaseClient

它是如何工作的.

上述 Java 程序的源代码位于源代码存储库的chapter7/src/chapter7/hbase/HBaseClient.java文件中。 以下代码创建一个 HBase 配置对象,然后创建到testHBase 表的连接。 此步骤使用 ZooKeeper 获取 HBase 主机名和端口。 在高吞吐量生产场景下,建议使用HConnection实例连接 HBase 表。

Configuration conf = HBaseConfiguration.create();
HTable table = new HTable(conf, "test");

下面的命令将向 HBase 表添加一个数据行:

Put put = new Put("row1".getBytes());
put.add("cf".getBytes(), "b".getBytes(), "val2".getBytes());
table.put(put);

通过执行扫描搜索数据,如下所示:

Scan s = new Scan();
s.addFamily(Bytes.toBytes("cf")); 
ResultScanner results = table.getScanner(s);

在 HBase 上运行 MapReduce 作业

这个配方解释了如何运行 MapReduce 作业,该作业直接从 HBase 存储读取数据和从 HBase 存储写入数据。

HBase 提供抽象的映射器和减少器实现,用户可以扩展到直接从 HBase 读取和写入。 本食谱解释了如何使用这些映射器和减少器编写示例 MapReduce 应用。

我们将使用世界银行的**《人类发展报告》(HDR)数据,其中显示每个国家的人均国民总收入**(GNI)。 数据集可在hdr.undp.org/en/statisti…找到。 示例源代码存储库中的chapter7/resources/hdi-data.csv文件中提供了该数据集的示例。 使用 MapReduce,我们将按国家计算人均国民总收入的平均值。

做好准备

本食谱要求 Apache HBase 安装与 Hadoop Yarn 集群集成。 在开始之前,请确保启动所有已配置的 HBase Master 和 RegionServer 进程。

怎么做……

本节演示如何对存储在 HBase 中的数据运行 MapReduce 作业。 继续执行以下步骤:

  1. 从源代码库的chapter7文件夹执行命令编译源代码,如下所示:

    $ gradle build 
    
    
  2. chapter7文件夹运行以下命令,将示例数据上传到 HBase。 此命令使用chapter7/src/chapter7/hbase/HDIDataUploader上载数据:

    $ gradle executeHDIDataUpload
    
    
  3. 通过从HADOOP_HOME运行以下命令来运行 MapReduce 作业:

    $ hadoop jar hcb-c7-samples.jar \
     chapter7.hbase.AverageGINByCountryCalcualtor
    
    
  4. 通过从 HBase shell 运行以下命令在 HBase 中查看结果:

    $ hbase shell
    hbase(main):009:0> scan  'HDIResult'
    
    

它是如何工作的.

您可以在chapter7/src/chapter7/hbase/AverageGINByCountryCalcualtor.java中找到 Java HBase MapReduce 示例。 由于我们将使用 HBase 来读取输入和写入输出,因此我们使用 HBaseTableMapperTableReducer帮助器类来实现 MapReduce 应用。 我们使用TableMapReduceUtil类中给出的实用程序方法配置TableMapperTableReducer。 对象Scan用于指定映射器在从 HBase 数据存储读取输入数据时要使用的条件。

使用配置单元将数据插入 HBase 表

配置单元-HBase 集成使我们能够使用配置单元查询语言(HQL)查询 HBase 表。 配置单元-HBase 集成支持将现有的 HBase 表映射到配置单元表,以及使用 HQL 创建新的 HBase 表。 HQL 支持从 HBase 表中读取数据和向 HBase 表中插入数据,包括执行配置单元映射的 HBase 表和传统配置单元表之间的连接。

下面的配方使用 HQL 创建一个 HBase 表来存储bookcrossing数据集的books表,并使用示例数据填充该表。

做好准备

遵循配置单元批处理模式-使用第 6 章Hadoop 生态系统的查询文件配方-Apache 配置单元创建我们将在本配方中使用的配置单元表。

怎么做……

本节演示如何从 Pig 访问 Hive 表。 继续执行以下步骤:

  1. 使用以下命令启动配置单元外壳:

    $ hive
    
    
  2. 在配置单元 shell 中发出以下命令以创建 HBase 表。 HBaseStorageHandler类负责与 HBase 的数据通信。 我们必须指定hbase.column.mapping属性来指示配置单元如何将 HBase 表的列映射到相应的配置单元表:

    CREATE TABLE IF NOT EXISTS books_hbase
     (key STRING,
     title STRING,
     author STRING,
     year INT,
     publisher STRING,
     image_s STRING,
     image_m STRING,
     image_l STRING)
    STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
    WITH SERDEPROPERTIES ('hbase.columns.mapping' =   ':key,f:title,f:author,f:year,f:publisher,img:image_s,img:image_m,img:image_l')
    TBLPROPERTIES ('hbase.table.name' = 'bx-books');
    
    
  3. 发出以下配置单元查询,将数据插入到新创建的 HBase 表中。 HBase 表中的行键必须是唯一的。 当有多行具有重复的行键时,HBase 只存储其中的一行,而丢弃其他行。 使用图书 ISBN(对于每本图书都是唯一的)作为以下示例中的 RowKey:

    hive> insert into table books_hbase select * from bookcrossing.books;
    ….
    Total MapReduce CPU Time Spent: 23 seconds 810 msec
    OK
    books.isbn    books.title    books.author    books.year    books.publisher    books.image_s    books.image_m    books.image_l
    Time taken: 37.817 seconds
    
    
  4. 使用以下命令检查插入配置单元映射 HBase 表的数据:

    hive> select * from books_hbase limit 10;
    
    
  5. 我们还可以在刚刚创建的表上执行配置单元函数,如count,如下所示:

    hive> select count(*) from books_hbase; 
    ...
    Total MapReduce CPU Time Spent: 22 seconds 510 msec
    OK
    _c0
    271379
    
    
  6. 启动 HBase shell 并发出list命令查看 HBase 中的表列表,如下所示:

    $ hbase shell
    hbase(main):001:0> list
    TABLE
    …… 
    SYSTEM.STATS
    bx-books
    …… 
    8 row(s) in 1.4260 seconds
    
    
  7. Inspect the data of the bx-books HBase table using the following command:

    hbase(main):003:0> scan 'bx-books', {'LIMIT' => 5}
    
    

    前面命令的输出如下所示:

    How to do it...

另请参阅

  • HCatalog-在映射到配置单元表的数据上执行 Java MapReduce 计算-从 Java MapReduce 计算配方将数据写入配置单元表第 6 章Hadoop 生态系统-Apache 配置单元

Apache Mahout 入门

Mahout 是一种使用 Hadoop MapReduce 框架实现众所周知的机器学习数据挖掘算法的努力。 用户可以在他们的数据处理应用中使用 Mahout 算法实现,而无需经历使用 Hadoop MapReduce 从头开始实现这些算法的复杂性。

这个食谱解释了如何开始使用 Mahout。

为了安装 Mahout,我们建议您使用可免费获得的商业 Hadoop 发行版之一,如章 1,*《Hadoopv2 入门》中所述。 另一种选择是使用 Apache bigtop 安装 Mahout。 有关使用 Apache bigtop 发行版安装 Mahout 的步骤,请参阅第 1 章《Hadoop v2 入门》*中与 bigtop 相关的食谱。

怎么做……

本节演示如何通过运行示例 KMeans 集群计算来开始使用 Mahout。 您可以通过执行以下步骤来运行和验证 Mahout 安装:

  1. archive.ics.uci.edu/ml/database…下载输入数据如下:

    $ wget http://archive.ics.uci.edu/ml/databases/synthetic_control/synthetic_control.data 
    
    
  2. 创建名为testdata的 HDFS 目录,并使用以下命令将下载的文件复制到该目录:

    $ hdfs dfs –mkdir testdata
    $ hdfs dfs –copyFromLocal synthetic_control.data  testdata
    
    
  3. 通过运行以下命令运行 K 均值示例:

    $ mahout org.apache.mahout.clustering.syntheticcontrol.kmeans.Job
    
    
  4. 如果一切正常,它将处理并打印出群集:

    12/06/19 21:18:15 INFO kmeans.Job: Running with default arguments
    12/06/19 21:18:15 INFO kmeans.Job: Preparing Input
    12/06/19 21:18:15 WARN mapred.JobClient: Use GenericOptionsParser for parsing the arguments. Applications should implement Tool for the same.
    .....
    2/06/19 21:19:38 INFO clustering.ClusterDumper: Wrote 6 clusters
    12/06/19 21:19:38 INFO driver.MahoutDriver: Program took 83559 ms (Minutes: 1.39265)
    
    

它是如何工作的.

Mahout 是 MapReduce 作业的集合,您可以使用mahout命令运行它们。 前面的说明通过运行 Mahout 发行版附带的K-Means示例安装并验证了 Mahout。

还有更多...

除了本章的下一个配方外,本书第 10 章海量文本数据处理中的以下配方也使用 Mahout:

  • 创建用于第 10 章海量文本数据处理的文本数据配方的 TF 和 TF-IDF 向量
  • 第 10 章海量文本数据处理中的使用 Apache Mahout配方对文本数据进行聚类
  • 使用潜在狄利克雷分配(LDA)配方的主题发现第 10 章海量文本数据处理
  • 使用 Mahout 朴素贝叶斯分类器的文档分类配方第 10 章海量文本数据处理

使用 Mahout 运行 K-Means

K-Means 是一种聚类算法。 聚类算法采用在和N 维空间中定义的数据点,并通过考虑这些数据点之间的距离将它们分组到多个中。 群集是一组数据点,使得群集内的数据点之间的距离远远小于群集内的数据点到群集外的数据点的距离。 关于 K-Means 聚类的更多细节可以在 Google 的集群计算和 MapReduce系列讲座的第 4 讲(www.youtube.com/watch?v=1ZD…)中找到。

在这个配方中,我们将使用一个数据集,其中包括按国家/地区列出的人类发展报告(HDR)。 人类发展报告根据几项人类发展指标描述了不同的国家。 您可以在hdr.undp.org/en/statisti…找到该数据集。 示例源代码存储库中的chapter7/resources/hdi-data.csv文件中提供了该数据集的示例。 本食谱将使用 K-Means 根据 HDR 维度对国家进行分类。

做好准备

这个食谱需要一个 Mahout 安装。 如果您还没有安装 Mahout,请按照前面的方法安装。

怎么做……

本节演示如何使用 Mahout K-Means 算法处理数据集。 继续执行以下步骤:

  1. 使用以下 Gradle 命令编译示例:

    $ gradle build 
    
    
  2. 将文件chapter7/resources/countries4Kmean.data复制到 HDFS 中的testdata目录。 创建testdata目录。

  3. 通过运行以下命令运行示例:

    $ gradle executeKMeans
    
    

它是如何工作的.

前面的示例显示了如何配置和使用 Java 中的 K-Means 实现。 您可以在chapter7/src/chapter7/KMeansSample.java文件中找到此示例的来源。 当我们运行代码时,它会初始化 K-Means MapReduce 作业,并使用 MapReduce 框架执行它。

使用 Apache Sqoop 将数据从关系数据库导入 HDFS

ApacheSqoop 是一个支持在 Apache Hadoop 生态系统和关系数据存储之间高效地批量传输数据的项目。 Sqoop 可用于自动执行从 RDBMS(如 MySQL、PostgreSQL、Oracle 等)导入数据或将数据导出到 RDBMS 的过程。 Sqoop 还支持 Netezza 和 Teradata 等数据库设备。 它支持使用多个 Map 任务并行导入/导出数据,还支持节流以减少外部 RDBMS 的负载。

在本食谱中,我们将使用 Sqoop2 将数据从 PostgreSQL 数据库导入到 HDFS。 我们还包括了 Sqoop 1.4.x 的说明,因为该 Sqoop 版本在当前 Hadoop 发行版中有广泛的可用性和用途。

我们建议您使用第 1 章,*《Hadoop v2 入门》*中描述的免费商业 Hadoop 发行版之一来安装 Apache Sqoop2 或 Sqoop 1.4.x。 另一种选择是使用 Apache bigtop 安装 Apache Sqoop2。

做好准备

本食谱需要一个安装了 Sqoop2 或 Sqoop 1.4.x 的工作 Hadoop2 集群。

我们将使用 PostgreSQL 数据库。 您也可以使用另一个 RDBMS 来实现此目的,但是必须相应地更改以下配方中的某些步骤。

怎么做……

本节演示如何使用 SQOOP 将数据从 PostgreSQL 数据库导入到 HDFS。 继续执行以下步骤:

  1. Download the appropriate PostgreSQL JDBC driver from jdbc.postgresql.org/download.ht… and copy it to the lib directory of the SQOOP web app using the following command and restart the SQOOP server:

    $ cp postgresql-XXXX.jdbcX.jar \
    /usr/lib/sqoop/webapps/sqoop/WEB-INF/lib/
    
    

    备注

    对于 Sqoop 1.4.x,将 PostgreSQL JDBC 驱动程序 JAR 复制到 Sqoop 安装的 lib 文件夹。

  2. 在 PostgreSQL 中创建用户和数据库,如下所示。 也可以使用您的操作系统用户名作为 PostgreSQL 数据库中的用户。 对于本食谱,您可以使用现有的 PostgreSQL 用户和现有的数据库:

    $ sudo su - postgres
    $ psql
    postgres=# CREATE USER aluck WITH PASSWORD 'xxx123';
    CREATE ROLE
    postgres=# CREATE DATABASE test;
    CREATE DATABASE
    postgres=# GRANT ALL PRIVILEGES ON DATABASE test TO aluck;
    GRANT
    postgres=# \q
    
    
  3. 登录到新创建的数据库。 使用 PostgreSQL 外壳中的以下语句创建模式和数据库表:

    $ psql test
    
    test=> CREATE SCHEMA bookcrossing;
    CREATE SCHEMA
    test=> CREATE TABLE bookcrossing.ratings 
     (user_id INT, 
     isbn TEXT, 
     rating TEXT);
    CREATE TABLE
    
    
  4. Load the book-ratings.txt dataset in the chapter7 folder of the Git repository into the table we just created, using the following command:

    test=> \COPY bookcrossing.ratings FROM '…/chapter7/book-ratings.txt' DELIMITER ';'
    test=# select * from bookcrossing.ratings limit 10;
    
     user_id |    isbn    | rating 
    ---------+------------+--------
     276725 | 034545104X | 0
     276726 | 0155061224 | 5
     276727 | 0446520802 | 0
     276729 | 052165615X | 3
     276729 | 0521795028 | 6
     276733 | 2080674722 | 0
     276736 | 3257224281 | 8
     276737 | 0600570967 | 6
     276744 | 038550120X | 7
     276745 | 342310538  | 10
    (10 rows)
    
    

    备注

    以下步骤(6 到 9)适用于 Sqoop2。 有关 Sqoop 1.4.x 的说明,请跳至步骤 10。

  5. 在 SQOOP 命令行客户端中使用以下命令创建 SQOOP 连接,并回答提示的问题:

    $ sqoop
    sqoop:000> create connection --cid 1 
    Creating connection for connector with id 1
    Please fill following values to create new connection object
    
    Name: t2
    
    Connection configuration
    
    JDBC Driver Class: org.postgresql.Driver 
    JDBC Connection String: jdbc:postgresql://localhost:5432/test
    Username: testuser
    Password: ****
    JDBC Connection Properties: 
    There are currently 0 values in the map:
    …
    New connection was successfully created with validation status FINE and persistent id 3
    
    
  6. 创建 SQOOP 作业以将数据导入 HDFS,如下所示:

    sqoop:000> create job --xid 1 --type import
    
    Creating job for connection with id 1
    Please fill following values to create new job object
    
    Name: importest 
    Database configuration
    Schema name: bookcrossing
    Table name: ratings
    Table SQL statement: 
    Table column names: 
    Partition column name: user_id
    Boundary query: 
    
    Output configuration
    Storage type: 
     0 : HDFS
    Choose: 0
    Output format: 
     0 : TEXT_FILE
     1 : SEQUENCE_FILE
    Choose: 0
    Output directory: /user/test/book_ratings_import
    New job was successfully created with validation status FINE  and persistent id 8
    
    
  7. 使用以下命令提交 Sqoop 作业:

    sqoop:000> submission start --jid 8 
    Submission details
    Job id: 8
    Status: BOOTING 
    Creation date: 2014-10-15 00:01:20 EDT
    
    
  8. 使用以下命令监视作业状态:

    sqoop:000> submission status --jid 8
    Submission details
    Job id: 8
    Status: SUCCEEDED
    Creation date: 2014-10-15 00:01:20 EDT
    
    
  9. 检查 HDFS 目录中的数据。 您可以将此数据映射到配置单元表以供进一步查询。 接下来的两步只适用于 Sqoop 1.4.x。 如果您使用的是 Sqoop 2,请跳过它们。

  10. 发出以下 Sqoop 命令,将数据从 PostgreSQL 直接导入配置单元表。 相应地替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口和数据库用户名。 成功执行以下命令后,将在 HDFS 主目录中创建一个名为‘Rating’的文件夹,其中包含从 PostgreSQL 导入的数据:

```scala
$ sqoop import \
--connect jdbc:postgresql://<ip_address>:5432/test \
--table ratings \
--username aluck -P \
--direct -- --schema bookcrossing

```

11. 发出以下 Sqoop 命令,将数据从 PostgreSQL 导入到 HDFS 主目录。 相应地替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口和数据库用户名。 成功执行以下命令后,将在当前配置单元数据库中创建一个名为‘Rating’的配置单元表,其中包含从 PostgreSQL 导入的数据:

```scala
$ sqoop import \
--connect jdbc:postgresql://<ip_address>:5432/test \
--table ratings \
--username aluck -P \
--hive-import \
--direct -- --schema bookcrossing

```

使用 Apache Sqoop 将数据从 HDFS 导出到关系数据库

在这个配方中,我们将使用 Sqoop2 或 Sqoop 1.4.x 将数据从 HDFS 导出到 PostgreSQL 数据库。

做好准备

本食谱需要一个安装了 Sqoop2 或 Sqoop 1.4.x 的工作 Hadoop2 集群。

我们将使用 PostgreSQL 数据库。 您也可以使用另一个 RDBMS 来实现此目的,但是必须相应地更改以下配方步骤。

遵循前面的配方,使用 Apache Sqoop将数据从关系数据库导入 HDFS。

怎么做……

本节演示如何使用 SQOOP 将数据从 HDFS 导出到 PostgreSQL 数据库。 继续执行以下步骤:

  1. 按照前面中的步骤 1 使用 Apache Sqoop配方将数据从关系数据库导入 HDFS,在 PostgreSQL 数据库中创建一个用户和一个数据库。

  2. Create a database table using the following statements in the PostgreSQL shell:

    $ psql test
    test=> CREATE TABLE bookcrossing.ratings_copy
     (user_id INT,
     isbn TEXT, 
     rating TEXT);
    
    

    备注

    以下步骤(3 到 5)适用于 Sqoop2。 有关 Sqoop 1.4.x 的说明,请跳至步骤 6。

  3. 创建 SQOOP 作业以从 HDFS 导出数据,如下所示:

    sqoop:000> create job --xid 1 --type export
    
    Creating job for connection with id 1
    Please fill following values to create new job object
    
    Name: exporttest
    
    Database configuration
    Schema name: bookcrossing
    Table name: ratings_copy
    Table SQL statement: 
    Table column names: 
    Input configuration
    Input directory: /user/test/book_ratings_import
    Throttling resources
    Extractors: 
    Loaders: 
    New job was successfully created with validation status FINE  and persistent id 13
    
    
  4. 使用以下命令提交 Sqoop 作业:

    sqoop:000> submission start --jid 13 
    Submission details
    Job id: 13
    Status: BOOTING 
     …..
    
    
  5. 使用此命令监视作业状态。 跳到步骤 7:

    sqoop:000> submission status --jid 13
    Submission details
    Job id: 13
    Status: SUCCEEDED
    
    
  6. 此步骤仅适用于 Sqoop 1.4.x。 使用 Apache Sqoop 配方重新执行前面*步骤中的步骤 11,将数据从关系数据库导入 HDFS,以确保 HDFS 主目录中有包含导入数据的“Rating”文件夹。 发出以下 Sqoop 命令将数据从 HDFS 直接导出到 PostgreSQL 表中。 替换 PostgreSQL 数据库 IP 地址(或主机名)、数据库端口、数据库用户名,相应导出数据源目录。 执行此步骤将导致 Hadoop MapReduce 作业:

    $ sqoop export \
    --connect jdbc:postgresql://<ip_address>:5432/test \
    --table ratings_copy \
    --username aluck -P \
    --export-dir /user/aluck/ratings
    --input-fields-terminated-by ','
    --lines-terminated-by '\n'
    -- --schema bookcrossing
    
    ```* 
    
  7. 登录 PostgreSQL shell 并检查导入的数据:

    test=# select * from bookcrossing.ratings_copy limit 10;
     user_id |    isbn    | rating 
    ---------+------------+--------
     276725 | 034545104X | 0
     276726 | 0155061224 | 5
     276727 | 0446520802 | 0
     276729 | 052165615X | 3
     276729 | 0521795028 | 6
    
    

八、搜索和索引

在本章中,我们将介绍以下食谱:

  • 使用 Hadoop MapReduce 生成倒排索引
  • 使用 Apache Nutch 进行域内 Web 爬行
  • 使用 Apache Solr 对 Web 文档进行索引和搜索
  • 将 Apache HBase 配置为 Apache Nutch 的后端数据存储
  • 使用 Hadoop/HBase 群集使用 Apache Nutch 进行全 Web 爬行
  • 用于索引和搜索的 ElasticSearch
  • 为爬行网页生成内链接图

简介

MapReduce 框架非常适合大规模搜索和索引应用。 事实上,谷歌提出了最初的 MapReduce 框架,专门用来简化网络搜索涉及的各种操作。 Apache Hadoop 项目也是作为Apache Nutch搜索引擎的子项目开始的,然后作为单独的顶级项目衍生出来。

网络搜索由获取、索引、排序和检索组成。 考虑到数据量非常大,所有这些操作都需要可伸缩。 此外,检索的延迟也应该很低。 通常,获取是通过 Web 爬行执行的,其中爬行器获取获取队列中的一组页面,从获取的页面中提取链接,将提取的链接添加回获取队列,并重复此过程多次。 索引以快速高效的方式解析、组织和存储获取的数据,以便于查询和检索。 搜索引擎基于诸如 PageRank 之类的算法对文档进行离线排名,并基于查询参数对结果进行实时排名。

在本章中,我们将介绍几个可用于 Apache Hadoop 来执行大规模搜索和索引的工具。

提示

示例代码

本书的示例代码文件位于 gihub 的github.com/thilg/hcb-v…chapter8文件夹代码库包含本章的示例代码。

可以通过在代码库的chapter8文件夹中发出gradle build命令来编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle idea命令来生成。

使用 Hadoop MapReduce 生成倒排索引

Simple 文本搜索系统依靠倒排索引来查找包含给定词或术语的文档集。 在本食谱中,我们实现了一个简单的倒排索引构建应用,该应用计算文档中的术语列表、包含每个术语的文档集以及每个文档中的术语频率。 从倒排索引检索结果可以像返回包含给定项的文档集一样简单,也可以涉及复杂得多的操作,例如返回基于特定排名排序的文档集。

做好准备

您必须配置并安装 Apache Hadoopv2 才能遵循本指南。 编译和构建源代码需要 Gradle。

怎么做……

在以下步骤中,我们将使用 MapReduce 程序为文本数据集构建倒排索引:

  1. Create a directory in HDFS and upload a text dataset. This dataset should consist of one or more text files.

    $ hdfs dfs -mkdir input_dir
    $ hdfs dfs -put *.txt input_dir
    
    

    备注

    您可以按照www.gutenberg.org/wiki/Gutenb…给出的说明下载古腾堡计划书籍的文本版本。 请确保提供下载请求的filetypes查询参数为txt。 解压缩下载的文件。 您可以使用解压缩的文本文件作为此食谱的文本数据集。

  2. 通过从源资料库的chapter 8文件夹运行gradle build命令,编译源。

  3. 使用以下命令运行倒排索引 MapReduce 作业。 提供在步骤 2 中上载输入数据的 HDFS 目录作为第一个参数,并提供 HDFS 路径以存储输出作为第二个参数:

    $ hadoop jar hcb-c8-samples.jar \
     chapter8.invertindex.TextOutInvertedIndexMapReduce \
     input_dir output_dir
    
    
  4. 通过运行以下命令检查输出目录中的结果。 此程序的输出将由术语后跟逗号分隔的文件名和频率列表组成:

    $ hdfs dfs -cat output_dir/*
    ARE three.txt:1,one.txt:1,four.txt:1,two.txt:1,
    AS three.txt:2,one.txt:2,four.txt:2,two.txt:2,
    AUGUSTA three.txt:1,
    About three.txt:1,two.txt:1,
    Abroad three.txt:2,
    
    
  5. 为了更清楚地理解算法,我们在步骤 3 中使用了文本输出倒排索引 MapReduce 程序。 chapter8存储库的源文件夹中的chapter8/invertindex/InvertedIndexMapReduce.javaMapReduce 程序使用 HadoopSequenceFilesMapWritable类输出倒排索引。 该索引对机器处理更友好,存储效率更高。 您可以通过将步骤 3 中的命令替换为以下命令来运行此版本的程序:

    $ hadoop jar hcb-c8-samples.jar \
     chapter8.invertindex.InvertedIndexMapReduce \
     input_dir seq_output_dir
    
    

它是如何工作的.

Map 函数接收输入文档的一大块作为输入,并为每个单词输出术语和<docid,1>对。 在 Map 函数中,我们首先替换输入文本值中的所有非字母数字字符,然后再对其进行标记,如下所示:

public void map(Object key, Text value, ……… {
  String valString = value.toString().replaceAll("[^a-zA-Z0-9]+"," ");
  StringTokenizer itr = new StringTokenizer(valString);
   StringTokenizer(value.toString());

  FileSplit fileSplit = (FileSplit) context.getInputSplit();
  String fileName = fileSplit.getPath().getName();
  while (itr.hasMoreTokens()) {
    term.set(itr.nextToken());
    docFrequency.set(fileName, 1);
    context.write(term, docFrequency);
  }
}

我们使用MapContextgetInputSplit()方法来获取对分配给当前 Map 任务的InputSplit的引用。 此计算的InputSplits类是FileSplit的实例,这是因为使用了基于FileInputFormatInputFormat。 然后,我们使用FileSplitgetPath()方法获取包含当前拆分的文件的路径,并从中提取文件名。 在构建倒排索引时,我们使用这个提取的文件名作为文档 ID。

Reduce函数接收包含术语(键)作为输入的所有文档的 ID 和频率。 然后,Reduce函数将该术语和文档 ID 列表以及该术语在每个文档中出现的次数作为输出输出:

public void reduce(Text key, Iterable<TermFrequencyWritable> values,Context context) …………{

  HashMap<Text, IntWritable> map = new HashMap<Text, IntWritable>();
  for (TermFrequencyWritable val : values) {
    Text docID = new Text(val.getDocumentID());
    int freq = val.getFreq().get();
    if (map.get(docID) != null) {
      map.put(docID, new IntWritable(map.get(docID).get() + freq));
    } else {
      map.put(docID, new IntWritable(freq));
    }
  }
  MapWritable outputMap = new MapWritable();
  outputMap.putAll(map);
  context.write(key, outputMap);
}

在前面的模型中,我们为每个单词输出一条记录,在 Map 任务和 Reduce 任务之间生成大量中间数据。 我们使用以下合并器聚合 Map 任务发出的术语,从而减少需要在 Map 和 Reduce 任务之间传输的中间数据量:

public void reduce(Text key, Iterable<TermFrequencyWritable> values …… {
  int count = 0;
  String id = "";
  for (TermFrequencyWritable val : values) {
    count++;
    if (count == 1) {
      id = val.getDocumentID().toString();
    }
  }
  TermFrequencyWritable writable = new TermFrequencyWritable();
  writable.set(id, count);
  context.write(key, writable);
}

在驱动程序中,我们设置了 Mapper、Reducer 和 Combiner 类。 此外,当我们为 Map 任务和 Reduce 任务使用不同的值类型时,我们同时指定了 Output Value 和 MapOutput Value 属性。

…
job.setMapperClass(IndexingMapper.class);
job.setReducerClass(IndexingReducer.class);
job.setCombinerClass(IndexingCombiner.class);
…
job.setMapOutputValueClass(TermFrequencyWritable.class);
job.setOutputValueClass(MapWritable.class);
job.setOutputFormatClass(SequenceFileOutputFormat.class);

还有更多...

我们可以通过执行诸如过滤停用词、用词干替换词、存储更多关于词的上下文的信息等优化来改进该索引程序,从而使索引成为一个复杂得多的问题。 幸运的是,有几个开源索引框架可以用于索引目的。 本章后面的食谱将探索使用 Apache Solr 和 Elasticsearch 进行索引,它们基于 Apache Lucene 索引引擎。

下一节将介绍如何使用MapFileOutputFormat以索引随机访问的方式存储InvertedIndex

输出随机可访问索引 InvertedIndex

Apache Hadoop 支持一种名为MapFile的文件格式,可用于将索引存储到 SequenceFiles 中存储的数据中。 当我们需要随机访问存储在大型 SequenceFile 中的记录时,MapFile 非常有用。 您可以使用MapFileOutputFormat格式来输出 MapFiles,它将由一个包含实际数据的 SequenceFile 和另一个包含 SequenceFile 索引的文件组成。

chapter8源文件夹中的chapter8/invertindex/MapFileOutInvertedIndexMR.javaMapReduce 程序利用 MapFiles 将二级索引存储到倒排索引中。 您可以使用以下命令执行该程序。 第三个参数(sample_lookup_term)应该是输入数据集中的单词:

$ hadoop jar hcb-c8-samples.jar \
 chapter8.invertindex.MapFileOutInvertedIndexMR \
 input_dir indexed_output_dir sample_lookup_term

如果您选中indexed_output_dir,您将能够看到名为part-r-xxxxx的文件夹,每个文件夹包含一个data和一个index文件。 我们可以将这些索引加载到 MapFileOutputFormat,并对数据执行随机查找。 在MapFileOutInvertedIndexMR.java程序中给出了一个使用此方法的简单查找示例,如下所示:

MapFile.Reader[] indexReaders = MapFileOutputFormat.getReaders(new Path(args[1]), getConf());
MapWritable value = new MapWritable();
Text lookupKey = new Text(args[2]);
// Performing the lookup for the values if the lookupKey
Writable map = MapFileOutputFormat.getEntry(indexReaders, new HashPartitioner<Text, MapWritable>(), lookupKey, value);

为了使用此功能,您需要通过设置以下属性来确保禁止 Hadoop 在output文件夹中写入 _SUCCESS文件。 使用 MapFileOutputFormat 查找索引中的值时,_SUCCESS文件的存在可能会导致错误:

job.getConfiguration().setBoolean("mapreduce.fileoutputcommitter.marksuccessfuljobs", false);

另请参阅

  • 第 10 章海量文本数据处理中,为文本数据配方创建 TF 和 TF-IDF 矢量。

使用 Apache Nutch 进行域内网络爬行

网络爬行是访问和下载 Internet 上的所有或部分网页的过程。 虽然爬行和实现简单爬行器的概念听起来很简单,但构建一个成熟的爬行器需要大量的工作。 一个成熟的爬行器需要是分布式的,必须遵守最佳实践,如不使服务器过载并遵守robots.txt、执行定期爬行、确定要爬行的页面的优先顺序、识别多种格式的文档等。 Apache Nutch 是一个开源搜索引擎,它提供了一个高度可伸缩的爬行器。 Apache Nutch 提供了礼貌、健壮性和可伸缩性等特性。

在本食谱中,我们将在独立模式下使用 Apache Nutch 进行小规模的域内 Web 爬行。 几乎所有的 Nutch 命令都是作为 Hadoop MapReduce 应用实现的,正如您在执行本菜谱的步骤 10 到 18 时会注意到的那样。 Nutch Standalone 在本地模式下使用 Hadoop 执行这些应用。

本食谱以wiki.apache.org/nutch/Nutch…给出的说明为基础。

做好准备

设置JAVA_HOME环境变量。 安装 Apache Ant 并将其添加到PATH环境变量。

怎么做……

以下步骤显示了如何在独立模式下使用 Apache Nutch 进行小规模 Web 爬网:

  1. Apache Nutch 独立模式使用 HyperSQL 数据库作为默认数据存储。 从sourceforge.net/projects/hs…下载 HyperSQL。 解压缩发行版并转到数据目录:

    $ cd hsqldb-2.3.2/hsqldb
    
    
  2. 使用以下命令启动 HyperSQL 数据库。 以下数据库使用data/nutchdb.*作为数据库文件,并使用nutchdb作为数据库别名。 在步骤 7:

    $ java -cp lib/hsqldb.jar \
    org.hsqldb.server.Server \
    --database.0 file:data/nutchdb \
    --dbname.0 nutchtest
    ......
    [Server@79616c7]: Database [index=0, id=0, db=file:data/nutchdb, alias=nutchdb] opened sucessfully in 523 ms.
    ......
    
    

    中,我们将在gora.sqlstore.jdbc.url属性中使用此数据库别名

  3. nutch.apache.org/下载 Apache Nutch 2.2.1 并解压缩。

  4. Go to the extracted directory, which we will refer as NUTCH_HOME. Change the gora-core dependency version to 0.2.1 and uncomment the gora-sql dependency by modifying the Gora artifacts section of the ivy/ivy.xml file as follows:

    <!--================-->
    <!-- Gora artifacts -->
    <!--================-->
    <dependency org="org.apache.gora" name="gora-core" rev="0.2.1" conf="*->default"/>
    
    <dependency org="org.apache.gora" name="gora-sql" rev="0.1.1-incubating" conf="*->default" />
    

    备注

    您还可以通过更新conf/gora.properties文件的Default SqlStore properties部分中的必要数据库配置,将 MySQL 数据库用作 Nutch 独立模式 Web 爬网的后端数据库。 您还必须取消注释ivy/ivy.xml文件的Gora artifacts部分中的mysql-connector-java依赖项。

  5. 使用以下命令构建 Apache Nutch:

    $ ant runtime
    
    
  6. 确保在NUTCH_HOME/runtime/local/conf/gora.properties文件中包含以下内容。 提供步骤 2 中使用的数据库别名:

    ###############################
    # Default SqlStore properties #
    ###############################
    gora.sqlstore.jdbc.driver=org.hsqldb.jdbc.JDBCDriver
    gora.sqlstore.jdbc.url=jdbc:hsqldb:hsql://localhost/nutchtest
    gora.sqlstore.jdbc.user=sa
    
  7. 转到runtime/local目录并运行bin/nutch命令以验证 Nutch 安装。 成功的安装将打印出如下的 Nutch 命令列表:

    $ cd runtime/local
    $ bin/nutch 
    Usage: nutch COMMAND
    where COMMAND is one of:…..
    
    
  8. 将以下内容添加到NUTCH_HOME/runtime/local/conf/nutch-site.xml。 您可以为指定http.agent.name

    <configuration>
    <property>
      <name>storage.data.store.class</name>
      <value>org.apache.gora.sql.store.SqlStore</value>
    </property>
    <property>
      <name>http.agent.name</name>
      <value>NutchCrawler</value>
    </property>
    <property>
      <name>http.robots.agents</name>
      <value>NutchCrawler,*</value>
    </property>
    </configuration>
    

    的值的任何名称

  9. 您可以通过编辑位于NUTCH_HOME/runtime/local/conf/regex-urlfiler.txt文件来限制要爬网的域名。 例如,为了将域限制为apache.org,请在NUTCH_HOME/runtime/local/conf/regex-urlfilter.txt处替换以下行:

    # accept anything else
    +.
    
  10. 使用以下正则表达式:

```scala
+^http://([a-z0-9]*\.)*apache.org/
```

11. 创建名为urls的目录,并在该目录内创建名为seed.txt的文件。 将您的种子 URL 添加到此文件。 种子 URL 用于开始爬网,并且将是最先爬网的页面。 在下面的示例中,我们使用apache.org作为种子 URL:

```scala

$ mkdir urls

$ echo http://apache.org/ > urls/seed.txt

```

12. 使用以下命令将种子 URL 注入 Nutch 数据库:

```scala
$ bin/nutch inject urls/
InjectorJob: starting
InjectorJob: urlDir: urls
……
Injector: finished

```

13. 使用以下命令验证种子是否注入到 Nutch 数据库。 此命令打印的TOTAL urls应与您的seed.txt文件中的 URL 数量相匹配。 您还可以在后面的周期中使用以下命令来了解数据库中的网页条目数量:

```scala
$ bin/nutch readdb  -stats
WebTable statistics start
Statistics for WebTable: 
min score:  1.0
....
TOTAL urls:  1

```

14. 使用以下命令从注入的种子 URL 生成获取列表。 这将准备在爬行的第一个周期中要获取的网页列表。 生成将为当前生成的获取列表分配一个批处理 ID,该获取列表可在后续命令中使用:

```scala
$ bin/nutch generate –topN 1
GeneratorJob: Selecting best-scoring urls due for fetch.
GeneratorJob: starting
GeneratorJob: filtering: true
GeneratorJob: done
GeneratorJob: generated batch id: 1350617353-1356796157

```

15. 使用以下命令获取在步骤 12 中准备的页面列表。此步骤执行网页的实际获取。 参数–all用于通知 Nutch 获取所有生成的批次:

```scala
$ bin/nutch fetch -all
FetcherJob: starting
FetcherJob: fetching all
FetcherJob: threads: 10
......

fetching http://apache.org/
......

-activeThreads=0
FetcherJob: done

```

16. 使用下面的命令从抓取的网页中解析并提取有用的数据,如页面的文本内容、页面的元数据、从抓取的页面链接的页面集合等。 我们将从获取的页面链接的一组页面称为该特定获取的页面的外部链接。 外部链接数据将用于发现要获取的新页面,以及使用链接分析算法(如 PageRank:

```scala
$ bin/nutch parse -all
ParserJob: starting
......
ParserJob: success

```

)对页面进行排名

17. 执行以下命令,使用上一步中提取的数据更新 Nutch 数据库。 此步骤包括更新获取的页面的内容以及添加通过获取的页面中包含的链接发现的页面的新条目。

```scala
$ bin/nutch updatedb
DbUpdaterJob: starting
……
DbUpdaterJob: done

```

18. 执行以下命令,使用先前获取的数据中的信息生成新的获取列表。 topN参数限制为下一个提取周期生成的 URL 数量:

```scala
$ bin/nutch generate -topN 100
GeneratorJob: Selecting best-scoring urls due for fetch.
GeneratorJob: starting
......
GeneratorJob: done
GeneratorJob: generated batch id: 1350618261-1660124671

```

19. 获取新列表,对其进行解析,然后更新数据库。

```scala

$ bin/nutch fetch –all
......

$ bin/nutch parse -all 
......

$ bin/nutch updatedb
......

```

20. 重复步骤 17 和 18,直到从起始 URL 获得所需页数或所需深度。

另请参阅

  • 使用 Hadoop/HBase 集群的 Apache Nutch 进行的全网爬行和使用 Apache Solr食谱索引和搜索 Web 文档的*。*
  • 有关使用 HyperSQL 的更多信息,请参考www.hsqldb.org/doc/2.0/gui…

使用 Apache Solr 索引和搜索 Web 文档

Apache Solr是一个开源搜索平台,是Apache Lucene项目的一部分。 它支持强大的全文搜索、分面搜索、动态集群、数据库集成、丰富的文档(例如 Word 和 PDF)处理和地理空间搜索。 在本食谱中,我们将为 Apache Nutch 抓取的网页编制索引,以供 Apache Solr 使用,并使用 Apache Solr 搜索这些网页。

做好准备

  1. 按照使用 Apache Nutch 配方的域内 Web 爬网,使用 Apache Nutch 爬网一组网页
  2. Solr 4.8 及更高版本需要 JDK 1.7

怎么做……

以下步骤显示如何索引和搜索已爬网的网页数据集:

  1. lucene.apache.org/solr/下载并提取 Apache Solr。 对于本章中的示例,我们使用 Apache Solr 4.10.3。 从这里开始,我们将提取的目录称为$SOLR_HOME

  2. 使用位于$NUTCH_HOME/runtime/local/conf/下的 schema.solr4.xml文件替换位于$SOLR_HOME/examples/solr/collection1/conf/下的schema.xml文件,如下所示:

    $ cp $NUTCH_HOME/conf/schema-solr4.xml \
     $SOLR_HOME/example/solr/collection1/conf/schema.xml
    
    
  3. 将以下配置添加到<fields>标记下的$SOLR_HOME/examples/solr/collection1/conf/schema.xml

    <fields>
      <field name="_version_" type="long" indexed="true" stored="true"/>
    ……
    </fields>
    
  4. 通过从$SOLR_HOME/下的example目录执行以下命令启动 Solr:

    $ java -jar start.jar
    
    
  5. 转到 URLhttp://localhost:8983/solr以验证 Apache Solr 安装。

  6. 通过从$NUTCH_HOME/runtime/local目录发出以下命令,将使用 Apache Nutch 获取的数据索引到 Apache Solr 中。 此命令通过 Solr Web 服务接口将 Nutch 抓取的数据推送到 Solr 中:

    $ bin/nutch solrindex http://127.0.0.1:8983/solr/ -reindex 
    
    
  7. Go to Apache Solr search UI at http://localhost:8983/solr/#/collection1/query. Enter a search term in the q textbox and click on Execute Query, as shown in the following screenshot:

    How to do it...

  8. 您还可以使用 HTTP get 请求直接发出搜索查询。 将http://localhost:8983/solr/collection1/select?q=hadoop&start=5&rows=5&wt=xmlURL 粘贴到您的浏览器。

它是如何工作的.

Apache Solr 是使用 Apache Lucene 文本搜索库构建的。 Apache Solr 在 Apache Lucene 之上添加了许多功能,并提供了一个开箱即用的文本搜索 Web 应用。 前面的步骤部署 Apache Solr,并将 Nutch 抓取的数据导入到部署的 Solr 实例中。

我们计划使用 Solr 索引和搜索的文档的元数据需要通过 Solrschema.xml文件指定。 Solr 模式文件应该定义文档中的数据字段,以及 Solr 应该如何处理这些数据字段。 我们使用随 Nutch($NUTCH_HOME/conf/schema-solr4.xml)提供的模式文件,该文件定义了 Nutch 抓取的网页的模式,作为本食谱的 Solr 模式文件。 有关 Solr 模式文件的更多信息可以在wiki.apache.org/solr/Schema…中找到。

另请参阅

将 Apache HBase 配置为 Apache Nutch 的后端数据存储

Apache Nutch 集成了 Apache Gora 以添加对不同后端数据存储的支持。 在本食谱中,我们将 Apache HBase 配置为 Apache Nutch 的后端数据存储。 同样,可以通过 Gora 插入数据存储,如 RDBMS 数据库、Cassandra 和其他。

本食谱以wiki.apache.org/nutch/Nutch…给出的说明为基础。

备注

从 Apache Nutch 2.2.1 版本开始,Nutch 项目还没有正式迁移到 Hadoop 2.x,整个网络爬行仍然依赖 Hadoop 1.x。 但是,可以使用 Hadoop 2.x 集群执行 Nutch 作业,该集群利用 Hadoop 的向后兼容性特性。

Nutch HBaseStore 集成进一步依赖于 HBase 0.90.6,而 HBase 0.90.6 不支持 Hadoop2。因此,此配方仅适用于 Hadoop 1.x 集群。 我们期待着一个完全支持 Hadoop2.x 的新 Nutch 版本。

做好准备

  1. 安装 ApacheAnt 并将其添加到PATH环境变量。

怎么做……

以下步骤说明如何将 Apache HBase 本地模式配置为 Apache Nutch 的后端数据存储,以存储已爬网的数据:

  1. 安装 Apache HBase。 Apache Nutch 2.2.1 和 Apache Gora 0.3 推荐使用 HBase 0.90.6 版本。

  2. Create two directories to store the HDFS data and Zookeeper data. Add the following to the hbase-site.xml file under $HBASE_HOME/conf/ replacing the values with the paths to the two directories. Start HBase:

    <configuration>
    <property>
        <name>hbase.rootdir</name>
        <value>file:///u/software/hbase-0.90.6/hbase-data</value>
      </property>
    <property>
        <name>hbase.zookeeper.property.dataDir</name>
        <value>file:///u/software/hbase-0.90.6/zookeeper-data</value>
      </property>
    </configuration>
    

    提示

    在继续之前,请使用 HBase Shell 测试您的 HBase 安装。

  3. 如果您没有为本章前面的食谱下载 Apache Nutch,请从nutch.apache.org下载 Nutch 并解压缩。

  4. 将以下内容添加到$NUTCH_HOME/conf/下的nutch-site.xml 文件中:

    <property>
     <name>storage.data.store.class</name>
     <value>org.apache.gora.hbase.store.HBaseStore</value>
     <description>Default class for storing data</description>
    
    </property>
    <property>
     <name>http.agent.name</name>
     <value>NutchCrawler</value>
    </property>
    <property>
      <name>http.robots.agents</name>
      <value>NutchCrawler,*</value>
    </property>
    
  5. 取消注释$NUTCH_HOME/ivy/ivy.xml 文件的Gora artifacts部分中后面的。 恢复您对早期配方中的 ivy/ivy.xml 文件所做的更改,并确保gora-core依赖项版本为 0.3。 此外,请确保注释gora-sql依赖项:

    <dependency org="org.apache.gora" name="gora-hbase" rev="0.3" conf="*->default" />
    
  6. 将以下内容添加到$NUTCH_HOME/conf/下的gora.properties文件中,以将 HBase 存储设置为默认的 GORA 数据存储:

    gora.datastore.default=org.apache.gora.hbase.store.HBaseStore
    
  7. $NUTCH_HOME目录中执行以下命令,以 HBase 作为后端数据存储构建 Apache Nutch:

    $ ant clean
    $ ant runtime
    
    
  8. 按照使用 Apache Solr 配方的域内 Web 爬网步骤 9 到 19 进行操作。

  9. 启动 HBase shell 并发出以下命令以查看获取的数据:

    $ hbase shell
    HBase Shell; enter 'help<RETURN>' for list of supported commands.
    Type "exit<RETURN>" to leave the HBase Shell
    Version 0.90.6, r1295128, Wed Feb 29 14:29:21 UTC 2012
    hbase(main):001:0> list
    TABLE 
    webpage 
    1 row(s) in 0.4970 seconds
    
    hbase(main):002:0> count 'webpage'
    Current count: 1000, row: org.apache.bval:http/release-management.html 
    Current count: 2000, row: org.apache.james:http/jspf/index.html 
    Current count: 3000, row: org.apache.sqoop:http/team-list.html 
    Current count: 4000, row: org.onesocialweb:http/ 
    4065 row(s) in 1.2870 seconds
    
    hbase(main):005:0> scan 'webpage',{STARTROW => 'org.apache.nutch:http/', LIMIT=>10}
    ROW                                   COLUMN+CELL
     org.apache.nutch:http/               column=f:bas, timestamp=1350800142780, value=http://nutch.apache.org/
     org.apache.nutch:http/               column=f:cnt, timestamp=1350800142780, value=<....
    ......
    10 row(s) in 0.5160 seconds
    
    
  10. 遵循索引**中的步骤,使用 Apache Solr配方搜索 Web 文档,并使用 Apache Solr 搜索获取的数据。

它是如何工作的.

以上步骤使用 Apache HBase 作为存储后端配置并运行 Apache Nutch。 配置后,Nutch 将获取的网页数据和其他元数据存储在 HBase 表中。 在本食谱中,我们使用独立的 HBase 部署。 然而,正如使用 Hadoop/HBase 集群的 Apache Nutch 进行全网爬行食谱所示,Nutch 也可以与分布式 HBase 部署一起使用。 使用 HBase 作为后端数据存储为 Nutch 爬行提供了更高的可伸缩性和性能。

另请参阅

使用 Hadoop/HBase 群集使用 Apache Nutch 进行全 Web 爬行

通过利用 MapReduce 集群的强大功能,可以高效地爬行大量的个 Web 文档。

备注

从 Apache Nutch 2.2.1 版本开始,Nutch 项目还没有正式迁移到 Hadoop 2.x,整个网络爬行仍然依赖 Hadoop 1.x。 但是,可以使用 Hadoop 2.x 集群执行 Nutch 作业,该集群利用 Hadoop 的向后兼容性特性。

Nutch HBaseStore 集成进一步依赖于 HBase 0.90.6,而 HBase 0.90.6 不支持 Hadoop2。因此,此配方仅适用于 Hadoop 1.x 集群。 我们期待着一个完全支持 Hadoop2.x 的新 Nutch 版本。

做好准备

我们假设您已经部署了 Hadoop 1.x 和 HBase 集群。

怎么做……

以下步骤说明如何将 Apache Nutch 与 Hadoop MapReduce 集群和 HBase 数据存储配合使用,以执行大规模 Web 爬行:

  1. 确保可以从命令行访问hadoop命令。 如果没有,请将$HADOOP_HOME/bin目录添加到计算机的PATH环境变量中,如下所示:

    $ export PATH=$PATH:$HADOOP_HOME/bin/
    
    
  2. 按照将 Apache HBase 配置为 Apache Nutch的后端数据存储食谱中的步骤 3 到 7 进行操作。 如果您已经按照该食谱操作,则可以跳过此步骤。

  3. 在 HDFS 中创建一个目录以上载种子 URL。

    $ hadoop dfs -mkdir urls
    
    
  4. Create a text file with the seed URLs for the crawl. Upload the seed URLs file to the directory created in the preceding step.

    $ hadoop dfs -put seed.txt urls
    
    

    备注

    您可以使用 Open Directory 项目 RDF 转储(rdf.dmoz.org/)来创建种子 URL。 Nutch 提供了一个实用程序类,用于从提取的 DMOZ RDF 数据中选择 URL 子集,如bin/nutch org.apache.nutch.tools.DmozParser content.rdf.u8 -subset 5000 > dmoz/urls

  5. $NUTCH_HOME/runtime/deploy发出下面的命令,将种子 URL 注入到 Nutch 数据库,并生成初始获取列表:

    $ bin/nutch inject urls
    $ bin/nutch generate
    
    
  6. $NUTCH_HOME/runtime/deploy发出以下命令:

    $ bin/nutch fetch -all
    14/10/22 03:56:39 INFO fetcher.FetcherJob: FetcherJob: starting
    14/10/22 03:56:39 INFO fetcher.FetcherJob: FetcherJob: fetching all
    ......
    
    $ bin/nutch parse -all
    14/10/22 03:48:51 INFO parse.ParserJob: ParserJob: starting
    ......
    
    14/10/22 03:50:44 INFO parse.ParserJob: ParserJob: success
    
    $ bin/nutch updatedb
    14/10/22 03:53:10 INFO crawl.DbUpdaterJob: DbUpdaterJob: starting
    ....
    14/10/22 03:53:50 INFO crawl.DbUpdaterJob: DbUpdaterJob: done
    
    $ bin/nutch generate -topN 10
    14/10/22 03:51:09 INFO crawl.GeneratorJob: GeneratorJob: Selecting best-scoring urls due for fetch.
    14/10/22 03:51:09 INFO crawl.GeneratorJob: GeneratorJob: starting
    ....
    14/10/22 03:51:46 INFO crawl.GeneratorJob: GeneratorJob: done
    14/10/22 03:51:46 INFO crawl.GeneratorJob: GeneratorJob: generated batch id: 1350892269-603479705
    
    
  7. 根据需要重复步骤 6 中的命令,以爬网所需的页数或深度。

  8. 遵循使用 Apache Solr索引和搜索 Web 文档的食谱,使用 Apache Solr 为获取的数据编制索引。

它是如何工作的.

我们在本配方中使用的所有 Nutch 操作,包括获取和解析,都是作为 MapReduce 程序实现的。 这些 MapReduce 程序利用 Hadoop 集群以分布式方式执行 Nutch 操作,并使用 HBase 跨 HDFS 集群存储数据。 您可以通过 Hadoop 集群的监控 UI 监控这些 MapReduce 计算。

Apache Nutch Ant Build 创建一个 Hadoop 作业文件,其中包含$NUTCH_HOME/runtime/deploy文件夹中的所有依赖项。 bin/nutch脚本使用此作业文件将 MapReduce 计算提交给 Hadoop 集群。

另请参阅

  • 使用 Apache Nutch 配方的域内 Web 爬行。

用于索引和搜索的 Elasticsearch

Elasticsearch(www.elasticsearch.org/)是一个 Apache2.0 许可的开源搜索解决方案,构建在 Apache Lucene 的之上。 ElasticSearch 是一个分布式、多租户和面向文档的搜索引擎。 ElasticSearch 通过将索引分解为碎片并跨集群中的节点分布碎片来支持分布式部署。 Elasticsearch 和 Apache Solr 都使用 Apache Lucene 作为核心搜索引擎,而 Elasticsearch 的目标是提供比 Apache Solr 更适合云环境的可伸缩性和分布式解决方案。

做好准备

安装 Apache Nutch 并按照域内 Web 爬网使用 Apache Nutch或使用 Hadoop/HBase 群集配方使用 Apache Nutch 爬网爬网一些网页。 确保 Nutch 的后端 HBase(或 HyperSQL)数据存储仍然可用。

怎么做……

以下步骤说明如何使用 Elasticsearch 索引和搜索 Nutch 搜索到的数据:

  1. 下载并从www.elasticsearch.org/download/解压弹性搜索。

  2. 转到解压的 Elasticsearch 目录,执行以下命令在前台启动 Elasticsearch 服务器:

    $ bin/elasticsearch
    
    
  3. 在新控制台中运行以下命令以验证您的安装:

    > curl localhost:9200
    {
     "status" : 200,
     "name" : "Talisman",
     "cluster_name" : "elasticsearch",
     "version" : {
     "number" : "1.4.2",
     ……
     "lucene_version" : "4.10.2"
     },
     "tagline" : "You Know, for Search"
    }
    
    
  4. 转到$NUTCH_HOME/runtime/deploy(如果您在本地模式下运行 Nutch,则转到$NUTCH_HOME/runtime/local)目录。 执行以下命令,将 Nutch 抓取的数据索引到 Elasticsearch 服务器:

    $ bin/nutch elasticindex elasticsearch -all 
    14/11/01 06:11:07 INFO elastic.ElasticIndexerJob: Starting
    …...
    
    
  5. 发出以下命令执行搜索:

    $ curl -XGET 'http://localhost:9200/_search?q=hadoop'
    ....
    {"took":3,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":36,"max_score":0.44754887,"hits":[{"_index":"index","_type":"doc","_id": 100 30551  100  30551 "org.apache.hadoop:http/","_score":0.44754887, ....
    
    

它是如何工作的.

与 Apache Solr 类似,Elasticsearch 也是使用 Apache Lucene 文本搜索库构建的。 在前面的步骤中,我们将 Nutch 爬行的数据导出到 Elasticsearch 实例中,以用于索引和搜索。

您也可以将 Elasticsearch 安装为一项服务。 有关将 www.elasticsearch.org/guide/refer… 作为服务安装的更多详细信息,请参阅Elasticsearch

我们使用 Nutch 的 ElasticIndex 作业将 Nutch 抓取的数据导入到 Elasticsearch 服务器。 弹性索引命令的用法如下:

bin/nutch  elasticindex  <elastic cluster name> \
 (<batchId> | -all | -reindex) [-crawlId <id>]

弹性集群名称恢复为默认值 ElasticSearch。 您可以通过编辑config/下的elasticsearch.yml文件中的cluster.name属性来更改群集名称。 群集名称用于自动发现,并且对于单个网络中的每个 Elasticsearch 部署都应该是唯一的。

另请参阅

  • 使用 Apache Solr配方索引和搜索 Web 文档。

为抓取的网页生成内链接图

从其他页面到特定网页的个链接的数量,即内链接的数量,被广泛认为是衡量一个网页的受欢迎程度或重要性的一个很好的度量标准。 事实上,网页内链接的数量和这些链接来源的重要性已经成为大多数流行的链接分析算法(如 Google 引入的 PageRank)不可或缺的组成部分。

在本食谱中,我们将从 Apache Nutch 获取并存储在 Apache HBase 后端数据存储中的一组网页中提取内链接信息。 在我们的 MapReduce 程序中,我们首先检索存储在 Nutch HBase 数据库中的那组网页的外部链接信息,然后使用该信息计算这组网页的内部链接图。 所计算的内链接图将仅包含来自获取的 web 图的子集的链接信息。

做好准备

遵循使用 Hadoop/HBase 群集配方使用 Apache Nutch 进行全网爬行,或者将 Apache HBase 配置为 Apache Nutch配方的后端数据存储,并使用 Apache Nutch 将一组网页爬行到后端 HBase 数据存储。

怎么做……

以下步骤显示如何从存储在 Nutch HBase 数据存储中的网页中提取出链接图,以及如何使用提取出的出链图计算入链接图:

  1. 启动 HBase 外壳:

    $ hbase shell
    
    
  2. 创建名为linkdata的 HBase 表和名为il的列族。 退出 HBase 外壳:

    hbase(main):002:0> create 'linkdata','il'
    0 row(s) in 1.8360 seconds
    hbase(main):002:0> quit
    
    
  3. 解压缩本章的源包,并通过从chapter8源目录执行gradle build来编译它。

  4. 通过发出以下命令运行 Hadoop 程序:

    $ hadoop jar hcb-c8-samples.jar \
    chapter8.InLinkGraphExtractor
    
    
  5. 启动 HBase shell 并使用以下命令扫描linkdata表,以检查 MapReduce 程序的输出:

    $ hbase shell
    hbase(main):005:0> scan 'linkdata',{COLUMNS=>'il',LIMIT=>10}
    ROW                            COLUMN+CELL 
    ....
    
    

它是如何工作的.

由于我们将使用 HBase 来读取输入以及写入输出,因此我们使用 HBaseTableMapperTableReducer帮助器类来实现 MapReduce 应用。 我们使用TableMapReduceUtil类中给出的实用程序方法配置TableMapperTableReducer类。 对象Scan用于指定从 HBase 数据存储读取输入数据时映射器要使用的条件:

Configuration conf = HBaseConfiguration.create();
Job job = new Job(conf, "InLinkGraphExtractor");
job.setJarByClass(InLinkGraphExtractor.class);
Scan scan = new Scan();
scan.addFamily("ol".getBytes());
TableMapReduceUtil.initTableMapperJob("webpage", scan, ……);
TableMapReduceUtil.initTableReducerJob("linkdata",……);

MAP 实现接收 HBase 行作为输入记录。 在我们的实现中,每行对应一个获取的网页。 Map函数的输入键由网页 URL 组成,值由从该特定网页链接的网页组成。 Map函数为每个链接网页发出一条记录,其中Map输出记录的关键字是链接页面的 URL,Map输出记录的值是Map函数的输入键(当前处理网页的 URL):

public void map(ImmutableBytesWritable row, Result values,……){
  List<KeyValue> results = values.list();      
  for (KeyValue keyValue : results) {
    ImmutableBytesWritable userKey = new     ImmutableBytesWritable(keyValue.getQualifier());
    try {
      context.write(userKey, row);
    } catch (InterruptedException e) {
      throw new IOException(e);
    }
  }
}

Reduce 实现接收网页 URL 作为关键字,并接收包含到该网页的链接(在关键字中提供)的网页列表作为值。 Reduce 函数将该数据存储到 HBase 表中:

public void reduce(ImmutableBytesWritable key,
  Iterable<ImmutableBytesWritable> values, ……{

Put put = new Put(key.get());
  for (ImmutableBytesWritable immutableBytesWritable :values)   {
    put.add(Bytes.toBytes("il"), Bytes.toBytes("link"),
            immutableBytesWritable.get());
  }
  context.write(key, put);
}

另请参阅

  • 第 7 章,Hadoop 生态系统 II 中的在 HBase配方上运行 MapReduce 作业-Pig、HBase、Mahout 和 Sqoop

九、分类、推荐和查找关系

在本章中,我们将介绍:

  • 执行基于内容的推荐
  • 利用朴素贝叶斯分类器进行分类
  • 使用 Adword Balance 算法将广告分配给关键字

简介

本章讨论如何将 Hadoop 用于更复杂的用例,如对数据集进行分类和提出建议。

以下是一些此类场景的几个实例:

  • 根据产品之间的相似性(例如,如果一个用户喜欢一本关于历史的书,他/她可能喜欢另一本关于同一主题的书)或基于用户行为模式(例如,如果两个用户相似,他们可能喜欢另一个人读过的书)向用户推荐产品
  • 对数据集进行聚类以标识相似实体;例如,标识具有相似兴趣的用户
  • 根据历史数据将数据分类为几组

在本食谱中,我们将使用 MapReduce 应用这些技术和其他技术。 对于本章中的食谱,我们将使用亚马逊产品联合采购网络元数据数据集,可从snap.stanford.edu/data/amazon…获取。

备注

本章的内容基于本书前一版Hadoop MapReduce Cookbook分类、建议和查找关系。 这一章是由合著者斯里纳特·佩雷拉(Srinath Perera)贡献的。

提示

示例代码

本书的示例代码和数据文件位于 GitHub 的github.com/thilg/hcb-v…中。 代码库的chapter9文件夹包含本章的示例源代码文件。 通过在代码库的chapter9文件夹中发出gradle build命令,可以编译和构建示例代码。 Eclipse IDE 的项目文件可以通过在代码存储库的主文件夹中运行gradle eclipse命令来生成。 IntelliJ IDEA IDE 的项目文件可以通过在代码库的主文件夹中运行gradle idea命令来生成。

执行基于内容的推荐

推荐是对某人可能感兴趣的事情的建议。 例如,你会把一本好书推荐给一位你知道和你有相似兴趣的朋友。 我们经常在网上零售中找到推荐的用例。 例如,当你浏览一款产品时,亚马逊会推荐购买该特定商品的用户也购买的其他产品。

像亚马逊这样的在线零售网站有非常多的商品。 虽然书籍被分成几个类别,但通常每个类别都有太多的书,不能一个接一个地浏览。 推荐使用户的生活变得更轻松,帮助他找到最适合自己口味的产品,同时增加销售。

提出建议的方式有很多种:

  • 基于内容的推荐:可以使用关于该产品的信息来识别类似的产品。 例如,您可以使用类别、内容相似性等来识别相似的产品,并将它们推荐给已经购买了特定产品的用户。
  • 协作过滤:另一个选项是使用用户行为来识别产品之间的相似性。 例如,如果同一用户给两个产品打了很高的分数,那么这两个产品之间就有一些相似之处。

这个食谱使用从亚马逊收集的关于产品的数据集来提供基于内容的推荐。 在数据集中,每个产品都有一个由 Amazon 预先确定的提供给用户的类似商品列表。 在本食谱中,我们将使用这些数据来提出建议。

怎么做……

  1. Download the dataset from the Amazon product co-purchasing network metadata available at snap.stanford.edu/data/amazon… and unzip it. We call this directory as DATA_DIR.

    通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  2. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  3. 使用以下命令运行最频繁的用户查找程序 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
     chapter9.MostFrequentUserFinder \
     data/input1 data/output1
    
    
  4. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output1/*
    
    
  5. 您将看到 MapReduce 作业已经从每个客户提取了购买数据,结果将如下所示:

    customerID=A1002VY75YRZYF,review=ASIN=0375812253#title=Really Useful Engines (Railway Series)#salesrank=623218#group=Book #rating=4#similar=0434804622|0434804614|0434804630|0679894780|0375827439|,review=ASIN=B000002BMD#title=EverythingMustGo#salesrank=77939#group=Music#rating=4#similar=B00000J5ZX|B000024J5H|B00005AWNW|B000025KKX|B000008I2Z
    
    
  6. 使用以下命令运行建议 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
    chapter9.ContentBasedRecommendation \
    data/output1 data/output2
    
    
  7. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output2/*
    
    

您将看到它将按如下方式打印结果。 结果的每一行都包含客户 ID 和针对该客户的产品推荐列表。

A10003PM9DTGHQ  [0446611867, 0446613436, 0446608955, 0446606812, 0446691798, 0446611867, 0446613436, 0446608955, 0446606812, 0446691798]

它是如何工作的.

下面的清单显示了数据集中一个产品的条目。 这里,每个数据条目包括 ID、标题、分类、与该项目类似的项目以及有关已审阅该项目的用户的信息。 在本例中,我们假设查看了该商品的客户已经购买了该商品。

Id:   13
ASIN: 0313230269
  title: Clockwork Worlds : Mechanized Environments in SF (Contributions to the Study of Science Fiction and Fantasy)
  group: Book
  salesrank: 2895088
  similar: 2  1559360968  1559361247
  categories: 3
   |Books[283155]|Subjects[1000]|Literature & Fiction[17]|History & Criticism[10204]|Criticism & Theory[10207]|General[10213]
   |Books[283155]|Subjects[1000]|Science Fiction & Fantasy[25]|Fantasy[16190]|History & Criticism[16203]
   |Books[283155]|Subjects[1000]|Science Fiction & Fantasy[25]|Science Fiction[16272]|History & Criticism[16288]
  reviews: total: 2  downloaded: 2  avg rating: 5
    2002-8-5  cutomer: A14OJS0VWMOSWO  rating: 5  votes:   2  helpful:   1
    2003-3-21  cutomer:  A2C27IQUH9N1Z  rating: 5  votes:   4  helpful:   4

我们已经编写了 Hadoop InputFormat 来解析 Amazon 产品数据;数据格式类似于我们使用第 5 章Analytics的 MapReduce 配方在简单分析中编写的格式。 源文件src/chapter9/amazondata/AmazonDataReader.javasrc/chapter9/amazondata/AmazonDataFormat.java包含 Amazon 数据格式化程序的代码。

Amazon 数据格式化程序将解析数据集,并将有关每个 Amazon 产品的数据作为键-值对发送给map函数。 关于每个 Amazon 产品的数据表示为一个字符串,AmazonCustomer.java类包括解析和写出有关 Amazon 客户的数据的代码。

此配方包括两个 MapReduce 计算。 这些任务的来源可以从src/chapter9/MostFrequentUserFinder.javasrc/chapter9/ ContentBasedRecommendation.java中找到。 第一个 MapReduce 作业的 Map 任务在日志文件中以不同的键-值对接收有关每个产品的数据。

当 Map 任务接收到产品数据时,它将发出客户 ID 作为关键字,并发出产品信息作为购买产品的每个客户的值。

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
    List<AmazonCustomer> customerList = AmazonCustomer.parseAItemLine(value.toString());
    for(AmazonCustomer customer: customerList){
        context.write(new Text(customer.customerID),
        new Text(customer.toString()));
    }
}

然后,Hadoop 收集键的所有值,并为每个键调用一次 Reducer。 每个客户都有一个reduce函数调用,每个调用都将接收客户购买的所有产品。 Reducer 发出每个客户购买的商品列表,从而构建客户配置文件。 每个项目也包含相似项目的列表。 为了限制数据集的大小,Reducer 将只发出购买了五种以上产品的客户的详细信息。

public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
    AmazonCustomer  customer = new AmazonCustomer();
    customer.customerID = key.toString();

    for(Text value: values){
        Set<ItemData> itemsBought =new AmazonCustomer(
        value.toString()).itemsBought;
        for(ItemData itemData: itemsBrought){
            customer.itemsBought.add(itemData);
        }
    }
    if(customer.itemsBought.size() > 5){
        context.write(
        new IntWritable(customer.itemsBrought.size()),
        new Text(customer.toString()));
    }
}

第二个 MapReduce 作业使用第一个 MapReduce 任务生成的数据为每个客户提供建议。 Map 任务接收有关每个客户的数据作为输入,MapReduce 作业使用以下三个步骤提出建议:

  1. 来自亚马逊的每个产品(项目)数据都包括与该项目类似的项目。 给定一个客户,map函数为该客户购买的每个项目创建一个所有类似项目的列表。

  2. 然后,map函数从相似项目列表中删除客户已经购买的任何项目。

  3. 最后,map函数选择 10 个项目作为推荐。

    public void map(Object key, Text value, Context context)
    throws IOException, InterruptedException {
      AmazonCustomer amazonCustomer =
      new AmazonCustomer(value.toString()
      .replaceAll("[0-9]+\\s+", ""));
    
      List<String> recommendations = new ArrayList<String>();
      for (ItemData itemData : amazonCustomer.itemsBrought) {
        recommendations.addAll(itemData.similarItems);
      }
    
      for (ItemData itemData : amazonCustomer.itemsBrought) {
        recommendations.remove(itemData.itemID);
      }
    
      ArrayList<String> finalRecommendations =
      new ArrayList<String>();
      for (int i = 0;
      i < Math.min(10, recommendations.size());i++) {
        finalRecommendations.add(recommendations.get(i));
      }
      context.write(new Text(amazonCustomer.customerID),
      new Text(finalRecommendations.toString()));
    }
    

还有更多...

您可以从 Anand Rajaraman 和 Jeffrey David Ullman 的推荐系统挖掘海量数据集剑桥大学出版社一书的推荐系统中了解更多基于内容的推荐。

Apache Mahout 在第 7 章Hadoop 生态系统 II-Pig、HBase、Mahout 和 Sqoop中介绍了,并在第 10 章海量文本数据处理中使用,包含几个推荐实现。 以下文章将为您提供有关在 Mahout 中使用基于用户和基于项目的推荐器的信息:

使用朴素贝叶斯分类器进行分类

分类器根据输入的某些属性(也称为特征)将输入分配到N类之一。 分类器有着广泛的应用,例如电子邮件垃圾邮件过滤、查找最有前途的产品、选择客户进行更密切的交互以及在机器学习的情况下做出决策。 让我们探索如何使用大型数据集实现分类器。 例如,垃圾邮件过滤器会将每个电子邮件分配给两个群集之一:垃圾邮件或非垃圾邮件。

分类算法有很多种。 最简单但有效的算法之一是朴素的贝叶斯分类器,它使用涉及条件概率的贝叶斯定理。

在本食谱中,我们还将一如既往地关注 Amazon 元数据数据集。 我们将查看产品的几个功能,例如收到的评论数量、正面评分和已知的类似项目,以确定有潜力进入前 10,000 个销售排名的产品。 我们将使用朴素贝叶斯分类器进行分类。

备注

您可以在en.wikipedia.org/wiki/Naive_…了解更多关于朴素拜耳分类器的信息。

怎么做……

  1. 从亚马逊产品的联合购买网络元数据(可从snap.stanford.edu/data/amazon…下载)下载数据集并解压缩。 我们将此目录命名为DATA_DIR

  2. 通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  3. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \ 
    chapter9.NaiveBayesProductClassifier \
    data/input1 data/output5
    
    
  5. 通过运行以下命令读取结果:

    $ hdfs dfs -cat data/output5/*
    
    
  6. 您将看到它将打印以下结果。 您可以将这些值与贝叶斯分类器一起使用来对输入进行分类:

    postiveReviews>30       0.635593220338983
    reviewCount>60  0.62890625
    similarItemCount>150    0.5720620842572062
    
    

它是如何工作的.

分类器使用以下特征作为指示符,表示该产品可以落入前 10,000 个产品内:

  • 对给定产品的评论次数
  • 对给定产品的正面评论数量
  • 给定产品的类似产品数量

我们首先运行 MapReduce 任务来计算以下概率,然后将这些概率与前面的公式一起使用来对给定产品进行分类:

  • P1:如果某项评论超过 60 条,则该条目在前 10,000 个产品中的概率
  • P2:如果某个项目的正面评价超过 30 条,则该项目在前 10,000 个产品中的概率
  • P3:如果某一项目有超过 150 个类似项目,则该项目在前 10,000 个产品内的概率

您可以在文件src/chapter9/``NaiveBayesProductClassifier.java中找到分类器的源。 Mapper 函数如下所示:

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
  List<AmazonCustomer> customerList = AmazonCustomer.parseAItemLine(value.toString());
  int salesRank = -1;
  int reviewCount = 0;
  int postiveReviews = 0;
  int similarItemCount = 0;

  for (AmazonCustomer customer : customerList) {
    ItemData itemData =  customer.itemsBrought.iterator().next();
    reviewCount++;
    if (itemData.rating > 3) {
      postiveReviews++;
    }
    similarItemCount = similarItemCount +
      itemData.similarItems.size();
    if (salesRank == -1) {
      salesRank = itemData.salesrank;
    }
  }

  boolean isInFirst10k = (salesRank <= 10000);
  context.write(new Text("total"),
  new BooleanWritable(isInFirst10k));
  if (reviewCount > 60) {
    context.write(new Text("reviewCount>60"),
    new BooleanWritable(isInFirst10k));
  }
  if (postiveReviews > 30) {
    context.write(new Text("postiveReviews>30"),
    new BooleanWritable(isInFirst10k));
  }
  if (similarItemCount > 150) {
    context.write(new Text("similarItemCount>150"),
    new BooleanWritable(isInFirst10k));
  }
}

Mapper 函数遍历每个产品,评估其特性。 如果功能评估为 true,它将发出功能名称作为关键字,并发出产品是否在前 10,000 个产品内作为值。

MapReduce 为每个功能调用一次 Reducer。 然后,每个 Reduce 作业接收特征为真的所有值,并计算产品在销售排名中的前 10,000 个产品的概率(假设特征为真)。

public void reduce(Text key, Iterable<BooleanWritable> values, Context context) throws IOException,
  InterruptedException {
    int total = 0;
    int matches = 0;
    for (BooleanWritable value : values) {
      total++;
      if (value.get()) {
        matches++;
      }
    }
  context.write(new Text(key), new DoubleWritable((double) matches / total));
  }

在给定产品的情况下,我们将检查并决定以下事项:

  • 它有 60 多条评论吗?
  • 它有 30 多条正面评论吗?
  • 有 150 多种类似的商品吗?

我们可以决定事件 A、B 和 C 的概率,我们可以使用贝叶斯定理计算给定项目在前 10,000 个产品中的概率。 以下代码实现此逻辑:

public static boolean classifyItem(int similarItemCount, int reviewCount, int postiveReviews){
  double reviewCountGT60 = 0.8;
  double postiveReviewsGT30 = 0.9;
  double similarItemCountGT150 = 0.7;
  double a , b, c;

  if (reviewCount > 60) {
    a = reviewCountGT60;
  }else{
    a= 1 - reviewCountGT60;
  }
  if (postiveReviews > 30) {
    b = postiveReviewsGT30;
  }else{
    b = 1- postiveReviewsGT30;
  }
  if (similarItemCount > 150) {
    c = similarItemCountGT150;
  }else{
    c = 1- similarItemCountGT150;
  }
  double p = a*b*c/ (a*b*c + (1-a)*(1-b)*(1-c));
  return p > 0.5;
}

当您运行分类器测试逻辑时,它将加载 MapReduce 作业生成的数据,并对随机选择的 1,000 种产品进行分类。

使用 Adword Balance 算法将广告分配给关键字

广告已经成为网络的主要收入媒介。 这是一项价值 10 亿美元的业务,也是硅谷大多数领先公司的收入来源。 此外,它还使谷歌、Facebook、雅虎和 Twitter 等公司可以免费运营其主要服务,同时通过广告收取收入。

AdWords 允许人们竞标关键词。 例如,广告商A可以 2 美元竞标关键字 Hadoop support,并提供最高 100 美元的预算。 广告商B可以以 1.50 美元的价格竞标关键字 Hadoop support,并提供最高 200 美元的预算。 当用户搜索具有给定关键字的文档时,系统将从对这些关键字的出价中选择一个或多个广告。 只有当用户点击广告时,广告商才会付费。

我们的目标是选择广告,使它们的收入最大化。 在设计这样的解决方案时,有几个因素在起作用:

  • 我们想展示更有可能经常被点击的广告,因为很多时候,只有点击,而不是展示广告,才能让我们赚到钱。 我们用广告被点击的次数来衡量这一点,而不是广告被播放的次数。 我们把这个关键字的点击率称为点击率。
  • 我们希望显示属于预算较高的广告商的广告,而不是预算较低的广告商的广告。

在本配方中,我们将实现一个可用于此类情况的 Adword 平衡算法的简化版本。 为简单起见,我们假设广告商只对单个单词出价。 此外,由于我们找不到真正的 BID 数据集,因此我们将生成一个样本 BID 数据集。 请看下图:

Assigning advertisements to keywords using the Adwords balance algorithm

假设您要使用 Amazon 数据集支持基于关键字的广告。 食谱的工作原理如下:

  1. 第一个 MapReduce 作业将使用 Amazon 销售指数近似计算关键字的点击率。 这里,我们假设在销售排名较高的产品标题中找到的关键字的点击率会更高。

  2. 然后,我们将运行一个 Java 程序来生成投标数据集。

  3. 现在,第二个 MapReduce 任务将把同一产品的投标组合在一起,并创建一个可供广告分配程序使用的输出。

  4. 最后,我们将使用广告分配程序为广告商分配关键字。 我们将使用以下公式来实现 Adword 平衡算法。 该公式根据每个广告商未花费预算的比例、投标值和点击率来分配优先级:

    Measure = bid value * click-through rate * (1-e^(-1*current budget/ initial budget))
    

怎么做……

  1. 从亚马逊产品联购网络元数据(可从snap.stanford.edu/data/amazon…下载)下载数据集并解压缩。 我们将此目录命名为DATA_DIR

  2. 通过运行以下命令将数据上传到 HDFS。 如果数据目录已经存在,请将其清理。

    $ hdfs dfs -mkdir data
    $ hdfs dfs -mkdir data/input1
    $ hdfs dfs -put <DATA_DIR>/amazon-meta.txt data/input1
    
    
  3. 通过从源代码存储库的chapter9目录运行gradle build命令编译源代码,并获得hcb-c9-samples.jar文件。

  4. 使用以下命令运行 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \ 
    chapter9.adwords.ClickRateApproximator \
    data/input1 data/output6
    
    
  5. 通过运行以下命令将结果下载到您的计算机:

    $ hdfs dfs -get data/output6/part-r-* clickrate.data
    
    
  6. 您将看到它将打印以下结果。 您可以将这些值与贝叶斯分类器一起使用来对输入进行分类:

    keyword:(Annals 74
    keyword:(Audio  153
    keyword:(BET    95
    keyword:(Beat   98
    keyword:(Beginners)     429
    keyword:(Beginning      110
    
    
  7. 通过运行以下命令生成 BID 数据集。 您可以在biddata.data文件中找到结果。

    $ java -cp hcb-c9-samples.jar \
     chapter9.adwords.AdwordsBidGenerator \
     clickrate.data
    
    
  8. 创建名为data/input2的目录,并使用以下命令将 BID 数据集和早期 MapReduce 任务的结果上传到 HDFS 的data/input2目录:

    $ hdfs dfs -put clickrate.data data/input2
    $ hdfs dfs -put biddata.data data/input2
    
    
  9. 按如下方式运行第二个 MapReduce 作业:

    $ hadoop jar hcb-c9-samples.jar \
     chapter9.adwords.AdwordsBalanceAlgorithmDataGenerator \
     data/input2 data/output7
    
    
  10. 通过运行以下命令将结果下载到您的计算机:

```scala
$ hdfs dfs -get data/output7/part-r-* adwords.data

```

11. 检查结果:

```scala
(Animated       client23,773.0,5.0,97.0|
(Animated)      client33,310.0,8.0,90.0|
(Annals         client76,443.0,13.0,74.0|
client51,1951.0,4.0,74.0|
(Beginners)     client86, 210.0,6.0,429.0|
 client6,236.0,5.0,429.0|
(Beginning      client31,23.0,10.0,110.0|

```

12. 通过运行以下命令对随机的组关键字执行匹配:

```scala
$ java jar hcb-c9-samples.jar \
 chapter9.adwords.AdwordsAssigner adwords.data

```

它是如何工作的.

正如我们所讨论的,该配方由两个 MapReduce 作业组成。 您可以从文件src/chapter9/adwords/ClickRateApproximator.java中找到第一个 MapReduce 作业的源。

Mapper 函数使用 Amazon 数据格式解析 Amazon 数据集,对于每个产品标题中的每个单词,它都会发出该产品的单词和销售排名。 该函数如下所示:

public void map(Object key, Text value, Context context) {
......
    String[] tokens = itemData.title.split("\\s");
    for(String token: tokens){
        if(token.length() > 3){
            context.write(new Text(token), new IntWritable(itemData.salesrank));
        }
    }
}

然后,MapReduce 框架按键对发出的键-值对进行排序,并为每个键调用一次 Reducer。 如下所示,减少器使用针对键发出的销售排名来计算点击率的近似值:

public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
    double clickrate = 0;
    for(IntWritable val: values){
        if(val.get() > 1){
            clickrate = clickrate + 1000/Math.log(val.get());
        }else{
            clickrate = clickrate + 1000;
        }
    }
    context.write(new Text("keyword:" +key.toString()),
    new IntWritable((int)clickrate));
}

没有公开可用的投标数据集。 因此,我们将使用AdwordsBidGenerator程序为我们的食谱生成一个随机投标数据集。 它将读取由前面的配方生成的关键字,并生成随机出价数据集。

然后,我们将使用第二个 MapReduce 作业将投标数据集与点击率合并,并生成一个具有根据关键字排序的投标信息的数据集。 您可以从文件src/chapter9/adwords/AdwordsBalanceAlgorithmDataGenerator.java中找到第二个 MapReduce 任务的源。 Mapper 函数如下所示:

public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
  String[] keyVal = value.toString().split("\\s");
  if (keyVal[0].startsWith("keyword:")) {
    context.write(
    new Text(keyVal[0].replace("keyword:", "")),
    new Text(keyVal[1]));
  } else if (keyVal[0].startsWith("client")) {
    List<String[]> bids = new ArrayList<String[]>();
    double budget = 0;
    String clientid = keyVal[0];
    String[] tokens = keyVal[1].split(",");
    for (String token : tokens) {
      String[] kp = token.split("=");
      if (kp[0].equals("budget")) {
        budget = Double.parseDouble(kp[1]);
      } else if (kp[0].equals("bid")) {
        String[] bidData = kp[1].split("\\|");
        bids.add(bidData);
      }
    }

    for (String[] bid : bids) {
      String keyword = bid[0];
      String bidValue = bid[1];
      Double.parseDouble(bidValue);
      context.write(new Text(keyword),
      new Text(new StringBuffer()
      .append(clientid).append(",")
      .append(budget).append(",")
      .append(bidValue).toString()));
    }
  }
}

Mapper 函数读取投标数据集和点击率数据集,并根据关键字发出这两种类型的数据。 然后,每个 Reducer 接收所有出价和每个关键字的相关点击数据。 接下来,Reducer 合并数据并发出针对每个关键字的出价列表。

public void reduce(Text key, Iterable<Text> values,
Context context) throws IOException, InterruptedException {
  String clientid = null;
  String budget = null;
  String bid = null;
  String clickRate = null;

  List<String> bids = new ArrayList<String>();
  for (Text val : values) {
    if (val.toString().indexOf(",") > 0) {
      bids.add(val.toString());
    } else {
      clickRate = val.toString();
    }
  }
  StringBuffer buf = new StringBuffer();
  for (String bidData : bids) {
    String[] vals = bidData.split(",");
    clientid = vals[0];
    budget = vals[1];
    bid = vals[2];
    buf.append(clientid).append(",")
    .append(budget).append(",")
    .append(Double.valueOf(bid)).append(",")
    .append(Math.max(1, Double.valueOf(clickRate)));
    buf.append("|");
  }
  if (bids.size() > 0) {
    context.write(key, new Text(buf.toString()));
  }
}

最后,Adword 分配器加载投标数据,并将其与关键字相对应地存储到存储器中。 在给定关键字的情况下,Adword 分配器查找具有以下等式的最大值的出价,并从所有出价中选择用于广告的出价:

Measure = bid value * click-through rate * (1-e^(-1*current budget/ initial budget))

还有更多...

前面的配方假设 Adword 分配器可以将所有数据加载到内存中,以做出广告分配决策。 实际上,由于广告竞价系统所需的毫秒级响应时间和大数据集,这些计算由大型集群进行实时决策,这些集群结合了 Apache Storm 等流媒体技术和 HBase 等高吞吐量数据库。

这个菜谱假定用户只对单个单词出价。 然而,为了支持多个关键字竞价,我们需要合并点击率,算法的其余部分可以像前面一样进行。

有关在线广告的更多信息可以在 Anand Rajaraman 和 Jeffrey David Ullman 所著的《挖掘海量数据集剑桥大学出版社》一书中找到。

十、海量文本数据处理

在本章中,我们将介绍以下主题:

  • 使用 Hadoop Streaming 和 Python 进行数据预处理(提取、清理和格式转换)
  • 使用 Hadoop 流消除重复数据
  • 将大型数据集加载到 Apache HBase 数据存储-import 和 Bulkload
  • 为文本数据创建 TF 和 TF-IDF 矢量
  • 使用 Apache Mahout 对文本数据进行聚类
  • 基于潜在狄利克雷分配(LDA)的主题发现
  • 基于 Mahout 朴素贝叶斯分类器的文档分类

简介

Hadoop MapReduce 与支持项目集一起,使其成为处理大型文本数据集和执行提取-转换-加载(ETL)类型操作的理想框架。

在本章中,我们将探讨如何使用 Hadoop Streaming 执行数据提取、格式转换和重复数据删除等数据预处理操作。 我们还将使用 HBase 作为数据存储来存储数据,并探索以最小的开销将大量数据加载到 HBase 的机制。 最后,我们将研究如何使用 Apache Mahout 算法执行文本分析。

我们将对本章中的食谱使用以下示例数据集:

  • 20 个新闻组数据集,位于qwone.com/~jason/20Ne…。 此数据集包含大约 20,000 个最初由 Ken Lang 收集的新闻组文档。

提示

示例代码

本书的示例代码文件位于 gihub 的github.com/thilg/hcb-v…。 代码库的chapter10文件夹包含本章的示例代码。

使用 Hadoop Streaming 和 Python 进行数据预处理

数据预处理是数据分析中重要的,通常也是必需的组件。 当使用从多个不同来源生成的非结构化文本数据时,Data 预处理变得更加重要。 数据预处理步骤包括清理数据、从数据中提取重要特征、从数据集中删除重复项、转换数据格式等操作。

Hadoop MapReduce 提供了在处理海量数据集时并行执行这些任务的理想环境。 除了使用 Java MapReduce 程序或 Pig 脚本或 Have 脚本对数据进行预处理外,Hadoop 还包含一些其他工具和功能,这些工具和功能可用于执行这些数据预处理操作。 其中一个特性是InputFormats,它为我们提供了通过实现自定义InputFormat来支持自定义数据格式的能力。另一个特性是 Hadoop 流支持,它允许我们使用我们最喜欢的脚本语言来执行实际的数据清理和提取,而 Hadoop 将并行计算到数百个计算和存储资源。

在本食谱中,我们将使用 Hadoop Streaming 和基于 Python 脚本的 Mapper 来执行数据提取和格式转换。

做好准备

  • 检查 Hadoop 工作节点上是否已经安装了 Python。 如果没有,请在所有 Hadoop 工作节点上安装 Python。

怎么做……

以下步骤显示如何从 20news 数据集中清理和提取数据,并将数据存储为制表符分隔的文件:

  1. qwone.com/~jason/20Ne…

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 在中,为了节省计算时间和资源,您只能使用数据集的子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 解压本章的资源包并找到MailPreProcessor.pyPython 脚本。

  4. 在您的计算机中找到 Hadoop 安装的hadoop-streaming.jarJAR 文件。 使用该 JAR 运行以下 Hadoop 流命令。 /usr/lib/hadoop-mapreduce/是基于 bigtop 的 Hadoop 安装的hadoop-streamingjar 文件的位置:

    $ hadoop jar \
    /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
    -input 20news-all/*/* \
    -output 20news-cleaned \
    -mapper MailPreProcessor.py \
    -file MailPreProcessor.py
    
    
  5. 使用以下命令检查结果:

    > hdfs dfs –cat 20news-cleaned/part-* | more
    
    

它是如何工作的.

Hadoop 使用默认的TextInputFormat作为上一次计算的输入规范。 使用TextInputFormat将为输入数据集中的每个文件生成一个 Map 任务,并为每行生成一条 Map 输入记录。 Hadoop 流通过标准输入向地图应用提供输入:

line =  sys.stdin.readline();
while line:
….
  if (doneHeaders):
    list.append( line )
  elif line.find( "Message-ID:" ) != -1:
    messageID = line[ len("Message-ID:"):]
  ….
  elif line == "":
    doneHeaders = True

   line = sys.stdin.readline();

前面的 Python 代码从标准输入读取输入行,直到它到达文件末尾。 我们解析新闻组文件的标题,直到遇到分隔标题和消息内容的空行。 消息内容将逐行读入列表:

value = ' '.join( list )
value = fromAddress + "\t" ……"\t" + value
print '%s\t%s' % (messageID, value)

前面的代码段将消息内容合并为单个字符串,并将流应用的输出值构造为一组以制表符分隔的所选头,后跟消息内容。 输出键值是从输入文件中提取的Message-ID头。 通过使用制表符来分隔键和值,将输出写入标准输出。

还有更多...

通过将SequenceFileOutputFormat指定为流计算的OutputFormat ,我们可以生成 HadoopSequenceFile格式的上一次计算的输出:

$ hadoop jar \
/usr/lib/Hadoop-mapreduce/hadoop-streaming.jar \
-input 20news-all/*/* \
-output 20news-cleaned \
-mapper MailPreProcessor.py \
-file MailPreProcessor.py \
-outputformat \
 org.apache.hadoop.mapred.SequenceFileOutputFormat \
-file MailPreProcessor.py

在第一次传递输入数据之后,最好将数据存储为SequenceFiles(或其他 Hadoop 二进制文件格式,如 avro),因为SequenceFiles占用的空间较少,并且支持压缩。 您可以使用hdfs dfs -text <path_to_sequencefile>SequenceFile的内容输出为文本:

$ hdfs dfs –text 20news-seq/part-* | more

但是,要使前面的命令起作用,SequenceFile中使用的任何可写类都应该在 Hadoop 类路径中可用。

另请参阅

  • 请参阅第 4 章开发复杂 Hadoop MapReduce 应用的中的将 Hadoop 与遗留应用结合使用-Hadoop Streaming添加对新输入数据格式的支持-实现自定义 InputFormat食谱。**

使用 Hadoop 流消除重复数据

通常,数据集包含需要消除的重复项,以确保结果的准确性。 在本食谱中,我们使用 Hadoop 删除 20news 数据集中的重复邮件记录。 这些重复记录是由于用户将同一消息交叉发布到多个新闻板造成的。

做好准备

  • 确保您的 Hadoop 计算节点上安装了 Python。

怎么做……

以下步骤显示了如何从 20news 数据集中删除由于跨列表交叉发布而导致的重复邮件:

  1. qwone.com/~jason/20Ne…

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您只能使用数据集的子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 我们将使用前面配方中的MailPreProcessor.pyPython 脚本,使用 Hadoop 流进行数据预处理,并使用 Python作为映射器。 在本章的源代码存储库中找到MailPreProcessorReduce.py文件。

  4. 执行以下命令:

    $ hadoop jar \
    /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
    -input 20news-all/*/* \
    -output 20news-dedup\
    -mapper MailPreProcessor.py \
    -reducer MailPreProcessorReduce.py \
    -file MailPreProcessor.py\
    -file MailPreProcessorReduce.py
    
    
  5. 使用以下命令检查结果:

    $ hdfs dfs –cat 20news-dedup/part-00000 | more
    
    

它是如何工作的.

Mapper Python 脚本输出 MessageID 作为键。 我们使用 MessageID 来标识跨不同新闻组交叉发布的重复邮件。

Hadoop Streaming 通过标准输入将每个键组的 Reducer 输入记录逐行提供给 Streaming Reducer 应用。 但是,Hadoop 流没有区分新键值组的机制。 当 Hadoop 开始向进程提供新密钥的记录时,流减少器应用需要跟踪输入键以识别新组。 由于我们使用 MessageID 输出 Mapper 结果,因此 Reducer 输入将按 MessageID 分组。 每个 MessageID 包含多个值(也称为一条消息)的任何组都包含重复项。 在下面的脚本中,我们只使用记录组的第一个值(Message),并丢弃其他值(即重复的消息):

#!/usr/bin/env python
import sys;

currentKey = ""

for line in sys.stdin:
  line = line.strip()
  key, value = line.split('\t',1)
  if currentKey == key :
    continue
  print '%s\t%s' % (key, value)

另请参阅

  • 在遗留应用中使用 Hadoop 的*-本章第 4 章、开发复杂 Hadoop MapReduce 应用的 Hadoop Streaming配方,以及本章的使用 Hadoop Streaming 和 Python配方的数据预处理。*

将大型数据集加载到 Apache HBase 数据存储-import 和 Bulkload

当以半结构化方式存储大规模数据时,Apache HBase data 存储非常有用,这样它就可以用于使用 Hadoop MapReduce 程序进行进一步处理,或者为客户端应用提供随机访问数据存储。 在本食谱中,我们将使用importtsvbulkload工具将大型文本数据集导入 HBase。

做好准备

  1. 在 Hadoop 群集中安装和部署 Apache HBase。
  2. 确保您的 Hadoop 计算节点中安装了 Python。

How to Do It…

以下步骤显示如何将 TSV(制表符分隔值)转换后的 20news 数据集加载到 HBase 表中:

  1. 遵循数据预处理(使用 Hadoop Streaming 和 Python配方)来执行此配方的数据预处理。 我们假设该配方的以下步骤 4 的输出存储在名为“20news-cleaned”的 HDFS 文件夹中:

    $ hadoop jar \
     /usr/lib/hadoop-mapreduce/hadoop-streaming.jar \
     -input 20news-all/*/* \
     -output 20news-cleaned \
     -mapper MailPreProcessor.py \
     -file MailPreProcessor.py
    
    
  2. 启动 HBase 外壳:

    $ hbase shell
    
    
  3. 通过在 HBase shell 中执行以下命令创建名为 20news-data 的表。 旧版本的importtsv命令(在下一步中使用)只能处理单个列族。 因此,在创建 HBase 表时,我们仅使用单个列族:

    hbase(main):001:0> create '20news-data','h'
    
    
  4. 执行以下命令以将预处理数据导入到前面创建的 HBase 表中:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.ImportTsv \
     -Dimporttsv.columns=HBASE_ROW_KEY,h:from,h:group,h:subj,h:msg \
     20news-data 20news-cleaned
    
    
  5. 启动 HBase Shell 并使用 HBase Shell 的 COUNT 和 SCAN 命令验证表的内容:

    hbase(main):010:0> count '20news-data'
     12xxx row(s) in 0.0250 seconds
    
    hbase(main):010:0> scan '20news-data', {LIMIT => 10}
     ROW                                       COLUMN+CELL 
     <1993Apr29.103624.1383@cronkite.ocis.te column=h:c1,    timestamp=1354028803355, value= katop@astro.ocis.temple.edu (Chris Katopis)>
     <1993Apr29.103624.1383@cronkite.ocis.te column=h:c2,  timestamp=1354028803355, value= sci.electronics 
    ......
    
    

以下是使用bulkload功能将20news数据集加载到 HBase 表的步骤:

  1. 按照步骤 1 到 3 操作,但使用不同的名称创建表:

    hbase(main):001:0> create '20news-bulk','h'
    
    
  2. 使用以下命令生成 HBasebulkload数据文件:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.ImportTsv \
    -Dimporttsv.columns=HBASE_ROW_KEY,h:from,h:group,h:subj,h:msg\
    -Dimporttsv.bulk.output=hbaseloaddir \
    20news-bulk–source 20news-cleaned
    
    
  3. 列出文件以验证是否生成了bulkload数据文件:

    $ hadoop fs -ls 20news-bulk-source
    ......
    drwxr-xr-x   - thilina supergroup          0 2014-04-27 10:06 /user/thilina/20news-bulk-source/h
    
    $ hadoop fs -ls 20news-bulk-source/h
    -rw-r--r--   1 thilina supergroup      19110 2014-04-27 10:06 /user/thilina/20news-bulk-source/h/4796511868534757870
    
    
  4. 以下命令通过将输出文件移动到正确位置将数据加载到 HBase 表中:

    $ hbase \
     org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles \
     20news-bulk-source 20news-bulk
    ......
    14/04/27 10:10:00 INFO mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://127.0.0.1:9000/user/thilina/20news-bulk-source/h/4796511868534757870 first= <1993Apr29.103624.1383@cronkite.ocis.temple.edu>last= <stephens.736002130@ngis>
    ......
    
    
  5. 启动 HBase Shell 和使用 HBase Shell 的countscan命令验证表的内容:

    hbase(main):010:0> count '20news-bulk' 
    hbase(main):010:0> scan '20news-bulk', {LIMIT => 10}
    
    

它是如何工作的.

MailPreProcessor.pyPython 脚本从新闻板消息中提取一组选定的数据字段,并将其作为制表符分隔的数据集输出:

value = fromAddress + "\t" + newsgroup 
+"\t" + subject +"\t" + value
print '%s\t%s' % (messageID, value)

我们使用importtsv工具将流式 MapReduce 计算生成的以制表符分隔的数据集导入 HBase。 importtsv工具要求数据除了分隔数据字段的制表符之外,不能有其他制表符。 因此,我们使用以下 Python 脚本片段删除输入数据中可能存在的任何制表符:

line = line.strip()
line = re.sub('\t',' ',line)

importtsv工具支持直接使用Put操作以及通过生成 HBase 内部HFiles将数据加载到 HBase 中。 以下命令使用Put操作直接将数据加载到 HBase。 我们生成的数据集在值中包含一个键和四个字段。 我们使用-Dimporttsv.columns参数将数据字段指定到数据集的表列名称映射。 此映射包括按照输入数据集中以制表符分隔的数据字段的顺序列出各自的表列名称:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 -Dimporttsv.columns=<data field to table column mappings> \ 
 <HBase tablename> <HDFS input directory>

我们可以使用命令后面的命令为数据集生成 HBase HFiles。 这些 HFile 无需通过 HBase API 即可直接加载到 HBase,从而减少了所需的 CPU 和网络资源量:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 -Dimporttsv.columns=<filed to column mappings> \ 
 -Dimporttsv.bulk.output=<path for hfile output> \
 <HBase tablename> <HDFS input directory>

只需将文件移动到正确的位置,就可以将这些生成的 HFile 加载到 HBase 表中。 此移动可通过使用completebulkload命令执行:

$ hbase \org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles \
 <HDFS path for hfiles> <table name>

还有更多...

您也可以使用importtsv工具,该工具具有带有其他数据字段分隔符字符的数据集,方法是指定‘-Dimport tsv.Separator’参数。 以下是使用逗号作为分隔符将逗号分隔的数据集导入到 HBase 表的示例:

$ hbase \
 org.apache.hadoop.hbase.mapreduce.ImportTsv \
 '-Dimporttsv.separator=,' \
 -Dimporttsv.columns=<data field to table column mappings> \ 
 <HBase tablename> <HDFS input directory>

在 MapReduce 作业控制台输出或 Hadoop 监控控制台中查找中的Bad Lines。 使用Bad Lines的原因之一是使用不需要的分隔符。 我们在数据清理步骤中使用的 Python 脚本删除了消息中的任何额外选项卡:

14/03/27 00:38:10 INFO mapred.JobClient:   ImportTsv
14/03/27 00:38:10 INFO mapred.JobClient:     Bad Lines=2

使用 HBase 进行重复数据消除

HBase 支持为每条记录存储多个版本的列值。 查询时,除非我们特别提到时间段,否则 HBase 会返回最新版本的值。 通过确保对重复值使用相同的 RowKey,可以使用 HBase 的此功能执行自动重复数据消除。 在我们的 20news 示例中,我们使用 MessageID 作为记录的 RowKey,确保重复的消息将显示为同一数据记录的不同版本。

HBase 允许我们配置每个列系列的最大或最小版本数。 将最大版本数设置为较低值将通过丢弃旧版本来减少数据使用量。 有关设置最大或最小版本数的详细信息,请参阅hbase.apache.org/book/schema…

另请参阅

  • 第 7 章Hadoop 生态系统 II 的 HBase配方上运行 MapReduce 作业的*--Pig、HBase、Mahout 和 Sqoop*。
  • 有关ImportTsv命令的更多信息,请参考hbase.apache.org/book/ops_mg…

为文本数据创建 TF 和 TF-IDF 矢量

大多数文本分析数据挖掘算法对矢量数据进行操作。 我们可以使用向量空间模型将文本数据表示为一组向量。 例如,我们可以通过获取数据集中出现的所有术语的集合并为术语集中的每个术语分配索引来构建向量空间模型。 术语集中的项的数量是结果向量的维度,并且向量的每个维度对应于一个项。 对于每个文档,向量包含每个术语在分配给该特定术语的索引位置的出现次数。 这将使用每个文档中的个词频创建向量空间模型,这与我们在使用第 8 章搜索和索引的 Hadoop MapReduce 配方生成倒排索引的中执行的计算结果类似。

向量可以如下所示:

Creating TF and TF-IDF vectors for the text data

词频和结果文档向量

然而,使用前面的术语计数模型创建向量时,许多文档中频繁出现的术语(例如,the、is、a、are、was、who 等等)会被赋予很高的权重,尽管这些频繁出现的术语在定义文档含义方面的贡献非常小()。 术语频率-逆文档频率(TF-IDF)模型通过利用倒置文档频率(IDF)来缩放术语频率(TF)来解决该问题。 IDF 通常为,计算方法是:首先对出现该术语的文档数(Df)进行计数,将其倒置(1/df),然后将其与文档数相乘,并使用所得值的对数进行归一化,大致如以下公式所示:

Creating TF and TF-IDF vectors for the text data

在本食谱中,我们将使用 Apache Mahout 的内置实用工具从文本数据集创建 TF-IDF 向量。

做好准备

使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

How to Do It…

下面的步骤向您展示了如何从到构建 20news 数据集的矢量模型:

  1. qwone.com/~jason/20Ne…

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您可以只使用数据集的一个子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 转到MAHOUT_HOME。 从上传的文本数据生成 Hadoop 序列文件:

    $ mahout seqdirectory -i 20news-all -o 20news-seq
    
    
  4. Generate TF and TF-IDF sparse vector models from the text data in the sequence files:

    $ mahout seq2sparse -i 20news-seq  -o 20news-vector 
    
    

    前面的命令启动一系列 MapReduce 计算。 等待以下计算完成:

    How to do it…

  5. 使用以下命令检查输出目录。 tfidf-vectors文件夹包含 TF-IDF 模型向量,tf-vectors文件夹包含术语计数模型向量,dictionary.file-0包含术语到术语索引的映射:

    $ hdfs dfs -ls 20news-vector
    
    Found 7 items
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:53 /user/u/20news-vector /df-count
    -rw-r--r--   1 u supergroup       7627 2012-11-27 16:51 /user/u/20news-vector/dictionary.file-0
    -rw-r--r--   1 u supergroup       8053 2012-11-27 16:53 /user/u/20news-vector/frequency.file-0
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:52 /user/u/20news-vector/tf-vectors
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:54 /user/u/20news-vector/tfidf-vectors
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:50 /user/u/20news-vector/tokenized-documents
    drwxr-xr-x   - u supergroup          0 2012-11-27 16:51 /user/u/20news-vector/wordcount
    
    
  6. 或者,可以使用以下命令将 TF-IDF 矢量转储为文本。 关键字是文件名,矢量内容的格式为<term index>:<TF-IDF value>

    $ mahout seqdumper -i 20news-vector/tfidf-vectors/part-r-00000
    
    ……
    Key class: class org.apache.hadoop.io.Text Value Class: class org.apache.mahout.math.VectorWritable
    
    Key: /54492: Value: {225:3.374729871749878,400:1.5389964580535889,321:1.0,324:2.386294364929199,326:2.386294364929199,315:1.0,144:2.0986123085021973,11:1.0870113372802734,187:2.652313232421875,134:2.386294364929199,132:2.0986123085021973,......}
    ……
    
    

…的工作原理

Hadoop SequenceFiles 将数据存储为二进制键-值对,并支持数据压缩。 Mahout 的seqdirectory命令将文本文件转换为 Hadoop SequenceFile,方法是使用文本文件的文件名作为键,将文本文件的内容用作值。 seqdirectory命令将所有文本内容存储在单个 SequenceFile 中。 但是,我们可以指定块大小来控制 HDFS 中 SequenceFile 数据块的实际存储。 以下是seqdirectory命令的一组选定选项:

mahout seqdirectory –i <HDFS path to text files> -o <HDFS output directory for sequence file> 
 -ow                   If present, overwrite the output directory 
 -chunk <chunk size>   In MegaBytes. Defaults to 64mb 
 -prefix <key prefix>  The prefix to be prepended to the key 

seq2sparse命令是一个 Apache Mahout 工具,支持从包含文本数据的 SequenceFiles 生成稀疏向量。 它支持生成 TF 和 TF-IDF 矢量模型。 此命令作为一系列 MapReduce 计算执行。 以下是为seq2sparse命令选择的一组选项:

mahout seq2sparse -i <HDFS path to the text sequence file> -o <HDFS output directory>
 -wt {tf|tfidf} 
 -chunk <max dictionary chunk size in mb to keep in memory> 
 --minSupport <minimum support>
 --minDF <minimum document frequency>
 --maxDFPercent <MAX PERCENTAGE OF DOCS FOR DF

minSupport命令是将单词视为特征的最低频率。 minDF是 Word 需要包含的最小文档数。 maxDFPercent是表达式的最大值(单词的文档频率/文档总数),以便将该单词视为文档中的良好特征。 这有助于删除诸如停用词等高频特征。

您可以使用 Mahoutseqdumper命令将使用 Mahout 可写数据类型的 SequenceFile 的内容转储为纯文本:

mahout seqdumper -i <HDFS path to the sequence file>
 -o <output directory>
 --count         Output only the number of key value pairs.
 --numItems      Max number of key value pairs to output
 --facets        Output the counts per key.

另请参阅

  • 使用 Hadoop MapReduce配方(第 9 章,分类、推荐和查找关系]的 Hadoop MapReduce配方生成倒排索引。
  • 有关从cwiki.apache.org/confluence/…处的文本数据创建矢量的信息,请参阅 Mahout 文档。

使用 Apache Mahout 对文本数据进行群集

聚类在数据挖掘计算中起着不可或缺的作用。 基于用例,使用数据项的一个或多个特征将数据集的相似项分组在一起。 文档聚类被用于许多文本挖掘操作,如文档组织、主题识别、信息表示等。 文档聚类与传统的数据聚类机制有许多相同的机制和算法。 然而,在确定用于聚类的特征以及构建表示文本文档的向量空间模型时,文档聚类有其独特的挑战。

第 7 章,Hadoop 生态系统 II-Pig、HBase、Mahout 和 SqoopRunning K-Means with Mahout配方侧重于使用 Mahout KMeansClusters 来集群统计数据。 本书前一版的分类、推荐和查找关系中的聚类亚马逊销售数据集配方侧重于使用聚类来识别具有相似兴趣的客户。 这两个食谱总体上提供了对使用集群算法的更深入的理解。 本食谱重点介绍 Apache Mahout 中可用于文档集群的几种集群算法中的两种。

做好准备

  • 使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或在您的计算机上手动安装最新的 Apache Mahout 版本。

怎么做……

以下步骤使用 Apache Mahout KmeansClusters 算法对 20news 数据集进行聚类:

  1. 请参阅本章中的为文本数据配方创建 TF 和 TF-IDF 矢量,并为 20news 数据集生成 TF-IDF 矢量。 我们假设 TF-IDF 矢量位于 HDFS 的20news-vector/tfidf-vectors文件夹中。

  2. 执行以下命令以运行 Mahout KMeansClusters 计算:

    $ mahout kmeans \
     --input 20news-vector/tfidf-vectors \
     --clusters 20news-seed/clusters
     --output 20news-km-clusters\
     --distanceMeasure \
    org.apache.mahout.common.distance.SquaredEuclideanDistanceMeasure-k 10 --maxIter 20 --clustering
    Execute the following command to convert the clusters to text:
    $ mahout clusterdump \
     -i 20news-km-clusters/clusters-*-final\
     -o 20news-clusters-dump \
     -d 20news-vector/dictionary.file-0 \
     -dt sequencefile \
     --pointsDir 20news-km-clusters/clusteredPoints
    
    $ cat 20news-clusters-dump
    
    

它是如何工作的.

下面的代码显示了 Mahout KMeans 算法的用法:

mahout kmeans 
 --input <tfidf vector input>
 --clusters <seed clusters>
 --output <HDFS path for output>
 --distanceMeasure <distance measure>-k <number of clusters>--maxIter <maximum number of iterations>--clustering

当为--clusters选项提供空的 HDFS 目录路径时,mahout 将生成随机种子群集。 Manhout 支持几种不同的距离计算方法,如欧几里得、余弦和曼哈顿。

以下是 Mahoutclusterdump命令的用法:

mahout clusterdump -i <HDFS path to clusters>-o <local path for text output>
 -d <dictionary mapping for the vector data points>
 -dt <dictionary file type (sequencefile or text)>

 --pointsDir <directory containing the input vectors to       clusters mapping>

另请参阅

  • 第 7 章,Hadoop 生态系统 II 的Running K-Means with Mahout配方--Pig、HBase、Mahout 和 Sqoop*。*

基于潜在 Dirichlet 分配(LDA)的主题发现

我们可以使用潜在 Dirichlet 分配(LDA)将给定的词集聚集成主题,将一组文档聚集成主题的组合。 LDA 在基于上下文识别文档或单词的含义时非常有用,而不是完全依赖于单词的数量或确切的单词。 LDA 从原始文本匹配向语义分析迈进了一步。 LDA 可用于在诸如搜索引擎的系统中识别意图并解决歧义词。 LDA 的其他一些示例用例包括针对特定主题识别有影响力的推特用户,以及 Twahpicc(twahpic.cloudapp.net)应用使用 LDA 识别推特上使用的主题。

LDA 使用 TF 向量空间模型,而不是 TF-IDF 模型,因为它需要考虑单词的共现和相关性。

做好准备

使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

How to Do It…

以下步骤向您展示了如何在 20news 数据集的子集上运行 Mahout LDA 算法:

  1. qwone.com/~jason/20Ne…

    $ wget http://qwone.com/~jason/20Newsgroups/20news-19997.tar.gz
    $ tar –xzf 20news-19997.tar.gz
    
    

    下载并解压 20news 数据集

  2. 将提取的数据上传到 HDFS。 为了节省计算时间和资源,您可以只使用数据集的一个子集:

    $ hdfs dfs -mkdir 20news-all
    $ hdfs dfs –put  <extracted_folder> 20news-all
    
    
  3. 从上传的文本数据生成序列文件:

    $ mahout seqdirectory -i 20news-all -o 20news-seq 
    
    
  4. 从序列文件中的文本数据生成稀疏向量:

    $ mahout seq2sparse \
    –i 20news-seq  -o 20news-tf \
    -wt tf -a org.apache.lucene.analysis.WhitespaceAnalyzer
    
    
  5. 将 TF 矢量从 SequenceFile转换为 SequenceFile:

    $ mahout rowid -i 20news-tf/tf-vectors -o 20news-tf-int
    
    ```</intwritable></text> 
    
  6. 运行以下命令以执行 LDA 计算:

    $ mahout cvb \
    -i 20news-tf-int/matrix -o lda-out \
    -k 10  -x 20  \
    -dict 20news-tf/dictionary.file-0 \
    -dt lda-topics \
    -mt lda-topic-model
    
    
  7. 转储并检查 LDA 计算的结果:

    $ mahout seqdumper -i lda-topics/part-m-00000
    
    Input Path: lda-topics5/part-m-00000
    Key class: class org.apache.hadoop.io.IntWritable Value Class: class org.apache.mahout.math.VectorWritable
    
    Key: 0: Value: {0:0.12492744375758073,1:0.03875953927132082,2:0.1228639250669511,3:0.15074522974495433,4:0.10512715697420276,5:0.10130565323653766,6:0.061169131590630275,7:0.14501579630233746,8:0.07872957132697946,9:0.07135655272850545}
    .....
    
    
  8. 将输出向量与术语到术语索引的字典映射连接起来:

    $ mahout vectordump \
    -i lda-topics/part-m-00000 \
    --dictionary 20news-tf/dictionary.file-0 \
    --vectorSize 10  -dt sequencefile 
    
    ......
    
    {"Fluxgate:0.12492744375758073,&:0.03875953927132082,(140.220.1.1):0.1228639250669511,(Babak:0.15074522974495433,(Bill:0.10512715697420276,(Gerrit:0.10130565323653766,(Michael:0.061169131590630275,(Scott:0.14501579630233746,(Usenet:0.07872957132697946,(continued):0.07135655272850545}
    {"Fluxgate:0.13130952097888746,&:0.05207587369196414,(140.220.1.1):0.12533225607394424,(Babak:0.08607740024552457,(Bill:0.20218284543514245,(Gerrit:0.07318295757631627,(Michael:0.08766888242201039,(Scott:0.08858421220476514,(Usenet:0.09201906604666685,(continued):0.06156698532477829}
    .......
    
    

…的工作原理

LDA 的 Mahout CVB 版本使用迭代 MapReduce 方法实现折叠变量贝叶斯推理算法:

mahout cvb \
-i 20news-tf-int/matrix \
-o lda-out -k 10  -x 20 \
-dict 20news-tf/dictionary.file-0 \
-dt lda-topics \
-mt lda-topic-model

-i参数提供输入路径,而-o参数提供存储输出的路径。 参数-k指定要学习的主题数,–x指定计算的最大迭代次数。 -dict参数指向包含术语到术语索引的映射的字典。 –dt参数中给出的路径存储训练主题分布。 –mt中给出的路径用作存储中间模型的临时位置。

通过调用help选项可以查询cvb命令的所有命令行选项,如下所示:

mahout  cvb  --help

将主题数量设置为非常小的值将显示极高级别的主题。 大量的主题会产生更多描述性的主题,但需要更长的处理时间。 可以使用maxDFPercent选项删除常用单词,从而加快处理速度。

另请参阅

  • T.W.Teh,D.Newman 和 M.Well 提出的潜在 Dirichlet 分配的折叠变分贝叶斯推理算法。 在 NIPS 中,2006 年第 19 卷,可在www.gatsby.ucl.ac.uk/~ywteh/rese…找到。

基于 Mahout 朴素贝叶斯分类器的文档分类

分类将文档或数据项分配给一组已知的具有已知属性的类。 当我们需要将文档分配到一个或多个类别时,可以使用文档分类。 这是信息检索和图书馆学中的常见用例。

第 9 章分类、建议和查找关系中的分类使用朴素贝叶斯分类器配方提供了关于分类用例的更详细描述,还概述了使用朴素贝叶斯分类器算法。 本食谱重点介绍 Apache Mahout 中对文本文档的分类支持。

做好准备

  • 使用 Hadoop 发行版在您的计算机上安装 Apache Mahout,或手动安装最新版本的 Apache Mahout。

怎么做……

以下个步骤使用 ApacheMahout 朴素贝叶斯算法对 20news 数据集进行聚类:

  1. 请参阅本章中的为文本数据配方创建 TF 和 TF-IDF 矢量,并为 20news 数据集生成 TF-IDF 矢量。 我们假设 TF-IDF 矢量位于 HDFS 的20news-vector/tfidf-vectors文件夹中。

  2. 将数据拆分为训练和测试数据集:

    $ mahout split \
     -i 20news-vectors/tfidf-vectors \
     --trainingOutput /20news-train-vectors \
     --testOutput /20news-test-vectors  \
     --randomSelectionPct 40 \
    --overwrite --sequenceFiles 
    
    
  3. 训练朴素贝叶斯模型:

    $ mahout trainnb \
     -i 20news-train-vectors -el \
     -o  model \
     -li labelindex 
    
    
  4. 测试数据集上的分类:

    $ mahout testnb \
     -i 20news-train-vectors \
     -m model \
     -l labelindex \
     -o 20news-testing 
    
    

它是如何工作的.

Mahout 的split命令可用于将数据集分割为训练数据集和测试数据集。 此命令适用于文本数据集以及 Hadoop SequenceFile 数据集。 以下是 Mahoutdata-splitting命令的用法。 您可以将--help选项与split命令一起使用,以打印出所有选项:

mahout split \
 -i <input data directory> \
 --trainingOutput <HDFS path to store the training dataset> \
 --testOutput <HDFS path to store the test dataset>  \
 --randomSelectionPct <percentage to be selected as test data> \ 
 --sequenceFiles 

sequenceFiles选项指定输入数据集在 Hadoop SequenceFiles 中。

以下是 Mahout naive Bayes 分类器训练命令的用法。 --el选项通知 Mahout 从输入数据集中提取标签:

mahout trainnb \
 -i <HDFS path to the training data set> \
 -el \
 -o <HDFS path to store the trained classifier model> \
 -li <Path to store the label index> \

以下是 Mahout naive Bayes 分类器测试命令的用法:

mahout testnb \
 -i <HDFS path to the test data set>
 -m <HDFS path to the classifier model>\
 -l <Path to the label index> \
 -o <path to store the test result>

另请参阅

  • 使用第 9 章分类、建议和查找关系的朴素贝叶斯分类器配方的分类