Python-开发者的-Spark-指南-全-

Python 开发者的 Spark 指南(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Spark for Python Developers旨在结合 Python 的优雅性和灵活性以及 Apache Spark 的强大功能和多功能性。Spark 是用 Scala 编写的,在 Java 虚拟机上运行。尽管如此,它仍然是多语言的,并为 Java、Scala、Python 和 R 提供了绑定和 API。Python 是一种设计良好的语言,拥有丰富的专用库集。本书在 PyData 生态系统中探讨了 PySpark。一些突出的 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 算法库不断扩大的算法库。它涵盖了监督学习和无监督学习、推荐系统、优化和特征提取算法。我们将从 Twitter 收集的数据集通过 Python Scikit-Learn 和 Spark MLlib K-means 聚类来分离与Apache Spark相关的推文。

第五章, 使用 Spark 进行实时数据流,阐述了流式架构应用的基础,并描述了它们的挑战、限制和优势。我们使用 TCP 套接字来阐述流式概念,然后直接从 Twitter 的实时数据流中获取并处理实时推文。我们还描述了 Flume,这是一个可靠、灵活且可扩展的数据摄取和传输管道系统。Flume、Kafka 和 Spark 的结合在不断变化的环境中提供了无与伦比的鲁棒性、速度和敏捷性。我们在本章的最后对两种流式架构范式,即 Lambda 架构和 Kappa 架构,进行了一些评论和观察。

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

您需要这本书所需的东西

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

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

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

这本书面向的对象

目标受众包括以下人群:

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

  • 专注于 Python 的软件开发者将能够迅速扩展他们的技能,使用 Spark 作为处理引擎和 Python 可视化库以及 Web 框架来创建数据密集型应用。

  • 能够创建快速数据管道并构建包含批处理和流处理以实时提供数据洞察的著名 Lambda 架构的数据架构师也将从这本书中受益。

惯例

在这本书中,您将找到许多不同风格的文章,以区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 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 管理器并点击新建按钮。”

注意

警告或重要提示将以这样的框显示。

小贴士

小技巧和窍门如下所示。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者反馈对我们开发您真正能从中获得最大价值的标题非常重要。

要发送给我们一般反馈,只需发送电子邮件到<feedback@packtpub.com>,并在您的邮件主题中提及书籍标题。

如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您已经是 Packt 书籍的骄傲所有者,我们有一些事情可以帮助您从您的购买中获得最大价值。

下载示例代码

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

错误清单

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误清单提交表链接,并输入您的错误清单详情来报告它们。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support选择您的标题来查看。

侵权

互联网上对版权材料的盗版是一个持续存在的问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者和我们提供有价值内容的能力方面的帮助。

问题和建议

如果你在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。

第一章. 设置 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 库。

  • 构建一个word count示例应用程序以确保一切正常工作。

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

这包括新的基础设施和 DevOps(代表开发和运营),这些概念利用了虚拟化、云计算技术和软件定义网络。

为了处理 PB 级的数据,Hadoop 被开发并开源,其灵感来自谷歌文件系统GFS)和相邻的分布式计算框架 MapReduce。在控制成本的同时克服扩展的复杂性,也导致了新的数据存储的激增。最近数据库技术的例子包括 Cassandra,一个列式数据库;MongoDB,一个文档数据库;以及 Neo4J,一个图数据库。

Hadoop 凭借其处理大量数据集的能力,催生了一个庞大的生态系统,通过 Pig、Hive、Impala 和 Tez 等工具以更迭代和交互的方式查询数据。由于 Hadoop 仅以批处理模式使用 MapReduce 进行操作,因此它显得有些繁琐。Spark 通过针对磁盘输入/输出和带宽密集型 MapReduce 作业的不足,正在创造分析和数据处理领域的革命。

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

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

构建数据密集型应用需要高度可扩展的基础设施、多语言存储、无缝数据集成、多范式分析处理和高效的可视化。以下段落描述了本书将采用的数据密集型应用架构蓝图。它是本书的骨架。我们将在大 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 驱动:连接收集纠正组合消费

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

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

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

  • Collect:考虑将哪些数据存储在哪里以及以何种格式,以简化后续的数据组合和消费

  • Compose:专注于如何将收集到的各种数据集混合在一起,并丰富信息,以构建一个引人入胜的数据驱动产品

  • 消费:负责数据提供和渲染,以及如何确保数据在正确的时间到达正确的个人。

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

