第 14 章 自动化和规模化

你已经从 API 和网站爬取了大量的数据,也已经清洗和组织了数据,并且运行了统计学分析,生成了可视化报告。现在是时候让 Python 大展身手,自动化你的数据处理了。在这一章中,我们会介绍如何自动化数据分析、收集和发布。我们会学习如何创建合适的日志和警报,这样可以充分地自动化脚本,得到成功、失败以及工作中碰到的任何问题的通知。

我们还会学习使用 Python 库规模化自动化程序,帮助执行多个任务,并且监控它们的成功和失败。我们会分析一些库和辅助工具,充分地在云端规模化数据。

Python 提供了大量的自动化和规模化选择。有一些简单直接的任务可以在几乎所有的机器上自动化执行,而不需要太多的设置,同时也有一些更大型、更复杂的方式来实现自动化。我们会涉及这两者的示例,同时也会讲解作为数据处理者如何规模化数据自动化。

14.1 为什么要自动化

自动化提供了一种轻松运行脚本的方式,而不需要在本地机器上执行脚本,甚至不需要你醒着!自动化的能力意味着你可以把时间花在其他的思维密集型项目上。如果拥有一个完备的脚本来执行数据清洗,你就可以专注于研究数据,写出更好的报告。

下面是一些自动化可以帮上忙的示例。

  • 每周二输出一些新的分析结果;你要编制一份报告,并将其发送给相关方。

  • 其他部门或同事需要能够在没有你的指导和支持下运行报告工具或清洗工具。

  • 你需要每周进行一次数据下载、清洗和发送。

  • 每次用户请求新报告,报告脚本需要运行,并且在报告生成后通知用户。

  • 你需要每周清洗一次数据库里错误的数据,并且将其备份到其他地方。

这其中的每一个问题都有无数的解决方案,但是有一件事是确定的:它们是非常适合自动化的任务。它们的输出和步骤非常清晰。它们有一个有限但特定的听众群体。它们有确定的时间或事件来触发行动。此外,在特定的环境下它们都是可以用脚本和程序解决的事情。

当任务清晰、定义完整,并且输出非常容易确定的时候,自动化是最容易的。不管怎样,即使输出并不总是很容易测试或预测,自动化也可以帮助完成任务的一部分,把剩余的留给你(或其他人)更细致地研究和分析。你可以想象这里的自动化类似于你在生活中自动化其他的事情。你可能有一个最喜欢的已保存的比萨订单,或者一个邮件的自动回复。如果一个任务输出明确并定期发生,那么这是一个很适合自动化的任务。

但是什么时候不应该自动化呢?以下条件预示着该任务不是一个好的自动化选择。

  • 任务很少发生,并且非常复杂,自己做更好(例如,填写报税单)。

  • 任务的成功输出很难确定(例如,小组讨论、社会研究或调查)。

  • 任务需要与人交互来确定合适的完成方式(例如,交通导航、诗歌翻译)。

  • 任务成功是当务之急。

其中的一些例子,尤其是需要人工输入的例子,适合一定程度的自动化。有些任务可以通过让机器寻找建议来做部分自动化,之后再确定这些建议是对还是错(使用人工反馈的机器学习)。其他的任务,比如少见又复杂的任务,或者是非常重要的商业任务,随着对任务的熟悉,可能最后会实现自动化或部分自动化。但是你可以看到整体的逻辑来确定什么时候自动化更好,什么时候这不是一个好的想法。

如果你不确定自动化是否适合你,可以尝试自动化一些定期运行的小任务,看它如何工作。一段时间后,你很可能会发现更合适的解决方案,而且自动化一件事的经验会使你在未来自动化更多的事情更加容易。

14.2 自动化步骤

因为自动化从一个清晰简单的目标开始,所以自动化的步骤应该同样清晰和简单。文档化下面的问题对开始自动化特别有帮助(通过列表、白板、图纸或故事板)。

  • 任务何时开始?

  • 这个任务是否有时间限制或最大长度?如果有的话,什么时候结束?

  • 对这个任务来说,有哪些必需的输入?

  • 对这个任务来说,什么是成功或部分成功?

  • 如果任务失败,应该发生什么 ?

  • 任务产生或提供什么?面向谁?以何种方式?

  • 在任务结束后应该发生什么(如果有的话)?

第 14 章 自动化和规模化 - 图1 如果能够回答其中 5 个或更多的问题,你就可以开始自动化。如果不能,在你开始自动化之前,需要做更多的研究和说明。如果要自动化之前从来没有做过的事情,或者不是经常做的事情,在执行任务时尝试文档化它,然后确定你是否能够回答上述问题。如果你的项目太大或太模糊,尝试将它分解为小任务,并且自动化其中的几个。或许你的任务包括一个报告,需要下载两个数据集,进行清洗和分析,然后根据输出的不同,发送结果到不同的群组。你可以分解这项任务为一些子任务,自动化每一个步骤。如果其中任何一个子任务失败了,停止执行下面的任务,警报负责维护脚本的人员,这样可以研究它,并且在 bug 或问题解决后重新执行。

所以,自动化的基本步骤如下(注意,根据任务类型的不同,步骤会有变化)。

(1) 定义你的问题集合,将其分解为更小的工作块。

(2) 精确地描述每一个子任务的输入是什么、输入做什么以及需要什么来确认任务完成。

(3) 确定哪里可以得到输入,以及何时运行任务。

(4) 开始编码你的任务,用真实或样例数据测试。

(5) 整理你的任务和脚本,添加文档。

(6) 添加日志,聚焦于调试错误和记录成功的任务。

(7) 提交你的代码到仓库中,手动测试它。按照需要做出修改。

(8) 通过将手动任务替换为自动化任务,为自动化准备好脚本。

(9) 在任务开始自动化后,关注日志和警报。修正所有的错误和 bug。更新你的测试和文档。

(10) 为日志中的错误检查频率制订一个长期计划。

自动化的第一个步骤永远是更好地定义你的任务和子任务,让它们足够小,这样可以轻易地完成它们,且它们的失败或成功是确定的。

后面的几个步骤同我们在全书中使用的处理过程类似。你应该确定怎样开始用 Python 解决问题。搜索库或者工具,帮助你修复问题或完成请求,然后开始编码。一旦脚本开始工作,你会想要使用一些不同的可用数据集或输入测试它。在成功的测试之后,简化和文档化它。将其上传到一个远程仓库(Bitbucket 或 GitHub)中,这样随着时间的推移你可以记录修改和额外的信息。

第 14 章 自动化和规模化 - 图2 一旦你完成了脚本,首先手动运行它(而不是通过自动化的方式)。当新的数据就位,或者到了运行它的时候,手动执行,持续观察程序的输出。可能会有隐藏的错误存在,也可能你需要添加额外的日志和调试信息。

根据满足需求的自动化类型,你可能创建了一个简单的定时任务,脚本按特定的时间间隔执行(本章后文会介绍关于定时任务的知识)。你可能需要稍微修改脚本,让它可以通过使用参数变量、数据库或者系统上特定的文件自主运行。当它运行时,你可能会添加它到任务队列来管理它。无论哪一种自动化合适,你的工作都还没有结束。

第 14 章 自动化和规模化 - 图3 当你的脚本第一次自动化时,在它每次运行时花时间来检查它是很必要的。查看日志并监控进程。你可能会找到小 bug,然后修改它们。再一次更新所有必要的日志和文档。

在大约经过了 5 次成功或日志记录下来的失败后,你可以减少人工检查的次数。然而,在每月或者每季度使用 grephttp://www.thegeekstuff.com/2009/03/15-practical-unix-grep-command-examples/)查看日志,看一下发生了什么,仍然是一个很好的主意。如果你正在使用一个日志聚合器,你完全可以自动化这一步骤,并且让这一任务发送错误和警报报告。

自动化不是小进程,但是早早投入时间和精力是值得的。一个运行良好的自动化任务集合需要一些时间来完成,但是结果通常比那些需要从始至终关注、修改和监控的一次性脚本要好。密切关注并花一些时间正确地自动化你的脚本。之后才真正投入到手头接下来的工作当中,而不是一直将你的一部分工作与监控和管理难以驾驭的任务相关联。

14.3 什么会出错

在你的自动化程序中,有很多事情可能会出问题。其中一些非常容易更正和解释,然而其他问题更加模糊,可能根本不会有一个真正的修正。自动化中的重要一课是搞清楚哪些类型的错误和问题值得花时间和精力修复,哪些问题最好使用另外的方式解决。

以在第 12 章讨论过的错误类型为例:网络爬取中的网络错误。如果碰到了重大网络错误,你只有几个好的选择。你可以改变运行任务的机器,看是否会有性能提升(这可能会带来经济和时间上的花销,取决于你的设置)。你可以找到网络提供商,寻求支持。你可以在不同的时间运行任务,看输出是否会有不同。你可以预测问题的发生,依据预测构建脚本(即在需求之外运行脚本,预测失败百分比)。

