图片来自Unsplash,作者 NeONBRAND
感谢你在我们设置环境时保持耐心。现在我们开始编码和操作数据。以下是这篇文章的计划:
设置虚拟环境
在该虚拟环境中安装ipython、jupyter、beautifulsoup4和pandas
学习抓取、组织和保存web数据
设置虚拟环境
我们为什么要这么做?虚拟环境只是作为python项目的隔离环境。它们允许你为不同的项目使用不同版本的模块,而不会导致版本之间的冲突。让我们从下载virtualenv开始。你可以打开终端并输入以下命令来下载:
现在,切换到medium_tutorials文件夹,我们将激活一个新的虚拟环境。我们通过输入以下命令来做到这一点:
创建一个虚拟环境
现在虚拟环境已经创建好了,我们再输入以下命令:
你可以看到,当我们激活该虚拟环境时,它会在命令行开头显示(.venv)。小事一桩。
进入虚拟环境
在虚拟环境中安装包
你已经安装或随python发行版附带的包通常在你的新的虚拟环境中是不可用的。我们来通过在我们的虚拟环境中安装ipython来实现我们的新空间的功能:
在我们的虚拟环境中安装ipython
现在我们开始安装Jupyter,以便我们可以开始使用notebook:
安装了Jupyter后,我们可以在我们的虚拟环境中安装一个Jupyter内核:
最后,我们来安装BeautifulSoup4、requests和pandas,这样我们就可以开始抓取数据并执行一些基本的计算:
是时候开始编码了!
https://media.giphy.com/media/5VKbvrjxpVJCM/giphy-downsized.gif
Notebook设置
首先在项目文件夹中创建一个名为web_scraping的新文件夹,并切换到该目录,然后进入终端输入以下命令来启动jupyter notebook:
这将启动带有一个文本菜单的浏览器,该文本菜单中带有说明notebook列表是空的的文本。让我们通过点击右上角的“new”,然后点击Notebook下的.venv来改变这一点。
使用虚拟环境内核创建notebook
现在我们有了一个使用虚拟环境的空白notebook。在开始编写代码之前,我们创建一些快速的文档说明。首先,我们单击屏幕顶部的名称“Untitled”来重命名我们的notebook。我们将其命名为web_scraping_tutorial。
重命名我们的notebook
接下来,我总是喜欢在notebook顶部的单元格中简单描述notebook的用途。我们可以用Markdown来写,这样格式就会很漂亮。要进入markdown模式,你可以从“Code”旁边的下拉菜单中选择“Markdown”,或者选择单元格并按下control+m, m。
如果你从未使用过markdown,它允许你使用基本命令来轻松地格式化文本。如果你想学习其基本语法,请阅读这里(https://www.markdownguide.org/basic-syntax/ ),你不需要在它上面花费太多时间。否则,跟着我说的做就好了。
我输入了一些初始描述文本。你可以看到左边的markdown版本和右边的结果格式:
Markdown文本和结果输出
导入模块
现在我们导入计划使用的模块。我们将导入用于提取html的requests、用于解析的BeautifulSoup4、用于操作数据表的pandas以及在保存数据时操作目录的os。
如果你不熟悉python基础或导入模块,那你现在就需要返回去学习一些教程。我在之前的文章中已经链接了这些教程,但是我强烈推荐Sentdex的Youtube教程和treehouse。
总之,导入应该是这样的:
模块已经导入,准备抓取数据!
从篮球参考请求数据
我们需要了解html的基础知识,以便从web上获取数据。如果您对html比较熟悉,那么你就可以开始了。如果你以前没有见过html或需要快速复习一下,这个12分钟的视频应该足以让你理解我们在做什么:
视频地址:https://youtu.be/bWPMSSsVdPk
现在我们只需要找到一个想要从中提取数据的页面。我们来看看篮球参考上James Harden的2018-19赛季的篮球比赛记录。单击该链接并复制url。我们的代码需要这个。在下一个单元格中,输入以下代码:
这段代码会将我们的url文本保存为一个名为url的变量。然后,我们将该url变量传递到requests库的get函数中,并将响应保存为page。最后一行的page调用将给出服务器响应。它应该会打印出,这是一个成功的服务器响应。
你不必遵循下面的几个部分,但如果你查看下面的截图,你会看到当我调用page.content时,它会返回一个以b '开头的杂乱的数据字符串。在Python中使用type函数,我们看到这是在返回字节,并且这些字节非常混乱。让我们开始使用BeautifulSoup来清理这个数据,并使这些字节变得有用。
我们获得了数据,而且很杂乱!
使用BeautifulSoup清理数据
我们已经在代码中导入了BeautifulSoup,所以现在只需将页面内容传递给它。我们将通过以下代码来实现:
这里,我们将page.content数据传入BeautifulSoup并使用html解析器。我们将这个输出保存为soup。然后我们只需美化我们的soup并打印输出。你也可以始终检查原始的soup变量。它的数据与美化后的版本相同,只是不那么漂亮。
我们已经在BeautifulSoup中解析了内容,但是我们如何知道在哪里可以找到我们需要的数据呢?我们可以滚动html文本,也可以通过简单的cmd-f来从html中的比赛记录页面中找到特定的值。Harden在本赛季的第一场比赛中打了34比43,这是非常特别的,所以我们来在html中搜索这个值。
搜索值比一直滚动更容易
我们首先注意到,stat包含在一个表数据标记中。此外,你还将看到一个值为“mp”的data-stat元素。查看下一个td标记,你将看到另一个值为“fg”的data-stat元素。如果搜索这些标记的其余部分,你会看到它们与在线表格的列是相对应的。
公正的警示——下一段脚本不是最优化的,但是我要做的是为我们想要获取的所有data-stat值创建一个列表。一个更好的方法应该是从该html中提取这些元素,但我们现在不担心这个。一旦我们将stats放置在一个列表中,我们将使用一个嵌套的列表推导式来获取James Harden的stats。这是代码:
我们来分解这个stats_list变量,这样我们都清楚这里发生了什么。当你在Python中学习循环时,它们通常是这样的:
通过一个列表推导式,我们可以执行同样的操作,同时保持代码更简洁更短小:
它更简洁,因为它能马上告诉我们正在执行的操作,而且通常更快。一般的经验法则是,如果列表推导式易于理解,则使用列表推导式而不是for循环。如果需要多个嵌套列表推导式或高级逻辑,请使用较慢的代码简洁的if语句。无论如何,通过上面的代码,我们将从内部列表推导式开始:
这段代码的意思是,为在搜索soup文本时找到的每个表数据元素获取表数据元素的文本值,并找到具有与我们的stat值相等的data-stat元素的所有表数据元素。由于stat值未被定义,这时外部列表就发挥作用了。我们将在这里使用伪代码:
.
我们知道内部代码获取的是data-stat 等于 stat的表数据值。这段代码只是为我们的stat列表中的每一个stat运行这个过程。运行此代码后,我们可以检查我们的stats_list变量,如下所示:
stats_list输出
从嵌套列表到一个Pandas DataFrame
使用Pandas,我们可以很容易地将数据放入一个DataFrame,这使得查看和操作数据变得很容易。
让我们从尝试直接从stats_list创建一个DataFrame开始,并查看前5行:
将测试负载初始化到一个DataFrame中
你可以马上看出这个数据看起来不太正确。实际上,我们看到有82列。对于我们的目的,数据没有正确地定向。让我们对数据进行转置,并查看前10行。
将测试负载转置为一个DataFrame
乍一看,这看起来很棒,但是请查看第4-6行。你可以看到第0列中没有加载任何数据。当我们对照篮球参考上的比赛记录来看这一点时,你会发现Harden在这些比赛中是不活跃的。尽管哈登处于不活跃状态,但我们的DataFrame显示的是stats。
这里的问题源于数据的html结构。当一个球员处于非活跃状态时,篮球参考只发送一次不活跃值,并且它会跨越结果列之后的所有列。我们需要转换一些数据并重新创建我们的dataframe。
修复我们的数据拉取并最终化DataFrame
如果你查看比赛记录,你会注意到无论玩家是否处于不活跃状态,对于整个结果列的比赛(g)来说,数据都是完整的。只有当玩家处于活跃状态时,比赛开始到加时的列才会被填入。我们来根据这个区分器将数据重新拉入单独的列表。
这与我们在上面为stats_list变量使用的代码相同,但是代替了循环遍历每个stat,stats_left变量会查看前7个stats, 而stats_right变量则循环遍历其余的stats。
现在,我们将把左边的值放入一个pandas DataFrame中,并查看前5行,以确保它正确运行。
这看起来不错。现在,我们只需要编写一段快速的代码,以便当Harden处于非活动状态时插入空格。让我们来试一试:
我们把这段代码分解一下,确保它是清楚的。现在,使用列表推导式肯定会使我们的代码难以阅读,所以我们使用了一个常规循环。第一行只是说明我们将获取df_left的长度并将其传递给range函数。我们知道这个长度是82,因为这个赛季有82场比赛。因此当i在0-81范围内时,我们将查看df_left的game_season列,并看看第i列是否为空。如果是,我们将运行另一个列表推导式。我们来单独看看这个列表推导式,确保它是清楚的:
记住stats_right是一个嵌套列表。因此,我们将在stats_right的索引值i处为stats_right中的每个stats插入一个空格(即df_left[' game_season ']为空的相同索引值)。
现在,让我们把这些数据的右边的值放在它们自己的DataFrame中,然后我们将合并这两个DataFrame:
使用有效数据完成的DataFrame
现在我们有了一个工作的DataFrame !让我们做一个简单的测试来确保这些数字是有意义的。我们来计算一下Harden每场比赛的平均得分:
这有点新奇。因为所有的值作为字符串读取,所以我们必须将这些得分变为整数。然而,我们不能将空格变为整数,所以我们只选择Harden参加了的比赛分值。然后我们取pts列的平均值,并把它四舍五入到一位小数点。这个值为:36.1。
保存DataFrame
最后一步是保存这个dataframe。首先,让我们在web_scraper文件夹中创建一个名为game_logs的新文件夹。我们导航到该文件夹,然后,在这里将我们的dataframe保存为一个csv。
第一行只是使用从当前位置到game_logs文件夹的相对路径来更改目录。下一行在我们的DataFrame上调用to_csv()方法。然后,我们对输出进行命名,并告诉python不进行索引。
不想编写自己版本的代码?那就在github上下载这个版本(https://github.com/hardwoodconvergence/medium_tutorials )。
接下来是什么
在本教程中,我们讨论了很多基础知识。我们学习了如何设置虚拟环境、抓取网站、使用列表解析操作列表、将数据存储在dataframe中以及将数据保存在一个csv中。接下来,我们将使用Harden的比赛记录来计算一些指标并创建基本的图表。在我们从一个小数据集中获得信息之后,我们将继续寻找更快、更简单的方法来访问更多的数据并扩展我们的分析。
事情将会从这里开始,因为我们将花费更多的时间来处理实际的篮球数据,而花更少的时间来设置我们的环境。我很期待,希望你也是!
谢谢你的关注。如果你从本教程中学到了任何东西,请点击拍手按钮并与朋友分享!???
英文原文:https://medium.com/hardwood-convergence/intro-to-virtual-environments-and-scraping-nba-data-with-beautifulsoup-6ce745f8c26e
译者:一瞬