以下图描述了数据获取和精炼的迭代过程,以便消费:

集成层

分析层

分析层是 Spark 使用各种模型、算法和机器学习管道处理数据以提取见解的地方。对于我们的目的,在本章中,分析层由 Spark 提供。我们将在后续章节中深入探讨 Spark 的优点。简而言之,使其如此强大的原因是它允许在单个统一平台上进行多种分析处理范式。它允许批量、流式和交互式分析。在大型数据集上进行的批量处理,具有较长的延迟期,使我们能够提取可以输入到流模式中实时事件中的模式和见解。交互式和迭代分析更适合数据探索。Spark 提供了 Python 和 R 的绑定和 API。通过其 SparkSQL 模块和 Spark DataFrame,它提供了一个非常熟悉的分析界面。

交互层

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

理解 Spark

随着数据的增长,Hadoop 横向扩展。Hadoop 在通用硬件上运行,因此具有成本效益。通过可扩展的分布式处理框架,密集型数据应用得以实现,这些框架允许组织在大型通用集群上分析 PB 级数据。Hadoop 是第一个开源的 map-reduce 实现。Hadoop 依赖于名为 HDFSHadoop 分布式文件系统)的分布式存储框架。Hadoop 在批处理作业中运行 map-reduce 任务。Hadoop 需要在每个 map、shuffle 和 reduce 处理步骤中将数据持久化到磁盘。此类批处理作业的额外开销和延迟会严重影响性能。

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

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

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

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

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

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

下图突出了前面的四种处理风格:

理解 Spark

Spark 运行在三种模式下:一种单机模式,在单个机器上独立运行,以及两种在机器集群上的分布式模式——在 Yarn 上,Hadoop 分布式资源管理器,或在 Mesos 上,与 Spark 同时由伯克利开发的开源集群管理器:

理解 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 并不是在 Java 允许的 Python 方言(如 Jython)上对 Spark 的转录版本。PySpark 在 Spark 周围提供了集成的 API 绑定,并允许在集群的所有节点上使用 Python 生态系统,包括 pickle Python 序列化和,更重要的是,提供了对 Python 的机器学习库(如 Scikit-Learn)或数据处理(如 Pandas)的丰富生态系统访问。

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

以下是一个示例,说明 Spark 驱动程序如何管理 PySpark 上下文以及 Spark 上下文,包括其本地文件系统和它与 Spark 工作节点通过集群管理器的交互:

PySpark 实践

弹性分布式数据集

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 的转换和操作:

Resilient Distributed Dataset

理解 Anaconda

Anaconda 是由Continuum维护的广泛使用的免费 Python 发行版(www.continuum.io/)。我们将使用 Anaconda 提供的现有软件堆栈来生成我们的应用程序。在本书中,我们将使用 PySpark 和 PyData 生态系统。PyData 生态系统由Continuum推广、支持和维护,并由Anaconda Python 发行版提供动力。Anaconda Python 发行版在安装 Python 环境时节省了时间和麻烦;我们将与 Spark 一起使用它。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 及其依赖项,具体如下。

  • 安装包含所有必需库(如 Pandas、Scikit-Learn、Blaze 和 Bokeh)的 Anaconda Python 2.7 环境,并启用 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 虚拟机。

让我们一步步通过使用 VirtualBox 与 Ubuntu 的步骤:

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

  2. 安装 VirtualBox 后,让我们打开 Oracle VM VirtualBox 管理器并点击 新建 按钮。

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

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

  5. 安装完成后,建议通过进入(从 VirtualBox 菜单,在新虚拟机运行时)设备 | 插入 Guest Additions CD 图像来安装 VirtualBox Guest Additions。在 Windows 主机中未能提供 Guest Additions 将导致一个非常有限的用户界面,窗口大小减小。

  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
    
    
  3. 接受许可条款后,您将被要求指定安装位置(默认为 ~/anaconda)。

  4. 自解压完成后,您应该将 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
    
    
  2. 设置 JAVA_HOME 环境变量并确保 Java 程序已添加到您的 PATH 中。

  3. 检查 JAVA_HOME 是否已正确安装:

    # 
    $ echo JAVA_HOME
    
    

安装 Spark

访问 Spark 下载页面 spark.apache.org/downloads.html

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

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

  1. 选择 Spark 版本 1.5.2 (Nov 09 2015)

  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 Notebook

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

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

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

