面向-Python-开发者的-Spark-全-

面向 Python 开发者的 Spark(全)

原文:zh.annas-archive.org/md5/1F2AF128A0828F73EE5EA24057C01070

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Python 开发人员的 Spark旨在将 Python 的优雅和灵活性与 Apache Spark 的强大和多功能性相结合。Spark 是用 Scala 编写的,并在 Java 虚拟机上运行。然而,它是多语言的,并为 Java、Scala、Python 和 R 提供了绑定和 API。Python 是一种设计良好的语言,具有广泛的专业库。本书探讨了 PySpark 在 PyData 生态系统中的应用。一些著名的 PyData 库包括 Pandas、Blaze、Scikit-Learn、Matplotlib、Seaborn 和 Bokeh。这些库是开源的。它们由数据科学家和 Python 开发人员社区开发、使用和维护。PySpark 与 PyData 生态系统很好地集成在一起,得到了 Anaconda Python 发行版的认可。本书提出了一个构建数据密集型应用程序的旅程,以及涵盖以下步骤的架构蓝图:首先,使用 Spark 建立基础设施。其次,获取、收集、处理和存储数据。第三,从收集的数据中获得见解。第四,实时传输数据并实时处理。最后,可视化信息。

本书的目标是通过构建分析社交网络上 Spark 社区互动的应用程序来学习 PySpark 和 PyData 库。重点是 Twitter 数据。

本书内容

第一章,“设置 Spark 虚拟环境”,介绍了如何创建一个分隔的虚拟机作为我们的沙盒或开发环境,以实验 Spark 和 PyData 库。它涵盖了如何安装 Spark 和 Python Anaconda 发行版,其中包括 PyData 库。在此过程中,我们解释了关键的 Spark 概念、Python Anaconda 生态系统,并构建了一个 Spark 词频统计应用程序。

第二章,“使用 Spark 构建批处理和流处理应用程序”,奠定了数据密集型应用程序架构的基础。它描述了应用程序架构蓝图的五个层次:基础设施、持久性、集成、分析和参与。我们与三个社交网络建立了 API 连接:Twitter、GitHub 和 Meetup。本章提供了连接到这三个非平凡 API 的工具,以便您在以后阶段创建自己的数据混搭。

第三章,“使用 Spark 处理数据”,介绍了如何从 Twitter 收集数据,并使用 Pandas、Blaze 和 SparkSQL 以及它们各自的数据框架数据结构进行处理。我们继续使用 Spark SQL 进行进一步的调查和技术,利用 Spark 数据框架数据结构。

第四章,“使用 Spark 从数据中学习”,概述了 Spark MLlib 算法库的不断扩展。它涵盖了监督学习和无监督学习、推荐系统、优化和特征提取算法。我们通过 Python Scikit-Learn 和 Spark MLlib K-means 聚类将 Twitter 收集的数据集进行了处理,以区分与Apache Spark相关的推文。

第五章,“使用 Spark 流式传输实时数据”,奠定了流式架构应用程序的基础,并描述了它们的挑战、约束和好处。我们用 TCP 套接字来说明流式传输的概念,然后直接从 Twitter firehose 进行实时推文摄取和处理。我们还描述了 Flume,这是一个可靠、灵活和可扩展的数据摄取和传输管道系统。Flume、Kafka 和 Spark 的结合在不断变化的环境中提供了无与伦比的稳健性、速度和灵活性。我们在本章结束时对两种流式架构范式——Lambda 和 Kappa 架构进行了一些评论和观察。

第六章,可视化洞察和趋势,侧重于一些关键的可视化技术。它涵盖了如何构建词云并展示它们直观的力量,以揭示成千上万条推文中携带的关键词、情绪和表情。然后,我们专注于使用 Bokeh 进行交互式地图可视化。我们从零开始构建世界地图,并创建关键推文的散点图。我们最终的可视化是将伦敦的实际谷歌地图叠加在一起,突出即将举行的聚会及其各自的主题。

本书所需内容

您需要好奇心、毅力和对数据、软件工程、应用架构和可扩展性以及简洁美观的可视化的热情。范围广泛。

您需要对 Python 或具有面向对象和函数式编程能力的类似语言有很好的理解。有使用 Python、R 或任何类似工具进行数据整理的初步经验会有所帮助。

您需要欣赏如何构想、构建和扩展数据应用程序。

本书的受众

目标受众包括以下内容:

  • 数据科学家是主要的利益相关方。本书将帮助您释放 Spark 的力量,并利用您的 Python、R 和机器学习背景。

  • 专注于 Python 的软件开发人员将很容易扩展他们的技能,使用 Spark 作为处理引擎和 Python 可视化库和 Web 框架创建数据密集型应用程序。

  • 数据架构师可以创建快速数据管道,并构建包含批处理和流处理的著名 Lambda 架构,以实时渲染数据洞察,使用 Spark 和 Python 丰富的生态系统,也将受益于本书。

约定

在本书中,您会发现一些区分不同类型信息的文本样式。以下是一些这些样式的示例,以及它们的含义解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“在存储 Jupyter 或 IPython 笔记本的目录examples/AN_Spark中使用IPYNB启动 PySpark”。

代码块设置如下:

# Word count on 1st Chapter of the Book using PySpark

# import regex module
import re
# import add from operator module
from operator import add

# read input file
file_in = sc.textFile('/home/an/Documents/A00_Documents/Spark4Py 20150315')

任何命令行输入或输出都以以下方式编写:

# install anaconda 2.x.x
bash Anaconda-2.x.x-Linux-x86[_64].sh

新术语重要单词以粗体显示。例如,屏幕上看到的单词,比如菜单或对话框中的单词,会在文本中以这种方式出现:“安装 VirtualBox 后,让我们打开 Oracle VM VirtualBox Manager 并单击New按钮。”

注意

警告或重要说明会以这种方式出现在一个框中。

提示

提示和技巧会以这种方式出现。

第一章:设置 Spark 虚拟环境

在本章中,我们将为开发目的构建一个隔离的虚拟环境。该环境将由 Spark 和 Python Anaconda 发行版提供的 PyData 库驱动。这些库包括 Pandas、Scikit-Learn、Blaze、Matplotlib、Seaborn 和 Bokeh。我们将执行以下活动:

  • 使用 Anaconda Python 发行版设置开发环境。这将包括启用由 PySpark 驱动的 IPython Notebook 环境,用于我们的数据探索任务。

  • 安装和启用 Spark,以及 Pandas、Scikit-Learn、Blaze、Matplotlib 和 Bokeh 等 PyData 库。

  • 构建一个“单词计数”示例应用程序,以确保一切正常运行。

过去十年见证了像亚马逊、谷歌、Twitter、LinkedIn 和 Facebook 这样的数据驱动巨头的崛起和主导地位。这些公司通过播种、分享或披露他们的基础设施概念、软件实践和数据处理框架,培育了一个充满活力的开源软件社区。这已经改变了企业技术、系统和软件架构。

这包括利用虚拟化、云技术和软件定义网络的新基础设施和 DevOps(开发和运维)概念。

为了处理千兆字节的数据,Hadoop 被开发并开源,它从Google 文件系统GFS)和相邻的分布式计算框架 MapReduce 中汲取了灵感。克服了扩展的复杂性,同时控制成本也导致了新数据存储的大量出现。最近的数据库技术示例包括列数据库 Cassandra、文档数据库 MongoDB 和图数据库 Neo4J。

由于其处理大型数据集的能力,Hadoop 已经培育出一个庞大的生态系统,可以使用 Pig、Hive、Impala 和 Tez 更迭地和交互地查询数据。Hadoop 在使用 MapReduce 时只能以批处理模式运行,因此它很繁琐。Spark 通过针对磁盘输入输出和带宽密集型 MapReduce 作业的缺点,正在为分析和数据处理领域带来革命。

Spark 是用 Scala 编写的,因此与由Java 虚拟机JVM)驱动的生态系统本地集成。Spark 早期提供了 Python API 和绑定,通过启用 PySpark。Spark 架构和生态系统本质上是多语言的,显然有着 Java 主导系统的强大存在。

本书将专注于 PySpark 和 PyData 生态系统。Python 是学术和科学界进行数据密集处理的首选语言之一。Python 在数据处理方面发展了丰富的库和工具生态系统,包括 Pandas 和 Blaze 的数据操作、Scikit-Learn 的机器学习以及 Matplotlib、Seaborn 和 Bokeh 的数据可视化。因此,本书的目标是构建一个由 Spark 和 Python 驱动的数据密集型应用程序的端到端架构。为了将这些概念付诸实践,我们将分析 Twitter、GitHub 和 Meetup 等社交网络。我们将关注 Spark 和开源软件社区的活动和社交互动,通过 GitHub、Twitter 和 Meetup 进行调查。

构建数据密集型应用程序需要高度可扩展的基础架构、多语言存储、无缝数据集成、多范式分析处理和高效的可视化。下一段描述了我们将在整本书中采用的数据密集型应用程序架构蓝图。这是本书的骨干。我们将在更广泛的 PyData 生态系统的背景下发现 Spark。

提示

下载示例代码

您可以从您在www.packtpub.com购买的所有 Packt 图书的帐户中下载示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便文件直接通过电子邮件发送给您。

理解数据密集型应用程序的架构

为了理解数据密集型应用程序的架构,使用以下概念框架。架构设计在以下五个层次上:

  • 基础设施层

  • 持久层

  • 集成层

  • 分析层

  • 参与层

以下屏幕截图描述了数据密集型应用程序框架的五个层次:

理解数据密集型应用程序的架构

从下到上,让我们逐层介绍它们的主要目的。

基础设施层

基础设施层主要涉及虚拟化,可扩展性和持续集成。在实际操作和虚拟化方面,我们将通过在 VirtualBox 中构建我们自己的开发环境,并使用 Spark 和 Python 的 Anaconda 发行版来驱动虚拟机。如果我们希望从那里扩展,我们可以在云中创建类似的环境。创建一个分隔的开发环境并移动到测试和生产部署的实践可以自动化,并且可以成为由 DevOps 工具(如VagrantChefPuppetDocker)驱动的持续集成周期的一部分。 Docker 是一个非常受欢迎的开源项目,它简化了新环境的安装和部署。本书将仅限于使用 VirtualBox 构建虚拟机。从数据密集型应用程序架构的角度来看,我们通过提及可扩展性和持续集成来描述基础设施层的基本步骤。

持久层

持久层根据数据需求和形状管理各种存储库。它确保设置和管理多语言数据存储。它包括关系数据库管理系统,如MySQLPostgreSQL;键值数据存储,如HadoopRiakRedis;列数据库,如HBaseCassandra;文档数据库,如MongoDBCouchbase;以及图数据库,如Neo4j。持久层管理 Hadoop 的 HDFS 等各种文件系统。它与从本地硬盘到 Amazon S3 的各种存储系统进行交互。它管理各种文件存储格式,如csvjsonparquet,这是一种面向列的格式。

集成层

集成层专注于数据获取、转换、质量、持久性、消费和治理。它基本上由以下五个 C 驱动:连接收集校正组合消费

这五个步骤描述了数据的生命周期。它们关注如何获取感兴趣的数据集,探索它,迭代地完善和丰富收集的信息,并准备好供使用。因此,这些步骤执行以下操作:

  • 连接:针对从各种数据源获取数据的最佳方式,这些数据源提供的 API,输入格式,如果存在的话,输入模式,数据收集速率和提供者的限制

  • 校正:专注于转换数据以进行进一步处理,并确保所接收的数据的质量和一致性得到维护

  • 收集:查看存储哪些数据以及以何种格式,以便在后期阶段轻松进行数据组合和使用

  • 组合:集中关注如何混合收集的各种数据集,并丰富信息以构建引人注目的数据驱动产品

  • 消费:负责数据供应和呈现,以及确保正确的数据在正确的时间到达正确的个人

  • 控制:随着数据、组织和参与者的增长,迟早会需要这第六个额外步骤,它关乎确保数据治理

以下图表描述了数据获取和精炼的迭代过程,以供使用:

集成层

分析层

分析层是 Spark 处理数据的地方,使用各种模型、算法和机器学习管道来获取洞察力。在本书中,分析层由 Spark 提供支持。我们将在随后的章节中更深入地探讨 Spark 的优点。简而言之,它之所以如此强大,是因为它允许在单一统一平台上进行多种分析处理范式。它允许批处理、流处理和交互式分析。在具有较长延迟周期的大型数据集上进行批处理允许我们提取模式和洞察力,这些可以用于流处理模式中的实时事件。交互式和迭代式分析更适合数据探索。Spark 提供了 Python 和 R 的绑定和 API。通过其 SparkSQL 模块和 Spark Dataframe,它提供了一个非常熟悉的分析接口。

参与层

参与层与最终用户进行交互,并提供仪表板、交互式可视化和警报。我们将重点关注 PyData 生态系统提供的工具,如 Matplotlib、Seaborn 和 Bokeh。

理解 Spark

Hadoop 随着数据增长而水平扩展。Hadoop 在廉价硬件上运行,因此具有成本效益。可扩展的分布式处理框架使得机构能够在大型廉价集群上分析 PB 级数据。Hadoop 是 map-reduce 的第一个开源实现。Hadoop 依赖于称为 HDFS(Hadoop 分布式文件系统)的分布式存储框架。Hadoop 在批处理作业中运行 map-reduce 任务。Hadoop 需要在每个 map、shuffle 和 reduce 过程步骤中将数据持久化到磁盘上。这种批处理作业的开销和延迟对性能产生不利影响。

Spark 是一个快速的、分布式的大规模数据处理通用分析计算引擎。与 Hadoop 的主要突破之处在于,Spark 允许数据在处理步骤之间通过内存处理进行共享。

Spark 独特之处在于它允许四种不同的数据分析和处理样式。Spark 可用于:

  • 批处理:此模式用于操作大型数据集,通常执行大型 map-reduce 作业

  • 流处理:此模式用于近实时处理传入信息

  • 迭代式:这种模式适用于机器学习算法,例如梯度下降,其中数据被重复访问以达到收敛

  • 交互式:此模式用于数据探索,因为大量数据在内存中,并且由于 Spark 的非常快的响应时间

以下图表突出了前面四种处理样式:

理解 Spark

Spark 有三种模式:单一模式,在单台机器上独立运行;两种分布式模式,在机器集群上运行——在 Hadoop 分布式资源管理器 Yarn 上,或者在与 Spark 同时开发的开源集群管理器 Mesos 上:

理解 Spark

Spark 提供了 Scala、Java、Python 和 R 的多语言接口。

Spark 库

Spark 自带一些强大的库:

  • SparkSQL:提供类似 SQL 的能力来查询结构化数据并交互式地探索大型数据集

  • SparkMLLIB:为机器学习提供主要算法和管道框架

  • Spark Streaming:用于对数据进行近实时分析,使用微批处理和滑动窗口处理传入的数据流

  • Spark GraphX:用于图处理和复杂连接实体和关系的计算

PySpark 的实际应用

Spark 是用 Scala 编写的。整个 Spark 生态系统自然地利用了 JVM 环境,并充分利用了 HDFS。 Hadoop HDFS 是 Spark 支持的许多数据存储之一。Spark 是不可知的,并且从一开始就与多个数据源、类型和格式进行交互。

PySpark 不是 Spark 在支持 Java 的 Python 方言(如 Jython)上的抄写版本。PySpark 提供了围绕 Spark 的集成 API 绑定,并允许在集群的所有节点中完全使用 Python 生态系统,使用 pickle Python 序列化,并更重要的是,提供对 Python 的丰富生态系统的访问,如 Scikit-Learn 等机器学习库或数据处理库,如 Pandas。

当我们初始化一个 Spark 程序时,Spark 程序必须做的第一件事是创建一个SparkContext对象。它告诉 Spark 如何访问集群。Python 程序创建一个PySparkContext。Py4J 是将 Python 程序绑定到 Spark JVM SparkContext的网关。JVM SparkContext序列化应用程序代码和闭包,并将它们发送到集群进行执行。集群管理器分配资源并安排,并将闭包发送到集群中的 Spark 工作程序,根据需要激活 Python 虚拟机。在每台机器上,Spark Worker 由控制计算、存储和缓存的执行者管理。

以下是 Spark 驱动程序如何管理 PySpark 上下文和 Spark 上下文以及其与集群管理器通过本地文件系统和与 Spark 工作程序的交互的示例:

PySpark in action

弹性分布式数据集

Spark 应用程序由驱动程序组成,驱动程序运行用户的主要函数,在集群上创建分布式数据集,并对这些数据集执行各种并行操作(转换和操作)。

Spark 应用程序作为一组独立的进程运行,由驱动程序中的SparkContext协调。

SparkContext将从集群管理器分配系统资源(机器、内存、CPU)。

SparkContext管理执行者,执行者管理集群中的工作节点。驱动程序具有需要运行的 Spark 作业。作业被拆分为任务,提交给执行者完成。执行者负责在每台机器上进行计算、存储和缓存。

Spark 中的关键构建块是RDD弹性分布式数据集)。数据集是元素的集合。分布式意味着数据集可以在集群中的任何节点上。弹性意味着数据集可能会丢失或部分丢失,而不会对正在进行的计算造成重大伤害,因为 Spark 将从内存中的数据血统重新计算,也称为操作的DAG有向无环图)。基本上,Spark 将在缓存中快照 RDD 的状态。如果在操作过程中其中一台计算机崩溃,Spark 将从缓存的 RDD 和操作的 DAG 重新构建 RDD。RDD 可以从节点故障中恢复。

RDD 上有两种操作类型:

  • 转换:转换获取现有的 RDD,并导致新转换的 RDD 的指针。RDD 是不可变的。一旦创建,就无法更改。每个转换都会创建一个新的 RDD。转换是惰性评估的。转换只有在发生操作时才会执行。在失败的情况下,转换的数据血统会重建 RDD。

  • 操作:对 RDD 的操作会触发一个 Spark 作业并产生一个值。操作操作会导致 Spark 执行(懒惰的)转换操作,这些操作是计算由操作返回的 RDD 所需的。操作会导致一系列操作的 DAG。DAG 被编译成阶段,每个阶段都作为一系列任务执行。任务是工作的基本单位。

以下是关于 RDD 的一些有用信息:

  • RDD 是从数据源(如 HDFS 文件或数据库查询)创建的。有三种方法可以创建 RDD:

  • 从数据存储中读取

  • 转换现有的 RDD

  • 使用内存集合

  • RDD 可以通过mapfilter等函数进行转换,产生新的 RDD。

  • 对 RDD 进行的操作,比如 first、take、collect 或 count,会将结果传递到 Spark 驱动程序。Spark 驱动程序是用户与 Spark 集群交互的客户端。

以下图示了 RDD 的转换和操作:

弹性分布式数据集

了解 Anaconda

Anaconda 是一个广泛使用的免费 Python 发行版,由Continuum维护(www.continuum.io/)。我们将使用 Anaconda 提供的主流软件堆栈来生成我们的应用程序。在本书中,我们将使用 PySpark 和 PyData 生态系统。PyData 生态系统由Continuum推广、支持和维护,并由Anaconda Python 发行版提供支持。Anaconda Python 发行版在安装 Python 环境方面节省了时间和烦恼;我们将与 Spark 一起使用它。Anaconda 有自己的软件包管理,补充了传统的pip installeasy-install。Anaconda 自带了一些最重要的软件包,比如 Pandas、Scikit-Learn、Blaze、Matplotlib 和 Bokeh。对已安装库的升级只需在控制台上输入一个简单的命令:

$ conda update

可以使用以下命令获取我们环境中安装的库的列表:

$ conda list

堆栈的关键组件如下:

  • Anaconda:这是一个免费的 Python 发行版,几乎包含了 200 个用于科学、数学、工程和数据分析的 Python 软件包。

  • Conda:这是一个软件包管理器,负责安装复杂软件堆栈的所有依赖项。它不仅限于 Python,还管理 R 和其他语言的安装过程。

  • Numba:它提供了在 Python 中加速代码的能力,具有高性能函数和即时编译。

  • Blaze:它通过提供统一和可适应的接口来访问各种数据提供程序(包括流式 Python、Pandas、SQLAlchemy 和 Spark),实现了大规模数据分析。

  • Bokeh:它为大型和流式数据集提供交互式数据可视化。

  • Wakari:这允许我们在托管环境中共享和部署 IPython Notebooks 和其他应用程序。

下图显示了 Anaconda 堆栈的组件:

了解 Anaconda

建立由 Spark 驱动的环境

在本节中,我们将学习如何设置 Spark:

  • 在运行 Ubuntu 14.04 的虚拟机中创建一个独立的开发环境,以便不会干扰任何现有系统。

  • 安装 Spark 1.3.0 及其依赖项,即。

  • 安装 Anaconda Python 2.7 环境以及所有必需的库,比如 Pandas、Scikit-Learn、Blaze 和 Bokeh,并启用 PySpark,以便可以通过 IPython Notebooks 访问。

  • 设置我们环境的后端或数据存储。我们将使用 MySQL 作为关系数据库,MongoDB 作为文档存储,Cassandra 作为列式数据库。

每个存储后端根据要处理的数据的性质提供特定的用途。MySQL RDBMs 用于可以使用 SQL 轻松查询的标准表格处理信息。由于我们将从各种 API 处理大量 JSON 类型数据,因此将它们存储在文档中是最简单的方式。对于实时和时间序列相关信息,Cassandra 最适合作为列式数据库。

以下图表显示了我们将在整本书中构建和使用的环境:

设置 Spark 动力环境

在 Ubuntu 上设置 Oracle VirtualBox

在 Ubuntu 14.04 上设置一个干净的新 VirtualBox 环境是创建一个开发环境的最安全方式,它不会与现有的库发生冲突,并且可以在云中使用类似的命令列表进行复制。

为了建立一个带有 Anaconda 和 Spark 的环境,我们将创建一个运行 Ubuntu 14.04 的 VirtualBox 虚拟机。

让我们来看看在 Ubuntu 上使用 VirtualBox 的步骤:

  1. Oracle VirtualBox VM 是免费的,可以从www.virtualbox.org/wiki/Downloads下载。安装非常简单。

  2. 安装 VirtualBox 后,让我们打开 Oracle VM VirtualBox Manager 并单击新建按钮。

  3. 我们将为新的虚拟机命名,并选择类型Linux和版本Ubuntu(64 位)

  4. 您需要从 Ubuntu 网站下载 ISO 并分配足够的 RAM(建议 4GB)和磁盘空间(建议 20GB)。我们将使用 Ubuntu 14.04.1 LTS 版本,可以在这里找到:www.ubuntu.com/download/desktop

  5. 安装完成后,建议通过转到(从新的虚拟机运行的 VirtualBox 菜单)设备 | 插入增强功能光盘映像来安装 VirtualBox 增强功能。在 Windows 主机中未提供增强功能会导致用户界面非常有限,窗口大小减小。

  6. 安装完成后,重新启动虚拟机,它将准备好使用。通过选择虚拟机并单击设置,然后转到常规 | 高级 | 共享剪贴板并单击双向,可以启用共享剪贴板。

安装带有 Python 2.7 的 Anaconda

PySpark 目前仅在 Python 2.7 上运行。(社区要求升级到 Python 3.3。)要安装 Anaconda,请按照以下步骤进行:

  1. continuum.io/downloads#all下载 Linux 64 位 Python 2.7 的 Anaconda 安装程序。

  2. 下载 Anaconda 安装程序后,打开终端并导航到安装程序保存的目录或文件夹。然后运行以下命令,将命令中的2.x.x替换为下载安装程序文件的版本号:

# install anaconda 2.x.x
bash Anaconda-2.x.x-Linux-x86[_64].sh

  1. 接受许可条款后,您将被要求指定安装位置(默认为~/anaconda)。

  2. 自解压完成后,您应该将 anaconda 二进制目录添加到您的 PATH 环境变量中:

# add anaconda to PATH
bash Anaconda-2.x.x-Linux-x86[_64].sh

安装 Java 8

Spark 在 JVM 上运行,并且需要 Java SDK(软件开发工具包)而不是JRE(Java 运行环境),因为我们将使用 Spark 构建应用程序。推荐的版本是 Java 版本 7 或更高版本。Java 8 是最合适的,因为它包括许多 Scala 和 Python 可用的函数式编程技术。

要安装 Java 8,请按照以下步骤进行:

  1. 使用以下命令安装 Oracle Java 8:
# install oracle java 8
$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:webupd8team/java
$ sudo apt-get update
$ sudo apt-get install oracle-java8-installer

  1. 设置JAVA_HOME环境变量,并确保 Java 程序在您的 PATH 上。

  2. 检查JAVA_HOME是否正确安装:

# 
$ echo JAVA_HOME

安装 Spark

转到 Spark 下载页面spark.apache.org/downloads.html

Spark 下载页面提供了下载早期版本的 Spark 和不同的软件包和下载类型的可能性。我们将选择最新版本,为 Hadoop 2.6 及更高版本预构建。安装 Spark 的最简单方法是使用为 Hadoop 2.6 及更高版本预构建的 Spark 软件包,而不是从源代码构建。将文件移动到根目录下的~/spark目录中。

下载最新版本的 Spark—2015 年 11 月 9 日发布的 Spark 1.5.2:

  1. 选择 Spark 版本1.5.2(2015 年 11 月 9 日发布)

  2. 选择软件包类型为 Hadoop 2.6 及更高版本预构建

  3. 选择下载类型直接下载

  4. 下载 Spark:spark-1.5.2-bin-hadoop2.6.tgz

  5. 使用 1.3.0 签名和校验和验证此版本,

这也可以通过运行以下命令来完成:

# download spark
$ wget http://d3kbcqa49mib13.cloudfront.net/spark-1.5.2-bin-hadoop2.6.tgz

接下来,我们将提取文件并清理:

# extract, clean up, move the unzipped files under the spark directory
$ tar -xf spark-1.5.2-bin-hadoop2.6.tgz
$ rm spark-1.5.2-bin-hadoop2.6.tgz
$ sudo mv spark-* spark

现在,我们可以运行 Spark Python 解释器:

# run spark
$ cd ~/spark
./bin/pyspark

您应该看到类似于这样的东西:

Welcome to
 ____              __
 / __/__  ___ _____/ /__
 _\ \/ _ \/ _ `/ __/  '_/
 /__ / .__/\_,_/_/ /_/\_\   version 1.5.2
 /_/
Using Python version 2.7.6 (default, Mar 22 2014 22:59:56)
SparkContext available as sc.
>>> 

解释器将已经为我们提供了一个 Spark 上下文对象sc,我们可以通过运行来查看:

>>> print(sc)
<pyspark.context.SparkContext object at 0x7f34b61c4e50>

启用 IPython 笔记本

我们将使用 IPython Notebook 以获得比控制台更友好的用户体验。

您可以使用以下命令启动 IPython Notebook:

$ IPYTHON_OPTS="notebook --pylab inline"  ./bin/pyspark

在存储 Jupyter 或 IPython 笔记本的examples/AN_Spark目录中使用IPYNB启动 PySpark:

# cd to  /home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark
# launch command using python 2.7 and the spark-csv package:
$ IPYTHON_OPTS='notebook' /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.databricks:spark-csv_2.11:1.2.0

# launch command using python 3.4 and the spark-csv package:
$ IPYTHON_OPTS='notebook' PYSPARK_PYTHON=python3
 /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.databricks:spark-csv_2.11:1.2.0

使用 PySpark 构建我们的第一个应用程序

我们现在准备检查一切是否正常工作。在处理本书第一章的单词计数时,将对其进行测试。

我们将要运行的代码在这里列出:

# Word count on 1st Chapter of the Book using PySpark

# import regex module
import re
# import add from operator module
from operator import add

# read input file
file_in = sc.textFile('/home/an/Documents/A00_Documents/Spark4Py 20150315')

# count lines
print('number of lines in file: %s' % file_in.count())

# add up lengths of each line
chars = file_in.map(lambda s: len(s)).reduce(add)
print('number of characters in file: %s' % chars)

# Get words from the input file
words =file_in.flatMap(lambda line: re.split('\W+', line.lower().strip()))
# words of more than 3 characters
words = words.filter(lambda x: len(x) > 3)
# set count 1 per word
words = words.map(lambda w: (w,1))
# reduce phase - sum count all the words
words = words.reduceByKey(add)

在此程序中,我们首先从目录/home/an/Documents/A00_Documents/Spark4Py 20150315中读取文件到file_in中。

然后通过计算每行的行数和每行的字符数来审查文件。

我们将输入文件拆分为单词并将它们转换为小写。为了避免较短和更频繁的单词(如theandfor)对计数产生偏向,我们选择长度超过三个字符的单词进行单词计数。通常,它们被认为是停用词,并且应该在任何语言处理任务中被过滤掉。

在这个阶段,我们准备进行 MapReduce 步骤。对于每个单词,我们将映射一个值1并通过对所有唯一单词求和来减少它。

以下是 IPython Notebook 中代码的示例。前 10 个单元格是对数据集上的单词计数进行预处理,该数据集是从本地文件目录中检索的。

使用 PySpark 构建我们的第一个应用程序

交换元组中的单词计数格式(count, word),以便按count排序,这现在是元组的主键:

# create tuple (count, word) and sort in descending
words = words.map(lambda x: (x[1], x[0])).sortByKey(False)

# take top 20 words by frequency
words.take(20)

为了显示我们的结果,我们创建元组(count, word)并按降序显示前 20 个最常用的单词:

使用 PySpark 构建我们的第一个应用程序

让我们创建一个直方图函数:

# create function for histogram of most frequent words

% matplotlib inline
import matplotlib.pyplot as plt
#

def histogram(words):
    count = map(lambda x: x[1], words)
    word = map(lambda x: x[0], words)
    plt.barh(range(len(count)), count,color = 'grey')
    plt.yticks(range(len(count)), word)

# Change order of tuple (word, count) from (count, word) 
words = words.map(lambda x:(x[1], x[0]))
words.take(25)

# display histogram
histogram(words.take(25))

在这里,我们通过在条形图中绘制它们来可视化最常用的单词。我们首先将元组从原始的(count, word)交换为(word, count)

使用 PySpark 构建我们的第一个应用程序

因此,您现在拥有的是:第一章中最常用的单词是Spark,其次是DataAnaconda

使用 Vagrant 虚拟化环境

为了创建一个可以轻松共享和克隆的便携式 Python 和 Spark 环境,可以使用vagrantfile构建开发环境。

我们将指向由伯克利大学和 Databricks提供的大规模在线开放课程MOOCs):

课程实验室是在由 PySpark 提供动力的 IPython 笔记本上执行的。它们可以在以下 GitHub 存储库中找到:github.com/spark-mooc/mooc-setup/

一旦在您的机器上设置了 Vagrant,请按照以下说明开始:docs.vagrantup.com/v2/getting-started/index.html

在您的工作目录中克隆spark-mooc/mooc-setup/ github存储库,并在克隆的目录中启动命令$ vagrant up

请注意,由于vagrantfile可能不是最新的,Spark 的版本可能已过时。

您将看到类似于这样的输出:

C:\Programs\spark\edx1001\mooc-setup-master>vagrant up
Bringing machine 'sparkvm' up with 'virtualbox' provider...
==> sparkvm: Checking if box 'sparkmooc/base' is up to date...
==> sparkvm: Clearing any previously set forwarded ports...
==> sparkvm: Clearing any previously set network interfaces...
==> sparkvm: Preparing network interfaces based on configuration...
 sparkvm: Adapter 1: nat
==> sparkvm: Forwarding ports...
 sparkvm: 8001 => 8001 (adapter 1)
 sparkvm: 4040 => 4040 (adapter 1)
 sparkvm: 22 => 2222 (adapter 1)
==> sparkvm: Booting VM...
==> sparkvm: Waiting for machine to boot. This may take a few minutes...
 sparkvm: SSH address: 127.0.0.1:2222
 sparkvm: SSH username: vagrant
 sparkvm: SSH auth method: private key
 sparkvm: Warning: Connection timeout. Retrying...
 sparkvm: Warning: Remote connection disconnect. Retrying...
==> sparkvm: Machine booted and ready!
==> sparkvm: Checking for guest additions in VM...
==> sparkvm: Setting hostname...
==> sparkvm: Mounting shared folders...
 sparkvm: /vagrant => C:/Programs/spark/edx1001/mooc-setup-master
==> sparkvm: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> sparkvm: to force provisioning. Provisioners marked to run always will still run.

C:\Programs\spark\edx1001\mooc-setup-master>

这将在localhost:8001上启动由 PySpark 提供动力的 IPython 笔记本:

使用 Vagrant 虚拟化环境

转移到云端

由于我们正在处理分布式系统,运行在单个笔记本电脑上的虚拟机上的环境对于探索和学习是有限的。我们可以转移到云端,以体验 Spark 分布式框架的强大和可扩展性。

在 Amazon Web Services 中部署应用程序

一旦我们准备好扩展我们的应用程序,我们可以将开发环境迁移到Amazon Web Services (AWS)。

如何在 EC2 上运行 Spark 在以下页面中清楚地描述:spark.apache.org/docs/latest/ec2-scripts.html

我们强调在设置 AWS Spark 环境时的五个关键步骤:

  1. 通过 AWS 控制台创建 AWS EC2 密钥对aws.amazon.com/console/

  2. 将您的密钥对导出到您的环境中:

export AWS_ACCESS_KEY_ID=accesskeyid
export AWS_SECRET_ACCESS_KEY=secretaccesskey

  1. 启动您的集群:
~$ cd $SPARK_HOME/ec2
ec2$ ./spark-ec2 -k <keypair> -i <key-file> -s <num-slaves> launch <cluster-name>

  1. SSH 进入集群运行 Spark 作业:
ec2$ ./spark-ec2 -k <keypair> -i <key-file> login <cluster-name>

  1. 在使用后销毁您的集群:
ec2$ ./spark-ec2 destroy <cluster-name>

使用 Docker 虚拟化环境

为了创建一个可以轻松共享和克隆的便携式 Python 和 Spark 环境,开发环境可以在 Docker 容器中构建。

我们希望利用 Docker 的两个主要功能:

  • 创建可以轻松部署在不同操作系统或云中的隔离容器。

  • 使用 DockerHub 允许轻松共享开发环境镜像及其所有依赖项。DockerHub 类似于 GitHub。它允许轻松克隆和版本控制。配置环境的快照图像可以作为进一步增强的基线。

以下图表说明了一个具有 Spark、Anaconda 和数据库服务器及其各自数据卷的 Docker 启用环境。

使用 Docker 虚拟化环境

Docker 提供了从 Dockerfile 克隆和部署环境的能力。

您可以在以下地址找到一个带有 PySpark 和 Anaconda 设置的示例 Dockerfile:hub.docker.com/r/thisgokeboysef/pyspark-docker/~/dockerfile/

按照以下链接提供的说明安装 Docker:

使用以下命令安装提供的 Dockerfile 的 docker 容器:

$ docker pull thisgokeboysef/pyspark-docker

关于如何dockerize您的环境的其他重要信息源可以在 Lab41 中找到。GitHub 存储库包含必要的代码:

github.com/Lab41/ipython-spark-docker

支持的博客文章中包含了构建 docker 环境所涉及的思维过程丰富的信息:lab41.github.io/blog/2015/04/13/ipython-on-spark-on-docker/

摘要

我们通过描述围绕基础设施、持久性、集成、分析和参与层的整体架构来设定构建数据密集型应用的背景。我们还讨论了 Spark 和 Anaconda 以及它们各自的构建模块。我们在 VirtualBox 中使用 Anaconda 和 Spark 设置了一个环境,并演示了使用第一章的文本内容作为输入的词频统计应用程序。

在下一章中,我们将更深入地探讨数据密集型应用的架构蓝图,并利用 Twitter、GitHub 和 Meetup 的 API 来感受我们将使用 Spark 进行挖掘的数据。

第二章:使用 Spark 构建批处理和流处理应用

本书的目标是通过构建一个应用程序来分析社交网络上 Spark 社区的互动,教会你关于 PySpark 和 PyData 库。我们将从 GitHub 收集有关 Apache Spark 的信息,在 Twitter 上检查相关的推文,并通过 Meetup 感受 Spark 在更广泛的开源软件社区中的热度。

在本章中,我们将概述各种数据和信息来源。我们将了解它们的结构。我们将概述从收集到批处理和流处理的数据处理流程。

在这一部分,我们将涵盖以下要点:

  • 从收集到批处理和流处理的数据处理流程,有效地描述我们计划构建的应用程序的架构。

  • 查看各种数据来源(GitHub、Twitter 和 Meetup)、它们的数据结构(JSON、结构化信息、非结构化文本、地理位置、时间序列数据等)以及它们的复杂性。我们还讨论了连接三种不同 API 的工具,这样你就可以构建自己的数据混搭。本书将在接下来的章节中重点关注 Twitter。

架构数据密集型应用

我们在上一章中定义了数据密集型应用框架架构蓝图。让我们重新将我们原始框架中将在整本书中使用的各种软件组件放回到上下文中。以下是数据密集型架构框架中映射的各种软件组件的示意图:

架构数据密集型应用

Spark 是一个非常高效的分布式计算框架。为了充分利用其功能,我们需要相应地设计我们的解决方案。出于性能原因,整体解决方案还需要考虑其在 CPU、存储和网络方面的使用情况。

这些要求驱动我们解决方案的架构:

  • 延迟:这种架构结合了慢速和快速处理。慢速处理是在批处理模式下对历史数据进行处理。这也被称为静态数据。这个阶段构建了将由快速处理部分在实时连续数据输入系统后使用的预先计算的模型和数据模式。数据的快速处理或实时分析是指处理运动中的数据。静态数据实际上是以批处理模式处理数据,具有较长的延迟。运动中的数据是指实时摄取的数据的流式计算。

  • 可扩展性:Spark 通过其分布式内存计算框架本身具有线性可扩展性。与 Spark 交互的数据库和数据存储也需要能够随着数据量的增长而线性扩展。

  • 容错性:当由于硬件、软件或网络原因发生故障时,架构应具有足够的弹性,并始终提供可用性。

  • 灵活性:在这种架构中建立的数据流程可以根据用例迅速进行调整和改装。

Spark 独特之处在于它允许在同一统一平台上进行批处理和流式分析。

我们将考虑两种数据处理流程:

  • 第一个处理静态数据,并专注于构建批量分析数据的流程。

  • 第二个流程是处理运动中的数据,目标是实时数据摄取和基于预先计算的模型和数据模式提供洞察力

处理静态数据

让我们了解一下静态数据或批处理流程。这个流程的目标是从 Twitter、GitHub 和 Meetup 中摄取各种数据集;为 Spark MLlib 准备数据,这是机器学习引擎;并推导出将在批处理模式或实时模式下应用的基本模型。

以下图表说明了数据流程,以便处理静态数据:

处理静态数据

处理运动中的数据

处理运动数据引入了新的复杂性,因为我们引入了新的失败可能性。如果我们想要扩展,我们需要考虑引入分布式消息队列系统,如 Kafka。我们将专门讨论理解流式分析的后续章节。

以下图表描述了用于处理运动数据的数据管道:

处理运动数据

交互式地探索数据

构建数据密集型应用程序并不像将数据库暴露给 Web 界面那样简单。在静态数据和运动数据处理的设置过程中,我们将利用 Spark 分析数据的能力,以交互方式分析和细化所需的机器学习和流处理活动所需的数据丰富性和质量。在这里,我们将进行数据收集、细化和调查的迭代循环,以获取我们应用程序感兴趣的数据集。

连接到社交网络

让我们深入探讨数据密集型应用程序架构集成层的第一步。我们将专注于收集数据,确保其完整性,并为 Spark 在下一阶段的批处理和流处理数据做准备。这个阶段描述了五个处理步骤:连接校正收集组合消费。这些是数据探索的迭代步骤,将使我们熟悉数据,并帮助我们为进一步处理调整数据结构。

以下图表描述了用于消费的数据采集和细化的迭代过程:

连接到社交网络

我们连接到感兴趣的社交网络:Twitter、GitHub 和 Meetup。我们将讨论如何访问APIs(应用程序编程接口)的方式,以及如何与这些服务创建 RESTful 连接,同时尊重社交网络施加的速率限制。REST(表示状态转移)是互联网上最广泛采用的架构风格,以实现可扩展的 Web 服务。它依赖于主要以JSON(JavaScript 对象表示)交换消息。RESTful APIs 和 Web 服务实现了四种最常见的动词GETPUTPOSTDELETEGET用于从给定的URI检索元素或集合。PUT使用新的集合更新一个集合。POST允许创建新条目,而DELETE则删除一个集合。

获取 Twitter 数据

Twitter 允许注册用户访问其搜索和流式推文服务,使用名为 OAuth 的授权协议,允许 API 应用程序安全地代表用户进行操作。为了创建连接,第一步是在 Twitter 上创建一个应用程序,网址为apps.twitter.com/app/new

获取 Twitter 数据

应用程序创建后,Twitter 将发出四个代码,允许其接入 Twitter 的数据流:

CONSUMER_KEY = 'GetYourKey@Twitter'
CONSUMER_SECRET = ' GetYourKey@Twitter'
OAUTH_TOKEN = ' GetYourToken@Twitter'
OAUTH_TOKEN_SECRET = ' GetYourToken@Twitter'

如果您想了解提供的各种 RESTful 查询,可以在开发控制台上探索 Twitter API,网址为dev.twitter.com/rest/tools/console

获取 Twitter 数据

我们将使用以下代码在 Twitter 上进行程序化连接,这将激活我们的 OAuth 访问,并允许我们在速率限制下接入 Twitter API。在流模式下,限制是针对 GET 请求的。

获取 GitHub 数据

GitHub 使用类似的身份验证流程来 Twitter。前往开发者网站,在developer.github.com/v3/上注册 GitHub 后,检索您的凭据:

获取 GitHub 数据

获取 Meetup 数据

可以使用在 Meetup.com 成员的开发资源中发行的令牌来访问 Meetup。可以在他们的开发者网站上获取 Meetup API 访问所需的令牌或 OAuth 凭据:secure.meetup.com/meetup_api

获取 Meetup 数据

分析数据

让我们首先感受一下从每个社交网络中提取的数据,并了解来自这些来源的数据结构。

发现推文的结构

在本节中,我们将建立与 Twitter API 的连接。Twitter 提供两种连接模式:REST API,允许我们搜索给定搜索词或标签的历史推文,以及流 API,它在限制速率下提供实时推文。

为了更好地了解如何操作 Twitter API,我们将按照以下步骤进行:

  1. 安装 Twitter Python 库。

  2. 通过 OAuth 以编程方式建立连接,这是 Twitter 所需的身份验证。

  3. 搜索查询Apache Spark的最新推文并探索所获得的结果。

  4. 决定感兴趣的关键属性,并从 JSON 输出中检索信息。

让我们一步一步地进行这个过程:

  1. 安装 Python Twitter 库。为了安装它,您需要从命令行中编写pip install twitter
$ pip install twitter

  1. 创建 Python Twitter API 类及其用于身份验证、搜索和解析结果的基本方法。self.auth从 Twitter 获取凭据。然后创建一个注册的 API 作为self.api。我们实现了两种方法:第一种是使用给定的查询搜索 Twitter,第二种是解析输出以检索相关信息,如推文 ID、推文文本和推文作者。代码如下:
import twitter
import urlparse
from pprint import pprint as pp

class TwitterAPI(object):
    """
    TwitterAPI class allows the Connection to Twitter via OAuth
    once you have registered with Twitter and receive the 
    necessary credentiials 
    """

# initialize and get the twitter credentials
     def __init__(self): 
        consumer_key = 'Provide your credentials'
        consumer_secret = 'Provide your credentials'
        access_token = 'Provide your credentials'
        access_secret = 'Provide your credentials'

        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret
        self.access_token = access_token
        self.access_secret = access_secret

#
# authenticate credentials with Twitter using OAuth
        self.auth = twitter.oauth.OAuth(access_token, access_secret, consumer_key, consumer_secret)
    # creates registered Twitter API
        self.api = twitter.Twitter(auth=self.auth)
#
# search Twitter with query q (i.e. "ApacheSpark") and max. result
    def searchTwitter(self, q, max_res=10,**kwargs):
        search_results = self.api.search.tweets(q=q, count=10, **kwargs)
        statuses = search_results['statuses']
        max_results = min(1000, max_res)

        for _ in range(10): 
            try:
                next_results = search_results['search_metadata']['next_results']
            except KeyError as e: 
                break

            next_results = urlparse.parse_qsl(next_results[1:])
            kwargs = dict(next_results)
            search_results = self.api.search.tweets(**kwargs)
            statuses += search_results['statuses']

            if len(statuses) > max_results: 
                break
        return statuses
#
# parse tweets as it is collected to extract id, creation 
# date, user id, tweet text
    def parseTweets(self, statuses):
        return [ (status['id'], 
                  status['created_at'], 
                  status['user']['id'],
                  status['user']['name'], 
                  status['text'], url['expanded_url']) 
                        for status in statuses 
                            for url in status['entities']['urls'] ]
  1. 用所需的身份验证实例化类:
t= TwitterAPI()
  1. 在查询词Apache Spark上运行搜索:
q="ApacheSpark"
tsearch = t.searchTwitter(q)
  1. 分析 JSON 输出:
pp(tsearch[1])

{u'contributors': None,
 u'coordinates': None,
 u'created_at': u'Sat Apr 25 14:50:57 +0000 2015',
 u'entities': {u'hashtags': [{u'indices': [74, 86], u'text': u'sparksummit'}],
               u'media': [{u'display_url': u'pic.twitter.com/WKUMRXxIWZ',
                           u'expanded_url': u'http://twitter.com/bigdata/status/591976255831969792/photo/1',
                           u'id': 591976255156715520,
                           u'id_str': u'591976255156715520',
                           u'indices': [143, 144],
                           u'media_url': 
...(snip)... 
 u'text': u'RT @bigdata: Enjoyed catching up with @ApacheSpark users &amp; leaders at #sparksummit NYC: video clips are out http://t.co/qrqpP6cG9s http://t\u2026',
 u'truncated': False,
 u'user': {u'contributors_enabled': False,
           u'created_at': u'Sat Apr 04 14:44:31 +0000 2015',
           u'default_profile': True,
           u'default_profile_image': True,
           u'description': u'',
           u'entities': {u'description': {u'urls': []}},
           u'favourites_count': 0,
           u'follow_request_sent': False,
           u'followers_count': 586,
           u'following': False,
           u'friends_count': 2,
           u'geo_enabled': False,
           u'id': 3139047660,
           u'id_str': u'3139047660',
           u'is_translation_enabled': False,
           u'is_translator': False,
           u'lang': u'zh-cn',
           u'listed_count': 749,
           u'location': u'',
           u'name': u'Mega Data Mama',
           u'notifications': False,
           u'profile_background_color': u'C0DEED',
           u'profile_background_image_url': u'http://abs.twimg.com/images/themes/theme1/bg.png',
           u'profile_background_image_url_https': u'https://abs.twimg.com/images/themes/theme1/bg.png',
           ...(snip)... 
           u'screen_name': u'MegaDataMama',
           u'statuses_count': 26673,
           u'time_zone': None,
           u'url': None,
           u'utc_offset': None,
           u'verified': False}}
  1. 解析 Twitter 输出以检索感兴趣的关键信息:
tparsed = t.parseTweets(tsearch)
pp(tparsed)

[(591980327784046592,
  u'Sat Apr 25 15:01:23 +0000 2015',
  63407360,
  u'Jos\xe9 Carlos Baquero',
  u'Big Data systems are making a difference in the fight against cancer. #BigData #ApacheSpark http://t.co/pnOLmsKdL9',
  u'http://tmblr.co/ZqTggs1jHytN0'),
 (591977704464875520,
  u'Sat Apr 25 14:50:57 +0000 2015',
  3139047660,
  u'Mega Data Mama',
  u'RT @bigdata: Enjoyed catching up with @ApacheSpark users &amp; leaders at #sparksummit NYC: video clips are out http://t.co/qrqpP6cG9s http://t\u2026',
  u'http://goo.gl/eF5xwK'),
 (591977172589539328,
  u'Sat Apr 25 14:48:51 +0000 2015',
  2997608763,
  u'Emma Clark',
  u'RT @bigdata: Enjoyed catching up with @ApacheSpark users &amp; leaders at #sparksummit NYC: video clips are out http://t.co/qrqpP6cG9s http://t\u2026',
  u'http://goo.gl/eF5xwK'),
 ... (snip)...  
 (591879098349268992,
  u'Sat Apr 25 08:19:08 +0000 2015',
  331263208,
  u'Mario Molina',
  u'#ApacheSpark speeds up big data decision-making http://t.co/8hdEXreNfN',
  u'http://www.computerweekly.com/feature/Apache-Spark-speeds-up-big-data-decision-making')]

探索 GitHub 世界

为了更好地了解如何操作 GitHub API,我们将按照以下步骤进行:

  1. 安装 GitHub Python 库。

  2. 通过使用在开发者网站上注册时提供的令牌来访问 API。

  3. 检索有关托管 spark 存储库的 Apache 基金会的一些关键事实。

让我们一步一步地进行这个过程:

  1. 安装 Python PyGithub 库。为了安装它,您需要从命令行中pip install PyGithub
pip install PyGithub
  1. 通过编程方式创建客户端来实例化 GitHub API:
from github import Github

# Get your own access token

ACCESS_TOKEN = 'Get_Your_Own_Access_Token'

# We are focusing our attention to User = apache and Repo = spark

USER = 'apache'
REPO = 'spark'

g = Github(ACCESS_TOKEN, per_page=100)
user = g.get_user(USER)
repo = user.get_repo(REPO)
  1. 从 Apache 用户检索关键事实。GitHub 中有 640 个活跃的 Apache 存储库:
repos_apache = [repo.name for repo in g.get_user('apache').get_repos()]
len(repos_apache)
640
  1. 从 Spark 存储库检索关键事实,Spark 存储库中使用的编程语言在此处给出:
pp(repo.get_languages())

{u'C': 1493,
 u'CSS': 4472,
 u'Groff': 5379,
 u'Java': 1054894,
 u'JavaScript': 21569,
 u'Makefile': 7771,
 u'Python': 1091048,
 u'R': 339201,
 u'Scala': 10249122,
 u'Shell': 172244}
  1. 检索广泛的 Spark GitHub 存储库网络中的一些关键参与者。在撰写本文时,Apache Spark 存储库中有 3,738 名关注者。这个网络是巨大的。第一个关注者是Matei Zaharia,他在伯克利读博士期间是 Spark 项目的联合创始人。
stargazers = [ s for s in repo.get_stargazers() ]
print "Number of stargazers", len(stargazers)
Number of stargazers 3738

[stargazers[i].login for i in range (0,20)]
[u'mateiz',
 u'beyang',
 u'abo',
 u'CodingCat',
 u'andy327',
 u'CrazyJvm',
 u'jyotiska',
 u'BaiGang',
 u'sundstei',
 u'dianacarroll',
 u'ybotco',
 u'xelax',
 u'prabeesh',
 u'invkrh',
 u'bedla',
 u'nadesai',
 u'pcpratts',
 u'narkisr',
 u'Honghe',
 u'Jacke']

通过 Meetup 了解社区

为了更好地了解如何操作 Meetup API,我们将按照以下步骤进行:

  1. 创建一个 Python 程序,使用身份验证令牌调用 Meetup API。

  2. 检索 Meetup 小组的过去事件信息,例如London Data Science

  3. 检索 Meetup 成员的个人资料,以分析他们参与类似 Meetup 小组的情况。

让我们一步一步地进行这个过程:

  1. 由于没有可靠的 Meetup API Python 库,我们将通过编程方式创建一个客户端来实例化 Meetup API:
import json
import mimeparse
import requests
import urllib
from pprint import pprint as pp

MEETUP_API_HOST = 'https://api.meetup.com'
EVENTS_URL = MEETUP_API_HOST + '/2/events.json'
MEMBERS_URL = MEETUP_API_HOST + '/2/members.json'
GROUPS_URL = MEETUP_API_HOST + '/2/groups.json'
RSVPS_URL = MEETUP_API_HOST + '/2/rsvps.json'
PHOTOS_URL = MEETUP_API_HOST + '/2/photos.json'
GROUP_URLNAME = 'London-Machine-Learning-Meetup'
# GROUP_URLNAME = 'London-Machine-Learning-Meetup' # 'Data-Science-London'

class Mee
tupAPI(object):
    """
    Retrieves information about meetup.com
    """
    def __init__(self, api_key, num_past_events=10, http_timeout=1,
                 http_retries=2):
        """
        Create a new instance of MeetupAPI
        """
        self._api_key = api_key
        self._http_timeout = http_timeout
        self._http_retries = http_retries
        self._num_past_events = num_past_events

    def get_past_events(self):
        """
        Get past meetup events for a given meetup group
        """
        params = {'key': self._api_key,
                  'group_urlname': GROUP_URLNAME,
                  'status': 'past',
                  'desc': 'true'}
        if self._num_past_events:
            params['page'] = str(self._num_past_events)

        query = urllib.urlencode(params)
        url = '{0}?{1}'.format(EVENTS_URL, query)
        response = requests.get(url, timeout=self._http_timeout)
        data = response.json()['results']
        return data

    def get_members(self):
        """
        Get meetup members for a given meetup group
        """
        params = {'key': self._api_key,
                  'group_urlname': GROUP_URLNAME,
                  'offset': '0',
                  'format': 'json',
                  'page': '100',
                  'order': 'name'}
        query = urllib.urlencode(params)
        url = '{0}?{1}'.format(MEMBERS_URL, query)
        response = requests.get(url, timeout=self._http_timeout)
        data = response.json()['results']
        return data

    def get_groups_by_member(self, member_id='38680722'):
        """
        Get meetup groups for a given meetup member
        """
        params = {'key': self._api_key,
                  'member_id': member_id,
                  'offset': '0',
                  'format': 'json',
                  'page': '100',
                  'order': 'id'}
        query = urllib.urlencode(params)
        url = '{0}?{1}'.format(GROUPS_URL, query)
        response = requests.get(url, timeout=self._http_timeout)
        data = response.json()['results']
        return data
  1. 然后,我们将从给定的 Meetup 小组中检索过去的事件:
m = MeetupAPI(api_key='Get_Your_Own_Key')
last_meetups = m.get_past_events()
pp(last_meetups[5])

{u'created': 1401809093000,
 u'description': u"<p>We are hosting a joint meetup between Spark London and Machine Learning London. Given the excitement in the machine learning community around Spark at the moment a joint meetup is in order!</p> <p>Michael Armbrust from the Apache Spark core team will be flying over from the States to give us a talk in person.\xa0Thanks to our sponsors, Cloudera, MapR and Databricks for helping make this happen.</p> <p>The first part of the talk will be about MLlib, the machine learning library for Spark,\xa0and the second part, on\xa0Spark SQL.</p> <p>Don't sign up if you have already signed up on the Spark London page though!</p> <p>\n\n\nAbstract for part one:</p> <p>In this talk, we\u2019ll introduce Spark and show how to use it to build fast, end-to-end machine learning workflows. Using Spark\u2019s high-level API, we can process raw data with familiar libraries in Java, Scala or Python (e.g. NumPy) to extract the features for machine learning. Then, using MLlib, its built-in machine learning library, we can run scalable versions of popular algorithms. We\u2019ll also cover upcoming development work including new built-in algorithms and R bindings.</p> <p>\n\n\n\nAbstract for part two:\xa0</p> <p>In this talk, we'll examine Spark SQL, a new Alpha component that is part of the Apache Spark 1.0 release. Spark SQL lets developers natively query data stored in both existing RDDs and external sources such as Apache Hive. A key feature of Spark SQL is the ability to blur the lines between relational tables and RDDs, making it easy for developers to intermix SQL commands that query external data with complex analytics. In addition to Spark SQL, we'll explore the Catalyst optimizer framework, which allows Spark SQL to automatically rewrite query plans to execute more efficiently.</p>",
 u'event_url': u'http://www.meetup.com/London-Machine-Learning-Meetup/events/186883262/',
 u'group': {u'created': 1322826414000,
            u'group_lat': 51.52000045776367,
            u'group_lon': -0.18000000715255737,
            u'id': 2894492,
            u'join_mode': u'open',
            u'name': u'London Machine Learning Meetup',
            u'urlname': u'London-Machine-Learning-Meetup',
            u'who': u'Machine Learning Enthusiasts'},
 u'headcount': 0,
 u'id': u'186883262',
 u'maybe_rsvp_count': 0,
 u'name': u'Joint Spark London and Machine Learning Meetup',
 u'rating': {u'average': 4.800000190734863, u'count': 5},
 u'rsvp_limit': 70,
 u'status': u'past',
 u'time': 1403200800000,
 u'updated': 1403450844000,
 u'utc_offset': 3600000,
 u'venue': {u'address_1': u'12 Errol St, London',
            u'city': u'EC1Y 8LX',
            u'country': u'gb',
            u'id': 19504802,
            u'lat': 51.522533,
            u'lon': -0.090934,
            u'name': u'Royal Statistical Society',
            u'repinned': False},
 u'visibility': u'public',
 u'waitlist_count': 84,
 u'yes_rsvp_count': 70}
  1. 获取有关 Meetup 成员的信息:
members = m.get_members()

{u'city': u'London',
  u'country': u'gb',
  u'hometown': u'London',
  u'id': 11337881,
  u'joined': 1421418896000,
  u'lat': 51.53,
  u'link': u'http://www.meetup.com/members/11337881',
  u'lon': -0.09,
  u'name': u'Abhishek Shivkumar',
  u'other_services': {u'twitter': {u'identifier': u'@abhisemweb'}},
  u'photo': {u'highres_link': u'http://photos3.meetupstatic.com/photos/member/9/6/f/3/highres_10898643.jpeg',
             u'photo_id': 10898643,
             u'photo_link': u'http://photos3.meetupstatic.com/photos/member/9/6/f/3/member_10898643.jpeg',
             u'thumb_link': u'http://photos3.meetupstatic.com/photos/member/9/6/f/3/thumb_10898643.jpeg'},
  u'self': {u'common': {}},
  u'state': u'17',
  u'status': u'active',
  u'topics': [{u'id': 1372, u'name': u'Semantic Web', u'urlkey': u'semweb'},
              {u'id': 1512, u'name': u'XML', u'urlkey': u'xml'},
              {u'id': 49585,
               u'name': u'Semantic Social Networks',
               u'urlkey': u'semantic-social-networks'},
              {u'id': 24553,
               u'name': u'Natural Language Processing',
...(snip)...
               u'name': u'Android Development',
               u'urlkey': u'android-developers'}],
  u'visited': 1429281599000}

预览我们的应用程序

我们的挑战是理解从这些社交网络中检索到的数据,找到关键关系并得出见解。一些感兴趣的元素如下:

  • 可视化顶级影响者:发现社区中的顶级影响者:

  • Apache Spark上的重度 Twitter 用户

  • GitHub 的提交者

  • 领先的 Meetup 演示

  • 了解网络:GitHub 提交者、观察者和星标用户的网络图

  • 确定热门位置:定位 Spark 最活跃的位置

以下截图提供了我们应用程序的预览:

预览我们的应用程序

总结

在本章中,我们阐述了我们应用程序的总体架构。我们解释了处理数据的两种主要范例:批处理,也称为静态数据,和流式分析,也称为动态数据。我们继续建立与三个感兴趣的社交网络的连接:Twitter、GitHub 和 Meetup。我们对数据进行了抽样,并提供了我们的构建目标的预览。本书的其余部分将专注于 Twitter 数据集。我们在这里提供了访问三个社交网络的工具和 API,这样你就可以在以后创建自己的数据混搭。我们现在准备调查收集的数据,这将是下一章的主题。

在下一章中,我们将深入研究数据分析,提取我们感兴趣的关键属性,并管理批处理和流处理的信息存储。

第三章:使用 Spark 玩弄数据

根据上一章中概述的批处理和流处理架构,我们需要数据来支持我们的应用程序。我们将从 Twitter 上收集关于 Apache Spark 的数据。本章的目标是准备数据以供机器学习和流处理应用程序进一步使用。本章重点介绍如何在分布式网络中交换代码和数据。我们将深入了解序列化、持久化、编组和缓存。我们将深入了解 Spark SQL,这是交互式地探索结构化和半结构化数据的关键 Spark 模块。支持 Spark SQL 的基本数据结构是 Spark dataframe。Spark dataframe 受到 Python Pandas dataframe 和 R dataframe 的启发。它是一种强大的数据结构,被具有 R 或 Python 背景的数据科学家充分理解和赞赏。

在本章中,我们将涵盖以下内容:

  • 连接到 Twitter,收集相关数据,然后以 JSON 和 CSV 等各种格式以及数据存储(如 MongoDB)进行持久化

  • 使用 Blaze 和 Odo 来分析数据,Odo 是 Blaze 的一个衍生库,用于连接和传输来自各种来源和目的地的数据

  • 介绍 Spark dataframe 作为各种 Spark 模块之间数据交换的基础,并使用 Spark SQL 交互式地探索数据

重新审视数据密集型应用架构

让我们首先将本章的重点与数据密集型应用架构放在一起。我们将集中精力放在集成层上,并基本上通过迭代循环来运行数据的获取、精炼和持久化。这个循环被称为五个 C。五个 C 代表连接、收集、校正、组合和消费。这些是我们在集成层中运行的基本流程,以便从 Twitter 中获取正确质量和数量的数据。我们还将深入研究持久化层,并设置一个数据存储,如 MongoDB,以便稍后收集我们的数据进行处理。

我们将使用 Blaze 和 Spark SQL 来探索数据,Blaze 是用于数据操作的 Python 库,而 Spark SQL 是由 Spark dataframe 支持的用于数据发现的交互模块。Dataframe 范式由 Python Pandas、Python Blaze 和 Spark SQL 共享。我们将了解这三种 dataframe 的细微差别。

以下图表设置了本章重点的背景,突出了集成层和持久化层:

重新审视数据密集型应用架构

序列化和反序列化数据

从网络 API 中收集数据时受到速率限制的约束,我们需要将它们存储起来。由于数据在分布式集群上进行处理,我们需要一致的方法来保存状态并在以后使用时检索它。

现在让我们定义序列化、持久化、编组和缓存或记忆化。

将 Python 对象序列化为一系列字节。当程序关闭时,需要检索 Python 对象以超出其存在范围,序列化的 Python 对象可以通过网络传输或存储在持久存储中。反序列化是相反的过程,将一系列字节转换为原始的 Python 对象,以便程序可以从保存的状态继续进行。Python 中最流行的序列化库是 Pickle。事实上,PySpark 命令通过 pickled 数据通过网络传输到工作节点。

持久化将程序的状态数据保存到磁盘或内存中,以便在重新启动时可以继续之前的工作。它将 Python 对象从内存保存到文件或数据库中,并在以后以相同的状态加载它。

编组将 Python 代码或数据通过网络 TCP 连接发送到多核或分布式系统中。

缓存将 Python 对象转换为内存中的字符串,以便以后可以用作字典键。Spark 支持将数据集缓存在整个集群的内存中。当数据被重复访问时,比如查询一个小的参考数据集或运行迭代算法(如 Google PageRank)时,这是非常有用的。

缓存对于 Spark 来说是一个关键概念,因为它允许我们将 RDD 保存在内存中或溢出到磁盘。缓存策略可以根据数据的血统或 RDD 应用的转换的 DAG(有向无环图的缩写)来选择,以最小化洗牌或跨网络的重数据交换。为了在 Spark 中实现良好的性能,要注意数据洗牌。良好的分区策略和 RDD 缓存的使用,再加上避免不必要的操作操作,可以提高 Spark 的性能。

收集和存储数据

在深入研究数据库持久存储(如 MongoDB)之前,我们将看一些广泛使用的有用文件存储:CSV(逗号分隔值的缩写)和 JSON(JavaScript 对象表示法的缩写)文件存储。这两种文件格式的持久受欢迎之处在于几个关键原因:它们易于阅读,简单,相对轻量级,易于使用。

在 CSV 中持久化数据

CSV 格式是轻量级的,易于阅读和使用。它具有带有固有表格模式的分隔文本列。

Python 提供了一个强大的csv库,可以将csv文件序列化为 Python 字典。为了实现我们的程序目的,我们编写了一个python类,用于管理以 CSV 格式持久化数据并从给定的 CSV 文件中读取。

让我们运行IO_csv类对象的代码。该类的__init__部分基本上实例化了文件路径、文件名和文件后缀(在本例中为.csv):

class IO_csv(object):

    def __init__(self, filepath, filename, filesuffix='csv'):
        self.filepath = filepath       # /path/to/file without the /' at the end
        self.filename = filename       # FILE_NAME
        self.filesuffix = filesuffix

该类的save方法使用 Python 命名元组和csv文件的标题字段,以便在持久化 CSV 的同时传递模式。如果csv文件已经存在,它将被追加而不是覆盖;否则将被创建:

    def save(self, data, NTname, fields):
        # NTname = Name of the NamedTuple
        # fields = header of CSV - list of the fields name
        NTuple = namedtuple(NTname, fields)

        if os.path.isfile('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix)):
            # Append existing file
            with open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix), 'ab') as f:
                writer = csv.writer(f)
                # writer.writerow(fields) # fields = header of CSV
                writer.writerows([row for row in map(NTuple._make, data)])
                # list comprehension using map on the NamedTuple._make() iterable and the data file to be saved
                # Notice writer.writerows and not writer.writerow (i.e. list of multiple rows sent to csv file
        else:
            # Create new file
            with open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix), 'wb') as f:
                writer = csv.writer(f)
                writer.writerow(fields) # fields = header of CSV - list of the fields name
                writer.writerows([row for row in map(NTuple._make, data)])
                #  list comprehension using map on the NamedTuple._make() iterable and the data file to be saved
                # Notice writer.writerows and not writer.writerow (i.e. list of multiple rows sent to csv file

该类的load方法还使用 Python 命名元组和csv文件的标题字段,以便使用一致的模式检索数据。load方法是一个内存高效的生成器,以避免在内存中加载大文件:因此我们使用yield代替return

    def load(self, NTname, fields):
        # NTname = Name of the NamedTuple
        # fields = header of CSV - list of the fields name
        NTuple = namedtuple(NTname, fields)
        with open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix),'rU') as f:
            reader = csv.reader(f)
            for row in map(NTuple._make, reader):
                # Using map on the NamedTuple._make() iterable and the reader file to be loaded
                yield row 

这是命名元组。我们使用它来解析推文,以便将它们保存或从csv文件中检索出来:

fields01 = ['id', 'created_at', 'user_id', 'user_name', 'tweet_text', 'url']
Tweet01 = namedtuple('Tweet01',fields01)

def parse_tweet(data):
    """
    Parse a ``tweet`` from the given response data.
    """
    return Tweet01(
        id=data.get('id', None),
        created_at=data.get('created_at', None),
        user_id=data.get('user_id', None),
        user_name=data.get('user_name', None),
        tweet_text=data.get('tweet_text', None),
        url=data.get('url')
    )

在 JSON 中持久化数据

JSON 是互联网应用程序中最流行的数据格式之一。我们正在处理的所有 API,Twitter、GitHub 和 Meetup,都以 JSON 格式传递它们的数据。与 XML 相比,JSON 格式相对轻量级且易于阅读,其模式嵌入在 JSON 中。与 CSV 格式相反,其中所有记录都遵循完全相同的表格结构,JSON 记录的结构可以有所不同。JSON 是半结构化的。JSON 记录可以映射到 Python 字典的字典中。

让我们运行IO_json类对象的代码。该类的__init__部分基本上实例化了文件路径、文件名和文件后缀(在本例中为.json):

class IO_json(object):
    def __init__(self, filepath, filename, filesuffix='json'):
        self.filepath = filepath        # /path/to/file without the /' at the end
        self.filename = filename        # FILE_NAME
        self.filesuffix = filesuffix
        # self.file_io = os.path.join(dir_name, .'.join((base_filename, filename_suffix)))

该类的save方法使用utf-8编码,以确保数据的读取和写入兼容性。如果 JSON 文件已经存在,它将被追加而不是覆盖;否则将被创建:

    def save(self, data):
        if os.path.isfile('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix)):
            # Append existing file
            with io.open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix), 'a', encoding='utf-8') as f:
                f.write(unicode(json.dumps(data, ensure_ascii= False))) # In python 3, there is no "unicode" function 
                # f.write(json.dumps(data, ensure_ascii= False)) # create a \" escape char for " in the saved file        
        else:
            # Create new file
            with io.open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix), 'w', encoding='utf-8') as f:
                f.write(unicode(json.dumps(data, ensure_ascii= False)))
                # f.write(json.dumps(data, ensure_ascii= False))

该类的load方法只返回已读取的文件。需要进一步应用json.loads函数以从读取的文件中检索出json

    def load(self):
        with io.open('{0}/{1}.{2}'.format(self.filepath, self.filename, self.filesuffix), encoding='utf-8') as f:
            return f.read()

设置 MongoDB

存储收集到的信息至关重要。因此,我们将 MongoDB 设置为我们的主要文档数据存储。由于收集的所有信息都是以 JSON 格式,而 MongoDB 以 BSON(Binary JSON 的缩写)存储信息,因此它是一个自然的选择。

现在我们将按以下步骤进行:

  • 安装 MongoDB 服务器和客户端

  • 运行 MongoDB 服务器

  • 运行 Mongo 客户端

  • 安装 PyMongo 驱动程序

  • 创建 Python Mongo 客户端

安装 MongoDB 服务器和客户端

为了安装 MongoDB 软件包,请按以下步骤执行:

  1. 导入软件包管理系统(在我们的情况下是 Ubuntu 的apt)使用的公钥。要导入 MongoDB 公钥,我们发出以下命令:
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10

  1. 为 MongoDB 创建一个列表文件。要创建列表文件,我们使用以下命令:
echo "deb http://repo.mongodb.org/apt/ubuntu "$("lsb_release -sc)"/ mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list

  1. 将本地软件包数据库更新为sudo
sudo apt-get update

  1. 安装 MongoDB 软件包。我们使用以下命令安装 MongoDB 的最新稳定版本:
sudo apt-get install -y mongodb-org

运行 MongoDB 服务器

让我们启动 MongoDB 服务器:

  1. 要启动 MongoDB 服务器,我们发出以下命令来启动mongod
sudo service mongodb start

  1. 要检查mongod是否已正确启动,我们发出以下命令:
an@an-VB:/usr/bin$ ps -ef | grep mongo
mongodb    967     1  4 07:03 ?        00:02:02 /usr/bin/mongod --config /etc/mongod.conf
an        3143  3085  0 07:45 pts/3    00:00:00 grep --color=auto mongo

在这种情况下,我们看到mongodb正在进程967中运行。

  1. mongod服务器发送一条消息,表示它正在等待端口 27017上的连接。这是 MongoDB 的默认端口。可以在配置文件中更改。

  2. 我们可以在/var/log/mongod/mongod.log中检查日志文件的内容:

an@an-VB:/var/lib/mongodb$ ls -lru
total 81936
drwxr-xr-x 2 mongodb nogroup     4096 Apr 25 11:19 _tmp
-rw-r--r-- 1 mongodb nogroup       69 Apr 25 11:19 storage.bson
-rwxr-xr-x 1 mongodb nogroup        5 Apr 25 11:19 mongod.lock
-rw------- 1 mongodb nogroup 16777216 Apr 25 11:19 local.ns
-rw------- 1 mongodb nogroup 67108864 Apr 25 11:19 local.0
drwxr-xr-x 2 mongodb nogroup     4096 Apr 25 11:19 journal

  1. 要停止mongodb服务器,只需发出以下命令:
sudo service mongodb stop

运行 Mongo 客户端

在控制台中运行 Mongo 客户端就像调用mongo一样简单,如以下命令所示:

an@an-VB:/usr/bin$ mongo
MongoDB shell version: 3.0.2
connecting to: test
Server has startup warnings: 
2015-05-30T07:03:49.387+0200 I CONTROL  [initandlisten] 
2015-05-30T07:03:49.388+0200 I CONTROL  [initandlisten] 

在 mongo 客户端控制台提示符下,我们可以使用以下命令查看数据库:

> show dbs
local  0.078GB
test   0.078GB

我们使用use test选择测试数据库:

> use test
switched to db test

我们显示测试数据库中的集合:

> show collections
restaurants
system.indexes

我们检查先前列出的餐厅集合中的一个示例记录:

> db.restaurants.find()
{ "_id" : ObjectId("553b70055e82e7b824ae0e6f"), "address : { "building : "1007", "coord" : [ -73.856077, 40.848447 ], "street : "Morris Park Ave", "zipcode : "10462 }, "borough : "Bronx", "cuisine : "Bakery", "grades : [ { "grade : "A", "score" : 2, "date" : ISODate("2014-03-03T00:00:00Z") }, { "date" : ISODate("2013-09-11T00:00:00Z"), "grade : "A", "score" : 6 }, { "score" : 10, "date" : ISODate("2013-01-24T00:00:00Z"), "grade : "A }, { "date" : ISODate("2011-11-23T00:00:00Z"), "grade : "A", "score" : 9 }, { "date" : ISODate("2011-03-10T00:00:00Z"), "grade : "B", "score" : 14 } ], "name : "Morris Park Bake Shop", "restaurant_id : "30075445" }

安装 PyMongo 驱动程序

使用 anaconda 安装 Python 驱动程序很容易。只需在终端运行以下命令:

conda install pymongo

创建 Python 客户端以用于 MongoDB

我们正在创建一个IO_mongo类,该类将用于我们的收集和处理程序中,以存储收集和检索到的信息。为了创建mongo客户端,我们将从pymongo导入MongoClient模块。我们在本地主机的端口 27017 上连接到mongodb服务器。命令如下:

from pymongo import MongoClient as MCli

class IO_mongo(object):
    conn={'host':'localhost', 'ip':'27017'}

我们通过客户端连接、数据库(在本例中为twtr_db)和要访问的集合(在本例中为twtr_coll)来初始化我们的类:

    def __init__(self, db='twtr_db', coll='twtr_coll', **conn ):
        # Connects to the MongoDB server 
        self.client = MCli(**conn)
        self.db = self.client[db]
        self.coll = self.db[coll]

save方法在预初始化的集合和数据库中插入新记录:

    def save(self, data):
        # Insert to collection in db  
        return self.coll.insert(data)

load方法允许根据条件和投影检索特定记录。在数据量大的情况下,它返回一个游标:

    def load(self, return_cursor=False, criteria=None, projection=None):

            if criteria is None:
                criteria = {}

            if projection is None:
                cursor = self.coll.find(criteria)
            else:
                cursor = self.coll.find(criteria, projection)

            # Return a cursor for large amounts of data
            if return_cursor:
                return cursor
            else:
                return [ item for item in cursor ]

从 Twitter 收集数据

每个社交网络都存在其局限性和挑战。收集数据的主要障碍之一是强加的速率限制。在运行重复或长时间连接的速率限制暂停时,我们必须小心避免收集重复数据。

我们已经重新设计了在前一章中概述的连接程序,以处理速率限制。

在这个TwitterAPI类中,根据我们指定的搜索查询连接和收集推文,我们添加了以下内容:

  • 使用 Python 日志库的记录功能,以便在程序失败时收集任何错误或警告。

  • 使用 MongoDB 的持久性功能,以及之前公开的IO_mongo类和使用IO_json类的 JSON 文件

  • API 速率限制和错误管理功能,因此我们可以确保更具弹性地调用 Twitter,而不会因为接入 firehose 而被禁止

让我们按以下步骤进行:

  1. 我们通过实例化 Twitter API 来初始化:
class TwitterAPI(object):
    """
    TwitterAPI class allows the Connection to Twitter via OAuth
    once you have registered with Twitter and receive the 
    necessary credentials 
    """

    def __init__(self): 
        consumer_key = 'get_your_credentials'
        consumer_secret = get your_credentials'
        access_token = 'get_your_credentials'
        access_secret = 'get your_credentials'
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret
        self.access_token = access_token
        self.access_secret = access_secret
        self.retries = 3
        self.auth = twitter.oauth.OAuth(access_token, access_secret, consumer_key, consumer_secret)
        self.api = twitter.Twitter(auth=self.auth)
  1. 我们通过提供日志级别来初始化记录器:
  • logger.debug(调试消息)

  • logger.info(信息消息)

  • logger.warn(警告消息)

  • logger.error(错误消息)

  • logger.critical(临界消息)

  1. 我们设置日志路径和消息格式:
        # logger initialisation
        appName = 'twt150530'
        self.logger = logging.getLogger(appName)
        #self.logger.setLevel(logging.DEBUG)
        # create console handler and set level to debug
        logPath = '/home/an/spark/spark-1.3.0-bin-hadoop2.4/examples/AN_Spark/data'
        fileName = appName
        fileHandler = logging.FileHandler("{0}/{1}.log".format(logPath, fileName))
        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
        fileHandler.setFormatter(formatter)
        self.logger.addHandler(fileHandler) 
        self.logger.setLevel(logging.DEBUG)
  1. 我们初始化 JSON 文件持久性指令:
        # Save to JSON file initialisation
        jsonFpath = '/home/an/spark/spark-1.3.0-bin-hadoop2.4/examples/AN_Spark/data'
        jsonFname = 'twtr15053001'
        self.jsonSaver = IO_json(jsonFpath, jsonFname)
  1. 我们初始化 MongoDB 数据库和集合以进行持久化:
        # Save to MongoDB Intitialisation
        self.mongoSaver = IO_mongo(db='twtr01_db', coll='twtr01_coll')
  1. searchTwitter方法根据指定的查询启动搜索:
    def searchTwitter(self, q, max_res=10,**kwargs):
        search_results = self.api.search.tweets(q=q, count=10, **kwargs)
        statuses = search_results['statuses']
        max_results = min(1000, max_res)

        for _ in range(10):
            try:
                next_results = search_results['search_metadata']['next_results']
                # self.logger.info('info' in searchTwitter - next_results:%s'% next_results[1:])
            except KeyError as e:
                self.logger.error('error' in searchTwitter: %s', %(e))
                break

            # next_results = urlparse.parse_qsl(next_results[1:]) # python 2.7
            next_results = urllib.parse.parse_qsl(next_results[1:])
            # self.logger.info('info' in searchTwitter - next_results[max_id]:', next_results[0:])
            kwargs = dict(next_results)
            # self.logger.info('info' in searchTwitter - next_results[max_id]:%s'% kwargs['max_id'])
            search_results = self.api.search.tweets(**kwargs)
            statuses += search_results['statuses']
            self.saveTweets(search_results['statuses'])

            if len(statuses) > max_results:
                self.logger.info('info' in searchTwitter - got %i tweets - max: %i' %(len(statuses), max_results))
                break
        return statuses
  1. saveTweets方法实际上将收集到的推文保存为 JSON 和 MongoDB:
    def saveTweets(self, statuses):
        # Saving to JSON File
        self.jsonSaver.save(statuses)

        # Saving to MongoDB
        for s in statuses:
            self.mongoSaver.save(s)
  1. parseTweets方法允许我们从 Twitter API 提供的大量信息中提取关键的推文信息:
    def parseTweets(self, statuses):
        return [ (status['id'], 
                  status['created_at'], 
                  status['user']['id'],
                  status['user']['name'] 
                  status['text''text'], 
                  url['expanded_url']) 
                        for status in statuses 
                            for url in status['entities']['urls'] ]
  1. getTweets方法调用了先前描述的searchTwitter方法。getTweets方法确保可靠地进行 API 调用,同时尊重强加的速率限制。代码如下:
    def getTweets(self, q,  max_res=10):
        """
        Make a Twitter API call whilst managing rate limit and errors.
        """
        def handleError(e, wait_period=2, sleep_when_rate_limited=True):
            if wait_period > 3600: # Seconds
                self.logger.error('Too many retries in getTweets: %s', %(e))
                raise e
            if e.e.code == 401:
                self.logger.error('error 401 * Not Authorised * in getTweets: %s', %(e))
                return None
            elif e.e.code == 404:
                self.logger.error('error 404 * Not Found * in getTweets: %s', %(e))
                return None
            elif e.e.code == 429: 
                self.logger.error('error 429 * API Rate Limit Exceeded * in getTweets: %s', %(e))
                if sleep_when_rate_limited:
                    self.logger.error('error 429 * Retrying in 15 minutes * in getTweets: %s', %(e))
                    sys.stderr.flush()
                    time.sleep(60*15 + 5)
                    self.logger.info('error 429 * Retrying now * in getTweets: %s', %(e))
                    return 2
                else:
                    raise e # Caller must handle the rate limiting issue
            elif e.e.code in (500, 502, 503, 504):
                self.logger.info('Encountered %i Error. Retrying in %i seconds' % (e.e.code, wait_period))
                time.sleep(wait_period)
                wait_period *= 1.5
                return wait_period
            else:
                self.logger.error('Exit - aborting - %s', %(e))
                raise e
  1. 在这里,我们根据指定的参数调用searchTwitterAPI 进行相关查询。如果我们遇到来自提供者的速率限制等错误,将由handleError方法处理:
        while True:
            try:
                self.searchTwitter( q, max_res=10)
            except twitter.api.TwitterHTTPError as e:
                error_count = 0 
                wait_period = handleError(e, wait_period)
                if wait_period is None:
                    return

使用 Blaze 探索数据

Blaze 是一个开源的 Python 库,主要由 Continuum.io 开发,利用 Python Numpy 数组和 Pandas 数据框架。Blaze 扩展到了核外计算,而 Pandas 和 Numpy 是单核的。

Blaze 在各种后端之间提供了一个适应性强、统一和一致的用户界面。Blaze 编排以下内容:

  • 数据:在不同存储之间无缝交换数据,如 CSV、JSON、HDF5、HDFS 和 Bcolz 文件。

  • 计算:使用相同的查询处理对计算后端进行计算,如 Spark、MongoDB、Pandas 或 SQL Alchemy。

  • 符号表达式:抽象表达式,如连接、分组、过滤、选择和投影,其语法类似于 Pandas,但范围有限。实现了 R 语言开创的分割-应用-合并方法。

Blaze 表达式是惰性评估的,在这方面与 Spark RDD 转换共享类似的处理范式。

让我们首先导入必要的库来深入了解 Blaze:numpypandasblazeodo。Odo 是 Blaze 的一个衍生项目,确保从各种后端迁移数据。命令如下:

import numpy as np
import pandas as pd
from blaze import Data, by, join, merge
from odo import odo
BokehJS successfully loaded.

我们通过读取保存在 CSV 文件twts_csv中的解析推文来创建一个 Pandas Dataframe

twts_pd_df = pd.DataFrame(twts_csv_read, columns=Tweet01._fields)
twts_pd_df.head()

Out[65]:
id    created_at    user_id    user_name    tweet_text    url
1   598831111406510082   2015-05-14 12:43:57   14755521 raulsaeztapia    RT @pacoid: Great recap of @StrataConf EU in L...   http://www.mango-solutions.com/wp/2015/05/the-...
2   598831111406510082   2015-05-14 12:43:57   14755521 raulsaeztapia    RT @pacoid: Great recap of @StrataConf EU in L...   http://www.mango-solutions.com/wp/2015/05/the-...
3   98808944719593472   2015-05-14 11:15:52   14755521 raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...    http://www.webex.com/ciscospark/
4   598808944719593472   2015-05-14 11:15:52   14755521 raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...   http://sparkjava.com/

我们运行 Tweets Panda Dataframedescribe()函数,以获取数据集的一些整体信息:

twts_pd_df.describe()
Out[66]:
id    created_at    user_id    user_name    tweet_text    url
count  19  19  19  19  19  19
unique    7  7   6   6     6   7
top    598808944719593472    2015-05-14 11:15:52    14755521 raulsaeztapia    RT @alvaroagea: Simply @ApacheSpark http://t.c...    http://bit.ly/1Hfd0Xm
freq    6    6    9    9    6    6

我们通过简单地通过Data()函数传递 Pandas dataframe将其转换为 Blaze dataframe

#
# Blaze dataframe
#
twts_bz_df = Data(twts_pd_df)

我们可以通过传递schema函数来检索 Blaze dataframe的模式表示:

twts_bz_df.schema
Out[73]:
dshape("""{
  id: ?string,
  created_at: ?string,
  user_id: ?string,
  user_name: ?string,
  tweet_text: ?string,
  url: ?string
  }""")

.dshape函数给出记录计数和模式:

twts_bz_df.dshape
Out[74]: 
dshape("""19 * {
  id: ?string,
  created_at: ?string,
  user_id: ?string,
  user_name: ?string,
  tweet_text: ?string,
  url: ?string
  }""")

我们可以打印 Blaze dataframe的内容:

twts_bz_df.data
Out[75]:
id    created_at    user_id    user_name    tweet_text    url
1    598831111406510082    2015-05-14 12:43:57   14755521 raulsaeztapia    RT @pacoid: Great recap of @StrataConf EU in L...    http://www.mango-solutions.com/wp/2015/05/the-...
2    598831111406510082    2015-05-14 12:43:57    14755521 raulsaeztapia    RT @pacoid: Great recap of @StrataConf EU in L...    http://www.mango-solutions.com/wp/2015/05/the-...
... 
18   598782970082807808    2015-05-14 09:32:39    1377652806 embeddedcomputer.nl    RT @BigDataTechCon: Moving Rating Prediction w...    http://buff.ly/1QBpk8J
19   598777933730160640     2015-05-14 09:12:38   294862170    Ellen Friedman   I'm still on Euro time. If you are too check o...http://bit.ly/1Hfd0Xm

我们提取列tweet_text并获取唯一值:

twts_bz_df.tweet_text.distinct()
Out[76]:
    tweet_text
0   RT @pacoid: Great recap of @StrataConf EU in L...
1   RT @alvaroagea: Simply @ApacheSpark http://t.c...
2   RT @PrabhaGana: What exactly is @ApacheSpark a...
3   RT @Ellen_Friedman: I'm still on Euro time. If...
4   RT @BigDataTechCon: Moving Rating Prediction w...
5   I'm still on Euro time. If you are too check o...

我们从dataframe中提取多列['id', 'user_name','tweet_text']并获取唯一记录:

twts_bz_df[['id', 'user_name','tweet_text']].distinct()
Out[78]:
  id   user_name   tweet_text
0   598831111406510082   raulsaeztapia   RT @pacoid: Great recap of @StrataConf EU in L...
1   598808944719593472   raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...
2   598796205091500032   John Humphreys   RT @PrabhaGana: What exactly is @ApacheSpark a...
3   598788561127735296   Leonardo D'Ambrosi   RT @Ellen_Friedman: I'm still on Euro time. If...
4   598785545557438464   Alexey Kosenkov   RT @Ellen_Friedman: I'm still on Euro time. If...
5   598782970082807808   embeddedcomputer.nl   RT @BigDataTechCon: Moving Rating Prediction w...
6   598777933730160640   Ellen Friedman   I'm still on Euro time. If you are too check o...

使用 Odo 传输数据

Odo 是 Blaze 的一个衍生项目。Odo 允许数据的交换。Odo 确保数据在不同格式(CSV、JSON、HDFS 等)和不同数据库(SQL 数据库、MongoDB 等)之间的迁移非常简单:

Odo(source, target)

要传输到数据库,需要使用 URL 指定地址。例如,对于 MongoDB 数据库,它看起来像这样:

mongodb://username:password@hostname:port/database_name::collection_name

让我们运行一些使用 Odo 的示例。在这里,我们通过读取一个 CSV 文件并创建一个 Blaze dataframe来说明odo

filepath   = csvFpath
filename   = csvFname
filesuffix = csvSuffix
twts_odo_df = Data('{0}/{1}.{2}'.format(filepath, filename, filesuffix))

计算dataframe中的记录数:

twts_odo_df.count()
Out[81]:
19

显示dataframe的前五条记录:

twts_odo_df.head(5)
Out[82]:
  id   created_at   user_id   user_name   tweet_text   url
0   598831111406510082   2015-05-14 12:43:57   14755521   raulsaeztapia   RT @pacoid: Great recap of @StrataConf EU in L...   http://www.mango-solutions.com/wp/2015/05/the-...
1   598831111406510082   2015-05-14 12:43:57   14755521   raulsaeztapia   RT @pacoid: Great recap of @StrataConf EU in L...   http://www.mango-solutions.com/wp/2015/05/the-...
2   598808944719593472   2015-05-14 11:15:52   14755521   raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...   http://www.webex.com/ciscospark/
3   598808944719593472   2015-05-14 11:15:52   14755521   raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...   http://sparkjava.com/
4   598808944719593472   2015-05-14 11:15:52   14755521   raulsaeztapia   RT @alvaroagea: Simply @ApacheSpark http://t.c...   https://www.sparkfun.com/

dataframe获取dshape信息,这给出了记录数和模式:

twts_odo_df.dshape
Out[83]:
dshape("var * {
  id: int64,
  created_at: ?datetime,
  user_id: int64,
  user_name: ?string,
  tweet_text: ?string,
  url: ?string
  }""")

将处理后的 Blaze dataframe保存为 JSON:

odo(twts_odo_distinct_df, '{0}/{1}.{2}'.format(jsonFpath, jsonFname, jsonSuffix))
Out[92]:
<odo.backends.json.JSONLines at 0x7f77f0abfc50>

将 JSON 文件转换为 CSV 文件:

odo('{0}/{1}.{2}'.format(jsonFpath, jsonFname, jsonSuffix), '{0}/{1}.{2}'.format(csvFpath, csvFname, csvSuffix))
Out[94]:
<odo.backends.csv.CSV at 0x7f77f0abfe10>

使用 Spark SQL 探索数据

Spark SQL 是建立在 Spark Core 之上的关系查询引擎。Spark SQL 使用名为Catalyst的查询优化器。

可以使用 SQL 或 HiveQL 表示关系查询,并针对 JSON、CSV 和各种数据库执行。Spark SQL 使我们能够在功能编程的 RDD 之上使用 Spark 数据框架的声明式编程的全部表达能力。

了解 Spark 数据框架

这是一条来自@bigdata的推文,宣布了 Spark 1.3.0 的到来,以及 Spark SQL 和数据框的出现。它还突出了图表下部的各种数据源。在图表的上部,我们可以注意到 R 作为新语言,将逐渐支持 Scala、Java 和 Python。最终,数据框的理念在 R、Python 和 Spark 之间普遍存在。

理解 Spark 数据框

Spark 数据框源自 SchemaRDDs。它将 RDD 与可以由 Spark 推断的模式结合在一起,如果请求的话,可以在注册数据框时推断出模式。它允许我们使用普通 SQL 查询复杂嵌套的 JSON 数据。惰性评估、血统、分区和持久性适用于数据框。

让我们通过首先导入SparkContextSQLContext来使用 Spark SQL 查询数据:

from pyspark import SparkConf, SparkContext
from pyspark.sql import SQLContext, Row
In [95]:
sc
Out[95]:
<pyspark.context.SparkContext at 0x7f7829581890>
In [96]:
sc.master
Out[96]:
u'local[*]'
''In [98]:
# Instantiate Spark  SQL context
sqlc =  SQLContext(sc)

我们读取了用 Odo 保存的 JSON 文件:

twts_sql_df_01 = sqlc.jsonFile ("/home/an/spark/spark-1.3.0-bin-hadoop2.4/examples/AN_Spark/data/twtr15051401_distinct.json")
In [101]:
twts_sql_df_01.show()
created_at           id                 tweet_text           user_id    user_name          
2015-05-14T12:43:57Z 598831111406510082 RT @pacoid: Great... 14755521   raulsaeztapia      
2015-05-14T11:15:52Z 598808944719593472 RT @alvaroagea: S... 14755521   raulsaeztapia      
2015-05-14T10:25:15Z 598796205091500032 RT @PrabhaGana: W... 48695135   John Humphreys     
2015-05-14T09:54:52Z 598788561127735296 RT @Ellen_Friedma... 2385931712 Leonardo D'Ambrosi
2015-05-14T09:42:53Z 598785545557438464 RT @Ellen_Friedma... 461020977  Alexey Kosenkov    
2015-05-14T09:32:39Z 598782970082807808 RT @BigDataTechCo... 1377652806 embeddedcomputer.nl
2015-05-14T09:12:38Z 598777933730160640 I'm still on Euro... 294862170  Ellen Friedman     

我们打印 Spark dataframe 的模式:

twts_sql_df_01.printSchema()
root
 |-- created_at: string (nullable = true)
 |-- id: long (nullable = true)
 |-- tweet_text: string (nullable = true)
 |-- user_id: long (nullable = true)
 |-- user_name: string (nullable = true)

我们从数据框中选择user_name列:

twts_sql_df_01.select('user_name').show()
user_name          
raulsaeztapia      
raulsaeztapia      
John Humphreys     
Leonardo D'Ambrosi
Alexey Kosenkov    
embeddedcomputer.nl
Ellen Friedman     

我们将数据框注册为表,这样我们就可以对其执行 SQL 查询:

twts_sql_df_01.registerAsTable('tweets_01')

我们对数据框执行了一条 SQL 语句:

twts_sql_df_01_selection = sqlc.sql("SELECT * FROM tweets_01 WHERE user_name = 'raulsaeztapia'")
In [109]:
twts_sql_df_01_selection.show()
created_at           id                 tweet_text           user_id  user_name    
2015-05-14T12:43:57Z 598831111406510082 RT @pacoid: Great... 14755521 raulsaeztapia
2015-05-14T11:15:52Z 598808944719593472 RT @alvaroagea: S... 14755521 raulsaeztapia

让我们处理一些更复杂的 JSON;我们读取原始的 Twitter JSON 文件:

tweets_sqlc_inf = sqlc.jsonFile(infile)

Spark SQL 能够推断复杂嵌套的 JSON 文件的模式:

tweets_sqlc_inf.printSchema()
root
 |-- contributors: string (nullable = true)
 |-- coordinates: string (nullable = true)
 |-- created_at: string (nullable = true)
 |-- entities: struct (nullable = true)
 |    |-- hashtags: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- indices: array (nullable = true)
 |    |    |    |    |-- element: long (containsNull = true)
 |    |    |    |-- text: string (nullable = true)
 |    |-- media: array (nullable = true)
 |    |    |-- element: struct (containsNull = true)
 |    |    |    |-- display_url: string (nullable = true)
 |    |    |    |-- expanded_url: string (nullable = true)
 |    |    |    |-- id: long (nullable = true)
 |    |    |    |-- id_str: string (nullable = true)
 |    |    |    |-- indices: array (nullable = true)
... (snip) ...
|    |-- statuses_count: long (nullable = true)
 |    |-- time_zone: string (nullable = true)
 |    |-- url: string (nullable = true)
 |    |-- utc_offset: long (nullable = true)
 |    |-- verified: boolean (nullable = true)

我们通过选择数据框中特定列(在本例中为['created_at', 'id', 'text', 'user.id', 'user.name', 'entities.urls.expanded_url'])提取感兴趣的关键信息:

tweets_extract_sqlc = tweets_sqlc_inf[['created_at', 'id', 'text', 'user.id', 'user.name', 'entities.urls.expanded_url']].distinct()
In [145]:
tweets_extract_sqlc.show()
created_at           id                 text                 id         name                expanded_url        
Thu May 14 09:32:... 598782970082807808 RT @BigDataTechCo... 1377652806 embeddedcomputer.nl ArrayBuffer(http:...
Thu May 14 12:43:... 598831111406510082 RT @pacoid: Great... 14755521   raulsaeztapia       ArrayBuffer(http:...
Thu May 14 12:18:... 598824733086523393 @rabbitonweb spea... 

...   
Thu May 14 12:28:... 598827171168264192 RT @baandrzejczak... 20909005   Paweł Szulc         ArrayBuffer()       

理解 Spark SQL 查询优化器

我们对数据框执行了一条 SQL 语句:

tweets_extract_sqlc_sel = sqlc.sql("SELECT * from Tweets_xtr_001 WHERE name='raulsaeztapia'")

我们可以详细查看 Spark SQL 执行的查询计划:

  • 解析逻辑计划

  • 分析逻辑计划

  • 优化逻辑计划

  • 物理计划

查询计划使用了 Spark SQL 的 Catalyst 优化器。为了从查询部分生成编译后的字节码,Catalyst 优化器通过逻辑计划解析和优化,然后根据成本进行物理计划评估和优化。

这在以下推文中有所体现:

理解 Spark SQL 查询优化器

回顾我们的代码,我们在刚刚执行的 Spark SQL 查询上调用了.explain函数,它提供了 Catalyst 优化器评估逻辑计划和物理计划并得出结果 RDD 所采取的步骤的全部细节:

tweets_extract_sqlc_sel.explain(extended = True)
== Parsed Logical Plan ==
'Project [*]
 'Filter ('name = raulsaeztapia)'name'  'UnresolvedRelation' [Tweets_xtr_001], None
== Analyzed Logical Plan ==
Project [created_at#7,id#12L,text#27,id#80L,name#81,expanded_url#82]
 Filter (name#81 = raulsaeztapia)
  Distinct 
   Project [created_at#7,id#12L,text#27,user#29.id AS id#80L,user#29.name AS name#81,entities#8.urls.expanded_url AS expanded_url#82]
    Relation[contributors#5,coordinates#6,created_at#7,entities#8,favorite_count#9L,favorited#10,geo#11,id#12L,id_str#13,in_reply_to_screen_name#14,in_reply_to_status_id#15,in_reply_to_status_id_str#16,in_reply_to_user_id#17L,in_reply_to_user_id_str#18,lang#19,metadata#20,place#21,possibly_sensitive#22,retweet_count#23L,retweeted#24,retweeted_status#25,source#26,text#27,truncated#28,user#29] JSONRelation(/home/an/spark/spark-1.3.0-bin-hadoop2.4/examples/AN_Spark/data/twtr15051401.json,1.0,None)
== Optimized Logical Plan ==
Filter (name#81 = raulsaeztapia)
 Distinct 
  Project [created_at#7,id#12L,text#27,user#29.id AS id#80L,user#29.name AS name#81,entities#8.urls.expanded_url AS expanded_url#82]
   Relation[contributors#5,coordinates#6,created_at#7,entities#8,favorite_count#9L,favorited#10,geo#11,id#12L,id_str#13,in_reply_to_screen_name#14,in_reply_to_status_id#15,in_reply_to_status_id_str#16,in_reply_to_user_id#17L,in_reply_to_user_id_str#18,lang#19,metadata#20,place#21,possibly_sensitive#22,retweet_count#23L,retweeted#24,retweeted_status#25,source#26,text#27,truncated#28,user#29] JSONRelation(/home/an/spark/spark-1.3.0-bin-hadoop2.4/examples/AN_Spark/data/twtr15051401.json,1.0,None)
== Physical Plan ==
Filter (name#81 = raulsaeztapia)
 Distinct false
  Exchange (HashPartitioning [created_at#7,id#12L,text#27,id#80L,name#81,expanded_url#82], 200)
   Distinct true
    Project [created_at#7,id#12L,text#27,user#29.id AS id#80L,user#29.name AS name#81,entities#8.urls.expanded_url AS expanded_url#82]
     PhysicalRDD [contributors#5,coordinates#6,created_at#7,entities#8,favorite_count#9L,favorited#10,geo#11,id#12L,id_str#13,in_reply_to_screen_name#14,in_reply_to_status_id#15,in_reply_to_status_id_str#16,in_reply_to_user_id#17L,in_reply_to_user_id_str#18,lang#19,metadata#20,place#21,possibly_sensitive#22,retweet_count#23L,retweeted#24,retweeted_status#25,source#26,text#27,truncated#28,user#29], MapPartitionsRDD[165] at map at JsonRDD.scala:41
Code Generation: false
== RDD ==

最后,这是查询的结果:

tweets_extract_sqlc_sel.show()
created_at           id                 text                 id       name          expanded_url        
Thu May 14 12:43:... 598831111406510082 RT @pacoid: Great... 14755521 raulsaeztapia ArrayBuffer(http:...
Thu May 14 11:15:... 598808944719593472 RT @alvaroagea: S... 14755521 raulsaeztapia ArrayBuffer(http:...
In [148]:

使用 Spark SQL 加载和处理 CSV 文件

我们将使用 Spark 包spark-csv_2.11:1.2.0。启动 PySpark 与 IPython Notebook 和spark-csv包应明确说明–packages参数的命令:

$ IPYTHON_OPTS='notebook' /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.databricks:spark-csv_2.11:1.2.0

这将触发以下输出;我们可以看到spark-csv包已安装及其所有依赖项:

an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark$ IPYTHON_OPTS='notebook' /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.databricks:spark-csv_2.11:1.2.0

... (snip) ...
Ivy Default Cache set to: /home/an/.ivy2/cache
The jars for the packages stored in: /home/an/.ivy2/jars
:: loading settings :: url = jar:file:/home/an/spark/spark-1.5.0-bin-hadoop2.6/lib/spark-assembly-1.5.0-hadoop2.6.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
com.databricks#spark-csv_2.11 added as a dependency
:: resolving dependencies :: org.apache.spark#spark-submit-parent;1.0
  confs: [default]
  found com.databricks#spark-csv_2.11;1.2.0 in central
  found org.apache.commons#commons-csv;1.1 in central
  found com.univocity#univocity-parsers;1.5.1 in central
:: resolution report :: resolve 835ms :: artifacts dl 48ms
  :: modules in use:
  com.databricks#spark-csv_2.11;1.2.0 from central in [default]
  com.univocity#univocity-parsers;1.5.1 from central in [default]
  org.apache.commons#commons-csv;1.1 from central in [default]
  ----------------------------------------------------------------
  |               |          modules            ||   artifacts   |
  |    conf     | number| search|dwnlded|evicted|| number|dwnlded|
  ----------------------------------------------------------------
  |    default     |   3   |   0   |   0   |   0   ||   3   |   0   
  ----------------------------------------------------------------
:: retrieving :: org.apache.spark#spark-submit-parent
  confs: [default]
  0 artifacts copied, 3 already retrieved (0kB/45ms)

现在我们准备加载我们的csv文件并处理它。让我们首先导入SQLContext

#
# Read csv in a Spark DF
#
sqlContext = SQLContext(sc)
spdf_in = sqlContext.read.format('com.databricks.spark.csv')\
                                    .options(delimiter=";").options(header="true")\
                                    .options(header='true').load(csv_in)

我们访问从加载的csv创建的数据框的模式:

In [10]:
spdf_in.printSchema()
root
 |-- : string (nullable = true)
 |-- id: string (nullable = true)
 |-- created_at: string (nullable = true)
 |-- user_id: string (nullable = true)
 |-- user_name: string (nullable = true)
 |-- tweet_text: string (nullable = true)

我们检查数据框的列:

In [12]:
spdf_in.columns
Out[12]:
['', 'id', 'created_at', 'user_id', 'user_name', 'tweet_text']

我们审查数据框的内容:

In [13]:
spdf_in.show()
+---+------------------+--------------------+----------+------------------+--------------------+
|   |                id|          created_at|   user_id|         user_name|          tweet_text|
+---+------------------+--------------------+----------+------------------+--------------------+
|  0|638830426971181057|Tue Sep 01 21:46:...|3276255125|     True Equality|ernestsgantt: Bey...|
|  1|638830426727911424|Tue Sep 01 21:46:...|3276255125|     True Equality|ernestsgantt: Bey...|
|  2|638830425402556417|Tue Sep 01 21:46:...|3276255125|     True Equality|ernestsgantt: Bey...|
... (snip) ...
| 41|638830280988426250|Tue Sep 01 21:46:...| 951081582|      Jack Baldwin|RT @cloudaus: We ...|
| 42|638830276626399232|Tue Sep 01 21:46:...|   6525302|Masayoshi Nakamura|PynamoDB 使いやすいです  |
+---+------------------+--------------------+----------+------------------+--------------------+
only showing top 20 rows

从 Spark SQL 查询 MongoDB

从 Spark 到 MongoDB 有两种主要的交互方式:第一种是通过 Hadoop MongoDB 连接器,第二种是直接从 Spark 到 MongoDB。

从 Spark 与 MongoDB 交互的第一种方法是设置一个 Hadoop 环境,并通过 Hadoop MongoDB 连接器进行查询。连接器的详细信息托管在 GitHub 上:github.com/mongodb/mongo-hadoop/wiki/Spark-Usage。MongoDB 的一系列博客文章中描述了一个实际用例:

设置完整的 Hadoop 环境有点复杂。我们将倾向于第二种方法。我们将使用由 Stratio 开发和维护的spark-mongodb连接器。我们使用托管在spark.packages.org上的Stratio spark-mongodb包。包的信息和版本可以在spark.packages.org中找到:

注意

发布

版本:0.10.1(8263c8 | zip | jar)/日期:2015-11-18 /许可证:Apache-2.0 / Scala 版本:2.10

spark-packages.org/package/Stratio/spark-mongodb

启动 PySpark 与 IPython 笔记本和spark-mongodb包的命令应明确说明 packages 参数:

$ IPYTHON_OPTS='notebook' /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.stratio.datasource:spark-mongodb_2.10:0.10.1

这将触发以下输出;我们可以看到spark-mongodb包与其所有依赖项一起安装:

an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark$ IPYTHON_OPTS='notebook' /home/an/spark/spark-1.5.0-bin-hadoop2.6/bin/pyspark --packages com.stratio.datasource:spark-mongodb_2.10:0.10.1
... (snip) ... 
Ivy Default Cache set to: /home/an/.ivy2/cache
The jars for the packages stored in: /home/an/.ivy2/jars
:: loading settings :: url = jar:file:/home/an/spark/spark-1.5.0-bin-hadoop2.6/lib/spark-assembly-1.5.0-hadoop2.6.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
com.stratio.datasource#spark-mongodb_2.10 added as a dependency
:: resolving dependencies :: org.apache.spark#spark-submit-parent;1.0
  confs: [default]
  found com.stratio.datasource#spark-mongodb_2.10;0.10.1 in central
[W 22:10:50.910 NotebookApp] Timeout waiting for kernel_info reply from 764081d3-baf9-4978-ad89-7735e6323cb6
  found org.mongodb#casbah-commons_2.10;2.8.0 in central
  found com.github.nscala-time#nscala-time_2.10;1.0.0 in central
  found joda-time#joda-time;2.3 in central
  found org.joda#joda-convert;1.2 in central
  found org.slf4j#slf4j-api;1.6.0 in central
  found org.mongodb#mongo-java-driver;2.13.0 in central
  found org.mongodb#casbah-query_2.10;2.8.0 in central
  found org.mongodb#casbah-core_2.10;2.8.0 in central
downloading https://repo1.maven.org/maven2/com/stratio/datasource/spark-mongodb_2.10/0.10.1/spark-mongodb_2.10-0.10.1.jar ...
  [SUCCESSFUL ] com.stratio.datasource#spark-mongodb_2.10;0.10.1!spark-mongodb_2.10.jar (3130ms)
downloading https://repo1.maven.org/maven2/org/mongodb/casbah-commons_2.10/2.8.0/casbah-commons_2.10-2.8.0.jar ...
  [SUCCESSFUL ] org.mongodb#casbah-commons_2.10;2.8.0!casbah-commons_2.10.jar (2812ms)
downloading https://repo1.maven.org/maven2/org/mongodb/casbah-query_2.10/2.8.0/casbah-query_2.10-2.8.0.jar ...
  [SUCCESSFUL ] org.mongodb#casbah-query_2.10;2.8.0!casbah-query_2.10.jar (1432ms)
downloading https://repo1.maven.org/maven2/org/mongodb/casbah-core_2.10/2.8.0/casbah-core_2.10-2.8.0.jar ...
  [SUCCESSFUL ] org.mongodb#casbah-core_2.10;2.8.0!casbah-core_2.10.jar (2785ms)
downloading https://repo1.maven.org/maven2/com/github/nscala-time/nscala-time_2.10/1.0.0/nscala-time_2.10-1.0.0.jar ...
  [SUCCESSFUL ] com.github.nscala-time#nscala-time_2.10;1.0.0!nscala-time_2.10.jar (2725ms)
downloading https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.6.0/slf4j-api-1.6.0.jar ...
  [SUCCESSFUL ] org.slf4j#slf4j-api;1.6.0!slf4j-api.jar (371ms)
downloading https://repo1.maven.org/maven2/org/mongodb/mongo-java-driver/2.13.0/mongo-java-driver-2.13.0.jar ...
  [SUCCESSFUL ] org.mongodb#mongo-java-driver;2.13.0!mongo-java-driver.jar (5259ms)
downloading https://repo1.maven.org/maven2/joda-time/joda-time/2.3/joda-time-2.3.jar ...
  [SUCCESSFUL ] joda-time#joda-time;2.3!joda-time.jar (6949ms)
downloading https://repo1.maven.org/maven2/org/joda/joda-convert/1.2/joda-convert-1.2.jar ...
  [SUCCESSFUL ] org.joda#joda-convert;1.2!joda-convert.jar (548ms)
:: resolution report :: resolve 11850ms :: artifacts dl 26075ms
  :: modules in use:
  com.github.nscala-time#nscala-time_2.10;1.0.0 from central in [default]
  com.stratio.datasource#spark-mongodb_2.10;0.10.1 from central in [default]
  joda-time#joda-time;2.3 from central in [default]
  org.joda#joda-convert;1.2 from central in [default]
  org.mongodb#casbah-commons_2.10;2.8.0 from central in [default]
  org.mongodb#casbah-core_2.10;2.8.0 from central in [default]
  org.mongodb#casbah-query_2.10;2.8.0 from central in [default]
  org.mongodb#mongo-java-driver;2.13.0 from central in [default]
  org.slf4j#slf4j-api;1.6.0 from central in [default]
  ---------------------------------------------------------------------
  |                  |            modules            ||   artifacts   |
  |       conf       | number| search|dwnlded|evicted|| number|dwnlded|
  ---------------------------------------------------------------------
  |      default     |   9   |   9   |   9   |   0   ||   9   |   9   |
  ---------------------------------------------------------------------
:: retrieving :: org.apache.spark#spark-submit-parent
  confs: [default]
  9 artifacts copied, 0 already retrieved (2335kB/51ms)
... (snip) ... 

我们现在准备从数据库twtr01_db的集合twtr01_coll上的localhost:27017查询 MongoDB。

我们首先导入SQLContext

In [5]:
from pyspark.sql import SQLContext
sqlContext.sql("CREATE TEMPORARY TABLE tweet_table USING com.stratio.datasource.mongodb OPTIONS (host 'localhost:27017', database 'twtr01_db', collection 'twtr01_coll')")
sqlContext.sql("SELECT * FROM tweet_table where id=598830778269769728 ").collect()

这是我们查询的输出:

Out[5]:
[Row(text=u'@spark_io is now @particle - awesome news - now I can enjoy my Particle Cores/Photons + @sparkfun sensors + @ApacheSpark analytics :-)', _id=u'55aa640fd770871cba74cb88', contributors=None, retweeted=False, user=Row(contributors_enabled=False, created_at=u'Mon Aug 25 14:01:26 +0000 2008', default_profile=True, default_profile_image=False, description=u'Building open source tools for and teaching enterprise software developers', entities=Row(description=Row(urls=[]), url=Row(urls=[Row(url=u'http://t.co/TSHp13EWeu', indices=[0, 22], 

... (snip) ...

 9], name=u'Spark is Particle', screen_name=u'spark_io'), Row(id=487010011, id_str=u'487010011', indices=[17, 26], name=u'Particle', screen_name=u'particle'), Row(id=17877351, id_str=u'17877351', indices=[88, 97], name=u'SparkFun Electronics', screen_name=u'sparkfun'), Row(id=1551361069, id_str=u'1551361069', indices=[108, 120], name=u'Apache Spark', screen_name=u'ApacheSpark')]), is_quote_status=None, lang=u'en', quoted_status_id_str=None, quoted_status_id=None, created_at=u'Thu May 14 12:42:37 +0000 2015', retweeted_status=None, truncated=False, place=None, id=598830778269769728, in_reply_to_user_id=3187046084, retweet_count=0, in_reply_to_status_id=None, in_reply_to_screen_name=u'spark_io', in_reply_to_user_id_str=u'3187046084', source=u'<a href="http://twitter.com" rel="nofollow">Twitter Web Client</a>', id_str=u'598830778269769728', coordinates=None, metadata=Row(iso_language_code=u'en', result_type=u'recent'), quoted_status=None)]
#

摘要

在本章中,我们从 Twitter 上收集了数据。一旦获取了数据,我们就使用Continuum.io的 Blaze 和 Odo 库来探索信息。Spark SQL 是交互式数据探索、分析和转换的重要模块,利用了 Spark dataframe 数据结构。 dataframe 的概念起源于 R,然后被 Python Pandas 成功采用。 dataframe 是数据科学家的得力助手。 Spark SQL 和 dataframe 的结合为数据处理创建了强大的引擎。

我们现在准备利用 Spark MLlib 从数据集中提取洞察。

第四章:使用 Spark 从数据中学习

在上一章中,我们已经为数据的收集奠定了基础,现在我们准备从数据中学习。机器学习是关于从数据中获取见解。我们的目标是概述 Spark MLlib(简称机器学习库)并将适当的算法应用于我们的数据集,以便得出见解。从 Twitter 数据集中,我们将应用无监督的聚类算法,以区分 Apache Spark 相关的推文和其他推文。我们首先需要预处理数据,以提取相关特征,然后将机器学习算法应用于我们的数据集,最后评估模型的结果和性能。

在本章中,我们将涵盖以下内容:

  • 提供 Spark MLlib 模块及其算法以及典型的机器学习工作流程的概述。

  • 预处理 Twitter 收集的数据集,以提取相关特征,应用无监督的聚类算法来识别 Apache Spark 相关的推文。然后,评估模型和获得的结果。

  • 描述 Spark 机器学习管道。

在应用架构中定位 Spark MLlib

让我们首先将本章的重点放在数据密集型应用架构上。我们将集中精力放在分析层,更确切地说是机器学习上。这将为流应用提供基础,因为我们希望将从数据的批处理中学到的知识应用于流分析的推理规则。

以下图表设置了本章重点的上下文,突出了分析层内的机器学习模块,同时使用了探索性数据分析、Spark SQL 和 Pandas 工具。

在应用架构中定位 Spark MLlib

对 Spark MLlib 算法进行分类

Spark MLlib 是 Spark 的一个快速发展模块,每次 Spark 发布都会添加新的算法。

以下图表提供了 Spark MLlib 算法的高级概述,分为传统的广义机器学习技术和数据的分类或连续性特性:

对 Spark MLlib 算法进行分类

我们将 Spark MLlib 算法分为两列,根据数据类型分为分类或连续。我们区分分类或更具有定性特征的数据与连续数据,后者是定量的。定性数据的一个例子是预测天气;给定大气压、温度和云的存在和类型,天气将是晴天、干燥、多雨或阴天。这些是离散值。另一方面,假设我们想要预测房价,给定位置、平方米和床的数量;可以使用线性回归来预测房地产价值。在这种情况下,我们谈论的是连续或定量值。

水平分组反映了所使用的机器学习方法的类型。无监督与监督机器学习技术取决于训练数据是否带有标签。在无监督学习挑战中,学习算法没有标签。目标是找到输入中的隐藏结构。在监督学习的情况下,数据是有标签的。重点是使用回归进行预测,如果数据是连续的,或者使用分类,如果数据是分类的。

机器学习的一个重要类别是推荐系统,它利用协同过滤技术。亚马逊网店和 Netflix 拥有非常强大的推荐系统来支持他们的推荐。

随机梯度下降是一种适合 Spark 分布式计算的机器学习优化技术之一。

对于处理大量文本,Spark 提供了关键的特征提取和转换库,如TF-IDF词项频率-逆文档频率),Word2Vec,标准缩放器和归一化器。

监督和无监督学习

我们在这里更深入地探讨了 Spark MLlib 提供的传统机器学习算法。我们根据数据是否有标签来区分监督学习和无监督学习。我们根据数据是离散的还是连续的来区分分类或连续。

以下图表解释了 Spark MLlib 监督和无监督机器学习算法以及预处理技术:

监督和无监督学习

以下监督和无监督的 MLlib 算法和预处理技术目前在 Spark 中可用:

  • 聚类:这是一种无监督的机器学习技术,其中数据没有标记。目的是从数据中提取结构:

  • K 均值:这将数据分区为 K 个不同的簇

  • 高斯混合:根据组件的最大后验概率分配簇

  • 幂迭代聚类(PIC):这基于图的顶点之间的成对边相似性进行分组

  • 潜在狄利克雷分配LDA):这用于将文本文档集合分组成主题

  • 流式 K 均值:这意味着使用传入数据的窗口函数动态地对流式数据进行聚类

  • 降维:这旨在减少考虑的特征数量。基本上,这减少了数据中的噪音,并专注于关键特征:

  • 奇异值分解SVD):这将包含数据的矩阵分解为更简单的有意义的部分。它将初始矩阵分解为三个矩阵。

  • 主成分分析PCA):这将高维数据集近似为低维子空间。

  • 回归和分类:回归使用标记的训练数据预测输出值,而分类将结果分组成类别。分类具有分类或无序的因变量,而回归具有连续和有序的因变量:

  • 线性回归模型(线性回归,逻辑回归和支持向量机):线性回归算法可以表示为旨在最小化基于权重变量向量的目标函数的凸优化问题。目标函数通过函数的正则化部分和损失函数控制模型的复杂性和模型的误差。

  • 朴素贝叶斯:这基于给定观察的标签的条件概率分布进行预测。它假设特征之间是相互独立的。

  • 决策树:这执行特征空间的递归二元分区。在树节点级别上最大化信息增益,以确定分区的最佳拆分。

  • 树的集成(随机森林和梯度提升树):树集成算法将基本决策树模型组合在一起,以构建一个高性能的模型。它们对于分类和回归任务非常直观和成功。

  • 保序回归:这最小化给定数据和观察响应之间的均方误差。

附加学习算法

Spark MLlib 提供的算法比监督和无监督学习算法更多。我们还有三种额外类型的机器学习方法:推荐系统,优化算法和特征提取。

附加学习算法

以下附加的 MLlib 算法目前在 Spark 中可用:

  • 协同过滤:这是推荐系统的基础。它创建一个用户-项目关联矩阵,并旨在填补空白。基于其他用户和项目以及它们的评分,它推荐目标用户尚未评分的项目。在分布式计算中,最成功的算法之一是ALS交替最小二乘法的缩写):

  • 交替最小二乘法:这种矩阵分解技术结合了隐式反馈、时间效应和置信水平。它将大型用户项目矩阵分解为较低维度的用户和项目因子。它通过交替固定其因子来最小化二次损失函数。

  • 特征提取和转换:这些是大型文本文档处理的基本技术。它包括以下技术:

  • 词频:搜索引擎使用 TF-IDF 对大量语料库中的文档相关性进行评分和排名。它还用于机器学习,以确定文档或语料库中单词的重要性。词频统计上确定了术语相对于语料库中的频率的权重。单独的词频可能会产生误导,因为它过分强调了诸如theofand这样提供很少信息的词语。逆文档频率提供了特定性或术语在语料库中所有文档中是罕见还是常见的度量。

  • Word2Vec:这包括两种模型,Skip-Gram连续词袋。Skip-Gram 根据单词的滑动窗口预测给定单词的相邻单词,而连续词袋根据相邻单词预测当前单词。

  • 标准缩放器:作为预处理的一部分,数据集通常必须通过均值去除和方差缩放进行标准化。我们计算训练数据的均值和标准差,并将相同的转换应用于测试数据。

  • 标准化器:我们将样本缩放为单位范数。它对于二次形式(如点积或核方法)非常有用。

  • 特征选择:通过选择模型中最相关的特征来减少向量空间的维度。

  • 卡方选择器:这是一种衡量两个事件独立性的统计方法。

  • 优化:这些特定的 Spark MLlib 优化算法专注于梯度下降的各种技术。Spark 提供了非常高效的梯度下降实现,可以在分布式机器集群上进行。它通过迭代沿着最陡的下降方向寻找局部最小值。由于需要迭代处理所有可用数据,因此计算密集型:

  • 随机梯度下降:我们最小化一个可微函数的总和。随机梯度下降仅使用训练数据的样本来更新特定迭代中的参数。它用于大规模和稀疏的机器学习问题,如文本分类。

  • 有限内存 BFGSL-BFGS):顾名思义,L-BFGS 使用有限内存,适用于 Spark MLlib 的分布式优化算法实现。

Spark MLlib 数据类型

MLlib 支持四种基本数据类型:本地向量标记点本地矩阵分布式矩阵。这些数据类型在 Spark MLlib 算法中被广泛使用:

  • 本地向量:这存在于单个机器中。它可以是密集的或稀疏的:

  • 密集向量是传统的双精度数组。密集向量的一个示例是[5.0, 0.0, 1.0, 7.0]

  • 稀疏向量使用整数索引和双精度值。因此,向量[5.0, 0.0, 1.0, 7.0]的稀疏表示将是(4, [0, 2, 3], [5.0, 1.0, 7.0]),其中表示向量的维度。

以下是 PySpark 中本地向量的示例:

import numpy as np
import scipy.sparse as sps
from pyspark.mllib.linalg import Vectors

# NumPy array for dense vector.
dvect1 = np.array([5.0, 0.0, 1.0, 7.0])
# Python list for dense vector.
dvect2 = [5.0, 0.0, 1.0, 7.0]
# SparseVector creation
svect1 = Vectors.sparse(4, [0, 2, 3], [5.0, 1.0, 7.0])
# Sparse vector using a single-column SciPy csc_matrix
svect2 = sps.csc_matrix((np.array([5.0, 1.0, 7.0]), np.array([0, 2, 3])), shape = (4, 1))
  • 标记点。标记点是在监督学习中使用的带有标签的稠密或稀疏向量。在二元标签的情况下,0.0 表示负标签,而 1.0 表示正值。

这是 PySpark 中标记点的一个示例:

from pyspark.mllib.linalg import SparseVector
from pyspark.mllib.regression import LabeledPoint

# Labeled point with a positive label and a dense feature vector.
lp_pos = LabeledPoint(1.0, [5.0, 0.0, 1.0, 7.0])

# Labeled point with a negative label and a sparse feature vector.
lp_neg = LabeledPoint(0.0, SparseVector(4, [0, 2, 3], [5.0, 1.0, 7.0]))
  • 本地矩阵:这个本地矩阵位于单个机器上,具有整数类型的索引和双精度类型的值。

这是 PySpark 中本地矩阵的一个示例:

from pyspark.mllib.linalg import Matrix, Matrices

# Dense matrix ((1.0, 2.0, 3.0), (4.0, 5.0, 6.0))
dMatrix = Matrices.dense(2, 3, [1, 2, 3, 4, 5, 6])

# Sparse matrix ((9.0, 0.0), (0.0, 8.0), (0.0, 6.0))
sMatrix = Matrices.sparse(3, 2, [0, 1, 3], [0, 2, 1], [9, 6, 8])
  • 分布式矩阵:利用 RDD 的分布式特性,分布式矩阵可以在一组机器的集群中共享。我们区分四种分布式矩阵类型:RowMatrixIndexedRowMatrixCoordinateMatrixBlockMatrix

  • RowMatrix:这需要一个向量的 RDD,并从向量的 RDD 创建一个带有无意义索引的行的分布式矩阵,称为RowMatrix

  • IndexedRowMatrix:在这种情况下,行索引是有意义的。首先,我们使用IndexedRow类创建索引行的 RDD,然后创建IndexedRowMatrix

  • CoordinateMatrix:这对于表示非常大和非常稀疏的矩阵很有用。CoordinateMatrix是从MatrixEntry点的 RDD 创建的,由(long, long, float)类型的元组表示。

  • BlockMatrix:这些是从子矩阵块的 RDD 创建的,其中子矩阵块是((blockRowIndex, blockColIndex), sub-matrix)

机器学习工作流程和数据流程

除了算法,机器学习还涉及到流程。我们将讨论监督和无监督机器学习的典型工作流程和数据流程。

监督机器学习工作流程

在监督机器学习中,输入训练数据集是有标签的。一个关键的数据实践是将输入数据分为训练集和测试集,并相应地验证模型。

在监督学习中,我们通常会经历一个六步的流程:

  • 收集数据:这一步基本上与前一章相关,并确保我们收集正确数量和粒度的数据,以使机器学习算法能够提供可靠的答案。

  • 预处理数据:这一步是关于通过抽样检查数据质量,填补缺失值(如果有的话),对数据进行缩放和归一化。我们还定义特征提取过程。通常,在大型基于文本的数据集的情况下,我们应用标记化、停用词去除、词干提取和 TF-IDF。

在监督学习中,我们将输入数据分为训练集和测试集。我们还可以实施各种采样和数据集拆分策略,以进行交叉验证。

  • 准备数据:在这一步中,我们将数据格式化或转换为算法所期望的格式或数据类型。在 Spark MLlib 中,这包括本地向量、稠密或稀疏向量、标记点、本地矩阵、带有行矩阵、索引行矩阵、坐标矩阵和块矩阵的分布式矩阵。

  • 模型:在这一步中,我们应用适合问题的算法,并获得评估步骤中最适合算法的结果。我们可能有多个适合问题的算法;它们在评估步骤中的性能将被评分以选择最佳的性能。我们可以实施模型的集成或组合,以达到最佳结果。

  • 优化:我们可能需要对某些算法的最佳参数进行网格搜索。这些参数在训练期间确定,并在测试和生产阶段进行微调。

  • 评估:最终我们对模型进行评分,并选择在准确性、性能、可靠性和可扩展性方面最好的模型。我们将最佳性能的模型移至测试集,以确定模型的预测准确性。一旦对经过微调的模型满意,我们将其移至生产环境以处理实时数据。

监督机器学习的工作流程和数据流程如下图所示:

监督机器学习工作流程

无监督机器学习工作流

与监督学习相反,在无监督学习的情况下,我们的初始数据没有标签,这在现实生活中是最常见的情况。我们将使用聚类或降维算法从数据中提取结构。在无监督学习的情况下,我们不会将数据分为训练和测试,因为我们无法进行任何预测,因为数据没有标签。我们将对数据进行六个步骤的训练,类似于监督学习。一旦模型训练完成,我们将评估结果并微调模型,然后将其投入生产。

无监督学习可以作为监督学习的初步步骤。换句话说,我们在进入学习阶段之前看一下如何降低数据的维度。

无监督机器学习工作流和数据流如下所示:

无监督机器学习工作流程

对 Twitter 数据集进行聚类

让我们先从 Twitter 中提取的数据中了解一下,并了解数据结构,以便准备并通过 K-Means 聚类算法运行。我们的攻击计划使用了前面描述的无监督学习的过程和数据流。步骤如下:

  1. 将所有推文文件合并为单个数据框。

  2. 解析推文,删除停用词,提取表情符号,提取 URL,最后规范化单词(例如,将它们映射为小写并删除标点和数字)。

  3. 特征提取包括以下内容:

  • 标记化:这将解析推文文本为单个单词或标记

  • TF-IDF:这将应用 TF-IDF 算法从标记化的推文文本中创建特征向量

  • 哈希 TF-IDF:这将对标记向量应用哈希函数

  1. 运行 K-Means 聚类算法。

  2. 评估 K-Means 聚类的结果:

  • 识别推文归属于聚类

  • 使用多维缩放或主成分分析算法将维度降低到两个维度

  • 绘制聚类

  1. 管道:
  • 微调相关聚类 K 的数量

  • 测量模型成本

  • 选择最佳模型

在 Twitter 数据集上应用 Scikit-Learn

Python 自带的 Scikit-Learn 机器学习库是最可靠、直观和强大的工具之一。在使用 Pandas 和 Scikit-Learn 进行预处理和无监督学习之前,通常有利于使用 Scikit-Learn 探索数据的样本。在使用 Spark MLlib 分离聚类之前,我们经常使用 Scikit-Learn 探索数据的样本。

我们有一袋杂货的 7540 条推文。它包含与 Apache Spark、Python、即将到来的总统选举以及 Lady Gaga 和 Justin Bieber 相关的时尚和音乐推文。我们正在使用 Python Scikit-Learn 对 Twitter 数据集进行 K-Means 聚类算法。我们首先将样本数据加载到 Pandas 数据框中:

import pandas as pd

csv_in = 'C:\\Users\\Amit\\Documents\\IPython Notebooks\\AN00_Data\\unq_tweetstxt.csv'
twts_df01 = pd.read_csv(csv_in, sep =';', encoding='utf-8')

In [24]:

twts_df01.count()
Out[24]:
Unnamed: 0    7540
id            7540
created_at    7540
user_id       7540
user_name     7538
tweet_text    7540
dtype: int64

#
# Introspecting the tweets text
#
In [82]:

twtstxt_ls01[6910:6920]
Out[82]:
['RT @deroach_Ismoke: I am NOT voting for #hilaryclinton http://t.co/jaZZpcHkkJ',
 'RT @AnimalRightsJen: #HilaryClinton What do Bernie Sanders and Donald Trump Have in Common?: He has so far been th... http://t.co/t2YRcGCh6…',
 'I understand why Bill was out banging other chicks........I mean look at what he is married to.....\n@HilaryClinton',
 '#HilaryClinton What do Bernie Sanders and Donald Trump Have in Common?: He has so far been th... http://t.co/t2YRcGCh67 #Tcot #UniteBlue']

我们首先从推文文本中进行特征提取。我们使用具有 10,000 个特征和英语停用词的 TF-IDF 向量化器对数据集应用稀疏矢量化器:

In [37]:

print("Extracting features from the training dataset using a sparse vectorizer")
t0 = time()
Extracting features from the training dataset using a sparse vectorizer
In [38]:

vectorizer = TfidfVectorizer(max_df=0.5, max_features=10000,
                                 min_df=2, stop_words='english',
                                 use_idf=True)
X = vectorizer.fit_transform(twtstxt_ls01)
#
# Output of the TFIDF Feature vectorizer
#
print("done in %fs" % (time() - t0))
print("n_samples: %d, n_features: %d" % X.shape)
print()
done in 5.232165s
n_samples: 7540, n_features: 6638

由于数据集现在被分成了 7540 个样本,每个样本有 6638 个特征向量,我们准备将这个稀疏矩阵输入 K-Means 聚类算法。我们最初选择七个聚类和 100 次最大迭代:

In [47]:

km = KMeans(n_clusters=7, init='k-means++', max_iter=100, n_init=1,
            verbose=1)

print("Clustering sparse data with %s" % km)
t0 = time()
km.fit(X)
print("done in %0.3fs" % (time() - t0))

Clustering sparse data with KMeans(copy_x=True, init='k-means++', max_iter=100, n_clusters=7, n_init=1,
    n_jobs=1, precompute_distances='auto', random_state=None, tol=0.0001,
    verbose=1)
Initialization complete
Iteration  0, inertia 13635.141
Iteration  1, inertia 6943.485
Iteration  2, inertia 6924.093
Iteration  3, inertia 6915.004
Iteration  4, inertia 6909.212
Iteration  5, inertia 6903.848
Iteration  6, inertia 6888.606
Iteration  7, inertia 6863.226
Iteration  8, inertia 6860.026
Iteration  9, inertia 6859.338
Iteration 10, inertia 6859.213
Iteration 11, inertia 6859.102
Iteration 12, inertia 6859.080
Iteration 13, inertia 6859.060
Iteration 14, inertia 6859.047
Iteration 15, inertia 6859.039
Iteration 16, inertia 6859.032
Iteration 17, inertia 6859.031
Iteration 18, inertia 6859.029
Converged at iteration 18
done in 1.701s

K-Means 聚类算法在 18 次迭代后收敛。在以下结果中,我们看到了七个聚类及其各自的关键词。聚类06是关于贾斯汀·比伯和 Lady Gaga 相关推文的音乐和时尚。聚类15与美国总统选举有关,包括唐纳德·特朗普和希拉里·克林顿相关的推文。聚类23是我们感兴趣的,因为它们涉及 Apache Spark 和 Python。聚类4包含泰国相关的推文:

#
# Introspect top terms per cluster
#

In [49]:

print("Top terms per cluster:")
order_centroids = km.cluster_centers_.argsort()[:, ::-1]
terms = vectorizer.get_feature_names()
for i in range(7):
    print("Cluster %d:" % i, end='')
    for ind in order_centroids[i, :20]:
        print(' %s' % terms[ind], end='')
    print()
Top terms per cluster:
Cluster 0: justinbieber love mean rt follow thank hi https whatdoyoumean video wanna hear whatdoyoumeanviral rorykramer happy lol making person dream justin
Cluster 1: donaldtrump hilaryclinton rt https trump2016 realdonaldtrump trump gop amp justinbieber president clinton emails oy8ltkstze tcot like berniesanders hilary people email
Cluster 2: bigdata apachespark hadoop analytics rt spark training chennai ibm datascience apache processing cloudera mapreduce data sap https vora transforming development
Cluster 3: apachespark python https rt spark data amp databricks using new learn hadoop ibm big apache continuumio bluemix learning join open
Cluster 4: ernestsgantt simbata3 jdhm2015 elsahel12 phuketdailynews dreamintentions beyhiveinfrance almtorta18 civipartnership 9_a_6 25whu72ep0 k7erhvu7wn fdmxxxcm3h osxuh2fxnt 5o5rmb0xhp jnbgkqn0dj ovap57ujdh dtzsz3lb6x sunnysai12345 sdcvulih6g
Cluster 5: trump donald donaldtrump starbucks trumpquote trumpforpresident oy8ltkstze https zfns7pxysx silly goy stump trump2016 news jeremy coffee corbyn ok7vc8aetz rt tonight
Cluster 6: ladygaga gaga lady rt https love follow horror cd story ahshotel american japan hotel human trafficking music fashion diet queen ahs

我们将通过绘制聚类来可视化结果。我们有 7,540 个样本,6,638 个特征。不可能可视化那么多维度。我们将使用多维缩放MDS)算法将聚类的多维特征降低到两个可处理的维度,以便能够将它们呈现出来:

import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.manifold import MDS

MDS()

#
# Bring down the MDS to two dimensions (components) as we will plot 
# the clusters
#
mds = MDS(n_components=2, dissimilarity="precomputed", random_state=1)

pos = mds.fit_transform(dist)  # shape (n_components, n_samples)

xs, ys = pos[:, 0], pos[:, 1]

In [67]:

#
# Set up colors per clusters using a dict
#
cluster_colors = {0: '#1b9e77', 1: '#d95f02', 2: '#7570b3', 3: '#e7298a', 4: '#66a61e', 5: '#9990b3', 6: '#e8888a'}

#
#set up cluster names using a dict
#
cluster_names = {0: 'Music, Pop', 
                 1: 'USA Politics, Election', 
                 2: 'BigData, Spark', 
                 3: 'Spark, Python',
                 4: 'Thailand', 
                 5: 'USA Politics, Election', 
                 6: 'Music, Pop'}
In [115]:
#
# ipython magic to show the matplotlib plots inline
#
%matplotlib inline 

#
# Create data frame which includes MDS results, cluster numbers and tweet texts to be displayed
#
df = pd.DataFrame(dict(x=xs, y=ys, label=clusters, txt=twtstxt_ls02_utf8))
ix_start = 2000
ix_stop  = 2050
df01 = df[ix_start:ix_stop]

print(df01[['label','txt']])
print(len(df01))
print()

# Group by cluster

groups = df.groupby('label')
groups01 = df01.groupby('label')

# Set up the plot

fig, ax = plt.subplots(figsize=(17, 10)) 
ax.margins(0.05) 

#
# Build the plot object
#
for name, group in groups01:
    ax.plot(group.x, group.y, marker='o', linestyle='', ms=12, 
            label=cluster_names[name], color=cluster_colors[name], 
            mec='none')
    ax.set_aspect('auto')
    ax.tick_params(\
        axis= 'x',         # settings for x-axis
        which='both',      # 
        bottom='off',      # 
        top='off',         # 
        labelbottom='off')
    ax.tick_params(\
        axis= 'y',         # settings for y-axis
        which='both',      # 
        left='off',        # 
        top='off',         # 
        labelleft='off')

ax.legend(numpoints=1)     #
#
# Add label in x,y position with tweet text
#
for i in range(ix_start, ix_stop):
    ax.text(df01.ix[i]['x'], df01.ix[i]['y'], df01.ix[i]['txt'], size=10)  

plt.show()                 # Display the plot

      label       text
2000      2       b'RT @BigDataTechCon: '
2001      3       b"@4Quant 's presentat"
2002      2       b'Cassandra Summit 201'

这是聚类2的绘图,大数据Spark用蓝色点表示,聚类3SparkPython用红色点表示,以及一些与各自聚类相关的示例推文:

在 Twitter 数据集上应用 Scikit-Learn

通过对 Scikit-Learn 进行探索和处理,我们对数据获得了一些有益的见解。现在我们将把注意力集中在 Spark MLlib 上,并在 Twitter 数据集上进行尝试。

预处理数据集

现在,我们将专注于特征提取和工程,以准备数据进行聚类算法运行。我们实例化 Spark 上下文,并将 Twitter 数据集读入 Spark 数据框。然后我们将逐步对推文文本数据进行标记化,对标记应用哈希词频算法,最后应用逆文档频率算法并重新调整数据。代码如下:

In [3]:
#
# Read csv in a Panda DF
#
#
import pandas as pd
csv_in = '/home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/data/unq_tweetstxt.csv'
pddf_in = pd.read_csv(csv_in, index_col=None, header=0, sep=';', encoding='utf-8')

In [4]:

sqlContext = SQLContext(sc)

In [5]:

#
# Convert a Panda DF to a Spark DF
#
#

spdf_02 = sqlContext.createDataFrame(pddf_in[['id', 'user_id', 'user_name', 'tweet_text']])

In [8]:

spdf_02.show()

In [7]:

spdf_02.take(3)

Out[7]:

[Row(id=638830426971181057, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: 9_A_6: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: dreamintentions:\u2026 http://t.co/VpD7FoqMr0'),
 Row(id=638830426727911424, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: PhuketDailyNews: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: CiviPa\u2026 http://t.co/VpD7FoqMr0'),
 Row(id=638830425402556417, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: 9_A_6: ernestsgantt: elsahel12: simbata3: JDHM2015: almtorta18: CiviPartnership: dr\u2026 http://t.co/EMDOn8chPK')]

In [9]:

from pyspark.ml.feature import HashingTF, IDF, Tokenizer

In [10]:

#
# Tokenize the tweet_text 
#
tokenizer = Tokenizer(inputCol="tweet_text", outputCol="tokens")
tokensData = tokenizer.transform(spdf_02)

In [11]:

tokensData.take(1)

Out[11]:

[Row(id=638830426971181057, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: 9_A_6: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: dreamintentions:\u2026 http://t.co/VpD7FoqMr0', tokens=[u'ernestsgantt:', u'beyhiveinfrance:', u'9_a_6:', u'dreamintentions:', u'elsahel12:', u'simbata3:', u'jdhm2015:', u'almtorta18:', u'dreamintentions:\u2026', u'http://t.co/vpd7foqmr0'])]

In [14]:

#
# Apply Hashing TF to the tokens
#
hashingTF = HashingTF(inputCol="tokens", outputCol="rawFeatures", numFeatures=2000)
featuresData = hashingTF.transform(tokensData)

In [15]:

featuresData.take(1)

Out[15]:

[Row(id=638830426971181057, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: 9_A_6: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: dreamintentions:\u2026 http://t.co/VpD7FoqMr0', tokens=[u'ernestsgantt:', u'beyhiveinfrance:', u'9_a_6:', u'dreamintentions:', u'elsahel12:', u'simbata3:', u'jdhm2015:', u'almtorta18:', u'dreamintentions:\u2026', u'http://t.co/vpd7foqmr0'], rawFeatures=SparseVector(2000, {74: 1.0, 97: 1.0, 100: 1.0, 160: 1.0, 185: 1.0, 742: 1.0, 856: 1.0, 991: 1.0, 1383: 1.0, 1620: 1.0}))]

In [16]:

#
# Apply IDF to the raw features and rescale the data
#
idf = IDF(inputCol="rawFeatures", outputCol="features")
idfModel = idf.fit(featuresData)
rescaledData = idfModel.transform(featuresData)

for features in rescaledData.select("features").take(3):
  print(features)

In [17]:

rescaledData.take(2)

Out[17]:

[Row(id=638830426971181057, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: 9_A_6: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: dreamintentions:\u2026 http://t.co/VpD7FoqMr0', tokens=[u'ernestsgantt:', u'beyhiveinfrance:', u'9_a_6:', u'dreamintentions:', u'elsahel12:', u'simbata3:', u'jdhm2015:', u'almtorta18:', u'dreamintentions:\u2026', u'http://t.co/vpd7foqmr0'], rawFeatures=SparseVector(2000, {74: 1.0, 97: 1.0, 100: 1.0, 160: 1.0, 185: 1.0, 742: 1.0, 856: 1.0, 991: 1.0, 1383: 1.0, 1620: 1.0}), features=SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 742: 5.5269, 856: 4.1406, 991: 2.9518, 1383: 4.694, 1620: 3.073})),
 Row(id=638830426727911424, user_id=3276255125, user_name=u'True Equality', tweet_text=u'ernestsgantt: BeyHiveInFrance: PhuketDailyNews: dreamintentions: elsahel12: simbata3: JDHM2015: almtorta18: CiviPa\u2026 http://t.co/VpD7FoqMr0', tokens=[u'ernestsgantt:', u'beyhiveinfrance:', u'phuketdailynews:', u'dreamintentions:', u'elsahel12:', u'simbata3:', u'jdhm2015:', u'almtorta18:', u'civipa\u2026', u'http://t.co/vpd7foqmr0'], rawFeatures=SparseVector(2000, {74: 1.0, 97: 1.0, 100: 1.0, 160: 1.0, 185: 1.0, 460: 1.0, 987: 1.0, 991: 1.0, 1383: 1.0, 1620: 1.0}), features=SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 460: 6.4432, 987: 2.9959, 991: 2.9518, 1383: 4.694, 1620: 3.073}))]

In [21]:

rs_pddf = rescaledData.toPandas()

In [22]:

rs_pddf.count()

Out[22]:

id             7540
user_id        7540
user_name      7540
tweet_text     7540
tokens         7540
rawFeatures    7540
features       7540
dtype: int64

In [27]:

feat_lst = rs_pddf.features.tolist()

In [28]:

feat_lst[:2]

Out[28]:

[SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 742: 5.5269, 856: 4.1406, 991: 2.9518, 1383: 4.694, 1620: 3.073}),
 SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 460: 6.4432, 987: 2.9959, 991: 2.9518, 1383: 4.694, 1620: 3.073})]

运行聚类算法

我们将使用 K-Means 算法对 Twitter 数据集进行处理。作为一个未标记和洗牌的推文包,我们想看看Apache Spark的推文是否被分组到一个单独的聚类中。从之前的步骤中,TF-IDF 稀疏特征向量被转换为将成为 Spark MLlib 程序输入的 RDD。我们用 5 个聚类、10 次迭代和 10 次运行来初始化 K-Means 模型:

In [32]:

from pyspark.mllib.clustering import KMeans, KMeansModel
from numpy import array
from math import sqrt

In [34]:

# Load and parse the data

in_Data = sc.parallelize(feat_lst)

In [35]:

in_Data.take(3)

Out[35]:

[SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 742: 5.5269, 856: 4.1406, 991: 2.9518, 1383: 4.694, 1620: 3.073}),
 SparseVector(2000, {74: 2.6762, 97: 1.8625, 100: 2.6384, 160: 2.9985, 185: 2.7481, 460: 6.4432, 987: 2.9959, 991: 2.9518, 1383: 4.694, 1620: 3.073}),
 SparseVector(2000, {20: 4.3534, 74: 2.6762, 97: 1.8625, 100: 5.2768, 185: 2.7481, 856: 4.1406, 991: 2.9518, 1039: 3.073, 1620: 3.073, 1864: 4.6377})]

In [37]:

in_Data.count()

Out[37]:

7540

In [38]:

# Build the model (cluster the data)

clusters = KMeans.train(in_Data, 5, maxIterations=10,
        runs=10, initializationMode="random")

In [53]:

# Evaluate clustering by computing Within Set Sum of Squared Errors

def error(point):
    center = clusters.centers[clusters.predict(point)]
    return sqrt(sum([x**2 for x in (point - center)]))

WSSSE = in_Data.map(lambda point: error(point)).reduce(lambda x, y: x + y)
print("Within Set Sum of Squared Error = " + str(WSSSE))

评估模型和结果

微调聚类算法的一种方法是改变聚类的数量并验证输出。让我们检查一下聚类,并对迄今为止的聚类结果有所了解:

In [43]:

cluster_membership = in_Data.map(lambda x: clusters.predict(x))

In [54]:

cluster_idx = cluster_membership.zipWithIndex()

In [55]:

type(cluster_idx)

Out[55]:

pyspark.rdd.PipelinedRDD

In [58]:

cluster_idx.take(20)

Out[58]:

[(3, 0),
 (3, 1),
 (3, 2),
 (3, 3),
 (3, 4),
 (3, 5),
 (1, 6),
 (3, 7),
 (3, 8),
 (3, 9),
 (3, 10),
 (3, 11),
 (3, 12),
 (3, 13),
 (3, 14),
 (1, 15),
 (3, 16),
 (3, 17),
 (1, 18),
 (1, 19)]

In [59]:

cluster_df = cluster_idx.toDF()

In [65]:

pddf_with_cluster = pd.concat([pddf_in, cluster_pddf],axis=1)

In [76]:

pddf_with_cluster._1.unique()

Out[76]:

array([3, 1, 4, 0, 2])

In [79]:

pddf_with_cluster[pddf_with_cluster['_1'] == 0].head(10)

Out[79]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text   _1   _2
6227   3   642418116819988480   Fri Sep 11 19:23:09 +0000 2015   49693598   Ajinkya Kale   RT @bigdata: Distributed Matrix Computations i...   0   6227
6257   45   642391207205859328   Fri Sep 11 17:36:13 +0000 2015   937467860   Angela Bassa   [Auto] I'm reading ""Distributed Matrix Comput...   0   6257
6297   119   642348577147064320   Fri Sep 11 14:46:49 +0000 2015   18318677   Ben Lorica   Distributed Matrix Computations in @ApacheSpar...   0   6297
In [80]:

pddf_with_cluster[pddf_with_cluster['_1'] == 1].head(10)

Out[80]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text   _1   _2
6   6   638830419090079746   Tue Sep 01 21:46:55 +0000 2015   2241040634   Massimo Carrisi   Python:Python: Removing \xa0 from string? - I ...   1   6
15   17   638830380578045953   Tue Sep 01 21:46:46 +0000 2015   57699376   Rafael Monnerat   RT @ramalhoorg: Noite de autógrafos do Fluent ...   1   15
18   41   638830280988426250   Tue Sep 01 21:46:22 +0000 2015   951081582   Jack Baldwin   RT @cloudaus: We are 3/4 full! 2-day @swcarpen...   1   18
19   42   638830276626399232   Tue Sep 01 21:46:21 +0000 2015   6525302   Masayoshi Nakamura   PynamoDB #AWS #DynamoDB #Python http://...   1   19
20   43   638830213288235008   Tue Sep 01 21:46:06 +0000 2015   3153874869   Baltimore Python   Flexx: Python UI tookit based on web technolog...   1   20
21   44   638830117645516800   Tue Sep 01 21:45:43 +0000 2015   48474625   Radio Free Denali   Hmm, emerge --depclean wants to remove somethi...   1   21
22   46   638829977014636544   Tue Sep 01 21:45:10 +0000 2015   154915461   Luciano Ramalho   Noite de autógrafos do Fluent Python no Garoa ...   1   22
23   47   638829882928070656   Tue Sep 01 21:44:47 +0000 2015   917320920   bsbafflesbrains   @DanSWright Harper channeling Monty Python. "...   1   23
24   48   638829868679954432   Tue Sep 01 21:44:44 +0000 2015   134280898   Lannick Technology   RT @SergeyKalnish: I am #hiring: Senior Back e...   1   24
25   49   638829707484508161   Tue Sep 01 21:44:05 +0000 2015   2839203454   Joshua Jones   RT @LindseyPelas: Surviving Monty Python in Fl...   1   25
In [81]:

pddf_with_cluster[pddf_with_cluster['_1'] == 2].head(10)

Out[81]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text   _1   _2
7280   688   639056941592014848   Wed Sep 02 12:47:02 +0000 2015   2735137484   Chris   A true gay icon when will @ladygaga @Madonna @...   2   7280
In [82]:

pddf_with_cluster[pddf_with_cluster['_1'] == 3].head(10)

Out[82]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text   _1   _2
0   0   638830426971181057   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: dreamint...   3   0
1   1   638830426727911424   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...   3   1
2   2   638830425402556417   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: ernestsg...   3   2
3   3   638830424563716097   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...   3   3
4   4   638830422256816132   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: elsahel12: 9_A_6: dreamintention...   3   4
5   5   638830420159655936   Tue Sep 01 21:46:55 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...   3   5
7   7   638830418330980352   Tue Sep 01 21:46:55 +0000 2015   3276255125   True Equality   ernestsgantt: elsahel12: 9_A_6: dreamintention...   3   7
8   8   638830397648822272   Tue Sep 01 21:46:50 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...   3   8
9   9   638830395375529984   Tue Sep 01 21:46:49 +0000 2015   3276255125   True Equality   ernestsgantt: elsahel12: 9_A_6: dreamintention...   3   9
10   10   638830392389177344   Tue Sep 01 21:46:49 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...   3   10
In [83]:

pddf_with_cluster[pddf_with_cluster['_1'] == 4].head(10)

Out[83]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text   _1   _2
1361   882   642648214454317056   Sat Sep 12 10:37:28 +0000 2015   27415756   Raymond Enisuoh   LA Chosen For US 2024 Olympic Bid - LA2016 See...   4   1361
1363   885   642647848744583168   Sat Sep 12 10:36:01 +0000 2015   27415756   Raymond Enisuoh   Prison See: https://t.co/x3EKAExeFi … … … … … ...   4   1363
5412   11   640480770369286144   Sun Sep 06 11:04:49 +0000 2015   3242403023   Donald Trump 2016   " igiboooy! @ Starbucks https://t.co/97wdL...   4   5412
5428   27   640477140660518912   Sun Sep 06 10:50:24 +0000 2015   3242403023   Donald Trump 2016   "  @ Starbucks https://t.co/wsEYFIefk7 " - D...   4   5428
5455   61   640469542272110592   Sun Sep 06 10:20:12 +0000 2015   3242403023   Donald Trump 2016   " starbucks @ Starbucks Mam Plaza https://t.co...   4   5455
5456   62   640469541370372096   Sun Sep 06 10:20:12 +0000 2015   3242403023   Donald Trump 2016   " Aaahhh the pumpkin spice latte is back, fall...   4   5456
5457   63   640469539524898817   Sun Sep 06 10:20:12 +0000 2015   3242403023   Donald Trump 2016   " RT kayyleighferry: Oh my goddd Harry Potter ...   4   5457
5458   64   640469537176031232   Sun Sep 06 10:20:11 +0000 2015   3242403023   Donald Trump 2016   " Starbucks https://t.co/3xYYXlwNkf " - Donald...   4   5458
5459   65   640469536119070720   Sun Sep 06 10:20:11 +0000 2015   3242403023   Donald Trump 2016   " A Starbucks is under construction in my neig...   4   5459
5460   66   640469530435813376   Sun Sep 06 10:20:10 +0000 2015   3242403023   Donald Trump 2016   " Babam starbucks'tan fotogtaf atıyor bende du...   4   5460

我们用一些示例推文对5个聚类进行了映射。聚类0是关于 Spark 的。聚类1是关于 Python 的。聚类2是关于 Lady Gaga 的。聚类3是关于泰国普吉岛新闻的。聚类4是关于唐纳德·特朗普的。

构建机器学习管道

我们希望在优化最佳调整参数的同时,组合特征提取、准备活动、训练、测试和预测活动,以获得最佳性能模型。

以下推文完美地捕捉了在 Spark MLlib 中实现的强大机器学习管道的五行代码:

构建机器学习管道

Spark ML 管道受 Python 的 Scikit-Learn 启发,并创建了一个简洁的、声明性的语句,用于对数据进行连续转换,以快速交付可调整的模型。

摘要

在本章中,我们概述了 Spark MLlib 不断扩展的算法库。我们讨论了监督学习和无监督学习、推荐系统、优化和特征提取算法。然后,我们将从 Twitter 中收集的数据放入机器学习过程、算法和评估中,以从数据中获取见解。我们通过 Python Scikit-Learn 和 Spark MLlib 的 K-means 聚类对 Twitter 收集的数据集进行了处理,以将与Apache Spark相关的推文分离出来。我们还评估了模型的性能。

这让我们为下一章做好准备,下一章将涵盖使用 Spark 进行流式分析。让我们马上开始吧。

第五章:使用 Spark 进行实时数据流

在本章中,我们将专注于流入 Spark 并进行处理的实时流数据。到目前为止,我们已经讨论了批处理的机器学习和数据挖掘。现在我们正在处理持续流动的数据,并在飞行中检测事实和模式。我们正在从湖泊转向河流。

我们将首先调查在这样一个动态和不断变化的环境中出现的挑战。在奠定流应用的先决条件的基础上,我们将调查使用实时数据源(如 TCP 套接字到 Twitter firehose)进行各种实现,并建立一个低延迟、高吞吐量和可扩展的数据管道,结合 Spark、Kafka 和 Flume。

在本章中,我们将涵盖以下几点:

  • 分析流应用的架构挑战、约束和要求

  • 使用 Spark Streaming 从 TCP 套接字处理实时数据

  • 直接连接到 Twitter firehose 以准实时解析推文

  • 建立一个可靠、容错、可扩展、高吞吐量、低延迟的集成应用,使用 Spark、Kafka 和 Flume

  • 关于 Lambda 和 Kappa 架构范式的结束语

奠定流架构的基础

按照惯例,让我们首先回到我们最初的数据密集型应用架构蓝图,并突出 Spark Streaming 模块,这将是我们感兴趣的主题。

以下图表通过突出 Spark Streaming 模块及其与整体数据密集型应用框架中的 Spark SQL 和 Spark MLlib 的交互来设定上下文。

奠定流架构的基础

数据来自股市时间序列、企业交易、互动、事件、网站流量、点击流和传感器。所有事件都是时间戳数据且紧急。这适用于欺诈检测和预防、移动交叉销售和升级,或者交通警报。这些数据流需要立即处理以进行监控,例如检测异常、异常值、垃圾邮件、欺诈和入侵;同时也需要提供基本统计数据、见解、趋势和建议。在某些情况下,汇总的聚合信息足以存储以供以后使用。从架构范式的角度来看,我们正在从面向服务的架构转向事件驱动的架构。

有两种模型用于处理数据流:

  • 按照记录的实时到达一个接一个地处理记录。在处理之前,我们不会将传入的记录缓冲在容器中。这是 Twitter 的 Storm、Yahoo 的 S4 和 Google 的 MillWheel 的情况。

  • 微批处理或在小时间间隔上进行批处理计算,如 Spark Streaming 和 Storm Trident 所执行的。在这种情况下,我们根据微批处理设置中规定的时间窗口将传入的记录缓冲在一个容器中。

Spark Streaming 经常与 Storm 进行比较。它们是两种不同的流数据模型。Spark Streaming 基于微批处理。Storm 基于处理记录的实时到达。Storm 还提供了微批处理选项,即其 Storm Trident 选项。

流应用中的驱动因素是延迟。延迟范围从RPC(远程过程调用的缩写)的毫秒级到微批处理解决方案(如 Spark Streaming)的几秒或几分钟。

RPC 允许请求程序之间的同步操作,等待远程服务器过程的结果。线程允许对服务器进行多个 RPC 调用的并发。

实现分布式 RPC 模型的软件示例是 Apache Storm。

Storm 使用拓扑结构或有向无环图来实现无界元组的无状态亚毫秒延迟处理,结合了作为数据流源的喷口和用于过滤、连接、聚合和转换等操作的螺栓。Storm 还实现了一个称为Trident的更高级抽象,类似于 Spark,可以处理微批次数据流。

因此,从亚毫秒到秒的延迟连续性来看,Storm 是一个很好的选择。对于秒到分钟的规模,Spark Streaming 和 Storm Trident 都是很好的选择。对于几分钟以上的范围,Spark 和诸如 Cassandra 或 HBase 的 NoSQL 数据库都是合适的解决方案。对于超过一小时且数据量大的范围,Hadoop 是理想的竞争者。

尽管吞吐量与延迟相关,但它并不是简单的反比线性关系。如果处理一条消息需要 2 毫秒,这决定了延迟,那么人们会认为吞吐量受限于每秒 500 条消息。如果我们允许消息缓冲 8 毫秒,批处理消息可以实现更高的吞吐量。在延迟为 10 毫秒的情况下,系统可以缓冲高达 10,000 条消息。通过容忍可接受的延迟增加,我们大大提高了吞吐量。这就是 Spark Streaming 利用的微批处理的魔力。

Spark Streaming 内部工作

Spark Streaming 架构利用了 Spark 核心架构。它在SparkContext上叠加了一个StreamingContext作为流功能的入口点。集群管理器将至少一个工作节点指定为接收器,这将是一个执行器,具有处理传入流的长任务。执行器从输入数据流创建离散化流或 DStreams,并默认情况下将 DStream 复制到另一个工作节点的缓存中。一个接收器服务于一个输入数据流。多个接收器提高了并行性,并生成多个 Spark 可以合并或连接的离散分布式数据集(RDD)。

下图概述了 Spark Streaming 的内部工作。客户端通过集群管理器与 Spark 集群交互,而 Spark Streaming 有一个专用的工作节点,运行长时间的任务,摄取输入数据流并将其转换为离散化流或 DStreams。数据由接收器收集、缓冲和复制,然后推送到一系列 RDD 的流中。

Spark Streaming 内部工作

Spark 接收器可以从许多来源获取数据。核心输入来源包括 TCP 套接字和 HDFS/Amazon S3 到 Akka Actors。其他来源包括 Apache Kafka、Apache Flume、Amazon Kinesis、ZeroMQ、Twitter 和自定义或用户定义的接收器。

我们区分了可靠的资源,它们确认接收到数据并进行复制以便可能的重发,与不确认消息接收的不可靠接收者。Spark 在工作节点、分区和接收者方面进行了扩展。

下图概述了 Spark Streaming 的内部工作,以及可能的来源和持久性选项:

Spark Streaming 内部工作

深入了解 Spark Streaming

Spark Streaming 由接收器组成,并由离散化流和用于持久性的 Spark 连接器提供支持。

至于 Spark Core,其基本数据结构是 RDD,而 Spark Streaming 的基本编程抽象是离散化流或 DStream。

下图说明了离散化流作为 RDD 的连续序列。DStream 的批次间隔是可配置的。

深入了解 Spark Streaming

DStreams 在批次间隔中快照传入的数据。这些时间步骤通常在 500 毫秒到几秒之间。DStream 的基本结构是 RDD。

DStream 本质上是一系列连续的 RDD。这很强大,因为它允许我们利用 Spark Streaming 中所有传统的函数、转换和 Spark Core 中可用的操作,并允许我们与 Spark SQL 对话,对传入的数据流执行 SQL 查询,并使用 Spark MLlib。类似于通用和键值对 RDD 上的转换是适用的。DStreams 受益于内部 RDD 的谱系和容错性。离散流操作还存在其他转换和输出操作。大多数 DStream 上的通用操作是transformforeachRDD

以下图表概述了 DStreams 的生命周期。从创建消息的微批处理到应用transformation函数和触发 Spark 作业的 RDD。分解图表中的步骤,我们从上到下阅读图表:

  1. 在输入流中,传入的消息根据微批处理的时间窗口分配在容器中进行缓冲。

  2. 在离散化流步骤中,缓冲的微批处理被转换为 DStream RDD。

  3. 映射的 DStream 步骤是通过将转换函数应用于原始 DStream 而获得的。这前三个步骤构成了在预定义时间窗口中接收到的原始数据的转换。由于底层数据结构是 RDD,我们保留了转换的数据谱系。

  4. 最后一步是对 RDD 的操作。它触发 Spark 作业。

深入了解 Spark Streaming

转换可以是无状态的或有状态的。无状态意味着程序不维护状态,而有状态意味着程序保持状态,这种情况下,先前的事务被记住并可能影响当前事务。有状态操作修改或需要系统的某些状态,而无状态操作则不需要。

无状态转换一次处理 DStream 中的每个批处理。有状态转换处理多个批次以获得结果。有状态转换需要配置检查点目录。检查点是 Spark Streaming 中容错的主要机制,用于定期保存有关应用程序的数据和元数据。

Spark Streaming 有两种类型的有状态转换:updateStateByKey和窗口转换。

updateStateByKey是维护流中每个键的状态的转换。它返回一个新的state DStream,其中每个键的状态都通过将给定函数应用于键的先前状态和每个键的新值来更新。一个示例是在推文流中给定标签的运行计数。

窗口转换在滑动窗口中跨多个批次进行。窗口具有指定的长度或持续时间,以时间单位指定。它必须是 DStream 批处理间隔的倍数。它定义了窗口转换中包括多少批次。

窗口具有指定的滑动间隔或滑动持续时间。它必须是 DStream 批处理间隔的倍数。它定义了滑动窗口或计算窗口转换的频率。

以下模式描述了在 DStreams 上进行窗口操作,以获得具有给定长度和滑动间隔的窗口 DStreams:

深入了解 Spark Streaming

一个示例函数是countByWindowwindowLengthslideInterval)。它返回一个新的 DStream,其中每个 RDD 都有一个由计算此 DStream 上的滑动窗口中的元素数量生成的单个元素。在这种情况下,一个示例是在推文流中每 60 秒对给定标签的运行计数。窗口时间范围是指定的。

分钟级窗口长度是合理的。小时级窗口长度不建议,因为它会消耗大量计算和内存。更方便的做法是在诸如 Cassandra 或 HBase 之类的数据库中聚合数据。

窗口转换根据窗口长度和窗口滑动间隔计算结果。Spark 的性能主要受窗口长度、窗口滑动间隔和持久性的影响。

建立容错

实时流处理系统必须 24/7 运行。它们需要对系统中的各种故障具有弹性。Spark 及其 RDD 抽象设计成无缝处理集群中任何工作节点的故障。

主要的 Spark Streaming 容错机制是检查点、自动驱动程序重启和自动故障转移。Spark 通过检查点实现了从驱动程序故障中恢复,从而保留了应用程序状态。

写前日志、可靠的接收器和文件流保证了从 Spark 版本 1.2 开始的零数据丢失。写前日志代表了一个容错的存储接收到的数据。

故障需要重新计算结果。DStream 操作具有精确一次的语义。转换可以多次重新计算,但结果将是相同的。DStream 输出操作具有至少一次的语义。输出操作可能会被执行多次。

使用 TCP 套接字处理实时数据

作为对流操作整体理解的一个基础,我们将首先尝试使用 TCP 套接字进行实验。TCP 套接字在客户端和服务器之间建立双向通信,可以通过已建立的连接交换数据。WebSocket 连接是长期存在的,不像典型的 HTTP 连接。HTTP 不适用于保持从服务器到 Web 浏览器的开放连接以持续推送数据。因此,大多数 Web 应用程序通过频繁的异步 JavaScriptAJAX)和 XML 请求采用了长轮询。WebSocket 在 HTML5 中标准化和实现,正在超越 Web 浏览器,成为客户端和服务器之间实时通信的跨平台标准。

设置 TCP 套接字

我们通过运行netcat创建一个 TCP 套接字服务器,netcat是大多数 Linux 系统中的一个小型实用程序,作为数据服务器使用命令> nc -lk 9999,其中9999是我们发送数据的端口:

#
# Socket Server
#
an@an-VB:~$ nc -lk 9999
hello world
how are you
hello  world
cool it works

一旦 netcat 运行起来,我们将打开第二个控制台,使用我们的 Spark Streaming 客户端接收和处理数据。一旦 Spark Streaming 客户端控制台开始监听,我们就开始输入要处理的单词,即hello world

处理实时数据

我们将使用 Spark 捆绑包中提供的 Spark Streaming 示例程序network_wordcount.py。它可以在 GitHub 存储库github.com/apache/spark/blob/master/examples/src/main/python/streaming/network_wordcount.py中找到。代码如下:

"""
 Counts words in UTF8 encoded, '\n' delimited text received from the network every second.
 Usage: network_wordcount.py <hostname> <port>
   <hostname> and <port> describe the TCP server that Spark Streaming would connect to receive data.
 To run this on your local machine, you need to first run a Netcat server
    `$ nc -lk 9999`
 and then run the example
    `$ bin/spark-submit examples/src/main/python/streaming/network_wordcount.py localhost 9999`
"""
from __future__ import print_function

import sys

from pyspark import SparkContext
from pyspark.streaming import StreamingContext

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: network_wordcount.py <hostname> <port>", file=sys.stderr)
        exit(-1)
    sc = SparkContext(appName="PythonStreamingNetworkWordCount")
    ssc = StreamingContext(sc, 1)

    lines = ssc.socketTextStream(sys.argv[1], int(sys.argv[2]))
    counts = lines.flatMap(lambda line: line.split(" "))\
                  .map(lambda word: (word, 1))\
                  .reduceByKey(lambda a, b: a+b)
    counts.pprint()

    ssc.start()
    ssc.awaitTermination()

在这里,我们解释了程序的步骤:

  1. 代码首先使用以下命令初始化 Spark Streaming 上下文:
ssc = StreamingContext(sc, 1)

  1. 接下来,设置流计算。

  2. 定义了一个或多个接收数据的 DStream 对象,以连接到本地主机或127.0.0.1上的端口 9999

stream = ssc.socketTextStream("127.0.0.1", 9999)

  1. 已定义 DStream 计算:转换和输出操作:
stream.map(x: lambda (x,1))
.reduce(a+b)
.print()
  1. 计算已经开始:
ssc.start()

  1. 程序终止等待手动或错误处理完成:
ssc.awaitTermination()

  1. 手动完成是一个选项,当已知完成条件时:
ssc.stop()

我们可以通过访问 Spark 监控主页localhost:4040来监视 Spark Streaming 应用程序。

这是运行程序并在netcat服务器控制台上输入单词的结果:

#
# Socket Client
# an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit examples/src/main/python/streaming/network_wordcount.py localhost 9999

通过连接到端口 9999上的本地主机运行 Spark Streaming network_count程序:

an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit examples/src/main/python/streaming/network_wordcount.py localhost 9999
-------------------------------------------
Time: 2015-10-18 20:06:06
-------------------------------------------
(u'world', 1)
(u'hello', 1)

-------------------------------------------
Time: 2015-10-18 20:06:07
-------------------------------------------
. . .
-------------------------------------------
Time: 2015-10-18 20:06:17
-------------------------------------------
(u'you', 1)
(u'how', 1)
(u'are', 1)

-------------------------------------------
Time: 2015-10-18 20:06:18
-------------------------------------------

. . .

-------------------------------------------
Time: 2015-10-18 20:06:26
-------------------------------------------
(u'', 1)
(u'world', 1)
(u'hello', 1)

-------------------------------------------
Time: 2015-10-18 20:06:27
-------------------------------------------
. . .
-------------------------------------------
Time: 2015-10-18 20:06:37
-------------------------------------------
(u'works', 1)
(u'it', 1)
(u'cool', 1)

-------------------------------------------
Time: 2015-10-18 20:06:38
-------------------------------------------

因此,我们已经通过端口 9999上的套接字建立了连接,流式传输了netcat服务器发送的数据,并对发送的消息进行了字数统计。

实时操作 Twitter 数据

Twitter 提供两种 API。一种是搜索 API,基本上允许我们根据搜索词检索过去的 tweets。这就是我们在本书的前几章中从 Twitter 收集数据的方式。有趣的是,对于我们当前的目的,Twitter 提供了一个实时流 API,允许我们摄取博客圈中发布的 tweets。

实时处理来自 Twitter firehose 的 Tweets

以下程序连接到 Twitter firehose 并处理传入的 tweets,排除已删除或无效的 tweets,并实时解析只提取screen name,实际 tweet 或tweet textretweet计数,geo-location信息。处理后的 tweets 由 Spark Streaming 收集到 RDD 队列中,然后以一秒的间隔显示在控制台上:

"""
Twitter Streaming API Spark Streaming into an RDD-Queue to process tweets live

 Create a queue of RDDs that will be mapped/reduced one at a time in
 1 second intervals.

 To run this example use
    '$ bin/spark-submit examples/AN_Spark/AN_Spark_Code/s07_twitterstreaming.py'

"""
#
import time
from pyspark import SparkContext
from pyspark.streaming import StreamingContext
import twitter
import dateutil.parser
import json

# Connecting Streaming Twitter with Streaming Spark via Queue
class Tweet(dict):
    def __init__(self, tweet_in):
        super(Tweet, self).__init__(self)
        if tweet_in and 'delete' not in tweet_in:
            self['timestamp'] = dateutil.parser.parse(tweet_in[u'created_at']
                                ).replace(tzinfo=None).isoformat()
            self['text'] = tweet_in['text'].encode('utf-8')
            #self['text'] = tweet_in['text']
            self['hashtags'] = [x['text'].encode('utf-8') for x in tweet_in['entities']['hashtags']]
            #self['hashtags'] = [x['text'] for x in tweet_in['entities']['hashtags']]
            self['geo'] = tweet_in['geo']['coordinates'] if tweet_in['geo'] else None
            self['id'] = tweet_in['id']
            self['screen_name'] = tweet_in['user']['screen_name'].encode('utf-8')
            #self['screen_name'] = tweet_in['user']['screen_name']
            self['user_id'] = tweet_in['user']['id']

def connect_twitter():
    twitter_stream = twitter.TwitterStream(auth=twitter.OAuth(
        token = "get_your_own_credentials",
        token_secret = "get_your_own_credentials",
        consumer_key = "get_your_own_credentials",
        consumer_secret = "get_your_own_credentials"))
    return twitter_stream

def get_next_tweet(twitter_stream):
    stream = twitter_stream.statuses.sample(block=True)
    tweet_in = None
    while not tweet_in or 'delete' in tweet_in:
        tweet_in = stream.next()
        tweet_parsed = Tweet(tweet_in)
    return json.dumps(tweet_parsed)

def process_rdd_queue(twitter_stream):
    # Create the queue through which RDDs can be pushed to
    # a QueueInputDStream
    rddQueue = []
    for i in range(3):
        rddQueue += [ssc.sparkContext.parallelize([get_next_tweet(twitter_stream)], 5)]

    lines = ssc.queueStream(rddQueue)
    lines.pprint()

if __name__ == "__main__":
    sc = SparkContext(appName="PythonStreamingQueueStream")
    ssc = StreamingContext(sc, 1)

    # Instantiate the twitter_stream
    twitter_stream = connect_twitter()
    # Get RDD queue of the streams json or parsed
    process_rdd_queue(twitter_stream)

    ssc.start()
    time.sleep(2)
    ssc.stop(stopSparkContext=True, stopGraceFully=True)

当我们运行这个程序时,它会产生以下输出:

an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ bin/spark-submit examples/AN_Spark/AN_Spark_Code/s07_twitterstreaming.py
-------------------------------------------
Time: 2015-11-03 21:53:14
-------------------------------------------
{"user_id": 3242732207, "screen_name": "cypuqygoducu", "timestamp": "2015-11-03T20:53:04", "hashtags": [], "text": "RT @VIralBuzzNewss: Our Distinctive Edition Holiday break Challenge Is In this article! Hooray!... -  https://t.co/9d8wumrd5v https://t.co/\u2026", "geo": null, "id": 661647303678259200}

-------------------------------------------
Time: 2015-11-03 21:53:15
-------------------------------------------
{"user_id": 352673159, "screen_name": "melly_boo_orig", "timestamp": "2015-11-03T20:53:05", "hashtags": ["eminem"], "text": "#eminem https://t.co/GlEjPJnwxy", "geo": null, "id": 661647307847409668}

-------------------------------------------
Time: 2015-11-03 21:53:16
-------------------------------------------
{"user_id": 500620889, "screen_name": "NBAtheist", "timestamp": "2015-11-03T20:53:06", "hashtags": ["tehInterwebbies", "Nutters"], "text": "See? That didn't take long or any actual effort. This is #tehInterwebbies ... #Nutters Abound! https://t.co/QS8gLStYFO", "geo": null, "id": 661647312062709761}

因此,我们得到了使用 Spark 流处理实时 tweets 并实时处理它们的示例。

构建可靠且可扩展的流媒体应用程序

摄取数据是从各种来源获取数据并立即或以后进行处理的过程。数据消费系统分散并且可能在物理上和架构上远离来源。数据摄取通常使用脚本和基本自动化手段手动实现。实际上需要像 Flume 和 Kafka 这样的更高级框架。

数据摄取的挑战在于数据来源分散且瞬息万变,这使得集成变得脆弱。天气、交通、社交媒体、网络活动、车间传感器、安全和监控的数据生产是持续不断的。不断增加的数据量和速率,再加上不断变化的数据结构和语义,使得数据摄取变得临时性和容易出错。

目标是变得更加敏捷、可靠和可扩展。数据摄取的敏捷性、可靠性和可扩展性决定了管道的整体健康状况。敏捷性意味着随着新来源的出现进行集成,并根据需要对现有来源进行更改。为了确保安全性和可靠性,我们需要保护基础设施免受数据丢失的影响,并防止数据入口处对下游应用程序造成静默数据损坏。可扩展性可以避免摄取瓶颈,同时保持成本可控。

摄取模式 描述 示例
手动或脚本 使用命令行界面或图形界面进行文件复制 HDFS 客户端,Cloudera Hue
批量数据传输 使用工具进行批量数据传输 DistCp,Sqoop
微批处理 小批量数据传输 Sqoop,Sqoop2Storm
流水线 流式事件传输 Flume Scribe
消息队列 发布订阅事件总线 Kafka,Kinesis

为了实现能够摄取多个数据流、在飞行中处理数据并理解所有内容以做出快速决策的事件驱动业务,统一日志是关键驱动因素。

统一日志是一个集中的企业结构化日志,可供实时订阅。所有组织的数据都放在一个中央日志中进行订阅。记录按照它们被写入的顺序从零开始编号。它也被称为提交日志或日志。统一日志的概念是 Kappa 架构的核心原则。

统一日志的属性如下:

  • 统一的:整个组织只有一个部署

  • 仅追加的:事件是不可变的并且是追加的

  • 有序的:每个事件在分片内具有唯一的偏移量

  • 分布式的:为了容错目的,统一日志在计算机集群上进行冗余分布

  • 快速的:系统每秒摄取数千条消息

设置 Kafka

为了将数据的下游特定消费与数据的上游发射隔离开来,我们需要将数据的提供者与数据的接收者或消费者解耦。 由于它们生活在两个不同的世界,具有不同的周期和约束条件,Kafka 解耦了数据管道。

Apache Kafka 是一个经过重新构想的分布式发布订阅消息系统,被重新构想为分布式提交日志。 消息按主题存储。

Apache Kafka 具有以下属性。 它支持:

  • 高吞吐量,适用于大量事件源

  • 实时处理新的和派生的数据源

  • 大数据积压和离线消费的持久性

  • 低延迟作为企业范围的消息传递系统

  • 由于其分布式性质,具有容错能力

消息存储在具有唯一顺序 ID 的分区中,称为“偏移量”。 消费者通过元组(“偏移量”,“分区”,“主题”)跟踪它们的指针。

让我们深入了解 Kafka 的结构。

Kafka 基本上有三个组件:生产者消费者代理。 生产者将数据推送并写入代理。 消费者从代理中拉取和读取数据。 代理不会将消息推送给消费者。 消费者从代理中拉取消息。 设置是由 Apache Zookeeper 分布和协调的。

代理在主题中管理和存储数据。 主题分为复制分区。 数据在代理中持久存在,但在消费之前不会被删除,而是在保留期间。 如果消费者失败,它可以随时返回代理以获取数据。

Kafka 需要 Apache ZooKeeper。 ZooKeeper 是分布式应用程序的高性能协调服务。 它集中管理配置,注册表或命名服务,组成员资格,锁定以及服务器之间的协调同步。 它提供具有元数据,监视统计信息和集群状态的分层命名空间。 ZooKeeper 可以动态引入代理和消费者,然后重新平衡集群。

Kafka 生产者不需要 ZooKeeper。 Kafka 代理使用 ZooKeeper 提供一般状态信息,并在故障时选举领导者。 Kafka 消费者使用 ZooKeeper 跟踪消息偏移量。 较新版本的 Kafka 将保存消费者通过 ZooKeeper 并可以检索 Kafka 特殊主题信息。 Kafka 为生产者提供自动负载平衡。

以下图表概述了 Kafka 的设置:

设置 Kafka

安装和测试 Kafka

我们将从专用网页kafka.apache.org/downloads.html下载 Apache Kafka 二进制文件,并使用以下步骤在我们的机器上安装软件:

  1. 下载代码。

  2. 下载 0.8.2.0 版本并“解压”它:

> tar -xzf kafka_2.10-0.8.2.0.tgz
> cd kafka_2.10-0.8.2.0

  1. 启动zooeeper。 Kafka 使用 ZooKeeper,因此我们需要首先启动 ZooKeeper 服务器。 我们将使用 Kafka 打包的便利脚本来获取单节点 ZooKeeper 实例。
> bin/zookeeper-server-start.sh config/zookeeper.properties
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/zookeeper-server-start.sh config/zookeeper.properties

[2015-10-31 22:49:14,808] INFO Reading configuration from: config/zookeeper.properties (org.apache.zookeeper.server.quorum.QuorumPeerConfig)
[2015-10-31 22:49:14,816] INFO autopurge.snapRetainCount set to 3 (org.apache.zookeeper.server.DatadirCleanupManager)...

  1. 现在启动 Kafka 服务器:
> bin/kafka-server-start.sh config/server.properties

an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-server-start.sh config/server.properties
[2015-10-31 22:52:04,643] INFO Verifying properties (kafka.utils.VerifiableProperties)
[2015-10-31 22:52:04,714] INFO Property broker.id is overridden to 0 (kafka.utils.VerifiableProperties)
[2015-10-31 22:52:04,715] INFO Property log.cleaner.enable is overridden to false (kafka.utils.VerifiableProperties)
[2015-10-31 22:52:04,715] INFO Property log.dirs is overridden to /tmp/kafka-logs (kafka.utils.VerifiableProperties) [2013-04-22 15:01:47,051] INFO Property socket.send.buffer.bytes is overridden to 1048576 (kafka.utils.VerifiableProperties)

  1. 创建一个主题。 让我们创建一个名为 test 的主题,其中只有一个分区和一个副本:
> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test

  1. 如果我们运行list主题命令,我们现在可以看到该主题:
> bin/kafka-topics.sh --list --zookeeper localhost:2181
Test
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
Created topic "test".
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-topics.sh --list --zookeeper localhost:2181
test

  1. 通过创建生产者和消费者来检查 Kafka 安装。 我们首先启动一个“生产者”并在控制台中输入消息:
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
[2015-10-31 22:54:43,698] WARN Property topic is not valid (kafka.utils.VerifiableProperties)
This is a message
This is another message

  1. 然后我们启动一个消费者来检查我们是否收到消息:
an@an-VB:~$ cd kafka/
an@an-VB:~/kafka$ cd kafka_2.10-0.8.2.0/
an@an-VB:~/kafka/kafka_2.10-0.8.2.0$ bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning
This is a message
This is another message

消息已被消费者正确接收:

  1. 检查 Kafka 和 Spark Streaming 消费者。 我们将使用 Spark 捆绑包中提供的 Spark Streaming Kafka 单词计数示例。 警告:当我们提交 Spark 作业时,我们必须绑定 Kafka 软件包--packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0。 命令如下:
./bin/spark-submit --packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0 \ examples/src/main/python/streaming/kafka_wordcount.py \

localhost:2181 test

  1. 当我们使用 Kafka 启动 Spark Streaming 单词计数程序时,我们会得到以下输出:
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit --packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0 examples/src/main/python/streaming/kafka_wordcount.py 
localhost:2181 test

-------------------------------------------
Time: 2015-10-31 23:46:33
-------------------------------------------
(u'', 1)
(u'from', 2)
(u'Hello', 2)
(u'Kafka', 2)

-------------------------------------------
Time: 2015-10-31 23:46:34
-------------------------------------------

-------------------------------------------
Time: 2015-10-31 23:46:35
-------------------------------------------

  1. 安装 Kafka Python 驱动程序,以便能够以编程方式开发生产者和消费者,并使用 Python 与 Kafka 和 Spark 进行交互。我们将使用 David Arthur 的经过测试的库,也就是 GitHub 上的 Mumrah(github.com/mumrah)。我们可以使用 pip 进行安装,如下所示:
> pip install kafka-python
an@an-VB:~$ pip install kafka-python
Collecting kafka-python
 Downloading kafka-python-0.9.4.tar.gz (63kB)
...
Successfully installed kafka-python-0.9.4

开发生产者

以下程序创建了一个简单的 Kafka 生产者,它将发送消息this is a message sent from the Kafka producer:五次,然后每秒跟一个时间戳:

#
# kafka producer
#
#
import time
from kafka.common import LeaderNotAvailableError
from kafka.client import KafkaClient
from kafka.producer import SimpleProducer
from datetime import datetime

def print_response(response=None):
    if response:
        print('Error: {0}'.format(response[0].error))
        print('Offset: {0}'.format(response[0].offset))

def main():
    kafka = KafkaClient("localhost:9092")
    producer = SimpleProducer(kafka)
    try:
        time.sleep(5)
        topic = 'test'
        for i in range(5):
            time.sleep(1)
            msg = 'This is a message sent from the kafka producer: ' \
                  + str(datetime.now().time()) + ' -- '\
                  + str(datetime.now().strftime("%A, %d %B %Y %I:%M%p"))
            print_response(producer.send_messages(topic, msg))
    except LeaderNotAvailableError:
        # https://github.com/mumrah/kafka-python/issues/249
        time.sleep(1)
        print_response(producer.send_messages(topic, msg))

    kafka.close()

if __name__ == "__main__":
    main()

当我们运行此程序时,会生成以下输出:

an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code$ python s08_kafka_producer_01.py
Error: 0
Offset: 13
Error: 0
Offset: 14
Error: 0
Offset: 15
Error: 0
Offset: 16
Error: 0
Offset: 17
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code$

它告诉我们没有错误,并给出了 Kafka 代理给出的消息的偏移量。

开发消费者

为了从 Kafka 代理获取消息,我们开发了一个 Kafka 消费者:

# kafka consumer
# consumes messages from "test" topic and writes them to console.
#
from kafka.client import KafkaClient
from kafka.consumer import SimpleConsumer

def main():
  kafka = KafkaClient("localhost:9092")
  print("Consumer established connection to kafka")
  consumer = SimpleConsumer(kafka, "my-group", "test")
  for message in consumer:
    # This will wait and print messages as they become available
    print(message)

if __name__ == "__main__":
    main()

当我们运行此程序时,我们有效地确认消费者接收了所有消息:

an@an-VB:~$ cd ~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code/
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/AN_Spark_Code$ python s08_kafka_consumer_01.py
Consumer established connection to kafka
OffsetAndMessage(offset=13, message=Message(magic=0, attributes=0, key=None, value='This is a message sent from the kafka producer: 11:50:17.867309Sunday, 01 November 2015 11:50AM'))
...
OffsetAndMessage(offset=17, message=Message(magic=0, attributes=0, key=None, value='This is a message sent from the kafka producer: 11:50:22.051423Sunday, 01 November 2015 11:50AM'))

为 Kafka 开发 Spark Streaming 消费者

根据 Spark Streaming 包中提供的示例代码,我们将为 Kafka 创建一个 Spark Streaming 消费者,并对存储在代理中的消息进行词频统计:

#
# Kafka Spark Streaming Consumer    
#
from __future__ import print_function

import sys

from pyspark import SparkContext
from pyspark.streaming import StreamingContext
from pyspark.streaming.kafka import KafkaUtils

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: kafka_spark_consumer_01.py <zk> <topic>", file=sys.stderr)
        exit(-1)

    sc = SparkContext(appName="PythonStreamingKafkaWordCount")
    ssc = StreamingContext(sc, 1)

    zkQuorum, topic = sys.argv[1:]
    kvs = KafkaUtils.createStream(ssc, zkQuorum, "spark-streaming-consumer", {topic: 1})
    lines = kvs.map(lambda x: x[1])
    counts = lines.flatMap(lambda line: line.split(" ")) \
        .map(lambda word: (word, 1)) \
        .reduceByKey(lambda a, b: a+b)
    counts.pprint()

    ssc.start()
    ssc.awaitTermination()

使用以下 Spark 提交命令运行此程序:

./bin/spark-submit --packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0 examples/AN_Spark/AN_Spark_Code/s08_kafka_spark_consumer_01.py localhost:2181 test

我们得到以下输出:

an@an-VB:~$ cd spark/spark-1.5.0-bin-hadoop2.6/
an@an-VB:~/spark/spark-1.5.0-bin-hadoop2.6$ ./bin/spark-submit \
>     --packages org.apache.spark:spark-streaming-kafka_2.10:1.5.0 \
>     examples/AN_Spark/AN_Spark_Code/s08_kafka_spark_consumer_01.py localhost:2181 test
...
:: retrieving :: org.apache.spark#spark-submit-parent
  confs: [default]
  0 artifacts copied, 10 already retrieved (0kB/18ms)
-------------------------------------------
Time: 2015-11-01 12:13:16
-------------------------------------------

-------------------------------------------
Time: 2015-11-01 12:13:17
-------------------------------------------

-------------------------------------------
Time: 2015-11-01 12:13:18
-------------------------------------------

-------------------------------------------
Time: 2015-11-01 12:13:19
-------------------------------------------
(u'a', 5)
(u'the', 5)
(u'11:50AM', 5)
(u'from', 5)
(u'This', 5)
(u'11:50:21.044374Sunday,', 1)
(u'message', 5)
(u'11:50:20.036422Sunday,', 1)
(u'11:50:22.051423Sunday,', 1)
(u'11:50:17.867309Sunday,', 1)
...

-------------------------------------------
Time: 2015-11-01 12:13:20
-------------------------------------------

-------------------------------------------
Time: 2015-11-01 12:13:21
-------------------------------------------

探索 flume

Flume 是一个持续的摄入系统。最初设计为日志聚合系统,但它发展到处理任何类型的流式事件数据。

Flume 是一个分布式、可靠、可扩展和可用的管道系统,用于高效地收集、聚合和传输大量数据。它内置支持上下文路由、过滤复制和多路复用。它是强大且容错的,具有可调节的可靠性机制和许多故障转移和恢复机制。它使用简单可扩展的数据模型,允许实时分析应用。

Flume 提供以下内容:

  • 保证交付语义

  • 低延迟可靠数据传输

  • 无需编码的声明性配置

  • 可扩展和可定制的设置

  • 与最常用的端点集成

Flume 的结构包括以下元素:

  • Event:事件是由 Flume 从源到目的地传输的基本数据单元。它类似于一个带有字节数组有效负载的消息,对 Flume 不透明,并且可选的标头用于上下文路由。

  • Client:客户端生成并传输事件。客户端将 Flume 与数据消费者解耦。它是生成事件并将其发送到一个或多个代理的实体。自定义客户端或 Flume log4J 附加程序或嵌入式应用代理可以是客户端。

  • Agent:代理是承载源、通道、sink 和其他元素的容器,使事件从一个地方传输到另一个地方。它为托管组件提供配置、生命周期管理和监控。代理是运行 Flume 的物理 Java 虚拟机。

  • Source:源是 Flume 接收事件的实体。源至少需要一个通道才能工作,以主动轮询数据或被动等待数据传递给它们。各种源允许收集数据,例如 log4j 日志和 syslogs。

  • Sink:Sink 是从通道中排出数据并将其传递到下一个目的地的实体。各种不同的 sink 允许数据流向各种目的地。Sink 支持序列化为用户的格式。一个例子是将事件写入 HDFS 的 HDFS sink。

  • 通道:通道是源和汇之间的导管,缓冲传入事件,直到被汇耗尽。源将事件馈送到通道,而汇则耗尽通道。通道解耦了上游和下游系统的阻抗。上游的数据突发通过通道被抑制。下游的故障被通道透明地吸收。调整通道容量以应对这些事件是实现这些好处的关键。通道提供两种持久性级别:内存通道,如果 JVM 崩溃则是易失性的,或者由预写日志支持的文件通道,将信息存储到磁盘上。通道是完全事务性的。

让我们说明所有这些概念:

探索水槽

使用 Flume、Kafka 和 Spark 开发数据管道

构建具有弹性的数据管道利用了前几节的经验。我们正在使用 Flume 将数据摄取和传输,使用 Kafka 作为可靠和复杂的发布和订阅消息系统进行数据经纪,最后使用 Spark Streaming 进行实时处理计算。

以下图示了流数据管道的组成,作为connectcollectconductcomposeconsumeconsigncontrol活动的序列。这些活动根据用例进行配置:

  • 连接建立与流式 API 的绑定。

  • 收集创建收集线程。

  • Conduct 通过创建缓冲队列或发布-订阅机制将数据生产者与消费者解耦。

  • Compose 专注于处理数据。

  • Consume 为消费系统提供处理后的数据。Consign 负责数据持久性。

  • 控制满足系统、数据和应用程序的治理和监控。

使用 Flume、Kafka 和 Spark 开发数据管道

以下图示了流数据管道的概念及其关键组件:Spark Streaming、Kafka、Flume 和低延迟数据库。在消费或控制应用程序中,我们正在实时监控我们的系统(由监视器表示),或者在某些阈值被突破时发送实时警报(由红灯表示)。

使用 Flume、Kafka 和 Spark 开发数据管道

以下图示了 Spark 在单一平台上处理运动数据和静态数据的独特能力,同时根据用例要求与多个持久性数据存储无缝接口。

这张图将到目前为止讨论的所有概念统一在一起。顶部描述了流处理管道,底部描述了批处理管道。它们都在图中间共享一个持久性层,描述了各种持久性和序列化模式。

使用 Flume、Kafka 和 Spark 开发数据管道

关于 Lambda 和 Kappa 架构的结束语

目前流行的有两种架构范式:Lambda 和 Kappa 架构。

Lambda 是 Storm 的创始人和主要贡献者 Nathan Marz 的心血结晶。它基本上主张在所有数据上构建一个功能架构。该架构有两个分支。第一个是批处理分支,旨在由 Hadoop 提供动力,其中历史、高延迟、高吞吐量的数据被预处理并准备好供消费。实时分支旨在由 Storm 提供动力,它处理增量流数据,实时推导见解,并将聚合信息反馈到批处理存储。

Kappa 是 Kafka 的主要贡献者之一 Jay Kreps 及其在 Confluent(以前在 LinkedIn)的同事的心血结晶。它主张一个完整的流水线,有效地在企业级别实现了前几页中所述的统一日志。

理解 Lambda 架构

Lambda 架构将批处理和流式数据结合,以提供对所有可用数据的统一查询机制。Lambda 架构设想了三个层次:批处理层存储预先计算的信息,速度层处理实时增量信息作为数据流,最后是服务层,将批处理和实时视图合并用于自由查询。以下图表概述了 Lambda 架构:

理解 Lambda 架构

理解 Kappa 架构

Kappa 架构提议以流式模式驱动整个企业。Kappa 架构起源于 LinkedIn 的 Jay Kreps 及其同事的批评。自那时起,他们转移并创建了以 Apache Kafka 为主要支持者的 Confluent,以实现 Kappa 架构愿景。其基本原则是以统一日志作为企业信息架构的主要支撑,在全流式模式下运行。

统一日志是一个集中的企业结构化日志,可供实时订阅。所有组织的数据都放在一个中央日志中进行订阅。记录从零开始编号,以便写入。它也被称为提交日志或日志。统一日志的概念是 Kappa 架构的核心原则。

统一日志的属性如下:

  • 统一的:整个组织只有一个部署

  • 仅追加:事件是不可变的,会被追加

  • 有序的:每个事件在一个分片内有唯一的偏移量

  • 分布式:为了容错目的,统一日志在计算机集群上进行冗余分布

  • 快速的:系统每秒摄入数千条消息

以下截图捕捉了 Jay Kreps 对 Lambda 架构的保留意见。他对 Lambda 架构的主要保留意见是在两个不同的系统 Hadoop 和 Storm 中实现相同的作业,每个系统都有其特定的特点,并伴随着所有相关的复杂性。Kappa 架构在由 Apache Kafka 提供支持的同一框架中处理实时数据并重新处理历史数据。

理解 Kappa 架构

总结

在本章中,我们阐述了流式架构应用程序的基础,并描述了它们的挑战、约束和好处。我们深入探讨了 Spark Streaming 的内部工作方式,以及它如何与 Spark Core 对话,并与 Spark SQL 和 Spark MLlib 配合。我们通过 TCP 套接字、直播推文摄入以及直接从 Twitter firehose 处理的方式来阐述流式概念。我们讨论了使用 Kafka 将上游数据发布与下游数据订阅和消费解耦的概念,以最大程度地提高整体流式架构的弹性。我们还讨论了 Flume——一个可靠、灵活和可扩展的数据摄入和传输管道系统。Flume、Kafka 和 Spark 的结合在不断变化的环境中提供了无与伦比的稳健性、速度和灵活性。我们在本章中还对两种流式架构范式——Lambda 和 Kappa 架构进行了一些评论和观察。

Lambda 架构将批处理和流式数据结合在一个通用的查询前端。最初它是以 Hadoop 和 Storm 为目标构想的。Spark 具有自己的批处理和流式范例,并提供了一个共同的代码库的单一环境,有效地将这种架构范式实现。

Kappa 架构宣扬了统一日志的概念,它创建了一个面向事件的架构,企业中的所有事件都被导入到一个中央提交日志中,并且实时提供给所有消费系统。

现在我们准备对迄今为止收集和处理的数据进行可视化。

第六章:可视化见解和趋势

到目前为止,我们已经专注于从 Twitter 收集、分析和处理数据。我们已经为使用我们的数据进行可视化呈现和提取见解和趋势做好了准备。我们将简要介绍 Python 生态系统中的可视化工具。我们将强调 Bokeh 作为渲染和查看大型数据集的强大工具。Bokeh 是 Python Anaconda Distribution 生态系统的一部分。

在本章中,我们将涵盖以下几点:

  • 通过图表和词云来衡量社交网络社区中的关键词和模因

  • 映射最活跃的地点,社区围绕特定主题或话题增长

重新审视数据密集型应用架构

我们已经达到了数据密集型应用架构的最终层:参与层。这一层关注如何综合、强调和可视化数据消费者的关键上下文相关信息。在控制台中的一堆数字不足以吸引最终用户。将大量信息以快速、易消化和有吸引力的方式呈现是至关重要的。

以下图表设置了本章重点的背景,突出了参与层。

重新审视数据密集型应用架构

对于 Python 绘图和可视化,我们有很多工具和库。对于我们的目的,最有趣和相关的是以下几个:

  • Matplotlib 是 Python 绘图库的鼻祖。Matplotlib 最初是 John Hunter 的创意,他是开源软件的支持者,并将 Matplotlib 建立为学术界和数据科学界最流行的绘图库之一。Matplotlib 允许生成图表、直方图、功率谱、条形图、误差图、散点图等。示例可以在 Matplotlib 专用网站 matplotlib.org/examples/index.html 上找到。

  • Seaborn,由 Michael Waskom 开发,是一个快速可视化统计信息的优秀库。它建立在 Matplotlib 之上,并与 Pandas 和 Python 数据堆栈(包括 Numpy)无缝集成。Seaborn 的图库展示了该库的潜力,网址为 stanford.edu/~mwaskom/software/seaborn/examples/index.html

  • ggplot 相对较新,旨在为 Python 数据处理者提供 R 生态系统中著名的 ggplot2 的等价物。它具有与 ggplot2 相同的外观和感觉,并使用 Hadley Wickham 阐述的相同图形语法。ggplot 的 Python 版本由 yhat 团队开发。更多信息可以在 ggplot.yhathq.com 找到。

  • D3.js 是由 Mike Bostock 开发的非常流行的 JavaScript 库。D3 代表 数据驱动文档,利用 HTML、SVG 和 CSS 在任何现代浏览器上为数据赋予生命。它通过操作 DOM(文档对象模型)提供动态、强大、交互式的可视化效果。Python 社区迫不及待地想要将 D3 与 Matplotlib 集成。在 Jake Vanderplas 的推动下,mpld3 被创建,旨在将 matplotlib 带到浏览器中。示例图形托管在以下地址:mpld3.github.io/index.html

  • Bokeh旨在在非常大或流式数据集上提供高性能的交互性,同时利用D3.js的许多概念,而不需要编写一些令人生畏的javascriptcss代码。Bokeh 在浏览器上提供动态可视化,无论是否有服务器。它与 Matplotlib、Seaborn 和 ggplot 无缝集成,并在 IPython 笔记本或 Jupyter 笔记本中呈现出色。Bokeh 由 Continuum.io 团队积极开发,并是 Anaconda Python 数据堆栈的一个重要组成部分。

Bokeh 服务器提供了一个完整的、动态的绘图引擎,可以从 JSON 中实现一个反应式场景图。它使用 Web 套接字来保持状态,并使用 Backbone.js 和 Coffee-script 更新 HTML5 画布。由于 Bokeh 是由 JSON 中的数据驱动的,因此它为其他语言(如 R、Scala 和 Julia)创建了简单的绑定。

这提供了主要绘图和可视化库的高级概述。这并不是详尽无遗的。让我们转向可视化的具体示例。

为可视化预处理数据

在进行可视化之前,我们将对收集到的数据进行一些准备工作:

In [16]:
# Read harvested data stored in csv in a Panda DF
import pandas as pd
csv_in = '/home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/data/unq_tweetstxt.csv'
pddf_in = pd.read_csv(csv_in, index_col=None, header=0, sep=';', encoding='utf-8')
In [20]:
print('tweets pandas dataframe - count:', pddf_in.count())
print('tweets pandas dataframe - shape:', pddf_in.shape)
print('tweets pandas dataframe - colns:', pddf_in.columns)
('tweets pandas dataframe - count:', Unnamed: 0    7540
id            7540
created_at    7540
user_id       7540
user_name     7538
tweet_text    7540
dtype: int64)
('tweets pandas dataframe - shape:', (7540, 6))
('tweets pandas dataframe - colns:', Index([u'Unnamed: 0', u'id', u'created_at', u'user_id', u'user_name', u'tweet_text'], dtype='object'))

为了进行我们的可视化活动,我们将使用一组包含 7,540 条推文的数据集。关键信息存储在tweet_text列中。我们通过在数据框上调用head()函数来预览存储在数据框中的数据:

In [21]:
pddf_in.head()
Out[21]:
  Unnamed: 0   id   created_at   user_id   user_name   tweet_text
0   0   638830426971181057   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: dreamint...
1   1   638830426727911424   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...
2   2   638830425402556417   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: ernestsg...
3   3   638830424563716097   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...
4   4   638830422256816132   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: elsahel12: 9_A_6: dreamintention...

我们现在将创建一些实用程序函数来清理推文文本并解析推特日期。首先,我们导入 Python 正则表达式 regex 库re和时间库来解析日期和时间:

In [72]:
import re
import time

我们创建一个正则表达式的字典,将对其进行编译,然后作为函数传递:

  • RT:带有关键字RT的第一个正则表达式在推文文本开头寻找关键字RT
re.compile(r'^RT'),
  • ALNUM:第二个带有关键字ALNUM的正则表达式寻找包括字母数字字符和下划线符号在内的单词,这些单词前面有@符号在推文文本中:
re.compile(r'(@[a-zA-Z0-9_]+)'),
  • HASHTAG:带有关键字HASHTAG的第三个正则表达式在推文文本中寻找包括#符号在内的单词:
re.compile(r'(#[\w\d]+)'),
  • SPACES:带有关键字SPACES的第四个正则表达式在推文文本中寻找空格或换行符:
re.compile(r'\s+'), 
  • URL:带有关键字URL的第五个正则表达式在推文文本中寻找包括以https://http://标记开头的url地址:
re.compile(r'([https://|http://]?[a-zA-Z\d\/]+[\.]+[a-zA-Z\d\/\.]+)')
In [24]:
regexp = {"RT": "^RT", "ALNUM": r"(@[a-zA-Z0-9_]+)",
          "HASHTAG": r"(#[\w\d]+)", "URL": r"([https://|http://]?[a-zA-Z\d\/]+[\.]+[a-zA-Z\d\/\.]+)",
          "SPACES":r"\s+"}
regexp = dict((key, re.compile(value)) for key, value in regexp.items())
In [25]:
regexp
Out[25]:
{'ALNUM': re.compile(r'(@[a-zA-Z0-9_]+)'),
 'HASHTAG': re.compile(r'(#[\w\d]+)'),
 'RT': re.compile(r'^RT'),
 'SPACES': re.compile(r'\s+'),
 'URL': re.compile(r'([https://|http://]?[a-zA-Z\d\/]+[\.]+[a-zA-Z\d\/\.]+)')}

我们创建一个实用程序函数来识别推文是转发还是原始推文:

In [77]:
def getAttributeRT(tweet):
    """ see if tweet is a RT """
    return re.search(regexp["RT"], tweet.strip()) != None

然后,我们提取推文中的所有用户句柄:

def getUserHandles(tweet):
    """ given a tweet we try and extract all user handles"""
    return re.findall(regexp["ALNUM"], tweet)

我们还提取推文中的所有标签:

def getHashtags(tweet):
    """ return all hashtags"""
    return re.findall(regexp["HASHTAG"], tweet)

提取推文中的所有 URL 链接如下:

def getURLs(tweet):
    """ URL : [http://]?[\w\.?/]+"""
    return re.findall(regexp["URL"], tweet)

我们剥离推文文本中以@符号开头的所有 URL 链接和用户句柄。这个函数将成为我们即将构建的词云的基础:

def getTextNoURLsUsers(tweet):
    """ return parsed text terms stripped of URLs and User Names in tweet text
        ' '.join(re.sub("(@[A-Za-z0-9]+)|([⁰-9A-Za-z \t])|(\w+:\/\/\S+)"," ",x).split()) """
    return ' '.join(re.sub("(@[A-Za-z0-9]+)|([⁰-9A-Za-z \t])|(\w+:\/\/\S+)|(RT)"," ", tweet).lower().split())

我们对数据进行标记,以便我们可以创建词云的数据集组:

def setTag(tweet):
    """ set tags to tweet_text based on search terms from tags_list"""
    tags_list = ['spark', 'python', 'clinton', 'trump', 'gaga', 'bieber']
    lower_text = tweet.lower()
    return filter(lambda x:x.lower() in lower_text,tags_list)

我们以yyyy-mm-dd hh:mm:ss格式解析推特日期:

def decode_date(s):
    """ parse Twitter date into format yyyy-mm-dd hh:mm:ss"""
    return time.strftime('%Y-%m-%d %H:%M:%S', time.strptime(s,'%a %b %d %H:%M:%S +0000 %Y'))

我们在处理之前预览数据:

In [43]:
pddf_in.columns
Out[43]:
Index([u'Unnamed: 0', u'id', u'created_at', u'user_id', u'user_name', u'tweet_text'], dtype='object')
In [45]:
# df.drop([Column Name or list],inplace=True,axis=1)
pddf_in.drop(['Unnamed: 0'], inplace=True, axis=1)
In [46]:
pddf_in.head()
Out[46]:
  id   created_at   user_id   user_name   tweet_text
0   638830426971181057   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: dreamint...
1   638830426727911424   Tue Sep 01 21:46:57 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...
2   638830425402556417   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: 9_A_6: ernestsg...
3   638830424563716097   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: BeyHiveInFrance: PhuketDailyNews...
4   638830422256816132   Tue Sep 01 21:46:56 +0000 2015   3276255125   True Equality   ernestsgantt: elsahel12: 9_A_6: dreamintention...

我们通过应用所描述的实用程序函数来创建新的数据框列。我们为htag、用户句柄、URL、从 URL 中剥离的文本术语和不需要的字符以及标签创建一个新列。最后我们解析日期:

In [82]:
pddf_in['htag'] = pddf_in.tweet_text.apply(getHashtags)
pddf_in['user_handles'] = pddf_in.tweet_text.apply(getUserHandles)
pddf_in['urls'] = pddf_in.tweet_text.apply(getURLs)
pddf_in['txt_terms'] = pddf_in.tweet_text.apply(getTextNoURLsUsers)
pddf_in['search_grp'] = pddf_in.tweet_text.apply(setTag)
pddf_in['date'] = pddf_in.created_at.apply(decode_date)

以下代码快速展示了新生成的数据框的情况:

In [83]:
pddf_in[2200:2210]
Out[83]:
  id   created_at   user_id   user_name   tweet_text   htag   urls   ptxt   tgrp   date   user_handles   txt_terms   search_grp
2200   638242693374681088   Mon Aug 31 06:51:30 +0000 2015   19525954   CENATIC   El impacto de @ApacheSpark en el procesamiento...   [#sparkSpecial]   [://t.co/4PQmJNuEJB]   el impacto de en el procesamiento de datos y e...   [spark]   2015-08-31 06:51:30   [@ApacheSpark]   el impacto de en el procesamiento de datos y e...   [spark]
2201   638238014695575552   Mon Aug 31 06:32:55 +0000 2015   51115854   Nawfal   Real Time Streaming with Apache Spark\nhttp://...   [#IoT, #SmartMelboune, #BigData, #Apachespark]   [://t.co/GW5PaqwVab]   real time streaming with apache spark iot smar...   [spark]   2015-08-31 06:32:55   []   real time streaming with apache spark iot smar...   [spark]
2202   638236084124516352   Mon Aug 31 06:25:14 +0000 2015   62885987   Mithun Katti   RT @differentsachin: Spark the flame of digita...   [#IBMHackathon, #SparkHackathon, #ISLconnectIN...   []   spark the flame of digital india ibmhackathon ...   [spark]   2015-08-31 06:25:14   [@differentsachin, @ApacheSpark]   spark the flame of digital india ibmhackathon ...   [spark]
2203   638234734649176064   Mon Aug 31 06:19:53 +0000 2015   140462395   solaimurugan v   Installing @ApacheMahout with @ApacheSpark 1.4...   []   [1.4.1, ://t.co/3c5dGbfaZe.]   installing with 1 4 1 got many more issue whil...   [spark]   2015-08-31 06:19:53   [@ApacheMahout, @ApacheSpark]   installing with 1 4 1 got many more issue whil...   [spark]
2204   638233517307072512   Mon Aug 31 06:15:02 +0000 2015   2428473836   Ralf Heineke   RT @RomeoKienzler: Join me @velocityconf on #m...   [#machinelearning, #devOps, #Bl]   [://t.co/U5xL7pYEmF]   join me on machinelearning based devops operat...   [spark]   2015-08-31 06:15:02   [@RomeoKienzler, @velocityconf, @ApacheSpark]   join me on machinelearning based devops operat...   [spark]
2205   638230184848687106   Mon Aug 31 06:01:48 +0000 2015   289355748   Akim Boyko   RT @databricks: Watch live today at 10am PT is...   []   [1.5, ://t.co/16cix6ASti]   watch live today at 10am pt is 1 5 presented b...   [spark]   2015-08-31 06:01:48   [@databricks, @ApacheSpark, @databricks, @pwen...   watch live today at 10am pt is 1 5 presented b...   [spark]
2206   638227830443110400   Mon Aug 31 05:52:27 +0000 2015   145001241   sachin aggarwal   Spark the flame of digital India @ #IBMHackath...   [#IBMHackathon, #SparkHackathon, #ISLconnectIN...   [://t.co/C1AO3uNexe]   spark the flame of digital india ibmhackathon ...   [spark]   2015-08-31 05:52:27   [@ApacheSpark]   spark the flame of digital india ibmhackathon ...   [spark]
2207   638227031268810752   Mon Aug 31 05:49:16 +0000 2015   145001241   sachin aggarwal   RT @pravin_gadakh: Imagine, innovate and Igni...   [#IBMHackathon, #ISLconnectIN2015]   []   gadakh imagine innovate and ignite digital ind...   [spark]   2015-08-31 05:49:16   [@pravin_gadakh, @ApacheSpark]   gadakh imagine innovate and ignite digital ind...   [spark]
2208   638224591920336896   Mon Aug 31 05:39:35 +0000 2015   494725634   IBM Asia Pacific   RT @sachinparmar: Passionate about Spark?? Hav...   [#IBMHackathon, #ISLconnectIN]   [India..]   passionate about spark have dreams of clean sa...   [spark]   2015-08-31 05:39:35   [@sachinparmar]   passionate about spark have dreams of clean sa...   [spark]
2209   638223327467692032   Mon Aug 31 05:34:33 +0000 2015   3158070968   Open Source India   "Game Changer" #ApacheSpark speeds up #bigdata...   [#ApacheSpark, #bigdata]   [://t.co/ieTQ9ocMim]   game changer apachespark speeds up bigdata pro...   [spark]   2015-08-31 05:34:33   []   game changer apachespark speeds up bigdata pro...   [spark]

我们以 CSV 格式保存处理过的信息。我们有 7,540 条记录和 13 列。在您的情况下,输出将根据您选择的数据集而有所不同:

In [84]:
f_name = '/home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/data/unq_tweets_processed.csv'
pddf_in.to_csv(f_name, sep=';', encoding='utf-8', index=False)
In [85]:
pddf_in.shape
Out[85]:
(7540, 13)

一瞥词语、情绪和迷因

我们现在准备构建词云,这将让我们了解这些推文中携带的重要词语。我们将为收集的数据集创建词云。词云提取单词列表中的前几个词,并创建单词的散点图,其中单词的大小与其频率相关。数据集中单词的频率越高,词云呈现的字体大小就越大。它们包括三个非常不同的主题和两个竞争或类似的实体。我们的第一个主题显然是数据处理和分析,其中 Apache Spark 和 Python 是我们的实体。我们的第二个主题是 2016 年总统竞选活动,有两位竞争者:希拉里·克林顿和唐纳德·特朗普。我们最后的主题是流行音乐界,贾斯汀·比伯和 Lady Gaga 是两位代表。

设置词云

我们将通过分析与 Spark 相关的推文来说明编程步骤。我们加载数据并预览数据框:

In [21]:
import pandas as pd
csv_in = '/home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/data/spark_tweets.csv'
tspark_df = pd.read_csv(csv_in, index_col=None, header=0, sep=',', encoding='utf-8')
In [3]:
tspark_df.head(3)
Out[3]:
  id   created_at   user_id   user_name   tweet_text   htag   urls   ptxt   tgrp   date   user_handles   txt_terms   search_grp
0   638818911773856000   Tue Sep 01 21:01:11 +0000 2015   2511247075   Noor Din   RT @kdnuggets: R leads RapidMiner, Python catc...   [#KDN]   [://t.co/3bsaTT7eUs]   r leads rapidminer python catches up big data ...   [spark, python]   2015-09-01 21:01:11   [@kdnuggets]   r leads rapidminer python catches up big data ...   [spark, python]
1   622142176768737000   Fri Jul 17 20:33:48 +0000 2015   24537879   IBM Cloudant   Be one of the first to sign-up for IBM Analyti...   [#ApacheSpark, #SparkInsight]   [://t.co/C5TZpetVA6, ://t.co/R1L29DePaQ]   be one of the first to sign up for ibm analyti...   [spark]   2015-07-17 20:33:48   []   be one of the first to sign up for ibm analyti...   [spark]
2   622140453069169000   Fri Jul 17 20:26:57 +0000 2015   515145898   Arno Candel   Nice article on #apachespark, #hadoop and #dat...   [#apachespark, #hadoop, #datascience]   [://t.co/IyF44pV0f3]   nice article on apachespark hadoop and datasci...   [spark]   2015-07-17 20:26:57   [@h2oai]   nice article on apachespark hadoop and datasci...   [spark]

注意

我们将使用的词云库是由 Andreas Mueller 开发的,并托管在他的 GitHub 帐户上github.com/amueller/word_cloud

该库需要PIL(即Python Imaging Library的缩写)。PIL 可以通过调用conda install pil轻松安装。PIL 是一个复杂的库,尚未移植到 Python 3.4,因此我们需要运行 Python 2.7+环境才能看到我们的词云:

#
# Install PIL (does not work with Python 3.4)
#
an@an-VB:~$ conda install pil

Fetching package metadata: ....
Solving package specifications: ..................
Package plan for installation in environment /home/an/anaconda:

将下载以下软件包:

    package                    |            build
    ---------------------------|-----------------
    libpng-1.6.17              |                0         214 KB
    freetype-2.5.5             |                0         2.2 MB
    conda-env-2.4.4            |           py27_0          24 KB
    pil-1.1.7                  |           py27_2         650 KB
    ------------------------------------------------------------
                                           Total:         3.0 MB

将更新以下软件包:

    conda-env: 2.4.2-py27_0 --> 2.4.4-py27_0
    freetype:  2.5.2-0      --> 2.5.5-0     
    libpng:    1.5.13-1     --> 1.6.17-0    
    pil:       1.1.7-py27_1 --> 1.1.7-py27_2

Proceed ([y]/n)? y

接下来,我们安装词云库:

#
# Install wordcloud
# Andreas Mueller
# https://github.com/amueller/word_cloud/blob/master/wordcloud/wordcloud.py
#

an@an-VB:~$ pip install wordcloud
Collecting wordcloud
  Downloading wordcloud-1.1.3.tar.gz (163kB)
    100% |████████████████████████████████| 163kB 548kB/s 
Building wheels for collected packages: wordcloud
  Running setup.py bdist_wheel for wordcloud
  Stored in directory: /home/an/.cache/pip/wheels/32/a9/74/58e379e5dc614bfd9dd9832d67608faac9b2bc6c194d6f6df5
Successfully built wordcloud
Installing collected packages: wordcloud
Successfully installed wordcloud-1.1.3

创建词云

在这个阶段,我们准备使用推文文本生成的术语列表调用词云程序。

让我们通过首先调用%matplotlib inline 来开始词云程序,以在我们的笔记本中显示词云:

In [4]:
%matplotlib inline
In [11]:

我们将数据框txt_terms列转换为单词列表。我们确保将其全部转换为str类型,以避免任何意外,并检查列表的前四条记录:

len(tspark_df['txt_terms'].tolist())
Out[11]:
2024
In [22]:
tspark_ls_str = [str(t) for t in tspark_df['txt_terms'].tolist()]
In [14]:
len(tspark_ls_str)
Out[14]:
2024
In [15]:
tspark_ls_str[:4]
Out[15]:
['r leads rapidminer python catches up big data tools grow spark ignites kdn',
 'be one of the first to sign up for ibm analytics for apachespark today sparkinsight',
 'nice article on apachespark hadoop and datascience',
 'spark 101 running spark and mapreduce together in production hadoopsummit2015 apachespark altiscale']

我们首先调用 Matplotlib 和词云库:

import matplotlib.pyplot as plt
from wordcloud import WordCloud, STOPWORDS

从输入的术语列表中,我们创建一个由空格分隔的术语统一字符串作为词云程序的输入。词云程序会删除停用词:

# join tweets to a single string
words = ' '.join(tspark_ls_str)

# create wordcloud 
wordcloud = WordCloud(
                      # remove stopwords
                      stopwords=STOPWORDS,
                      background_color='black',
                      width=1800,
                      height=1400
                     ).generate(words)

# render wordcloud image
plt.imshow(wordcloud)
plt.axis('off')

# save wordcloud image on disk
plt.savefig('./spark_tweets_wordcloud_1.png', dpi=300)

# display image in Jupyter notebook
plt.show()

在这里,我们可以看到 Apache Spark 和 Python 的词云。显然,在 Spark 的情况下,“Hadoop”、“大数据”和“分析”是主题词,而 Python 则回顾了其名称 Monty Python 的根源,专注于“开发人员”、“apache spark”和一些涉及到 java 和 ruby 的编程。

创建词云

我们还可以从以下词云中看到北美 2016 年总统候选人希拉里·克林顿和唐纳德·特朗普所关注的词语。看起来希拉里·克林顿被她的对手唐纳德·特朗普和伯尼·桑德斯所掩盖,而特朗普则只关注自己:

创建词云

有趣的是,在贾斯汀·比伯和 Lady Gaga 的情况下,出现了“爱”这个词。在比伯的情况下,“关注”和“belieber”是关键词,而在 Lady Gaga 的情况下,“节食”、“减肥”和“时尚”是她的关注点。

创建词云

地理定位推文和标记聚会

现在,我们将深入使用 Bokeh 创建交互地图。首先,我们创建一个世界地图,在地图上标出样本推文的地理位置,当我们的鼠标移动到这些位置时,我们可以在悬停框中看到用户及其相应的推文。

第二张地图专注于标记伦敦即将举行的聚会。它可以是一个交互式地图,作为特定城市即将举行的聚会的日期、时间和地点的提醒。

地理定位推文

目标是在地图上创建重要推文位置的世界地图散点图,悬停在这些点上可以显示推文和作者。我们将通过三个步骤来构建这个交互式可视化:

  1. 首先通过加载包含各个世界国家边界的字典,定义它们的经度和纬度,创建背景世界地图。

  2. 加载我们希望通过其相应坐标和作者进行地理定位的重要推文。

  3. 最后,在世界地图上绘制推文坐标的散点图,并激活悬停工具,以交互方式可视化地图上突出点的推文和作者。

在第一步中,我们创建了一个名为 data 的 Python 列表,其中包含所有世界国家边界及其相应的纬度和经度:

In [4]:
#
# This module exposes geometry data for World Country Boundaries.
#
import csv
import codecs
import gzip
import xml.etree.cElementTree as et
import os
from os.path import dirname, join

nan = float('NaN')
__file__ = os.getcwd()

data = {}
with gzip.open(join(dirname(__file__), 'AN_Spark/data/World_Country_Boundaries.csv.gz')) as f:
    decoded = codecs.iterdecode(f, "utf-8")
    next(decoded)
    reader = csv.reader(decoded, delimiter=',', quotechar='"')
    for row in reader:
        geometry, code, name = row
        xml = et.fromstring(geometry)
        lats = []
        lons = []
        for i, poly in enumerate(xml.findall('.//outerBoundaryIs/LinearRing/coordinates')):
            if i > 0:
                lats.append(nan)
                lons.append(nan)
            coords = (c.split(',')[:2] for c in poly.text.split())
            lat, lon = list(zip(*[(float(lat), float(lon)) for lon, lat in
                coords]))
            lats.extend(lat)
            lons.extend(lon)
        data[code] = {
            'name'   : name,
            'lats'   : lats,
            'lons'   : lons,
        }
In [5]:
len(data)
Out[5]:
235

在第二步中,我们加载了一组希望可视化的重要推文样本及其相应的地理位置信息:

In [69]:
# data
#
#
In [8]:
import pandas as pd
csv_in = '/home/an/spark/spark-1.5.0-bin-hadoop2.6/examples/AN_Spark/data/spark_tweets_20.csv'
t20_df = pd.read_csv(csv_in, index_col=None, header=0, sep=',', encoding='utf-8')
In [9]:
t20_df.head(3)
Out[9]:
    id  created_at  user_id     user_name   tweet_text  htag    urls    ptxt    tgrp    date    user_handles    txt_terms   search_grp  lat     lon
0   638818911773856000  Tue Sep 01 21:01:11 +0000 2015  2511247075  Noor Din    RT @kdnuggets: R leads RapidMiner, Python catc...   [#KDN]  [://t.co/3bsaTT7eUs]    r leads rapidminer python catches up big data ...   [spark, python]     2015-09-01 21:01:11     [@kdnuggets]    r leads rapidminer python catches up big data ...   [spark, python]     37.279518   -121.867905
1   622142176768737000  Fri Jul 17 20:33:48 +0000 2015  24537879    IBM Cloudant    Be one of the first to sign-up for IBM Analyti...   [#ApacheSpark, #SparkInsight]   [://t.co/C5TZpetVA6, ://t.co/R1L29DePaQ]    be one of the first to sign up for ibm analyti...   [spark]     2015-07-17 20:33:48     []  be one of the first to sign up for ibm analyti...   [spark]     37.774930   -122.419420
2   622140453069169000  Fri Jul 17 20:26:57 +0000 2015  515145898   Arno Candel     Nice article on #apachespark, #hadoop and #dat...   [#apachespark, #hadoop, #datascience]   [://t.co/IyF44pV0f3]    nice article on apachespark hadoop and datasci...   [spark]     2015-07-17 20:26:57     [@h2oai]    nice article on apachespark hadoop and datasci...   [spark]     51.500130   -0.126305
In [98]:
len(t20_df.user_id.unique())
Out[98]:
19
In [17]:
t20_geo = t20_df[['date', 'lat', 'lon', 'user_name', 'tweet_text']]
In [24]:
# 
t20_geo.rename(columns={'user_name':'user', 'tweet_text':'text' }, inplace=True)
In [25]:
t20_geo.head(4)
Out[25]:
    date    lat     lon     user    text
0   2015-09-01 21:01:11     37.279518   -121.867905     Noor Din    RT @kdnuggets: R leads RapidMiner, Python catc...
1   2015-07-17 20:33:48     37.774930   -122.419420     IBM Cloudant    Be one of the first to sign-up for IBM Analyti...
2   2015-07-17 20:26:57     51.500130   -0.126305   Arno Candel     Nice article on #apachespark, #hadoop and #dat...
3   2015-07-17 19:35:31     51.500130   -0.126305   Ira Michael Blonder     Spark 101: Running Spark and #MapReduce togeth...
In [22]:
df = t20_geo
#

在第三步中,我们首先导入了所有必要的 Bokeh 库。我们将在 Jupyter Notebook 中实例化输出。我们加载了世界各国的边界信息。我们获取了地理定位的推文数据。我们实例化了 Bokeh 交互工具,如滚动和框选放大,以及悬停工具。

In [29]:
#
# Bokeh Visualization of tweets on world map
#
from bokeh.plotting import *
from bokeh.models import HoverTool, ColumnDataSource
from collections import OrderedDict

# Output in Jupiter Notebook
output_notebook()

# Get the world map
world_countries = data.copy()

# Get the tweet data
tweets_source = ColumnDataSource(df)

# Create world map 
countries_source = ColumnDataSource(data= dict(
    countries_xs=[world_countries[code]['lons'] for code in world_countries],
    countries_ys=[world_countries[code]['lats'] for code in world_countries],
    country = [world_countries[code]['name'] for code in world_countries],
))

# Instantiate the bokeh interactive tools 
TOOLS="pan,wheel_zoom,box_zoom,reset,resize,hover,save"

现在,我们已经准备好将收集到的各种元素层叠到一个名为p的对象图中。定义p的标题、宽度和高度。附加工具。通过具有浅色背景和边界的补丁创建世界地图背景。根据其相应的地理坐标绘制推文的散点图。然后,激活悬停工具,显示用户及其相应的推文。最后,在浏览器上渲染图片。代码如下:

# Instantiante the figure object
p = figure(
    title="%s tweets " %(str(len(df.index))),
    title_text_font_size="20pt",
    plot_width=1000,
    plot_height=600,
    tools=TOOLS)

# Create world patches background
p.patches(xs="countries_xs", ys="countries_ys", source = countries_source, fill_color="#F1EEF6", fill_alpha=0.3,
        line_color="#999999", line_width=0.5)

# Scatter plots by longitude and latitude
p.scatter(x="lon", y="lat", source=tweets_source, fill_color="#FF0000", line_color="#FF0000")
# 

# Activate hover tool with user and corresponding tweet information
hover = p.select(dict(type=HoverTool))
hover.point_policy = "follow_mouse"
hover.tooltips = OrderedDict([
    ("user", "@user"),
   ("tweet", "@text"),
])

# Render the figure on the browser
show(p)
BokehJS successfully loaded.

inspect

#
#

以下代码概述了世界地图,红点代表推文来源的位置:

地理定位推文

我们可以悬停在特定的点上,以显示该位置的推文:

地理定位推文

我们可以放大到特定位置:

地理定位推文

最后,我们可以在给定的放大位置中显示推文:

地理定位推文

在 Google 地图上显示即将举行的聚会

现在,我们的目标是专注于伦敦即将举行的聚会。我们正在对Data Science LondonApache SparkMachine Learning三次聚会进行地图绘制。我们在 Bokeh 可视化中嵌入了 Google 地图,并根据它们的坐标进行地理定位,并使用悬停工具获取每次聚会的名称等信息。

首先,导入所有必要的 Bokeh 库:

In [ ]:
#
# Bokeh Google Map Visualization of London with hover on specific points
#
#
from __future__ import print_function

from bokeh.browserlib import view
from bokeh.document import Document
from bokeh.embed import file_html
from bokeh.models.glyphs import Circle
from bokeh.models import (
    GMapPlot, Range1d, ColumnDataSource,
    PanTool, WheelZoomTool, BoxSelectTool,
    HoverTool, ResetTool,
    BoxSelectionOverlay, GMapOptions)
from bokeh.resources import INLINE

x_range = Range1d()
y_range = Range1d()

我们将实例化 Google 地图,它将作为我们的 Bokeh 可视化的基础:

# JSON style string taken from: https://snazzymaps.com/style/1/pale-dawn
map_options = GMapOptions(lat=51.50013, lng=-0.126305, map_type="roadmap", zoom=13, styles="""
[{"featureType":"administrative","elementType":"all","stylers":[{"visibility":"on"},{"lightness":33}]},
 {"featureType":"landscape","elementType":"all","stylers":[{"color":"#f2e5d4"}]},
 {"featureType":"poi.park","elementType":"geometry","stylers":[{"color":"#c5dac6"}]},
 {"featureType":"poi.park","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":20}]},
 {"featureType":"road","elementType":"all","stylers":[{"lightness":20}]},
 {"featureType":"road.highway","elementType":"geometry","stylers":[{"color":"#c5c6c6"}]},
 {"featureType":"road.arterial","elementType":"geometry","stylers":[{"color":"#e4d7c6"}]},
 {"featureType":"road.local","elementType":"geometry","stylers":[{"color":"#fbfaf7"}]},
 {"featureType":"water","elementType":"all","stylers":[{"visibility":"on"},{"color":"#acbcc9"}]}]
""")

从上一步的类GMapPlot中实例化 Bokeh 对象绘图,使用先前步骤的尺寸和地图选项:

# Instantiate Google Map Plot
plot = GMapPlot(
    x_range=x_range, y_range=y_range,
    map_options=map_options,
    title="London Meetups"
)

引入我们希望绘制的三次聚会的信息,并通过悬停在相应坐标上获取信息:

source = ColumnDataSource(
    data=dict(
        lat=[51.49013, 51.50013, 51.51013],
        lon=[-0.130305, -0.126305, -0.120305],
        fill=['orange', 'blue', 'green'],
        name=['LondonDataScience', 'Spark', 'MachineLearning'],
        text=['Graph Data & Algorithms','Spark Internals','Deep Learning on Spark']
    )
)

定义要在 Google 地图上绘制的点:

circle = Circle(x="lon", y="lat", size=15, fill_color="fill", line_color=None)
plot.add_glyph(source, circle)

定义要在此可视化中使用的 Bokeh 工具的字符串:

# TOOLS="pan,wheel_zoom,box_zoom,reset,hover,save"
pan = PanTool()
wheel_zoom = WheelZoomTool()
box_select = BoxSelectTool()
reset = ResetTool()
hover = HoverTool()
# save = SaveTool()

plot.add_tools(pan, wheel_zoom, box_select, reset, hover)
overlay = BoxSelectionOverlay(tool=box_select)
plot.add_layout(overlay)

激活hover工具,并携带信息:

hover = plot.select(dict(type=HoverTool))
hover.point_policy = "follow_mouse"
hover.tooltips = OrderedDict([
    ("Name", "@name"),
    ("Text", "@text"),
    ("(Long, Lat)", "(@lon, @lat)"),
])

show(plot)

渲染给出伦敦相当不错的视图的绘图:

在 Google 地图上显示即将举行的聚会

一旦我们悬停在突出显示的点上,我们就可以获取给定聚会的信息:

在 Google 地图上显示即将举行的聚会

完整的平滑缩放功能得到保留,如下面的截图所示:

在 Google 地图上显示即将举行的聚会

总结

在本章中,我们专注于一些可视化技术。我们看到了如何构建词云及其直观的力量,一眼就可以揭示成千上万条推文中的关键词、情绪和流行词。

然后我们讨论了使用 Bokeh 进行交互式地图可视化。我们从零开始构建了一张世界地图,并创建了一个关键推文的散点图。一旦地图在浏览器上呈现出来,我们就可以交互式地从一个点悬停到另一个点,并显示来自世界各地不同地区的推文。

我们最终的可视化重点是在 Spark、数据科学和机器学习上映射伦敦即将举行的聚会,以及它们各自的主题,使用实际的谷歌地图制作了一个美丽的交互式可视化。

posted @ 2024-05-21 12:55  绝不原创的飞龙  阅读(24)  评论(0编辑  收藏  举报