数据分析

我的第一份与 Web 相关的工作是在 1995 年,为时代华纳的多个产业开发 Web 应用程序。当我刚开始在那里工作时,我们只有少数程序员和经理处理所有的任务。但随着时间的推移,正如所有成长中的公司和组织一样,我们开始专业化。其中一位开发人员负责日志文件——存储它们,然后对它们进行一些基本的分析。

虽然我认识到这项工作很重要,但我花了多年时间才意识到,在某些方面,他的工作比我编写的应用程序对业务更重要。那位处理这些日志文件并为我们的老板分析它们的开发人员,使得我们能够知道谁在使用我们的系统,他们在查看和使用什么,他们来自哪里,以及我们可以在日志提供的不同数据点之间找到什么关联。当然,我们提供了将人们带到网站的内容和应用程序,但正是分析日志文件的人员确保了我们的工作能够收回成本并实现我们的业务目标。

在过去的十年中,我更加体会到这种分析的必要性,因为 Web 的普及程度呈爆炸式增长,企业已经学会使用这些数据来提高盈利能力,并且数据科学已成为一个不断发展的领域。我们现在正淹没在数据中,而使用分析工具和库来理解数据比以往任何时候都更加重要。

在本文中,我将开始探索使用 Python 进行数据科学,以及如何使用像 Apache 日志文件这样普通的东西,从中提取信息,以便更好地了解您的访客及其行为。在接下来的文章中,我计划介绍如何使用数据科学方法以多种不同的方式分析此日志文件,从而深入了解其提供的原始数据,并回答有关您的 Web 应用程序的问题。我还将介绍如何将此分析呈现给您的经理和客户,提供您执行的分析的强大可视化效果。

数据科学与 Python

我在研究生院学习了一种叫做“学习科学”的学科。在那里的时候,我们经常开玩笑说,任何名称中包含“科学”一词的学科可能都不是真正的科学。无论数据科学是否是“真正的”科学,它都是一个庞大、重要且不断发展的领域——一个允许企业根据他们收集的数据做出决策的领域。数据越多,您越智能地使用这些数据,您就越能更好地预测用户和客户的需求和愿望。

数据科学被粗略地定义为编程和统计学的交叉,应用于特定领域。您收集一些数据,然后使用统计方法来回答数据可能能够回答的问题。统计学背景可能会有所帮助,不仅因为它会向您展示您可能想要应用的分析类型,还因为它让您对您发现的相关性产生健康的怀疑态度。您真的发现您的网站仅在世界特定地区的人群中受欢迎吗?或者,您只是在世界某个地区大力宣传它,从而影响了谁更有可能访问?

您可以通过提出问题来启动数据科学项目,或者您可以开始以各种方式探索数据,希望您会找到有趣的相关性。无论如何,数据科学期望您了解各种方法,您可以从中选择一种或多种适合回答您问题的方法。然后,您应用这些方法,使用统计检验来确定您的答案是否显着——也就是说,它们是否仅仅可能是随机的。

Python 长期以来被系统管理员、Web 开发人员和研究人员使用,并且正在成为数据科学从业人员中越来越受欢迎的选择。这是多种因素共同作用的结果。首先,Python 具有出了名的浅学习曲线,允许非程序员在短时间内入门并完成工作。

其次,Python 可以轻松、简洁地处理各种数据格式和数据库。因此,无论您的原始数据是文本文件、关系数据库、NoSQL 数据库、CSV 文件、Excel 文件还是更不寻常的东西,Python 都很有可能能够轻松快速地从中读取数据。

第三,Python 中用于分析数据的许多库,例如 NumPy、SciPy 和 Matplotlib,已经开发多年,在可用性、表达能力和高效执行之间提供了极好的平衡。近年来,Pandas 库在此基础上添加了一个更有用的层。