examples/AN_Spark 目录(其中存储了 Jupyter 或 IPython Notebooks)中启动 PySpark,使用 IPYNB

# 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进行交换,以便按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 Notebooks 上执行的。它们可以在以下 GitHub 仓库中找到:github.com/spark-mooc/mooc-setup/

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

在你的工作目录中克隆spark-mooc/mooc-setup/ GitHub 仓库,并在克隆的目录中运行命令$ vagrant up

请注意,Spark 的版本可能已经过时,因为vagrantfile可能不是最新的。

你将看到类似以下的输出:

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 Notebooks:

使用 Vagrant 虚拟化环境

转移到云

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

在 Amazon Web Services 中部署应用

一旦我们准备好扩展我们的应用,我们可以将我们的开发环境迁移到亚马逊 Web ServicesAWS)。

如何在 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
    
    
  3. 启动你的集群:

    ~$ cd $SPARK_HOME/ec2
    ec2$ ./spark-ec2 -k <keypair> -i <key-file> -s <num-slaves> launch <cluster-name>
    
    
  4. 通过 SSH 连接到集群以运行 Spark 作业:

    ec2$ ./spark-ec2 -k <keypair> -i <key-file> login <cluster-name>
    
    
  5. 使用完毕后销毁你的集群:

    ec2$ ./spark-ec2 destroy <cluster-name>
    
    

使用 Docker 虚拟化环境

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

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

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

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

以下图示展示了启用了 Docker 的环境,包括 Spark、Anaconda 和数据库服务器及其相应的数据卷。

使用 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。我们将在下一章中专门讨论理解流式分析。

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

动态数据处理

交互式探索数据

构建一个数据密集型应用并不像将数据库暴露给网络界面那样简单。在设置静态数据和动态数据处理的过程中,我们将利用 Spark 分析数据交互式的能力,以及优化机器学习和流式活动所需的数据丰富性和质量。在这里,我们将通过数据收集、精炼和调查的迭代周期,以获取我们应用感兴趣的数据集。

连接到社交网络

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

以下图表描述了数据获取和精炼的迭代过程,以便进行消费:

连接到社交网络

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

获取 Twitter 数据

Twitter 允许在 OAuth 授权协议下访问其搜索和流推文服务,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 查询,您可以在 dev.twitter.com/rest/tools/console 的开发者控制台中探索 Twitter API。

获取 Twitter 数据

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

获取 GitHub 数据

GitHub 使用与 Twitter 类似的认证过程。前往开发者网站,在 GitHub 上注册后获取您的凭证,注册地址为 developer.github.com/v3/

获取 GitHub 数据

获取 Meetup 数据

Meetup 可以通过 Meetup.com 开发者资源中发放的令牌访问。获取 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
    
    
  2. 创建 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'] ]
    
  3. 使用所需的认证实例化类:

    t= TwitterAPI()
    
  4. 对查询词 Apache Spark 进行搜索:

    q="ApacheSpark"
    tsearch = t.searchTwitter(q)
    
  5. 分析 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}}
    
  6. 解析 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
    
  2. 以编程方式创建客户端以实例化 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)
    
  3. 从 Apache 用户中检索关键事实。GitHub 上有 640 个活跃的 Apache 仓库:

    repos_apache = [repo.name for repo in g.get_user('apache').get_repos()]
    len(repos_apache)
    640
    
  4. 从 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}
    
  5. 获取 Spark GitHub 仓库网络的一些关键参与者。在撰写本文时,Apache Spark 仓库有 3,738 个 star。网络非常庞大。第一个 star 是 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. 检索类似 London Data Science 的 Meetup 群组过去活动的信息。

  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
    
  2. 然后,我们将从给定的 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}
    
  3. 获取 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}
    

预览我们的应用程序

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

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

    • 高频 Twitter 用户在Apache Spark

    • GitHub 的提交者

    • 领先的 Meetup 演讲

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

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

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

预览我们的应用程序

摘要

在本章中,我们概述了我们的应用程序的整体架构。我们解释了处理数据的主要范式:批处理,也称为静态数据,以及流分析,称为动态数据。我们继续建立与三个感兴趣的社会网络的连接:Twitter、GitHub 和 Meetup。我们采样了数据,并提供了我们旨在构建的预览。本书的其余部分将专注于 Twitter 数据集。我们提供了访问三个社交网络的工具和 API,以便您可以在以后阶段创建自己的数据混合。我们现在准备调查收集到的数据,这将是下一章的主题。