这里有很多通过自动化运行任务时可能遇到的错误:

  • 数据库连接错误导致丢失或损坏数据;

  • 脚本漏洞和错误,任务没有正确完成;

  • 来自网站或 API 的超时错误或者过多的请求错误;

  • 边界问题,报告的数据或一部分没有保证一致,导致脚本错误;

  • 服务器负载问题或其他的硬件问题;

  • 时间不当,竞争条件(https://en.wikipedia.org/wiki/Race_condition,如果脚本依赖于之前其他任务的完成,竞争条件能够使数据无效)。

第 14 章 自动化和规模化 - 图4 当然还有很多潜在的你无法预料的问题。你工作的团队越大,糟糕的文档、糟糕的理解、糟糕的团队沟通伤害自动化程序的可能性就越大。你无法预防每一个错误,但是可以尝试提供最好的团队沟通和文档。尽管如此,你同样需要接受有些时候自动化程序会失败。

为了为最后的失败准备,你会希望在问题发生时收到警报。你应该确定多少比例的错误是可接受的。不是每一种服务在 100% 的时间里都表现良好(因此存在状态页面);然而,我们可以追求完美,并确定自动化值得付出多少时间和努力。

根据你的自动化程序和它的弱点,这里有一些方法来应对这些问题。下面是一些构建弹性自动化系统的方式。

  • 以特定的时间间隔重复失败的任务。

  • 确保你的代码有很多的 try…except 代码块,让它能够处理错误。

  • 在处理到机器、数据库或 API 的连接的代码周围,构建特殊的异常代码块。

  • 定期维护和监控你为自动化使用的机器。

  • 使用测试数据定期测试你的任务和自动化程序,确保它们正常执行。

  • 注意脚本中出现的依赖、竞争条件和 API 规则,根据这些知识编写代码。

  • 使用类似 requestsmultiprocessing 的库,让困难的问题变得更简单,并尝试理解使脚本运行困难的问题所在。

我们会使用这其中的一些技术和想法,研究如何最好地监控和自动化脚本。现在,让我们讨论一下自动化中使用的工具,使数据处理更加容易和简单,并给出一些在哪里以及如何使用这些工具的建议。

14.4 在哪里自动化

根据脚本需求的不同,决定在哪里运行它是重要的第一步。无论脚本在哪里第一次执行,你都可以将它移到别的地方,但是这可能需要重写一些代码。在一开始,你可能需要它在本地运行。在本地运行脚本或任务就是在你自己的计算机上运行。

远程执行意味着在另外的机器上运行,可能是其他地方的服务器。一旦脚本运行成功并且测试良好,你会想要移动它到远程。如果你管理或拥有服务器,或者为一个有服务器的组织工作,这样迁移脚本到服务器上会相对简单。这允许你在自己的机器(笔记本电脑或台式计算机)上工作,并且不用担心在什么时间关闭和打开它。在远程运行脚本也意味着对你的网络服务提供商来说,你不是独立的。

如果无法访问服务器,但是有一台旧的不再使用的计算机或笔记本电脑,在必要的情况下,你可以将其变成服务器。如果它运行着老旧的操作系统,你可以将它升级,以便在其上运行 Python,或者可以彻底重来,安装 Linux。

第 14 章 自动化和规模化 - 图5 使用家庭计算机作为远程设备意味着它需要总是运行着,并且连接到你的家庭互联网中。如果你还想安装一个之前没有使用过的操作系统,像 Linux,这是一个学习新操作系统的简单方式,并且可以帮助你过渡到管理自己的服务器。如果你刚刚开始使用 Linux,建议选择一个流行的分发版本,例如 Ubuntu(http://www.makeuseof.com/tag/ubuntu-an-absolute-beginners-guide/)或 LinuxMint(http://linuxmint.com/)。

如果你想要管理自己的服务器,但是刚起步,不用惊慌!即使你没有管理过或帮助他人管理过服务器,云服务提供商之间日益激烈的竞争,让服务器管理越来越容易。云服务提供商让你不需要了解太多的技术知识,便可以使用新机器,运行自己的服务器。服务商 DigitalOcean 有很多关于开始使用云服务的很棒的文章,包括如何创建第一台服务器(https://www.digitalocean.com/community/tutorials/how-to-create-your-first-digitalocean-droplet-virtual-server)和运行服务器(https://www.digitalocean.com/help/getting-started/setting-up-your-server/)。

无论在本地还是远程运行脚本,都有大量的工具可以用来很好地监测和更新计算机或服务器。你希望能够非常简单地管理和更新脚本和任务,并且可以定期运行脚本以完成任务。最后,你还希望可以轻松地配置和文档化它们。在下面的几节中,我们会介绍所有这些主题,从可以让你的脚本更加自动友好的 Python 工具开始。

14.5 自动化的特殊工具

Python 提供了许多用于自动化的特殊工具。我们会查看一些使用 Python 管理自动化程序的方法,同时也会使用其他的机器和服务器完成任务。我们还会讨论怎样使用一些内置的 Python 工具管理脚本的输入,自动化看起来需要人工输入的事情。

14.5.1 使用本地文件、参数及配置文件

根据脚本的工作情况,你可能需要存储在数据库或 API 之外的参数或输入。当有一个简单的输入或输出时,你可以使用本地文件和参数来传递数据。

  • 本地文件

使用本地文件作为输入和输出时,你需要确保脚本可以每天运行在相同的机器上,或者可以简单地与输入和输出文件一起迁移。随着脚本的开发,很可能需要同时移动并改变脚本和所用文件。

我们之前使用过本地文件,但是让我们看一下如何从更加函数式的代码的角度来使用它。这段代码给了你使用标准数据类型打开和写文件的能力,并且根据脚本的需求,其复用性和扩展性很好。

  1. from csv import reader, writer
  2.  
  3.  
  4. def read_local_file(file_name):
  5. if '.csv' in file_name:
  6. rdr = reader(open(file_name, 'rb'))
  7. return rdr
  8. return open(file_name, 'rb')
  9.  
  10.  
  11. def write_local_file(file_name, data):
  12. with open(file_name, 'wb') as open_file:
  13. if type(data) is list:
  14. wr = writer(open_file)
  15. for line in data:
  16. wr.writerow(line)
  17. else:
  18. open_file.write(data)

❶ 这行代码测试文件是否适合使用 csv 模块打开。如果文件以 .csv 结尾,之后可以使用 CSV 读取器来打开它。

❷ 如果没有返回 CSV 读取器,这段代码返回打开的文件。如果想要根据文件扩展类型,构建一系列不同的方式来打开并解析文件,也可以做到(例如,为 JSON 文件使用 json 模块,或者为 PDF 文件使用 pdfminer)。

❸ 这段代码使用 with…as 返回 open 函数的输出,将其赋值给 open_file 变量。当缩进的代码块结束时,Python 会自动地关闭文件。

❹ 如果正在处理列表,这一行代码使用 CSV 输出器输出每一行列表对象为一行数据。如果有字典,可以使用 DictWriter 类。

❺ 我们想要一个好的备选计划,以防数据不是列表。因此,要将原始数据写到文件中。此外,根据数据类型的不同,还可以写不同的代码。

看一个例子,我们需要文件夹中最近使用的文件,这在按照日期解析日志文件,或者查看最近的网络爬虫运行结果时很有用处。

  1. import os
  2. def get_latest(folder):
  3. files = [os.path.join(folder, f) for f in os.listdir(folder)]
  4. files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
  5. return files[0]

❶ 使用 Python 内置的 os 模块列出每一个文件(listdir 方法),之后使用 path 模块的 join 方法创建一个长字符串,表示一个完整的文件路径。这是获取文件夹中所有文件的一种简单方式,只需要传递一个字符串(文件夹路径)。

❷ 通过最后修改时间来为文件排序。因为 files 是一个列表,所以我们可以调用 sort 方法,并且给它一个键作为排序的依据。这段代码传递完整的文件路径给 getmtime 函数,这是 os 模块“获取修改时间”的方法。reverse 参数确保最近的文件出现在列表的顶部。

❸ 返回最近的文件。

这段代码返回最近使用的文件夹,但是如果想要返回整个以最近使用文件开始的文件列表,我们可以直接修改代码,不返回第一个索引,而是返回整个列表或一个切片。

第 14 章 自动化和规模化 - 图6 os 库有很多强有力的工具来查找、修改和改变本地文件(或者服务器的本地文件)。在 Stack Overflow 上直接搜索会返回很多答案,例如如何找到过去 7 天内唯一修改的文件,或者过去一个月中唯一修改的 .csv 文件,等等。使用本地文件,特别是当你需要的数据已经存在时(或者很容易使用 wget 拿到),是一个很棒的简化自动化程序的方式。

  • 配置文件

为敏感信息创建本地配置文件是必需的。正像 Twelve-Factor 应用(http://12factor.net/config)中声称的那样,在代码主干之外保存配置(例如密码、登录名、邮件地址和其他的敏感信息)是成为一个好的开发者的一部分。如果你连接到数据库,发送一封邮件,使用 API 或保存支付信息,这些敏感数据应该保存在一个配置文件中。

通常情况下,我们在仓库内的一个独立文件夹存储配置文件(例如,config/)。仓库中的所有代码都会访问这些文件,但是通过使用 .gitignore 文件(见 8.4 节),可以保持这些配置文件在版本控制之外。如果有其他开发者或服务器需要这些文件,你需要手动复制它们。

第 14 章 自动化和规模化 - 图7 建议在仓库的 README.md 中使用一个小节,介绍特殊配置文件的位置以及如何处理它们,这样新用户和合作者知道向谁索要合适的文件。

使用文件夹而不是文件允许你在运行脚本的不同机器或环境中有不同的配置。你可能想要在测试环境中有一个配置文件,使用测试的 API key,并且在生产环境使用生产环境的配置文件。你可能想要根据脚本使用的机器使用多个数据库。你可以使用一个 .cfg 文件保存这些特殊信息,如下。

  1. # 示例配置文件
  2. [address]
  3. name = foo
  4. email = myemail@bar.com
  5. postalcode = 10177
  6. street = Schlangestr. 4
  7. city = Berlin
  8. telephone = 015745738292950383
  9. [auth_login]
  10. user = test@mysite.com
  11. pass = goodpassword
  12. [db]
  13. name = my_awesome_db
  14. user = script_user
  15. password = 7CH+89053FJKwjker)
  16. host = my.host.io
  17. [email]
  18. user = script.email@gmail.com
  19. password = 788Fksjelwi&

❶ 每一小节用一对方括号和放置于方括号中间的一个容易阅读的字符串表示。

❷ 每一行包含一个 key = value 的序对。ConfigParser 将这些序对作为字符串解释。值可以包含任何的字符,包括特殊字符,但是键必需遵循 PEP-8 易于阅读的语法和结构。

在配置中有小节、键和值,这允许我们使用小节的名字和键访问配置的值。这增加了 Python 脚本的清晰度,而不会变得晦涩。一旦你有了一个类似前面示例中创建的配置文件,使用 Python 解析以及在脚本中使用和自动化就很简单了。下面是一个示例。

  1. import ConfigParser
  2. from some_api import get_client
  3.  
  4.  
  5. def get_config(env):
  6. config = ConfigParser.ConfigParser()
  7. if env == 'PROD':
  8. return config.read(['config/production.cfg'])
  9. elif env == 'TEST':
  10. return config.read(['config/test.cfg'])
  11. return config.read(['config/development.cfg'])
  12.  
  13.  
  14. def api_login():
  15. config = get_config('PROD')
  16. my_client = get_client(config.get('api_login', 'user'),
  17. config.get('api_login', 'auth_key'))
  18. return my_client

❶ 这是一个导入 API 客户端钩子的示例。

❷ 这段代码通过调用 ConfigParser 类初始化一个配置对象。现在这是一个空的配置对象。

❸ 这行代码调用配置解析器对象的 read 方法,并且传递一个配置文件的列表给这个函数。这里,在项目根目录下名为 config 的文件夹下保存所有的文件。

❹ 如果传递的环境变量不匹配生产环境或测试环境,则永远返回开发配置。在配置代码中做类似的判断是一个很好的做法,以防止定义环境变量失败的情况。

❺ 我们假设示例需要生产环境的 API,所以这行代码试图获取 PROD 配置。你同样可以保存这些不同环境的类型到 bash 环境中,并且使用内置的 os.environ 方法(https://docs.python.org/2/library/os.html#process-parameters)读取它们。

❻ 这行代码调用小节名称和键名称访问存储在配置中的值。这会将值作为字符串返回,所以如果你需要整数或者其他的类型,你需要转换它们。

内置的 ConfigParser 库使我们能够简单地访问配置文件中存储的小节、键和值。如果希望在不同的文件中存储不同的信息,并且为每个特定的脚本解析一个文件列表,你的代码应该类似这样:

  1. config = ConfigParser.ConfigParser()
  2. config.read(['config/email.cfg', 'config/database.cfg', 'config/staging.cfg'])

根据需求,由你组织代码和配置。访问配置值的语法很简单,只是使用配置中小节的名称(即 [section_name])和键的名称。所以,类似下面的配置文件:

  1. [email]
  2. user = test@mydomain.org
  3. pass = my_super_password

可以通过下面的方式访问:

  1. email_addy = config.get('email', 'user')
  2. email_pass = config.get('email', 'pass')

第 14 章 自动化和规模化 - 图8 配置文件是一个将所有的敏感信息保存到一个地方的简单的工具。如果你更倾向于使用 .yml 或其他扩展类型的文件,Python 也有这些文件类型的读取器。确保使用某种手段将认证、敏感信息同代码分开存储。

  • 命令行参数

Python 让我们能够在自动化程序中传递命令行参数。这些参数传递关于脚本应该如何运行的信息。举个例子,如果需要脚本知道我们希望它使用开发配置运行,可以这样做:

  1. python my_script.py DEV

我们正在使用相同的语法在命令行中运行一个文件,调用 python,之后是脚本名称,然后在这一行的最后添加 DEV。怎样才能使用 Python 解析额外的参数呢?让我们编写下面的代码:

  1. from import_config import get_config
  2. import sys
  3.  
  4.  
  5. def main(env):
  6. config = get_config(env)
  7. print config
  8.  
  9.  
  10. if __name__ == '__main__':
  11. if len(sys.argv) > 1:
  12. env = sys.argv(1)
  13. else:
  14. env = 'TEST'
  15. main(env)

❶ 内置的 sys 模块,帮助完成系统任务,包括解析命令行参数。如果命令行参数列表的长度大于 1,这里存在额外的参数。第一个参数永远保存着脚本的名称(所以如果参数长度为 1,脚本名称是唯一的参数)。

❷ 为了得到参数的值,传递参数的索引到 sys 模块的 argv 方法。这行代码设置 env 为参数的值。记住,argv 方法的索引 0 永远是 Python 的脚本名称,所以从索引 1 开始解析参数。

❸ 这行代码使用解析后的参数,根据命令行参数修改代码。

第 14 章 自动化和规模化 - 图9 如果想要解析多个额外变量,可以测试参数的长度,来确保我们有足够的参数,之后再继续解析。你可以将任何数量的参数组合到一个字符串中,但是我们建议保持在 4 个以下。如果你需要 4 个以上的参数,考虑编写一些逻辑到脚本中(例如,在星期二只执行测试,所以如果今天是星期二,使用测试部分的代码,等等)。

参数变量在你需要重新使用相同的代码执行不同的任务或者在不同的环境下运行时很有用。或许你有一个脚本来收集或分析数据,并且希望能够切换使用的环境。你或许会像下面这样运行脚本:

  1. python my_script.py DEV ANALYSIS
  2. python my_script.py PROD COLLECTION

或者你可能有一个脚本需要同最近更新的文件夹进行交互,抓取最新的日志——比如从多个地方抓取日志:

  1. python my_script.py DEV varlog/apache2/
  2. python my_script.py PROD varlog/nginx/

有了命令行参数,参数变量的简单变化可以创建一个可移植和健壮的自动化程序。不是每一个脚本都需要使用这些额外的变量,但是有这样一个内置到标准库中的解决方案是很棒的,它同时提供了灵活性。

除了这些相当简单直接的方式来解析数据,给脚本额外的信息,你也可以使用更加复杂和分布式的方式,比如基于云计算的数据和数据存储。我们会在下面查看这部分内容。

14.5.2 在数据处理中使用云

这个名词通常用来代表一个资源共享池,例如服务器。有许多公司提供云服务——亚马逊网络服务(AWS),是最著名的云服务提供商之一。

第 14 章 自动化和规模化 - 图10 云这个词经常被过度使用。如果你正在云服务器上运行代码,最好说“我正在一个服务器上执行它”,而不是“我在云上执行它”。

什么时候适合使用云?如果数据太大,不能在自己的计算机上执行,或者程序需要很长的时间执行,云都是一个很好的处理方式。将大多数想要自动化的任务放到云上执行,这样你不用担心电脑打开或关闭时脚本是否在运行。

如果选择 AWS,第一次登录后会看到许多不同的服务选择。在数据处理中你只需要几种服务(见表 14-1)。

表14-1:AWS云服务

服务 在数据处理中的目的
简单存储服务(S3) 一个简单的文件存储服务,用来备份数据文件(JSON、XML 等)
弹性计算(EC2) 一个按需分配的服务器。这是运行脚本的地方
弹性 MapReduce(EMR) 通过一个托管的 Hadoop 框架提供分布式的数据处理进程

这些是你需要熟悉的基本的 AWS 服务。还有各种各样的竞争者,包括 IBM 的 Bluemix 和沃特森开发者云(让你得以使用各种大型数据平台,包括沃特森的逻辑和自然语言处理能力)。你还可以使用 DigitalOcean 或 Rackspace,它们提供了廉价的云资源。

无论使用什么,你都需要在云服务器上部署代码。为此,我们建议使用 Git(https://git-scm.com)。

使用Git部署Python

如果想让自动化程序运行在本地机器之外的地方,你需要部署 Python 脚本。我们会介绍几种简单的方式,然后介绍一些稍微复杂的方式。

第 14 章 自动化和规模化 - 图11 版本控制让你的团队在同一个仓库上并行工作,不会在每个人之间产生问题。Git 允许创建不同的分支,这样你或者团队中的其他人可以在特定的需求集合上工作,或者各自完成新的集成,然后将它们合并回代码主干,而不会丢失任何核心功能。它还能确保每个人有最新的代码(包括服务器和远程机器)。

部署 Python 最简单和直观的方式是使用 Git 把仓库置于版本控制之下,用 Git 部署钩子将代码移到远程机器上。首先,需要安装 Git(https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)。

如果你是 Git 新手,建议你参加 GitHub 上的 Code School 入门教程(https://try.github.io/levels/1/challenges/1)或者浏览 Atlassian 上的 Git 入门教程(https://www.atlassian.com/git/tutorials/)。上手 Git 相当简单,你会很快掌握常用命令。如果你独自在仓库上工作,不用过于担心拉取远程变化的问题,但是设置一个清晰的工作日程总是好的。

一旦 Git 安装完成,在项目代码文件夹下运行下面这些命令。

  1. git init .
  2. git add my_script.py
  3. git commit -a

❶ 初始化当前工作目录为 Git 仓库的根目录。

❷ 添加 my_script.py 到仓库中。使用来自仓库的文件名或文件夹名称——不是配置文件!

❸ 将这些改变连同任何其他运行改变(-a)提交到仓库。

收到提示后,编写一个提交信息,简短地解释你做出的改变,这段解释应该明确并且清晰。你可能在之后需要找到哪个提交对应代码中的哪个改变。如果信息始终编写得很清晰,这会帮助你搜索和找到这些提交。这同样会帮助团队中的其他人或者合作者理解你的代码和提交。

第 14 章 自动化和规模化 - 图12 习惯于使用 git fetch 拉取远程变化,或使用 git pull --rebase 命令通过新的提交更新本地仓库。之后,在代码上工作,提交工作,推送提交到活跃的分支。当适合合并分支到主干时,你可以发送一个拉取请求(https://help.github.com/articles/using-pull-requests/),让其他人检查合并的代码,之后直接合并到主干。不要忘记删除没有用处的老的分支。

创建一个 .gitignore 文件是必需的,在这个文件中列出所有想让 Git 在推送 / 拉取时忽略的文件模式,正像在 8.4 节附注栏“Git 和 .gitignore”中讨论的那样。你可以在每个文件夹下使用一个文件,也可以只在仓库的基目录下使用一个。大多数的 Python .gitignore 文件类似于这样:

  1. .pyc
  2. .csv
  3. .log
  4. config/

这个文件会阻止仓库存储编译过的 Python 文件、CSV 文件、日志文件和配置文件。根据仓库文件夹中存在的其他文件类型,你可能想要添加更多的模式。

你可以在一些站点上挂载仓库。GitHub(https://github.com/)提供了免费的公开仓库,但是没有私有仓库。如果你需要代码是私有的,Bitbucket(https://bitbucket.org/)有免费的私有仓库。如果你已经开始在本地使用 Git,推送已有的 Git 仓库到 GitHub(https://help.github.com/articles/set-up-git/)或 Bitbucket(http://bit.ly/create_bitbucket_repo)是很简单的。

一旦创建了仓库,使用 Git 设置远程端点(服务器或服务器集群,http://git-scm.com/docs/git-remote)很简单。如果你正在部署一个使用 ssh 访问的目录,下面是一个示例。

  1. git remote add deploy ssh://user@342.165.22.33homeuser/my_script

在推送代码到服务器之前,你需要以一些命令设置接收端的文件夹。在计划进行开发的服务器文件夹下运行这些命令:

  1. git init .
  2. git config core.worktree `pwd`
  3. git config receive.denycurrentbranch ignore

这里初始化了一个空的仓库来从本地机器发送代码,同时定义了一些简单的配置,这样 Git 知道它是一个远程端点。你还需要创建一个推送—接收钩子。通过在刚刚初始化的文件夹 .git/hooks 下创建一个名为 post-receive 的可执行文件(通过许可),你可以创建一个钩子。这个文件会在部署端点接收到任何的 Git 推送后执行。它应该包含你需要在每一次推送后执行的所有任务,例如同步数据库、清理缓存或者重启任何的进程。至少,它会需要更新端点。

一个简单的 .git/hooks/post-receive 文件看起来类似于这样:

  1. #!/bin/sh
  2. git checkout -f
  3. git reset --hard

这会重置所有本地的变化(在远程机器上)并且更新代码。

第 14 章 自动化和规模化 - 图13 你需要在本地机器上做出所有的修改,测试它们,之后推送它们到部署端点。从一开始就使用这种方式是好的习惯。通过这种方式,所有代码都在版本控制之下,你可以确保没有由于直接在服务器上修改代码而导致的断断续续的 bug 或错误。

一旦远程端点设置完成,你可以直接在本地仓库运行下面的命令,使用所有最新的提交更新服务器上的代码:

  1. git push deploy master

这样做是一个非常棒的管理仓库和服务器或远程机器的方式;很容易使用和设置,也让迁移(如果需要的话)变得直观。

如果你刚开始接触部署和版本控制,建议你从 Git 开始,熟悉它之后再转而使用更复杂的部署选择,比如 Fabric(http://www.fabfile.org/)。在本章的后面,我们会介绍一些大规模的自动化工具,用于在多个服务器之间部署和管理代码。

14.5.3 使用并行处理

并行处理对脚本自动化来说是一个很棒的工具,让你可以在一个脚本中运行多个并发进程。如果脚本需要多个进程,Python 内置的 multiprocessing 库会成为你自动化的得力工具。如果你有一系列的任务需要并行执行,或者通过并行化可以加速执行任务,多重处理(multiprocessing)是合适的工具。

如何使用 multiprocessing 呢?下面是一个简单的示例:

  1. from multiprocessing import Process, Manager
  2. import requests
  3. ALL_URLS = ['google.com', 'bing.com', 'yahoo.com',
  4. 'twitter.com', 'facebook.com', 'github.com',
  5. 'python.org', 'myreallyneatsiteyoushouldread.com']
  6. def is_up_or_not(url, is_up, lock):
  7. resp = requests.get('http://www.isup.me/%s' % url)
  8. if 'is up.' in resp.content:
  9. is_up.append(url)
  10. else:
  11. with lock:
  12. print 'HOLY CRAP %s is down!!!!!' % url
  13. def get_procs(is_up, lock):
  14. procs = []
  15. for url in ALL_URLS:
  16. procs.append(Process(target=is_up_or_not,
  17. args=(url, is_up, lock)))
  18. return procs
  19. def main():
  20. manager = Manager()
  21. is_up = manager.list()
  22. lock = manager.Lock()
  23. for p in get_procs(is_up, lock):
  24. p.start()
  25. p.join()
  26. print is_up
  27. if __name__ == '__main__':
  28. main()

❶ 从内置的 multiprocessing 库导入 ProcessManager 类,来帮助我们管理进程。

❷ 定义主要 worker 函数 is_up_or_not,培训需要 3 个参数:一个 URL、一个共享列表和一个共享锁。其中的列表和锁是在所有的进程间共享的,让其中的每一个进程能够修改或使用它们。

❸ 使用 requests 检查 isup.me,判定给定的 URL 当前是否在线并且可用。

❹ 测试是否可以在页面中找到文本“is up.”。如果文本存在,这个 URL 就是我们想要的。

❺ 通过 with 代码块调用锁的 acquire 方法。这会得到锁,继续执行下面缩进的代码,然后在代码块的最后释放锁。锁(http://www.laurentluce.com/posts/python-threads-synchronization-locks-rlocks-semaphores-conditions-events-and-queues/)是阻塞的,并且只能在代码中需要阻塞时使用(举个例子,如果你需要确保只有一个进程运行一组特殊的逻辑,比如检查一个共享值是否变化,或是否到达了一个终止点)。

❻ 当生成进程时,传递共享的锁和列表到函数中使用。

❼ 通过传递关键参数创建一个进程对象:目标(即,我应该执行哪个函数)和参数(即,使用什么参数)。这行代码追加所有的进程到一个列表,这样我们可以在一个地方管理它们。

❽ 初始化 Manager 对象,这帮助我们管理共享的对象和进程间的日志。

❾ 创建一个共享列表对象,跟踪每个站点的状态。每一个进程都能改变这个列表。

❿ 创建一个共享的锁对象,如果一个站点中不存在“is up”,停止并且宣布它。如果这些是我们管理的所有站点,我们可能有了一块重要的业务逻辑来处理紧急情况,也因此有了“停止所有程序”的理由。

⓫ 分别开始由函数 get_proc 返回的每一个进程。一旦它们开始执行,join 方法会让 Manager 对象和所有的子进程通信,直到最后一个进程完成。

使用多重处理的时候,你通常会有一个管理者进程和一堆子进程。你可以传递参数给子进程,可以使用共享内存和共享变量。这使你能够确定如何利用和架构 multiprocessing。根据脚本的需要,你或许想要让管理器运行脚本中一系列的逻辑,同时使用子进程运行高延迟或长时间运行的部分代码。

第 14 章 自动化和规模化 - 图14 共享锁对象(https://docs.python.org/2/library/threading.html#lock-objects)提供了同步执行多个进程的能力,同时能保护内部逻辑的特定区域。有效使用它们的一个方式是直接放置锁逻辑到 withhttps://docs.python.org/2/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement)语句中。

如果不确定脚本是否适合使用多重处理,可以先测试脚本中的一部分代码或者一个子任务,确定你是否能够实现并行编程的目标,或者它是否将逻辑复杂化了。有一些任务使用大规模的自动化和队列来处理会更好,我们会在本章的后面讨论。

14.5.4 使用分布式处理

除了并行处理和多重处理,还有分布式处理,它分发进程到多台机器上(不同于发生在一台机器上的并行处理)。当计算机可以处理时,并行处理更快,但是有些时候你需要更强大的能力。

第 14 章 自动化和规模化 - 图15 分布式处理涉及多个类型的计算问题。有些工具和库可以管理分布在多台计算机上的进程,还有些工具和库可以管理跨越计算机的存储。与这些问题相关的概念包括分布式计算、MapReduce、Hadoop、HDFS、Spark、Pig 和 Hive。

在 2008 年早期,克林顿总统图书馆和国家档案局发布了希拉里·克林顿在 1993 年到 2001 年作为第一夫人的日程表。这份文档包含 17 000 多页的 PDF 图片,为了将其转换为有用的数据集,需要进行光学字符识别,或者使用 OCR 技术。由于正值民主党总统候选人初选阶段,新闻组织想要发布这份数据。为了完成这项工作,《华盛顿邮报》使用了分布式进程服务,将 17 000 多张图片转化为文本。通过分发工作到 100 多台计算机上,他们不到 24 小时就完成了这项工作。

使用类似 Hadoop 的框架的分布式处理包含两个主要的步骤。第一步是映射数据或输入。

这一进程像是一个过滤器。使用映射器说“分离文本文件中所有的单词”或者“分离过去一个小时里推文中包含特定标签的所有用户”。下一步是 reduce(规约)映射后的数据为可用的形式。这类似于在第 9 章使用的聚合函数。如果我们查看所有来自 Spritzer feed 的 Twitter 句柄,可能想要根据不同的地理信息或主题统计每一个句柄的推文数量,或者所有句柄的聚合(即,所有来自这一时区的使用这些单词最多的推文)。规约部分帮助我们处理这些大数据,“规约”它为可阅读和可操作的报告。

正如你可能看到的,不是所有的数据集都需要 mapreduce,而且 MapReduce 背后的理论已经在许多 Python 数据库中可用。无论怎样,如果你有一个非常大的数据集,使用类似 Hadoop 的 MapReduce 工具会节省大量的计算时间。如果你需要一个很棒的教程,建议你阅读 Micheal Noll 写的关于用 Python 编写 Hadoop MapReduce 程序的指南(http://www.michael-noll.com/tutorials/writing-an-hadoop-mapreduce-program-in-python/),它使用一个单词计数程序来探索 Python 和 Hadoop。还有很棒的关于 mrjob(https://pythonhosted.org/mrjob/)的文档,由在 Yelp(http://www.yelp.com)的开发者编写和维护。如果你想要阅读更多关于这个主题的信息,可以查看 Kevin Schmidt 和 Christopher Phillips 的图书 Programming Elastic MapReduce(O'Reilly)。

如果数据集很大,但是分离存储或是实时的(或接近实时),你可能想要了解一下另外一个 Apache 项目——Spark(http://spark.apache.org/)。Spark 因为它的速度、机器学习的使用和处理流的能力获得了流行。如果你的任务处理实时的流数据(来自服务、API,甚至是日志),那么相对于 Hadoop,Spark 会是一个更灵活的选择,它可以处理相同的 MapResuce 计算结构。在你需要使用机器学习或其他需要生成数据并且“喂”给数据集群的数据分析时,Spark 也很有效。PySpark(http://spark.apache.org/docs/latest/api/python/)是 Spark 的 Python API 库,由相同的开发者维护,让你能够在 Spark 进程中编写 Python。

为了开始使用 Spark,建议你阅读 Benjamin Bengfort 的详细的博客文章(https://districtdatalabs.silvrback.com/getting-started-with-spark-in-python),其内容包括如何安装 Spark,同 Jupyter notebook 集成,以及创建第一个项目。你同样可以查看 John Ramey 关于 PySpark 和 Jupyter notebook 集成的文章(http://ramhiser.com/2015/02/01/configuring-ipython-notebook-support-for-pyspark/),在你的 notebook 中进一步探索数据收集和分析的可能性。

14.6 简单的自动化

在 Python 中,简单的自动化很容易。如果你的代码不需要在多台机器上运行,如果你拥有一台服务器,或者你的任务不是事件驱动的(或者可以每天在相同时间执行),简单的自动化会很有效。开发的一个主要原则是选择最清晰和简单的方法。自动化也是如此!如果你可以轻松地使用一个定时任务(corn job)自动化工作,绝不要浪费时间过度工程化它,或者让它变得更加复杂。

在学习简单自动化的过程中,我们会介绍内置的 cron(一个基于 Unix 系统的任务管理器)和各种 Web 接口,让团队得以轻松访问所编写的脚本。这些代表着简单的自动化解决方案,不需要你的直接干预。

14.6.1 CronJobs

Cron(http://en.wikipedia.org/wiki/Cron)是一个基于 Unix 系统的任务调度工具,用来使用服务器日志和管理工具运行脚本。Cron 需要你确定任务执行的频率和时间。

第 14 章 自动化和规模化 - 图16 如果你不能轻松地为脚本确定一个时间线,cron 可能不是一个好的选择。另外,你可以运行一个规律的 cron 任务来测试是否存在运行任务所必需的条件,然后使用一个数据库或本地文件通知运行的时间。在多个定时任务的帮助下,你可以检查文件或数据库,并且执行任务。

如果你之前从来没有使用过 cron 文件,它们非常直观。大多数的任务可以通过输入下面的指令来编辑:

  1. crontab -e

第 14 章 自动化和规模化 - 图17 取决于操作系统,如果之前从来没有编写过 cron 文件,你可能会收到提示,让你选择一个编辑器。你可以使用默认的配置,或者根据其他的偏好改变它。

你会在文件中看到一些文档和注释,解释 cron 文件是如何工作的。cron 文件中每一个不以 # 符号开头的行,都定义了一个 cron 任务。每一个 cron 任务都需要下面这个参数列表:

  1. minute hour day_of_month month day_of_week usercommand

如果脚本需要在一天中的每个小时都执行,但是只在工作日执行,你需要写类似于下面的代码:

  1. 0 * 1-5 python run_this.py

这告诉 cron 在周一到周五的每个整点开始执行脚本。有很多好的入门教程(https://help.ubuntu.com/community/CronHowto)详细地讲解了哪些选项对你可用,但是下面有几条建议。

  • 总是在所有代码行前添加 MAIL_TO=your@email.com 变量。通过这种方式,如果有哪个脚本失败了,cron 会给你发送邮件,报告异常,这样你会知道它不再工作了。你需要设置笔记本电脑、台式计算机或服务器来发送邮件。根据操作系统和互联网服务提供商的不同,你可能需要做一些设置。有一个很好的 GitHub gist(https://gist.github.com/kany/c44c077881047ead8faa)可以帮助 Mac 用户上手,还有一篇来自 HolaRails 的有用的面向 Ubuntu 用户的文章(https://holarails.wordpress.com/2013/11/17/configure-sendmail-in-ubuntu-12-04-and-make-it-fast/)。

  • 如果所运行的服务当计算机重启时需要重新启动,使用 @reboot 特性。

  • 如果必需运行一些路径环境或者其他的命令来恰当地执行脚本,你应该在仓库中编写一个 cron.sh 文件。将所有必需的命令整理到一个文件中,并且直接运行这个文件,而不是使用一个用 && 连接的很长的命令列表。

  • 不要害怕去搜索答案。如果你是第一次使用 cron,并且遇到了问题,很有可能已经有人发布过解决方案,通过 Google 搜索就可以解决问题。

为了测试如何使用 cron,我们会创建一个简单的 Python 示例。从创建一个新的名为 hello_time.py 的 Python 文件开始,并且把这些代码放在文件里:

  1. from datetime import datetime
  2. print 'Hello, it is now %s.' % datetime.now().strftime('%d-%m-%Y %H:%M:%S')

下面,在相同文件夹下创建一个简单的 cron.sh 文件,并且在里面编写下面的 bash 命令:

  1. export ENV=PROD
  2. cd homeyour_home/folder_name
  3. python hello_time.py

我们不需要设置环境变量,因为并不经常使用它,并且你需要更新 cd 这行代码,让它正确地变成代码所在的文件夹(这是指向当前文件的路径)。无论如何,这是一个很好的实例,展示了如何使用 bash 命令设置变量,查看虚拟环境变量,复制和移动文件或改变目录到新的文件夹,然后调用 Python 文件。从本书的开始,你就在使用 bash 了,所以即使你仍然是初学者也不要害怕。

最后,让我们使用 crontab -e 创建 cron 任务。使用编辑器在文挡下方添加这些代码:

  1. MAIL_TO=youremail@yourdomain.com
  2. */5 bash homeyour_home/folder_name/cron.sh > varlog/my_cron.log 2>&1

你需要使用真实地址替换这个示例中编造的 Email 地址,编写到刚刚创建的 cron 文件的正确路径。记住,hello_time.py 脚本应该在相同的文件夹中。在这个例子里,我们还创建了一个 cron 使用的日志文件(varlog/my_cron.log)。最后的语句 2>&1 告诉 cron 将输出和所有错误打印到日志文件中。一旦退出编辑器,并正确地保存了 cron 文件,你应该会看见一个信息,确认新 cron 任务已经安装。等待几分钟,然后检查日志文件。你应该会看到脚本输出的信息。如果没有的话,你可以通过在系统日志(通常为 varlog/syslog)中搜索,或在 cron 日志(通常为 varlog/cron)中搜索 cron 错误日志信息。为了删除这个 cron 任务,再一次编辑 crontab,删除这行代码或在这行代码的开头添加#,将它注释掉。

第 14 章 自动化和规模化 - 图18 Cron 是自动化脚本和警报的非常简单的方式。它是在 20 世纪 70 年代中期最初的 Unix 系统的开发中,由贝尔实验室设计的非常强大的工具,直到今日,仍被广泛地使用。如果可以很简单地预测自动化程序运行时间,或者只需要几个 bash 命令就可以运行,cron 是一个自动化代码的有效方式。

如果你需要为 cron 任务传递命令行参数(见 14.5.1 节),那么文件中的代码可能类似下面这样。

  1. /20 10-22 python my_arg_code.py arg1 arg2 arg3
  2. 0,30 10-22 * python my_arg_code.py arg4 arg5 arg6

Cron 是一个相当灵活也很简单的工具。如果它符合你的需要,那很棒!如果不符合,继续阅读来学习自动化数据处理的其他简单方式。

14.6.2 Web接口

如果你需要脚本、爬虫或者报告任务按需执行,一个简单的解决方案是直接构建一个 Web 接口,人们可以登录进去并点击按钮执行任务。Python 有很多不同的网络框架供你选择,所以使用哪一个框架以及花费多少时间在 Web 接口上完全取决于你。

一个简单的方式是使用 Flask-Admin(https://flask-admin.readthedocs.org/en/v1.0.9/),这是一个基于 Flask 网络框架(http://flask.pocoo.org/)构建的管理站点。Flask 是一个微框架,这意味着它并不需要太多的代码上手。在依照快速开始指引(http://flask.pocoo.org/docs/0.10/quickstart/)创建并运行了网站后,你只需要在 Flask 应用程序中创建一个视图来执行任务。

第 14 章 自动化和规模化 - 图19 确保任务可以在完成时用其他的方式通知用户或你(邮件、通知等),因为它不太可能及时地完成并给出一个合适的 Web 响应。同样确保在任务开始时通知用户,这样他们不会发出一连串的让任务开始运行的请求。

另外一个流行并且经常使用的 Python 框架是 Bottle(http://bottlepy.org/docs/dev/index.html)。Bottle 的用法类似于 Flask,如果用户点击了按钮(或做了其他简单的操作),Bottle 用视图来执行任务。

Python 开发者使用的一个大型 Python 网络框架是 Django(https://www.djangoproject.com/)。Django 最初被开发用来使新闻编辑室可以更简单地发布内容,它拥有一个内置的认证和数据库系统,并使用配置文件配置大多数的特性。

无论你使用什么框架,或者如何创建视图,你会想要在一些地方挂载框架,这样其他人可以请求任务。你可以使用 DigitalOcean 或者亚马逊网络服务(查看附录 G)轻松挂载自己的站点。你同样可以使用支持 Python 环境的服务提供商,比如 Heroku(https://www.heroku.com/)。如果你对这些选择感兴趣,Kenneth Reitz 非常出色地介绍了如何使用 Heroku 部署 Python 应用(https://devcenter.heroku.com/articles/getting-started-with-python#introduction)。

第 14 章 自动化和规模化 - 图20 无论使用什么框架或微框架,你都需要考虑认证和安全问题。无论使用什么 Web 服务器,你都可以在服务器端创建它,或者探索框架给你的一些选择(包括插件或者其他支持的特性)。

14.6.3 Jupyter notebook

第 10 章介绍了如何创建 Jupyter notebook,它是另外一种分享代码的好方式,特别是对于那些不需要了解 Python,但是需要查看图表或者其他脚本输出的人来说。如果你教他们如何使用简单的命令,例如运行 notebook 中所有的单元,并在下载新的报告后终止 notebook 服务,你会发现它会节省大量的时间。

第 14 章 自动化和规模化 - 图21 添加 Markdown 单元来解释如何使用你的共享 notebook 是一种很好的方式,可以确保所有人明白如何使用代码,并且可以更容易地继续前进,而不需要你的帮助。

如果你的脚本功能组织得很好,不需要做什么修改,直接将仓库放到 Jupyter notebook 可以导入和使用代码的地方(设置的服务器或者 notebook 的 PYTHONPATHhttp://stackoverflow.com/questions/3402168/permanently-add-a-directory-to-pythonpath)是个好主意,这样你正在使用的模块总是可用的)。通过这种方式,你可以导入那些 main 函数到 notebook 中,在有人点击 notebook 的“运行所有”按钮时,所有的脚本会运行并且生成报告。

14.7 大规模自动化

如果系统需要多台机器或者服务器来处理,或者报告是关于一个分布式系统或者其他事件驱动系统的,很可能你会需要比 Web 接口、notebook 和 corn 更加健壮的工具。如果你需要一个真正的任务管理系统并且想要使用 Python,你很幸运。在这一节中,我们会介绍一个健壮的任务管理工具,Celery(http://www.celeryproject.org),它能处理大量的任务,自动化 worker(你会在下一节学习 worker 相关的知识),并且提供监控解决方案。

我们还会介绍操作自动化。如果你管理着一系列的服务器或不同需求的环境,这会很有帮助。Ansible(http://www.ansible.com)是一个非常棒的自动化工具,能够帮助完成各种任务,从数据库迁移到大规模集成部署。

还有一些 Celery 的替代方案,例如 Spotify 的 Luigi(https://github.com/spotify/luigi)。如果你正在使用 Hadoop,并且有大规模任务管理的需要(特别是长时间运行的任务,这可能成为一个痛点),它非常有用。关于操作自动化的好的替代品确实有很多。如果你只需要管理很少的服务器,对于只使用 Python 的部署,一个好的选择是 Fabric(http://www.fabfile.org/)。

对于大规模的服务器管理,一个很好的选择是 SaltStack(http://saltstack.com/),或同很多部署与管理工具[例如 Chef(https://www.chef.io/chef/)或 Puppet(https://puppetlabs.com/)]一起使用 Vagrant(https://www.vagrantup.com/)。我们选择重点介绍在这一节中使用的一些工具,但是它们不是使用 Python 处理大规模自动化程序的唯一选择。考虑到领域的声望和必要性,我们建议在你最喜欢的科技论坛上参与大规模自动化的讨论,例如 Hacker News(https://news.ycombinator.com/)。

14.7.1 Celery:基于队列的自动化

Celery(http://www.celeryproject.org/)是一个用来创建分布式队列系统的 Python 库。有了 Celery,可以通过调度器或者通过时间和消息来管理任务。如果你正在寻找能够处理长时间运行的时间驱动的任务的、可扩展的工具,Celery 是一个很完整的解决方案。Celery 很好地集成了几个不同的队列。它使用设置文件、用户接口和 API 调用来管理任务。它非常容易上手,所以如果这是你第一次使用任务管理系统的话,没有必要害怕。

无论你如何创建 Celery 项目,都很可能包含下面的任务管理系统组件。

消息中间件类似于一个等待处理的任务的队列。

  • 任务管理器 / 队列管理器(Celery)

这个服务会跟踪一些逻辑,这些逻辑控制着使用多少 worker、什么任务有优先权、以及何时重试,等等。

  • worker

worker 是通过 Celery 控制的 Python 进程,执行 Python 代码。它们知道你让它们去执行什么任务,并且尝试执行这些 Python 代码到结束。

允许查看 worker 和队列,对于回答类似于“昨晚什么失败了”这样的问题很有用。

Celery 有一个很有用的上手指南(http://docs.celeryproject.org/en/latest/getting-started/first-steps-with-celery.html),但是我们发现最大的问题并不是学习如何使用 Celery,而是了解什么类型的任务适合于队列、什么类型不适合于队列。表 14-2 综述了一些有关基于队列自动化的问题和原则。

表14-2:队列还是非队列?

基于队列的任务管理需求 不需要队列的自动化需求
任务没有一个确定的截止日期 任务可以并且的确有截止日期
不需要知道有多少任务 可以轻松地量化需要完成什么任务
只是大体上知道任务的优先级 清楚地知道任务的优先级
任务不需要总按顺序发生,或者通常不是有顺序的 任务必需按顺序发生
任务有时花费很长的时间,有时花费很短的时间 需要知道任务花费多长时间
任务调用(或排队)基于一个事件或其他的 任务结束任务基于时间或其他可预测的事情
任务失败是可以的;可以重试 必须了解每一次任务的失败
有大量的任务,而且很有可能继续增长 每天只有几个任务

这些都是一般性的需求,但是它们指出了关于何时选择任务队列和何时最好运行在一个有警报、监控和日志的调度器上,这两者之间的一些理念区别。

第 14 章 自动化和规模化 - 图22 任务的不同部分位于不同的系统中是没问题的,而且你在大公司会经常看到不同的任务“桶”(bucket)。也可以同时测试基于队列的和不基于队列的任务管理工具,以确定哪一种最适合你和你的项目。

Python 也有其他的任务和队列管理系统,包括 Python RQ(http://python-rq.org/)和 PyRes(https://github.com/binarydud/pyres)。这两者都是很新的库,因此在解决问题方面,可能没有足够的资源能够通过 Google 搜索到,但是如果你想要从 Celery 开始,之后转移到其他的选择上,你有了备选项。

14.7.2 Ansible:操作自动化

如果你当前的规模需要 Celery 帮助管理任务,很可能你同样需要一些帮助,来管理其他的服务和操作。如果项目需要在一个分布式系统上维护,你应该开始组织它们,这样可以轻松地通过自动化实现分布式。

Ansible(http://www.ansible.com/home)是一个自动化项目操作的非常棒的系统。Ansible 提供了一系列的工具,你可以用来迅速编写、部署和管理代码。你可以使用 Ansible 来迁移项目,备份远程机器上的数据。你还可以使用它根据需要来使用安全补丁或新的包更新服务器。

Ansible 有一个快速开始的视频(http://docs.ansible.com/quickstart.html),可用来了解所有的基础知识,但是我们同样想强调文档中描述的几个最有用的特性:

同样建议你查看 Justin Ellingwood 的关于 Ansible 操作的介绍(https://www.digitalocean.com/community/tutorials/how-to-create-ansible-playbooks-to-automate-system-configuration-on-ubuntu)和 Servers for Hackers 关于 Ansible 的扩展介绍(https://serversforhackers.com/an-ansible-tutorial)。

第 14 章 自动化和规模化 - 图23 如果你只有一两台服务器,或者只部署一两个项目,Ansible 很可能太高级或过于复杂,但是随着项目的成长,你需要一些工具来帮助保持项目的有效组织时,它就是一个很好的资源。如果你对操作和系统管理有兴趣,这是一个很值得学习和掌握的工具。

如果你更喜欢把操作留给你创建的一个很好的镜像上,每一次操作时只需要重新启动,大量的云服务提供商允许你这么做!对于你的数据处理需求来说,你并不需要成为一个操作自动化专家。

14.8 监控自动化程序

花费时间监控自动化程序是必需的。如果你不知道任务是否完成了,或者不知道任务成功还是失败了,你最好还是不要运行它们了。因此,监控你的脚本和运行它们的机器是过程中非常重要的一部分。

举个例子,如果有一个隐藏的 bug,数据并没有被加载,而且每天或每周,你都在老的数据上运行报告,这是个非常可怕的消息。在自动化程序中,失败并不总是很明显,因为脚本可能使用旧数据或者在存在错误和不一致的情况下继续运行。监控是观察脚本成功或者失败,即使所有的迹象都表明脚本仍然在正常地执行。

第 14 章 自动化和规模化 - 图24 监控可以有小的或者大的足迹,这取决于任务的规模和需求。如果你会有一个大规模的自动化程序,跨越多个服务器,你可能需要使用一个大型的分布式监控系统,或者具有监控功能的服务。然而,如果你正在一个家庭服务器上运行任务,可能只需要使用内置的 Python 日志工具。

你可能还想在脚本中使用一些警报和通知工具。在 Python 中,上传、下载、用邮件发送,甚至用 SMS 发送结果都非常简单。在这一节,我们会介绍几种不同的日志选择,并查看发送通知的方法。在全面测试并深入理解日常监控所有潜在的错误之后,你可以充分地自动化任务,并通过警报管理错误。

14.8.1 Python日志

最基本的监控脚本的方式是日志。你很幸运,Python 有一个非常健壮并且有丰富特性的日志环境,它是标准库的一部分。与你交互的客户端或库通常都拥有集成了 Python 日志系统的日志工具。

使用 Python 内置的日志模块中提供的简单基本的配置,可以实例化日志器,并且开始记录。之后你可以使用很多不同的配置选项,来迎合脚本特殊的日志需求。Python 的日志模块允许你设置特定的日志等级(https://docs.python.org/2/library/logging.html#logging-levels)和日志的记录属性(https://docs.python.org/2/library/logging.html#logrecord-attributes),调整日志的格式。日志器对象同样拥有方法和属性(https://docs.python.org/2/library/logging.html#logger-objects),根据你的需要,这也会很有用处。

下面是在代码中如何创建并使用日志的实例。

  1. import logging
  2. from datetime import datetime
  3. def start_logger():
  4. logging.basicConfig(filename='varlog/my_script/daily_report_%s.log' %
  5. datetime.strftime(datetime.now(), '%m%d%Y_%H%M%S'),
  6. level=logging.DEBUG,
  7. format='%(asctime)s %(message)s',
  8. datefmt='%m-%d %H:%M:%S')
  9. def main():
  10. start_logger()
  11. logging.debug("SCRIPT: I'm starting to do things!")
  12. try:
  13. 20 / 0
  14. except Exception:
  15. logging.exception('SCRIPT: We had a problem!')
  16. logging.error('SCRIPT: Issue with division in the main() function')
  17. logging.debug('SCRIPT: About to wrap things up!')
  18. if __name__ == '__main__':
  19. main()

❶ 使用 logging 模块的 basicConfig 方法,初始化日志系统,这需要一个日志文件名称。这段代码输出日志到 varlog 文件夹中的文件夹 myscript。文件名是 daily_report<DATEINFO>.log,其中 <DATEINFO> 是脚本开始执行的时间,包括月、日、年、小时、分钟和秒。这告诉我们何时以及为什么脚本开始运行,同时这是一个好的日志实践。

❷ 设置日志级别。通常,你会想要把级别设置为 DEBUG,这样可以在代码中留下调试信息,在日志中跟踪它们。如果想要更多的信息,你可以使用 INFO 级别,这会展示更多的来自辅助库的日志信息。一些人更喜欢简略的日志,设置日志级别为 WARNINGERROR

❸ 使用日志的记录属性设置 Python 日志的格式。这里我们记录发送给日志的信息和打印日志的时间。

❹ 设置一个易于阅读的日期格式,这样很容易使用我们喜欢的日期格式解析或搜索日志。这里我们记录了月、日、小时、分钟和秒。

❺ 调用模块的 debug 方法开始记录日志。这个方法需要一个字符串。我们用单词 SCRIPT: 作为脚本日志实体的前缀。在日志中添加类似这样可搜索的标签,会在之后帮助你确定哪个进程或库写了这条日志。

❻ 使用 logging 模块的 exception 方法,输出你发送的字符串和 Python 异常的回溯信息,因此只能够在一个异常代码块中使用。这在调试错误和查看脚本中有多少异常时非常有用。

❼ 使用级别 error 打印一个长一点的错误信息。logging 模块能够打印不同级别的错误信息,包括 debugerrorinfowarning。日志级别应该与打印的内容一致,为正常信息使用 infodebug 级别,用 error 打印脚本中的错误和异常信息。通过这种方式,你永远知道去哪里找问题,以及如何在检查中正确地解析日志。

正像在示例中做的那样,我们发现开始日志信息时,用一个关于正在输出信息的代码模块或区域的标签是很有用处的。这会帮助确定错误发生在哪里。这也使得日志便于搜索和解析,因为你可以清晰地看到脚本碰到了什么错误或问题。学习日志最好的方式是在第一次编写脚本的时候确定在哪里放置信息,并在脚本中保留重要的信息,以确定是否发生了问题以及在哪些节点发生了问题。

第 14 章 自动化和规模化 - 图25 即使已经预料到了异常,每一个异常也应该通过日志记录下来。这会帮助你跟踪异常发生的频率,以及你的代码是否应按照正常情况处理它们。logging 模块提供了 exceptionerror 方法,你可以打印异常和 Python 的回溯,并且可以使用 error 添加一些额外的信息,详细描述可能发生了什么,以及哪部分代码触发了这个错误。

你还应该将同数据库、API 和外部系统的交互通过日志记录下来。这会帮助你确定脚本与它们的交互何时出现了问题,确保它们是稳定的、可信赖的或者可以使用的。你与之交互的许多库同样能够根据配置打印日志。例如,requests 模块会将连接问题和请求直接记录到你的脚本日志中。

即使不为脚本创建任何其他的监控或警报,你也应该使用日志。它很简单,并且为未来的你和其他人提供了优秀的文档。日志并不是唯一的解决方案,但是它们是一个好的标准,并且是自动化程序监控的基础。

除了日志之外,你可以为脚本设置易于分析的警报。在下一节中,我们会介绍几种使脚本能够发送有关成功和失败信息的方式。

14.8.2 添加自动化信息

发送报告、跟踪脚本、通知自己错误的一个简单方式是使用邮件或者其他直接从脚本发送的信息。有很多 Python 库能帮助完成这个任务。精确地确定脚本和项目中需要什么类型的信息是一个好的开始。

问问自己下列选项是否适用于你的脚本。

  • 它会产生一个报告,并将报告发送到特定的收件人列表。

  • 它有一个清晰的成功 / 失败信息。

  • 它与其他的合作者或同事相关。

  • 它提供了不易在网站或快速指示板上查看的结果。

如果这其中的任何一个听起来像你的项目,你的项目很可能适合某种方式的信息自动化。

  • 邮件

使用 Python 处理邮件非常直观。建议你通过你最喜欢的邮件提供商(我们使用 Gmail),构建一个独立的脚本的 email 地址。如果它没有立即自动同 Python 集成,很可能能够通过在网上搜索发现合适的配置列表和有用的示例配置。

让我们看一下曾用来发送带有附件的邮件到收件人列表的脚本。我们修改了 @dbieber 编写的代码片段(https://gist.github.com/dbieber/5146518),这段代码修改自 Rodrigo Coutinho 的博文“使用 Python 发送 Gmail 邮件”(http://kutuma.blogspot.jp/2007/08/sending-emails-via-gmail-with-python.html):

  1. #!usrbin/python
  2. # Adapted from
  3. # http://kutuma.blogspot.com/2007/08/sending-emails-via-gmail-with-python.html
  4. # Modified again from: https://gist.github.com/dbieber/5146518
  5. # config file(s) should contain section 'email' and parameters
  6. # 'user' and 'password'
  7. import smtplib
  8. from email.MIMEMultipart import MIMEMultipart
  9. from email.MIMEBase import MIMEBase
  10. from email.MIMEText import MIMEText
  11. from email import Encoders
  12. import os
  13. import ConfigParser
  14.  
  15.  
  16. def get_config(env):
  17. config = ConfigParser.ConfigParser()
  18. if env == "DEV":
  19. config.read(['config/development.cfg'])
  20. elif env == "PROD":
  21. config.read(['config/production.cfg'])
  22. return config
  23.  
  24.  
  25. def mail(to, subject, text, attach=None, config=None):
  26. if not config:
  27. config = get_config("DEV")
  28. msg = MIMEMultipart()
  29. msg['From'] = config.get('email', 'user')
  30. msg['To'] = ", ".join(to)
  31. msg['Subject'] = subject
  32. msg.attach(MIMEText(text))
  33. if attach:
  34. part = MIMEBase('application', 'octet-stream')
  35. part.set_payload(open(attach, 'rb').read())
  36. Encoders.encode_base64(part)
  37. part.add_header('Content-Disposition',
  38. 'attachment; filename="%s"' % os.path.basename(attach))
  39. msg.attach(part)
  40. mailServer = smtplib.SMTP("smtp.gmail.com", 587)
  41. mailServer.ehlo()
  42. mailServer.starttls()
  43. mailServer.ehlo()
  44. mailServer.login(config.get('email', 'user'),
  45. config.get('email', 'password'))
  46. mailServer.sendmail(config.get('email', 'user'), to, msg.as_string())
  47. mailServer.close()
  48.  
  49.  
  50. def example():
  51. mail(['listof@mydomain.com', 'emails@mydomain.com'],
  52. "Automate your life: sending emails",
  53. "Why'd the elephant sit on the marshmallow?",
  54. attach="my_file.txt")

❶ Python 内置的 smtplib 库(https://docs.python.org/2/library/smtplib.html)提供了一个 SMTP 的封装,SMTP 是发送和接收邮件的标准协议。

❷ Python 的 email 库(https://docs.python.org/2/library/email.html)帮助创建邮件信息和附件,并用合适的形式保存它们。

❸ 函数 get_config 从一系列的本地配置文件中加载配置。我们传递一个环境变量,环境变量可以为字符串 "PROD""DEV",来表明这是在本地运行("DEV")还是在远程生产环境运行("PROD")的程序。如果只使用一个环境,你可以简单地返回项目中唯一的配置文件。

❹ 这行代码使用 Python 的 ConfigParser 读取 .cfg 文件,返回 config 对象。

❺ 我们的 mail 函数接受一个邮件地址列表,作为变量 to、邮件的主题和文本、一个可选的附件和一个可选的 config 变量。附件需要是本地文件的名称。config 对象需要是 ConfigParser 的实例。

❻ 这行代码设置了默认配置。安全起见,我们使用 "DEV" 配置。

❼ 这行代码使用了 ConfigParser 对象来从配置文件中拉取邮件地址。这个变量安全地保存地址,并与仓库代码分离。

❽ 这段代码拆分邮件列表,并且用逗号和一个空格分离它们。这会展开邮件地址的列表为一个字符串,因为这是我们 MIME 需要的类型。

❾ 如果有附件的话,这行代码按照 MIME 分组标准开始对附件做特殊处理,以发送附件。

❿ 这段代码使用传入的文件名字符串打开并读取完整的文件。

⓫ 如果你没有使用 Gmail,设置这些参数为邮件服务提供商 SMTP 的主机和端口。如果有好的文档的话,很容易找到这些参数。如果没有的话,直接搜索“SMTP 设置 < 你的邮件服务提供商名称 >”应该会得到答案。

⓬ 这是一些示例代码,提示你 mail 函数所需的参数。你可以看到需要的数据类型(字符串、列表、文件名)和顺序。

简单的 Python 内置库 smtplibemail 使用它们的类和方法帮助我们快速地创建和发送邮件信息。将脚本中的一些其他部分(例如在配置文件中保存邮件地址和密码)抽象出来是保证脚本和仓库安全可复用所必需的。一些默认的设置确保脚本永远可以发送邮件。

  • 短消息服务(SMS)和语音

如果想要集成电话信息到警报程序中,你可以使用 Python 来发送文本信息,或者打电话。Twilio(https://www.twilio.com)是一个经济有效的选择,支持多媒体信息和自动拨打电话。

第 14 章 自动化和规模化 - 图26 在开始使用 Twilio API 之前,你需要注册得到认证代码和 key,并且安装 Twilio 的 Python 客户端(https://github.com/twilio/twilio-python)。在 Python 客户端的文档(https://twilio-python.readthedocs.org/en/latest/)中有一个很长的代码示例列表,所以如果你需要做一些有关语音或者文本的事情,很可能这里有一个好的可用的特性。

看一下发送一个快速的文本信息是多么简单。

  1. from twilio.rest import TwilioRestClient
  2. import ConfigParser
  3.  
  4.  
  5. def send_text(sender, recipient, text_message, config=None):
  6. if not config:
  7. config = ConfigParser('config/development.cfg')
  8. client = TwilioRestClient(config.get('twilio', 'account_sid'),
  9. config.get('twilio', 'auth_token'))
  10. sms = client.sms.messages.create(body=text_message,
  11. to=recipient,
  12. from_=sender)
  13. def example():
  14. send_text("+11008675309", "+11088675309", "JENNY!!!!")

❶ 我们会使用 Twilio Python 客户端通过 Python 直接同 Twilio API 交互。

❷ 这行代码定义了一个可以用来发送文本的函数。我们需要发送者和接收者的电话号码(以国家代码开头),以及想要发送的简单文本信息,我们还能传递一个配置对象。我们会使用配置来同 Twilio API 进行认证。

❸ 这段代码创建了一个客户端对象,它会使用我们的 Twilio 账户认证。当你注册 Twilio 账户时,会收到一个 account_sid 和一个 auth_token。将它们放在脚本使用的配置文件中名为 twilio 的小节中。

❹ 为了发送一条信息,这段代码使用了客户端的 SMS 模块,并且调用了 message 资源的 create 方法。正像 Twilio 文档中写的(https://twilio-python.readthedocs.io/en/latest/usage/messages.html#sending-a-text-message),之后可以通过几个参数发送一条文本信息。

❺ Twilio 在全世界范围工作,并且需要基于国际的电话号码。如果你不确定要使用什么国际电话代码,维基有一个很棒的列表(https://en.wikipedia.org/wiki/List_of_country_calling_codes)。

第 14 章 自动化和规模化 - 图27 如果你对让脚本通过 Python“交谈”感兴趣,Python 的 text-to-speech 模块(https://pyttsx.readthedocs.org/en/latest/)可以在电话上“朗读”你的文字。

  • 集成聊天

如果你想要集成聊天室到警报系统中,或者你的团队或合作者普遍使用聊天室,有很多 Python 聊天室工具可用。根据聊天室客户端和需要,可以选择 Python 或者基于 API 的解决方案,你可以使用关于 REST 客户端的知识,连接并向正确的人发送消息。

如果你使用 HipChat,他们的 API(https://www.hipchat.com/docs/apiv2)很容易同 Python 应用程序或脚本集成。这里有各种各样的 Python 库(https://www.hipchat.com/docs/apiv2/libraries),可以简单地发送信息到聊天室或直接发送给个人。

为了开始使用 HipChat API,你需要首先登录(https://hipchat.com/account/api)并获取一个 API token。你可以使用 Python 库 HypChat(https://github.com/RidersDiscountCom/HypChat),发送一个快速信息到聊天室。

首先,使用 pip 安装 HypChat:

  1. pip install hypchat

现在,使用 Python 发送一条信息!

  1. from hypchat import HypChat
  2. from utils import get_config
  3.  
  4.  
  5. def get_client(config):
  6. client = HypChat(config.get('hipchat', 'token'))
  7. return client
  8.  
  9.  
  10. def message_room(client, room_name, message):
  11. try:
  12. room = client.get_room(room_name)
  13. room.message(message)
  14. except Exception as e:
  15. print e
  16.  
  17.  
  18. def main():
  19. config = get_config('DEV')
  20. client = get_client(config)
  21. message_room(client, 'My Favorite Room', "I'M A ROBOT!")

❶ 使用 HypChat 库同聊天室客户端交谈。这个库使用 HipChat token 初始化一个新的客户端,我们将 token 保存在配置文件中。

❷ 这行代码使用 get_room 方法,选择与字符串名称匹配的房间。

❸ 这行代码使用 message 方法发送一条信息到一个房间或者一个用户,传递给函数一个简单的字符串,里面包含想说的内容。

❹ 总是在基于 API 库的周围使用 try…except 代码块,以应对连接错误或者 API 改变带来的异常。这段代码打印了错误,但是你可能更想打印日志,以充分地自动化脚本。

❺ 这里使用的函数 get_config 是从不同的脚本中导入的。通过引入这些辅助函数,并且将它们放置到独立的模块中复用,我们遵循了模块化的代码设计。

如果想要登录到聊天室,你可以使用 HipLogging(https://github.com/invernizzi/hiplogging)探索这些选项。取决于需求和团队的工作方式,你可以根据自己的喜好设置聊天室的登录功能;但是好消息是,你总是可以在他们可能看见的地方留下笔记!

如果你更喜欢 Google Chat,有很多非常棒的示例展示了如何使用 SleekXMPP(http://sleekxmpp.com/getting_started/sendlogout.html)做这件事。你还可以使用 SleekXMPP 发送 Facebook 聊天信息(http://stackoverflow.com/questions/18906462/send-facebook-messages-via-sleekxmpp/21452035#21452035)。

关于 Slack 信息,查看 Slack 团队的 Python 客户端(https://github.com/slackhq/python-slackclient)。

对于其他的聊天客户端,建议你使用 Google 搜索“Python <你的客户端名称 >”。很有可能有人已经尝试使用他们的 Python 代码同这个客户端连接,又或者有 API 可供你使用。在第 13 章中,你知道了如何在工作中使用一个 API。

关于脚本(和自动化)成功或失败的警报的选择有这么多,很难知道应使用哪一个。重要的是选择一个你或你的团队经常使用的方式。优先考虑易用性和与日常生活的整合是必需的,自动化会帮助你节省时间,不让你花费更多的时间来检查服务。

14.8.3 上传和其他报告

如果你需要上传报告或图片到独立的服务或文件分享作为自动化的一部分,有很多很棒的工具。如果这是一种在线的形式,或者一个需要与之交互的网站,建议使用第 12 章中的 Selenium 爬取技巧。如果这是一个 FTP 服务器,Python 有一个标准 FTP 库(https://docs.python.org/2/library/ftplib.html)。如果需要发送报告给一个 API,或者通过 Web 协议发送,你可以使用 requests 库,或者在第 13 章中学到的 API 技巧。如果需要发送 XML,你可以使用 LXML(查看第 11 章)。

无论准备同哪些服务交互,你很有可能与服务有过接触和交流。我们希望你自信地练习这些技能,并且独立思考。

14.8.4 日志和监控服务

如果一个脚本满足不了需求,或者你想将自动化程序合并到一个大型的组织框架中,你可能需要研究将日志和监控作为一个服务的技术。有很多公司通过创建工具和系统来跟踪日志,致力于让数据分析师和开发者的生活更简单。这些工具通常有非常简单的 Python 库,将日志和监控信息发送到它们的平台。

第 14 章 自动化和规模化 - 图28 通过日志服务,你可以将更多的时间投入到研究和脚本中,将更少的时间用于管理监控和日志。这可以将“我们的脚本在不在工作,工作得怎么样?”这样的问题留给团队中的非开发者,因为其中的很多服务都有很棒的报表和内置的警报。

根据自动化程序的大小和布局,你可能同时需要系统监控以及脚本与错误监控。在这一节中,我们会查看几种能够同时完成这两件事的服务,同时也会查看一些更加专业的服务。即使你的自动化程序的规模没有大到需要它们,但是了解什么是可用的总是好的。

  • 日志和异常

基于 Python 的日志服务提供了打印日志到一个中心服务的能力,同时让你的脚本在一系列不同的机器上运行,无论是本地机器还是远程机器。

一个有着很棒的 Python 支持的类似服务是 Sentry(https://getsentry.com/welcome/)。只 需要每月支付很少的费用,你就可以访问错误的报告板,收到基于异常临界值发送的警报,监控基于日、周、月的错误和异常类型。Sentry 的 Python 客户端(https://github.com/getsentry/raven-python)非常容易安装、配置和使用。如果你正在使用类似 Django、Celery 的工具,甚至是简单的 Python 日志工具(https://docs.sentry.io/clients/python/),Sentry 都有相应的集成接口,所以你不需要大量地修改代码来开始工作。最重要的是,基础代码持续地更新,工作人员非常友善,在你有问题的时候乐于帮助你。

其他选择包括 Airbrake(https://airbrake.io/languages/python_bug_tracker)和 Rollbar(https://rollbar.com)。Airbrake 最开始是一个基于 Ruby 的异常跟踪器,现在开始支持 Python。这是一个很受欢迎的市场,所以在本书开始印刷时很可能会有新的库发布。

还有拉取和解析日志的服务,例如 Loggly(https://www.loggly.com/)和 Logstash(https://www.elastic.co/products/logstash)。这些服务允许你在聚合的日志级别上监控它们,同样也可以解析、搜索以及在日志中寻找问题。这些工具只在你有足够的日志和时间检查它们时才有用,但是对于有着大量日志的分布式系统非常有用处。

  • 日志和监控

如果你有分布式机器或者正在集成脚本到公司或大学的基于 Python 的服务器环境,你可能想要更健壮的监控,不仅仅是针对 Python,而是整个系统。有很多服务提供了系统数据库负载监控、Web 应用程序的监控,同样也有自动化任务的监控。

New Relic(http://newrelic.com/)是这方面最流行的服务之一,它可以监控服务器和系统进程,也可以监控 Web 应用程序。使用 MongoDB 和 AWS ?或者 MySQL 和 Apache ? New Relic 插件(http://newrelic.com/plugins)让你可以轻松地将服务的日志集成到用于监控服务器和应用健康的报告板中。除此之外,它们提供了一个 Python 代理(https://docs.newrelic.com/docs/agents/python-agent/getting-started/introduction-new-relic-python),通过它你可以很容易地为 Python 应用(或脚本)在相同的生态系统中打印日志。在整合所有的监控工具之后,发现问题和创建合适的警报系统会变得更简单,这样团队中的相关人员会及时了解任何发生的问题。

另外一个系统和应用监控服务是 Datadog(https://www.datadoghq.com/)。Datadog 允许你整合许多服务(https://www.datadoghq.com/product/integrations/)到一个报告板中。这会节省时间和精力,让你轻松地发现项目、应用和脚本中的错误。Datadog 的 Python 客户端(https://github.com/DataDog/datadogpy)能够打印你想监控的不同事件的日志,但是需要一些个性化设置。

无论你使用什么监控器,也无论你是决定构建自己的监控器还是使用一个服务,拥有规律的警报、深入了解你使用的服务、理解代码和自动化系统的完整性是非常重要的。

第 14 章 自动化和规模化 - 图29 当依赖自动化程序完成工作和项目的其他部分时,你应该确保监控系统容易使用并且直观,这样你可以专注于项目中更重要的部分,不用承担疏忽错误或其他问题的风险。

14.9 没有万无一失的系统

正如本章讨论的那样,完全依赖于任何系统都是鲁莽的,应该避免。无论你的脚本或系统看起来多么完美无缺,都会在一些时间点失败。如果你的脚本依赖于其他的系统,它们可能在任何时间点失败。如果你的脚本包含来自 API、服务或网站的数据,有可能 API 或网站会改变或崩溃,以致需要维修,或者可能会发生任意数量的其他事件,导致自动化失败。

如果一个任务是绝对的关键任务,就不应该自动化。你可以自动化其中的一部分或绝大部分,但是它总是需要高度的监督和工作人员来确保它没有失败。如果这很重要,但不是最重要的部分,对这一部分的监控和警报应该反映出它的重要程度。

第 14 章 自动化和规模化 - 图30 随着深入数据处理和自动化,你会花费越来越少的时间构建高质量的任务和脚本,更多的时间会花费在解决问题、关键问题的思考以及应用分析领域的方法论和领域知识到工作中。自动化可以帮助你做这些事情,但是对于自动化什么重要任务以及如何自动化,持谨慎的态度总是好的。

随着自动化程序的成熟和发展,你不仅改进了自动化程序,让它有更好的伸缩性,同时增加了对代码库、Python 以及数据与报告的理解。

14.10 小结

你已经学习了使用小规模和大规模解决方案来自动化大量的数据处理工作。你可以通过日志、监控和基于云的解决方案监控和跟踪脚本和任务以及子任务,这意味着你可以花费更少的时间跟踪这些事情,花费更多的时间在真正的报告上。你已经定义了自动化可能成功和失败的方式,并帮助创建了一套清晰的关于自动化的指南(理解所有的系统最后都会也必将失败)。你知道如何给其他的团队成员和同事权利,以后他们可以自己运行任务,同时你也学习了一些部署和设置 Python 自动化的知识。

表 14-3 总结了本章中的新概念和库。

表14-3:新的Python和编程概念和库

任务/库 目的
在远程运行脚本 在一台服务器或者其他的机器上运行代码,这样你就不必担心自己使用计算机时会受到干扰
命令行参数 在运行 Python 脚本时使用 argv 解析命令行参数
环境变量 使用环境变量参与实现脚本逻辑(例如运行在什么机器上,使用什么配置)
使用 Cron 在你的服务器或远程计算机上编写一个 shell 脚本来执行一个 cron 任务。是一种基本的自动化形式
配置文件 使用配置文件为脚本定义敏感或特殊的信息
Git 部署 使用 Git 轻松地部署代码到一台或更多的远程机器上
并行处理 Python 的 multiprocessing 模块让你能在同一时间轻松地运行很多进程,同时有共享数据和锁机制
MapReduce 有了分布式数据,你可以根据特定的属性映射数据,或者通过执行一系列的任务,之后规约数据,聚合分析
Hadoop 和 Spark 两个在云计算中工具用来执行 MapReduce 操作的工具。Hadoop 更适合于已经定义和存储的数据集,而 Spark 更适合于你有流式、特别的大或动态生成的数据时
Celery(任务队列使用和管理) 让你能够使用 Python 创建一个任务队列并管理它,允许你自动化没有清晰的开始和结束时间的任务
logging 模块 用于你的应用或脚本的内置日志模块,通过它你可以轻松地跟踪错误、调试信息和异常
smtpemail 模块 来自 Python 脚本的内置邮件警报
Twilio 一个有着 Python API 客户端的服务,提供电话和文本信息业务
HypChat 一个 Python API 库,可以使用 HipChat 聊天客户端构建聊天程序
日志服务 使用类似 Sentry 或 Logstash 的服务管理你的日志、错误率和异常
监控服务 使用类似 New Relic 或 Datadog 的服务监控日志、服务正常运行时间、数据库问题和性能(即发现硬件问题)

学习了本书前面章节中的丰富知识,你现在应该已经准备好去花时间构建高质量的工具,并让这些工具为你做枯燥乏味的工作了。你可以丢掉那些老套的电子表格公式,使用 Python 导入数据,运行分析,直接交付报告到邮箱。你可以真正地让 Python 管理刻板的任务(就像机器人助手一样),自己投身于报告中更关键和有挑战的部分。