最后,IPython(现在称为 Jupyter)的开发简直是革命性的,它为开发人员和数据科学家提供了与他们的程序和数据交互的能力(就像传统的 REPL 一样),但可以在一个 Web 页面上完成,该页面可以轻松地在协作者之间共享,或者通过电子邮件发送以进行离线使用和分析。事实上,我现在在我的所有 Python 课程中使用 IPython Notebook。它不仅为我提供了一种高质量的方式来展示我在课堂上做的实时编码演示,而且我可以将文档发送给我的学生,他们可以重播、修改并更好地理解我在课堂上讨论的内容。

导入数据

任何数据科学项目的第一步是准备好数据。在想要分析 Apache 日志文件的情况下,您可能会认为只需获取文件就足够了。但是,Pandas(我将用于分析此示例数据的 Python 库)就像许多其他数据科学系统(例如,R 语言)一样,期望数据采用 CSV(逗号分隔值)格式。这意味着您需要将日志文件转换为 CSV 文件,其中 Apache 日志中的字段将转换为 CSV 中的字段。

在 Python 中执行这种转换实际上非常简单。以下是我博客的 Apache 日志文件的示例


122.179.187.119 - - [22/Jan/2016:11:57:26 +0200] "GET
 ↪/wp-content/uploads/2014/10/3D_book.jpg HTTP/1.1" 200 302222
 ↪"http://blog.lerner.co.il/turning-postgresql-rows-arrays-array/"
 ↪"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like
 ↪Gecko) Chrome/47.0.2493.0 Safari/537.36"
122.179.187.119 - - [22/Jan/2016:11:57:27 +0200] "POST
 ↪/wp-admin/admin-ajax.php HTTP/1.1" 200 571
 ↪"http://blog.lerner.co.il/turning-postgresql-rows-arrays-array/"
 ↪"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like
 ↪Gecko) Chrome/47.0.2493.0 Safari/537.36"
54.193.228.6 - - [22/Jan/2016:11:57:29 +0200] "GET
 ↪/category/python/feed/ HTTP/1.1" 200 25856 "-" "Digg Feed
 ↪Fetcher 1.0 (Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1)
 ↪AppleWebKit/534.48.3 (KHTML, like Gecko) Version/5.1
 ↪Safari/534.48.3)"

每一行都包含以下组件

  • 发出请求的 IP 地址。

  • 与身份验证相关的两个字段(用 - 字符表示)。

  • 时间戳。

  • HTTP 请求,以 HTTP 请求方法(通常为 GETPOST)和一个 URL 开头。

  • 结果代码,其中 200 表示“OK”。

  • 传输的字节数。

  • 引用页,即用户来自的 URL。

  • 浏览器标识自身的方式。

这些信息可能看起来有点原始和有限,但您可以使用它来更好地了解与您博客访问者相关的许多因素。请注意,它不包括基于 JavaScript 的分析包(例如,Google Analytics)可以提供的信息,例如会话、浏览器信息和 Cookie。尽管如此,日志文件可以为您提供一些良好的基础知识。

任何数据科学项目的首要步骤之二是:1) 导入数据和 2) 清理数据。这是因为任何数据源都将包含对您的目的而言并非真正有用或相关的信息,这会扰乱统计数据或为您尝试导入的数据增加无用的膨胀。因此,在这里我将尝试将 Apache 日志文件读取到 Python 中,删除那些不相关的行。当然,什么是被认为是“不相关”的在某种程度上是主观的;我稍后会讲到这一点。

让我们从对 Apache 日志文件进行非常简单的解析开始。Python 程序员首先学习的事情之一是如何迭代文件的行


infile = 'short-access-log'
for line in open(infile):
    print(line)

以上代码将逐行打印文件。但是,对于此示例,我对打印它不感兴趣;相反,我感兴趣的是将其转换为 CSV 文件。此外,我想删除那些不太有趣或提供虚假(垃圾)数据的行。