在下一章中,我们将更深入地探讨数据分析,提取我们目的的关键属性,并管理批处理和流处理的信息存储。

第三章:使用 Spark 处理数据

根据上一章中概述的批处理和流式架构,我们需要数据来为我们的应用程序提供动力。我们将从 Twitter 收集专注于 Apache Spark 的数据。本章的目标是为机器学习和流式应用程序准备数据。本章重点介绍如何在分布式网络中交换代码和数据。我们将获得关于序列化、持久化、打包和缓存的实用见解。我们将掌握 Spark SQL,这是 Spark 中用于交互式探索结构化和半结构化数据的关键模块。驱动 Spark SQL 的基本数据结构是 Spark 数据框。Spark 数据框受到 Python Pandas 数据框和 R 数据框的启发。它是一个强大的数据结构,被具有 R 或 Python 背景的数据科学家广泛理解和欣赏。

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

  • 连接到 Twitter,收集相关数据,并将其以 JSON 和 CSV 等格式以及 MongoDB 等数据存储方式持久化

  • 使用 Blaze 和 Odo(Blaze 的一个衍生库)分析数据,以便从各种来源和目的地连接和传输数据

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

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

让我们首先从数据密集型应用架构的角度来界定本章的重点。我们将集中注意力于集成层,并基本上运行数据获取、精炼和持久化的迭代周期。这个周期被称为五个 C,即连接收集纠正组合消费。它们是我们为了从 Twitter 获取正确质量和数量的数据而在集成层运行的基本过程。我们还将深入了解持久化层,并设置一个如 MongoDB 这样的数据存储来收集我们的数据以便后续处理。

我们将使用 Blaze(一个用于数据操作的 Python 库)和 Spark SQL(Spark 的交互式数据发现模块,由 Spark 数据框驱动)探索数据。数据框范式由 Python Pandas、Python Blaze 和 Spark SQL 共享。我们将了解三种数据框风味的细微差别。

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

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

序列化和反序列化数据

由于我们在速率限制约束下从 Web API 收集数据,我们需要存储它们。由于数据在分布式集群上处理,我们需要一致的方式来保存状态并在以后使用时检索它。

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

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

持久化将程序的状态数据保存到磁盘或内存中,以便在重启时从上次停止的地方继续。它将 Python 对象从内存保存到文件或数据库,并在稍后以相同的状态加载它。

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

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

缓存对于 Spark 来说是一个关键概念,因为它允许我们将 RDDs 存储在内存中或通过溢出到磁盘。可以根据数据的血缘关系或应用于 RDDs 的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
    
    
  2. 为 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
    
    
  3. 使用sudo更新本地软件包数据库:

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

    sudo apt-get install -y mongodb-org
    
    

运行 MongoDB 服务器

让我们启动 MongoDB 服务器:

  1. 要启动 MongoDB 服务器,我们执行以下命令以启动mongod

    sudo service mongodb start
    
    
  2. 为了检查 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 中运行。

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

  4. 我们可以检查 /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
    
    
  5. 为了停止 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

创建 MongoDB 的 Python 客户端

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

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 的调用更加健壮,而不会因为访问数据流而被禁止。

让我们一步步来:

  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)
    
  2. 我们通过提供日志级别来初始化日志记录器:

    • logger.debug(调试信息)

    • logger.info(信息消息)

    • logger.warn(警告消息)

    • logger.error(错误消息)

    • logger.critical(关键信息)

  3. 我们设置日志路径和消息格式:

            # 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)
    
  4. 我们初始化 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)
    
  5. 我们初始化 MongoDB 数据库和集合以实现持久化:

            # Save to MongoDB Intitialisation
            self.mongoSaver = IO_mongo(db='twtr01_db', coll='twtr01_coll')
    
  6. 方法 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
    
  7. 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)
    
  8. 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'] ]
    
  9. 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
    
  10. 在这里,我们根据指定的参数调用 searchTwitter API 的相关查询。如果遇到任何错误,例如来自提供者的速率限制,这将由 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 dataframe。Blaze 扩展到离核计算,而 Pandas 和 Numpy 是单核。

Blaze 在各种后端提供可适应的、统一的和一致的用户界面。Blaze 协调以下操作:

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

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

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

Blaze 表达式是惰性评估的,在这方面与 Spark RDDs 转换具有相似的处理范式。

让我们首先导入必要的库: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 Dataframe 运行到 describe() 函数以获取有关数据集的一些总体信息:

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))

计算数据框中的记录数:

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 信息,它给我们记录数和 schema:

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 可以推断的 schema,如果需要,在注册 dataframe 时可以请求。它允许我们使用普通的 SQL 查询复杂的嵌套 JSON 数据。延迟评估、血缘、分区和持久化适用于数据框。

让我们使用 Spark SQL 查询数据,首先导入 SparkContextSQLContext

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 数据框的模式:

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)

我们从 dataframe 中选择 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     

我们将 dataframe 注册为表,因此可以在其上执行 SQL 查询:

twts_sql_df_01.registerAsTable('tweets_01')

我们在 dataframe 上执行 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 文件的 schema:

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)

我们通过在 dataframe 中选择特定的列(在这种情况下,['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 查询优化器

我们在 dataframe 上执行 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 并使用 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

与 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.orgStratio 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 Notebook 以及spark-mongodb包的命令应明确指定包参数:

$ 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_colllocalhost: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's的 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 的监督学习和无监督机器学习算法以及预处理技术:

监督学习和无监督学习

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

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

    • K-Means:这种算法将数据划分为 K 个不同的簇

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

    • 幂迭代聚类(PIC):这种算法根据成对边的相似性对图的顶点进行分组

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

    • 流 K-Means:这意味着使用输入数据的窗口函数动态地流式传输数据的聚类。

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

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

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

  • 回归和分类:回归通过使用标记的训练数据来预测输出值,而分类将结果分组到类别中。分类的因变量是分类的或无序的,而回归的因变量是连续的且有顺序的:

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

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

    • 决策树:这执行特征空间的递归二分分区。为了确定最佳的分割,树节点级别的信息增益被最大化。

    • 树集成(随机森林和梯度提升树):树集成算法通过组合基础决策树模型来构建一个性能良好的模型。它们直观且在分类和回归任务中非常成功。

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

额外的学习算法

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

额外的学习算法

以下是在 Spark 中当前可用的其他 MLlib 算法:

  • 协同过滤:这是推荐系统的基础。它创建一个用户-项目关联矩阵并试图填补空白。基于其他用户和项目及其评分,它推荐一个目标用户没有评分的项目。在分布式计算中,最成功的算法之一是 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]),其中 4 表示向量的维度。

      这里是 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

      • 坐标矩阵: 这对于表示非常大且非常稀疏的矩阵非常有用。坐标矩阵是由MatrixEntry点的 RDD 创建的,这些点由一个类型为(long,long 或 float)的元组表示。

      • 块矩阵: 这些矩阵是由子矩阵块 RDD 创建的,其中子矩阵块是 ((块行索引, 块列索引), 子矩阵)

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

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

监督式机器学习工作流程

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

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

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

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

    在监督学习的情况下,我们将输入数据分为训练集和测试集。我们还可以实施各种采样和分割数据集的策略,用于交叉验证。

  • 准备数据:在这一步,我们获取算法期望的格式或数据类型的数据。在 Spark MLlib 的情况下,这包括本地向量、密集或稀疏向量、标记点、本地矩阵、带有行矩阵的分布式矩阵、索引行矩阵、坐标矩阵和块矩阵。

  • 模型:在这一步,我们应用适合当前问题的算法,并获取评估步骤中最适合算法的结果。我们可能有多种适合问题的算法;它们各自的表现将在评估步骤中评分,以选择最佳表现者。我们可以实现集成或模型组合,以达到最佳结果。

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

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

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

监督机器学习工作流程

无监督机器学习工作流程

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

无监督学习可以是监督学习的一个初步步骤。也就是说,我们在攻击学习阶段之前查看减少数据的维度。

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

无监督机器学习工作流程

对 Twitter 数据集进行聚类

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

  1. 将所有推文文件合并到一个单独的数据框中。

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

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

    • 分词:这会将解析的推文文本分解成单个单词或标记

    • TF-IDF:这将对分词后的推文文本应用 TF-IDF 算法以创建特征向量

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

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

  5. 评估 K-Means 聚类结果:

    • 识别推文所属的聚类

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

    • 绘制聚类

  6. 管道:

    • 微调相关聚类数 K

    • 测量模型成本

    • 选择最佳模型