为了创建 CSV 文件,我将使用 Python 自带的 csv 模块。此模块的一个优点是它可以接受任何分隔符;尽管名称如此,但我更喜欢在列之间使用制表符,因为这样就不会将制表符与我传递的数据混淆。

但是,如何将日志文件中的数据导入 CSV 模块呢?处理此问题的一种简单方法是使用 str.split 方法来分解输入字符串。好消息是 split 将在一定程度上起作用,但坏消息是它解析事物的方式远不如您想象的那么优雅。而且,您最终会遇到各种疯狂的事情。

最重要的是,如果您想从 Apache 日志文件中读取数据,您需要弄清楚日志文件格式并读取它,可能使用正则表达式。或者,如果您更聪明一点,您可以使用现有的库,该库已经实现了 regexp 和逻辑。我在 PyPI(Python 包索引)上搜索并找到了 clfparser,这是一个包,它知道如何解析 Apache 日志文件,这种格式被称为“通用日志文件格式”,多年来被许多 HTTP 服务器使用。如果变量 line 包含我 Apache 日志文件中的一行,我可以执行以下操作


from clfparser import CLFParser
infilename = 'short-access-log'
for line in open(infilename):
    print CLFParser.logDict(line)

这样,我已经将日志文件的每一行都转换成了一个 Python 字典,字典中的每个键值对都引用了我日志文件行中的不同字段。

现在我可以回到我的 CSV 模块并使用它附带的 DictWriter 类。您可能可以猜到,DictWriter 允许您基于字典输出 CSV。您只需要声明您想要的字段,允许您忽略一些字段,甚至设置它们在生成的 CSV 文件中的顺序。然后您可以迭代您的文件并创建 CSV。

这是我想出的代码


import csv
from clfparser import CLFParser

infilename = 'short-access-log'
outfilename = 'access.csv'

with open(outfilename, 'w') as outfile, open(infilename) as infile:
    fieldnames = ['Referer', 'Useragent', 'b', 'h', 'l', 'r', 's',
     ↪'t', 'time', 'timezone', 'u']
    writer = csv.DictWriter(outfile, fieldnames=fieldnames,
     ↪delimiter='\t')
    writer.writeheader()

    for line in infile:
        writer.writerow(CLFParser.logDict(line))

让我们一次一块地逐步了解这段代码。它不是很复杂,但它确实将许多包和功能组合在一起,在很小的空间内提供了强大的功能

  • 首先,我从 clfparser 模块导入了 csv 模块和 CLFParser 类。我将在该程序中使用这两个模块;第一个模块将允许我输出 CSV,第二个模块将允许我从 Apache 日志中读取数据。

  • 我在这里设置了输入和输出文件的名称,既是为了清理下面的代码,也是为了以后更容易重用这段代码。

  • 然后我使用 with 语句,它调用了 Python 中所谓的“上下文管理器”。这里的基本思想是,我正在创建两个文件对象,一个用于读取(日志文件),一个用于写入(CSV 文件)。当 with 代码块结束时,两个文件都将关闭,确保没有数据被遗留或仍然在缓冲区中。

  • 鉴于我将要使用 CSV 模块的 DictWriter,我需要指示字段输出的顺序。我通过列表来完成此操作;此列表允许我删除或重新排序字段,如果我想要这样做的话。

  • 然后,我创建 csv.DictWriter 对象,告诉它我想将数据写入 outfile,使用我刚刚定义的字段名称,并使用制表符作为字段之间的分隔符。

  • 然后我向文件写入一个标题;虽然这不是至关重要的,但我建议您这样做,以便以后更容易调试。此外,据我所知,所有 CSV 解析器都能够毫无问题地处理此类事情。

  • 最后,我迭代访问日志的行,将每一行转换为字典,然后将该字典写入 CSV 文件。的确,您可以争辩说,那里的最后一行是该程序的全部要点;直到那一点为止的所有内容都只是序言。

清理数据