在 Twitter 数据集上应用 Scikit-Learn

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

我们有 7,540 条推文的混合体。它包含与 Apache Spark、Python、即将到来的总统选举(希拉里·克林顿和唐纳德·特朗普为主角)相关的推文,以及一些与时尚和音乐(Lady Gaga 和贾斯汀·比伯)相关的推文。我们正在使用 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']

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

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

由于数据集现在已分解为 7,540 个样本和 6,638 个特征向量,我们可以将这个稀疏矩阵输入到 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 稀疏向量被转换成一个 RDD,它将成为 Spark MLlib 程序的输入。我们用 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 不断扩大的算法库 Spark MLlib。我们讨论了监督学习和无监督学习、推荐系统、优化和特征提取算法。然后我们将从 Twitter 收集的数据放入机器学习过程、算法和评估中,以从数据中提取见解。我们将 Twitter 收集的数据集通过 Python Scikit-Learn 和 Spark MLlib K-means 聚类进行分离,以隔离与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 通过使用拓扑或有向无环图(DAG)实现无界元组的无状态亚毫秒延迟处理,其中将 spouts 作为数据流源,bolts 用于过滤、连接、聚合和转换等操作。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作为流功能入口点。集群管理器将至少分配一个工作节点作为接收器,该节点将是一个具有长任务的执行器,用于处理传入的流。执行器从输入数据流创建离散流或 DStream,并默认将其复制到另一个工作节点的缓存中。一个接收器服务一个输入数据流。多个接收器提高了并行性,并生成多个 Spark 可以联合或连接的弹性分布式数据集(RDD)。

下图概述了 Spark Streaming 的内部工作原理。客户端通过集群管理器与 Spark 集群交互,而 Spark Streaming 有一个专门的工人,它有一个长期运行的任务,用于摄取输入数据流并将其转换为离散流或 DStream。数据由接收器收集、缓冲和复制,然后推送到 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 连接器提供持久化的 Discretized Streams 组成。

对于 Spark Core 来说,基本的数据结构是 RDD,Spark Streaming 的基本编程抽象是离散流或 DStream。

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

深入 Spark Streaming 内部

DStream 在批处理间隔内快照传入的数据。这些时间步通常从 500 毫秒到几秒不等。DStream 的底层结构是一个 RDD。

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

下图概述了 DStream 的生命周期。从创建消息的微批到在 RDD 上应用转换函数和触发 Spark 作业的操作。分解图中展示的步骤,我们自上而下地读取图:

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

  2. 在离散流步骤中,缓冲的微批次被转换为 DStream RDDs。

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

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

Spark Streaming 内部结构

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

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

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

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

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

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

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

Spark Streaming 内部结构

一个示例函数是 countByWindow (windowLength, slideInterval)。它返回一个新的 DStream,其中每个 RDD 通过在此 DStream 上对滑动窗口中的元素数量进行计数来生成一个单一元素。在这种情况下,一个例子是每 60 秒对推文流中给定标签的运行计数。窗口时间范围被指定。

分分钟级的窗口长度是合理的。小时级的窗口长度不建议使用,因为它计算和内存密集。在 Cassandra 或 HBase 等数据库中聚合数据会更方便。

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

构建容错性

实时流处理系统必须全天候运行。它们需要能够抵御系统中的各种故障。Spark 及其 RDD 抽象设计用于无缝处理集群中任何工作节点的故障。

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

写入前日志、可靠的接收器和文件流确保自 Spark 版本 1.2 起零数据丢失。写入前日志代表接收数据的容错存储。

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

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

作为对流操作整体理解的垫脚石,我们首先将实验 TCP 套接字。TCP 套接字在客户端和服务器之间建立双向通信,并且可以通过建立的连接交换数据。WebSocket 连接是长连接,与典型的 HTTP 连接不同。HTTP 不旨在从服务器保持一个打开的连接以连续地向网络浏览器推送数据。因此,大多数网络应用都通过频繁的 异步 JavaScript (AJAX) 和 XML 请求进行长轮询。WebSocket,标准化并在 HTML5 中实现,正超越网络浏览器,成为客户端和服务器之间实时通信的跨平台标准。

设置 TCP 套接字