您现在已经看到,您可以将数据从另一种形式导入到 CSV 文件中,这是数据科学中最常用的格式之一。但是,正如我之前提到的,您还需要做的关键事情之一是清理数据;分析虚假数据会给您带来虚假的结果。

那么,这里需要清理什么样的数据呢?

一个明显的候选方法是删除任何不是真人用户的东西。也许您有兴趣了解 Web 爬虫(例如来自 Google 和 Yahoo 的爬虫)在做什么。但更有可能的是,您想知道人类在做什么,这意味着删除所有这些机器人。

当然,这提出了一个问题,您如何知道请求是否来自机器人。作为人类,您可以检查 User-agent 字符串并做出有根据的猜测。但是,鉴于您正试图删除所有机器人,并且不断添加新的机器人,因此自动化的方法会更好。

对于这个问题没有完美的答案,但为了本文的目的,我决定使用 PyPI 中的另一个 Python 模块,尽管它有点过时——一个称为 robot-detection 的模块。其思想是,您导入此模块,然后在 Useragent 字段上使用 is_robot 函数。如果它是机器人,is_robot 将返回 True。这是我修改后的代码


import csv
from clfparser import CLFParser
from collections import Counter
import robot_detection

infilename = 'medium-access-log.txt'
outfilename = 'access.csv'
robot_count = Counter()

with open(outfilename, 'w') as outfile, open(infilename) as infile:
    fieldnames = ['Referer', 'Useragent', 'b', 'h', 'l', 'r', 's',
     ↪'t', 'time', 'timezone', 'u']
    writer = csv.DictWriter(outfile, fieldnames=fieldnames,
     ↪delimiter='\t')
    writer.writeheader()

    for line in infile:
        d = CLFParser.logDict(line)
        if robot_detection.is_robot(d['Useragent']):
            robot_count[d['Useragent']] += 1
        else:
            writer.writerow(d)

上面的代码与之前的版本基本没有变化;两个修改是我现在使用 robot_detection 来过滤掉机器人,并且我使用 Python Counter 类来跟踪每个机器人发出请求的次数。仅此一项可能就很有用——也许现在不是,但在将来。例如,通过检查我的博客最近的 100,000 个请求,我发现有 1,000 多个请求来自“domain re-animator bot”,这是我以前从未听说过的东西。

鉴于我目前专注于用户数据,过滤掉这些机器人请求使我的数据更可靠,并且也大大缩短了。在 100,000 条记录中,只有 27,000 条来自真人用户。

结论

任何数据分析项目的第一步都是导入和清理数据。在这里,我已经获取了数据并将其放入 CSV 格式,过滤掉了一些不太感兴趣的行。但这只是我的分析的开始,而不是结束。下个月,我将解释如何将此数据导入到 Python 的 Pandas 包中,并开始以多种不同的方式分析日志文件。

资源

数据科学是一个热门话题,许多人都在撰写关于该主题的好书。我最近一直在阅读并享受 Jake VanderPlas 撰写的《Python 数据科学手册》的早期版本,其中包含有关数据科学以及在 Python 中使用它的精彩信息。Cathy O'Neil 和 Rachel Schutt 稍早的书籍《Doing Data Science》也很出色,从不同的角度探讨问题。这两本书均由 O'Reilly 出版,都是很棒的读物。

要了解有关数据科学中使用的 Python 工具的更多信息,请查看 NumPySciPy, PandasIPython 的网站。有很多东西要学习,所以请做好深入研究和大量阅读的准备。

Python 本身可在此处 获取,您可以从中下载本文中提到的所有包的 PyPI 包索引可在此处 获取

Reuven Lerner 在世界各地的公司教授 Python、数据科学和 Git。您可以订阅他的免费每周“更好的开发人员”电子邮件列表,并从他的书籍和课程中学习,网址为 http://lerner.co.il。Reuven 与他的妻子和孩子住在以色列的莫迪因。

加载 Disqus 评论