我们通过运行 netcat,一个在大多数 Linux 系统中找到的小工具,作为数据服务器,使用命令 > nc -lk 9999 来创建一个 TCP Socket 服务器,其中 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 Streaming 中的示例程序 network_wordcount.py,该程序包含在 Spark 包中。它可以在 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 Context:

    ssc = StreamingContext(sc, 1)
    
    
  2. 接下来,设置流计算。

  3. 定义了一个或多个接收数据的 DStream 对象,以连接到localhost127.0.0.1上的port 9999

    stream = ssc.socketTextStream("127.0.0.1", 9999)
    
    
  4. DStream 计算被定义:转换和输出操作:

    stream.map(x: lambda (x,1))
    .reduce(a+b)
    .print()
    
  5. 计算已经开始:

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

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

    ssc.stop()
    
    

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

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

#
# 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

通过连接到port 9999上的本地 socket 来运行 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
-------------------------------------------

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

实时操作 Twitter 数据

Twitter 提供了两个 API。一个是搜索 API,它本质上允许我们根据搜索词检索过去的推文。这就是我们在本书的前几章中从 Twitter 收集数据的方式。有趣的是,为了我们当前的目的,Twitter 提供了一个实时流 API,它允许我们实时获取博客圈中发出的推文。

实时从 Twitter 的 firehose 处理推文

以下程序连接到 Twitter 的 firehose,处理传入的推文以排除已删除或无效的推文,并即时解析相关的推文以提取screen name、实际的推文或tweet textretweet计数、geo-location信息。处理后的推文被 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 进行流推文并即时处理它们的例子。

构建一个可靠且可扩展的流应用程序

数据摄取是获取来自各种来源的数据并将其存储以供立即处理或稍后阶段处理的过程。数据消费系统分散,可能在物理和架构上远离来源。数据摄取通常通过脚本和基本的自动化手动实现。它实际上需要更高级别的框架,如 Flume 和 Kafka。

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

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

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

为了实现一个能够摄入多个数据流,在飞行中处理它,并从中快速做出决策的事件驱动型业务,关键驱动因素是统一日志。

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

统一日志的特性如下:

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

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

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

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

  • 快速:系统每秒处理数千条消息

设置 Kafka

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

Apache Kafka 是一个重新思考为分布式提交日志的分布式发布/订阅消息系统。消息按主题存储。

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

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

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

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

  • 作为企业级消息系统,具有低延迟

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

消息存储在具有唯一顺序 ID 的分区中,称为offset。消费者通过(offsetpartitiontopic)元组跟踪它们的指针。

让我们深入了解 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 版本并 un-tar 它:

    > tar -xzf kafka_2.10-0.8.2.0.tgz
    > cd kafka_2.10-0.8.2.0
    
    
  3. 启动 zooeper。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)...
    
    
  4. 现在启动 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)
    
    
  5. 创建一个主题。让我们创建一个名为 test 的主题,它只有一个分区和一个副本:

    > bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
    
    
  6. 如果我们运行 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
    
    
  7. 通过创建生产者和消费者来检查 Kafka 的安装。我们首先启动一个 producer 并在控制台输入一条消息:

    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
    
    
  8. 然后我们启动一个消费者来检查我们是否接收到了消息:

    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
    
    
  2. 当我们使用 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
    -------------------------------------------
    
    
  3. 为了能够以编程方式开发生产者和消费者,并与 Kafka 和 Spark 交互,请安装 Kafka Python 驱动程序。我们将使用 David Arthur(GitHub 上的 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:代理是一个容器,它托管源、通道、接收器和其它元素,这些元素能够使事件从一个地方传输到另一个地方。它为托管组件提供配置、生命周期管理和监控。代理是一个运行 Flume 的物理 Java 虚拟机。

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

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

  • Channel: Channel 是源和 sink 之间的通道,它缓冲传入的事件,直到被 sink 清空。源将事件喂入通道,sink 清空通道。通道解耦了上游和下游系统的阻抗。上游的数据突发通过通道得到抑制。下游的故障被通道透明地吸收。根据这些事件调整通道容量是实现这些关键的关键。通道提供两种持久化级别:一种是内存通道,如果 JVM 崩溃则不可靠;另一种是支持写入前日志的文件通道,它将信息存储到磁盘上。通道是完全事务性的。

让我们通过以下示例说明所有这些概念:

探索 Flume

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

构建弹性的数据管道利用了前几节中学到的知识。我们使用 Flume 进行数据摄取和传输,使用可靠且复杂的发布/订阅消息系统(如 Kafka)进行数据经纪,最后使用 Spark Streaming 在线处理计算。

以下图示说明了流式数据管道的组成,即一系列 connectcollectconductcomposeconsumeconsigncontrol 活动。这些活动可以根据用例进行配置:

  • Connect 建立与流式 API 的绑定。

  • Collect 创建收集线程。

  • Conduct 通过创建缓冲队列或发布/订阅机制将数据生产者与消费者解耦。

  • Compose 专注于数据处理。

  • Consume 为消费系统提供处理后的数据。Consign 负责数据持久化。

  • Control 负责系统、数据和应用的治理和监控。

使用 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 及其同事的批评。从那时起,他们搬到了 Confluent,并以 Apache Kafka 作为 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

  • Michael Waskom开发的Seaborn是一个快速可视化统计信息的优秀库。它建立在 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 套接字来保持状态并更新 HTML5 画布,背后使用 Backbone.js 和 Coffee-script。由于 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...

现在,我们将创建一些实用函数来清理推文文本并解析 Twitter 日期。首先,我们导入 Python 正则表达式库re和用于解析日期和时间的time库:

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...

我们通过应用描述的实用函数创建新的 dataframe 列,创建一个新列用于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)

以下代码提供了新生成的 dataframe 的快速快照:

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 相关的推文来展示编程步骤。我们加载数据并预览 dataframe:

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)。可以通过调用conda install pil轻松安装 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]:

我们将 dataframe 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 和 wordcloud 库:

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 的情况下,Hadoopbig dataanalytics 是热门话题,而 Python 则回忆起其名称的根源 Monty Python,重点在于 developerapache spark 和编程,并有一些关于 java 和 ruby 的提示。

创建词云

我们还可以从以下词云中一瞥占据北美 2016 年总统选举候选人注意力的词汇:希拉里·克林顿和唐纳德·特朗普。显然,希拉里·克林顿被她的对手唐纳德·特朗普和伯尼·桑德斯的存在所掩盖,而特朗普则主要集中在他自己身上:

创建词云

有趣的是,在贾斯汀·比伯和 Lady Gaga 的情况下,出现了单词 love。在比伯的情况下,followbelieber 是关键词,而 dietweight lossfashion 是 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

#
#

以下代码给出了带有红色点的世界地图概览,代表推文来源的位置:

地理定位推文

我们可以悬停在特定的点上,以揭示该位置上的推文:

地理定位推文

我们可以放大到特定位置:

地理定位推文

最后,我们可以揭示给定放大位置上的推文:

地理定位推文

在谷歌地图上显示即将举行的聚会

现在,我们的目标是关注伦敦的即将举行的聚会。我们正在绘制三个聚会数据科学伦敦Apache Spark机器学习。我们在一个 Bokeh 可视化中嵌入谷歌地图,并根据它们的坐标地理定位这三个聚会,并使用悬停工具获取每个聚会的即将举行活动的信息。

首先,导入所有必要的 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()

我们将实例化作为我们的 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 对象plot

# 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']
    )
)

定义要在谷歌地图上绘制的点:

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)

渲染出伦敦的视图:

在谷歌地图上显示即将举行的聚会

一旦我们悬停在突出显示的点上,我们就可以获取给定聚会的信息:

在谷歌地图上显示即将举行的聚会

如以下截图所示,保留了完整的平滑缩放功能:

在谷歌地图上显示即将举行的聚会

摘要

在本章中,我们关注了几种可视化技术。我们看到了如何构建词云及其直观的强大功能,可以一眼揭示成千上万推文中携带的关键词、情绪和梗。

我们随后讨论了使用 Bokeh 的交互式地图可视化。我们从零开始构建了一个世界地图,并创建了一个关键推文的散点图。一旦地图在浏览器上渲染,我们就可以交互式地从一点移动到另一点,揭示来自世界各地不同部分的推文。

我们的最终可视化集中在伦敦即将举行的 Spark、数据科学和机器学习聚会及其相应主题的映射上,使用实际的谷歌地图制作了一个美丽的交互式可视化。

posted @ 2025-10-23 15:20  绝不原创的飞龙  阅读(3)  评论(0)    收藏  举报