命令行上的数据科学第二版 三、获取数据

2023-03-31 14:41:29 浏览数 (2)

原文:https://datascienceatthecommandline.com/2e/chapter-3-obtaining-data.html 贡献者:Ting-xin

本章讨论 OSEMN 模型的第一步:获取数据。毕竟,没有任何数据,我们就没有多少数据科学可以做。我假设你已经有了解决数据科学问题所需的数据,第一步你需要把这些数据放到你的电脑上(也可能放到 Docker 容器里)。

在 Unix 哲学中,文本是一个通用接口。几乎每个命令行工具都将文本作为输入,或者以文本作为输出,或者两者都有。这就是为什么命令行工具可以很好地协同工作的主要原因。然而,正如我们将看到的,即使只是文本也可以有多种形式。

我们可以通过多种方式获取数据,例如从服务器下载数据、查询数据库或连接到 Web API。有时,数据以压缩的形式或二进制格式出现,如 Microsoft Excel 电子表格。在这一章中,我们将讨论了几个有助于从命令行解决这个问题的工具,包括:curlin2csvsql2csv,以及tar

3.1 概述

在本章中,你将学习如何:

  • 将本地文件复制到 Docker 镜像
  • 从互联网下载数据
  • 解压缩文件
  • 从电子表格中提取数据
  • 查询关系数据库
  • 调用 Web API

首先打开第三章的目录:

代码语言:javascript复制
$ cd /data/ch03

$ l
total 924K
-rw-r--r-- 1 dst dst 627K Mar  3 10:41 logs.tar.gz
-rw-r--r-- 1 dst dst 189K Mar  3 10:41 r-datasets.db
-rw-r--r-- 1 dst dst  149 Mar  3 10:41 tmnt-basic.csv
-rw-r--r-- 1 dst dst  148 Mar  3 10:41 tmnt-missing-newline.csv
-rw-r--r-- 1 dst dst  181 Mar  3 10:41 tmnt-with-header.csv
-rw-r--r-- 1 dst dst  91K Mar  3 10:41 top2000.xlsx

获取这些文件已经在第二章中做过了。任何其他文件都是使用命令行工具下载或生成的。

3.2 将本地文件复制到 Docker 容器

一种常见的情况是,你自己的计算机上已经有了需要的文件,本节介绍了如何将这些文件放入 Docker 容器。

我在第二章提到 Docker 容器是一个隔离的虚拟环境。但是有一个例外:文件可以在 Docker 容器中进出传输。运行docker run的本地目录会被映射到 Docker 容器中的一个目录。这个目录叫做/data。注意这不是主目录,主目录是/home/dst

如果你的本地计算机上有一个或多个文件,并且你想对它们应用一些命令行工具,那么你需要将这些文件复制或移动到那个映射的目录中。假设你的下载目录中有一个名为logs.csv的文件,现在我们来复制文件。

如果你正在运行 Windows,请打开命令提示符或 PowerShell 并运行以下两个命令:

代码语言:javascript复制
> cd %UserProfile%Downloads
> copy logs.csv MyDataScienceToolbox

如果你运行的是 Linux 或 macOS,请打开一个终端并在你的操作系统上执行以下命令(而不是在 Docker 容器中):

代码语言:javascript复制
$ cp ~/Downloads/logs.csv ~/my-data-science-toolbox

你也可以使用图形文件管理器(如 Windows Explorer 或 macOS Finder)将文件拖放到正确的目录中。

3.3 从互联网上下载数据

毫无疑问,互联网已经成为了数据的最大来源。当从互联网下载数据时,命令行工具curl被认为是命令行中的瑞士军刀。

3.3.1 curl介绍

当你浏览到一个代表统一资源定位符的 URL 时,你的浏览器会渲染它下载的数据。例如,浏览器会呈现 HTML 文件,自动播放视频文件,显示 PDF 文件。然而,当你使用curl来访问一个 URL 时,它会下载数据,并在默认情况下将其打印出来。curl不会做任何解释和渲染,但幸运的是可以使用其他命令行工具来进一步处理数据。

最简单的调用curl是指定一个 URL 作为命令行参数。现在让我们试着从维基百科下载一篇文章:

代码语言:javascript复制
$ curl "https://en.wikipedia.org/wiki/List_of_windmills_in_the_Netherlands" |
> trim # ➊
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<!
DOCTYPE html>
<html class="client-nojs" lang="en" dir="ltr">
<head>
<meta charset="UTF-8"/>
<title>List of windmills in the Netherlands - Wikipedia</title>
<script>document.documentElement.className="client-js";RLCONF={"wgBreakFrames":…
"wgRelevantPageName":"List_of_windmills_in_the_Netherlands","wgRelevantArticleI…
,"site.styles":"ready","user.styles":"ready","ext.globalCssJs.user":"ready","us…
"ext.growthExperiments.SuggestedEditSession"];</script>
<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.implement("user.options@…
100  250k  100  250k    0     0   866k      0 --:--:-- --:--:-- --:--:--  891k
… with 1760 more lines

➊ 记住,trim只是用来让输出很好地适应终端

如你所见,curl下载维基百科的服务器返回的原始 HTML 不进行任何解释,所有内容立即打印在标准输出上。因为这个 URL,你会认为这篇文章会列出荷兰所有的风车。然而,显然有太多的风车留下来,每个省都有自己的网页。令人着迷。

默认情况下,curl会输出一个进度条,显示下载速度和预期完成时间。这个输出不是写到标准输出,而是一个单独的通道,称为标准错误,所以当你在管道中添加另一个工具时,它们之间不会干扰。虽然这些信息在下载非常大的文件时会很有用,但它通常会分散我们的注意力,所以我指定了-s选项来让忽略这个输出。

代码语言:javascript复制
$ curl -s "https://en.wikipedia.org/wiki/List_of_windmills_in_Friesland" |
> pup -n 'table.wikitable tr' # ➊
234

➊ 我会讨论pup,一个方便的抓取网站的工具,更详细的内容在第五章。

你知道吗,仅在弗里斯兰省就有 234 座风车!(译者也不懂为啥突然来这么一句

3.3.2 保存

你可以通过添加-O选项将curl的输出保存到文件中,文件名将基于 URL 的最后一部分。

代码语言:javascript复制
$ curl -s "https://en.wikipedia.org/wiki/List_of_windmills_in_Friesland" -O

$ l
total 1.4M
-rw-r--r-- 1 dst dst 432K Mar  3 10:41 List_of_windmills_in_Friesland
-rw-r--r-- 1 dst dst 627K Mar  3 10:41 logs.tar.gz
-rw-r--r-- 1 dst dst 189K Mar  3 10:41 r-datasets.db
-rw-r--r-- 1 dst dst  149 Mar  3 10:41 tmnt-basic.csv
-rw-r--r-- 1 dst dst  148 Mar  3 10:41 tmnt-missing-newline.csv
-rw-r--r-- 1 dst dst  181 Mar  3 10:41 tmnt-with-header.csv
-rw-r--r-- 1 dst dst  91K Mar  3 10:41 top2000.xlsx

如果你不喜欢这个文件名,那么你可以选择使用-o选项和一个文件名来保存文件,或者将输出重定向到一个新的文件:

代码语言:javascript复制
$ curl -s "https://en.wikipedia.org/wiki/List_of_windmills_in_Friesland" > fries
land.html

3.3.3 其他协议

curl总共支持 20 多种协议。从 FTP 服务器(代表文件传输协议)下载文件同样也可以使用curl,下面显示从ftp.gnu.org下载文件welcome.msg:

代码语言:javascript复制
$ curl -s "ftp://ftp.gnu.org/welcome.msg" | trim
NOTICE (Updated October 15 2021):

If you maintain scripts used to access ftp.gnu.org over FTP,
we strongly encourage you to change them to use HTTPS instead.

Eventually we hope to shut down FTP protocol access, but plan
to give notice here and other places for several months ahead
of time.

--
… with 19 more lines

如果指定的 URL 是一个 DICT 协议,curl将列出该目录的内容。当 URL 受密码保护时,你可以使用-u选项指定用户名和密码。DICT 协议允许你访问各种字典和查找定义,根据《国际英语协作词典》,下面是“风车”的定义:

代码语言:javascript复制
$ curl -s "dict://dict.org/d:windmill" | trim
220 dict.dict.org dictd 1.12.1/rf on Linux 4.19.0-10-amd64 <auth.mime> <9813708…
250 ok
150 1 definitions retrieved
151 "Windmill" gcide "The Collaborative International Dictionary of English v.0…
Windmill Wind"mill`, n.
   A mill operated by the power of the wind, usually by the
   action of the wind upon oblique vanes or sails which radiate
   from a horizontal shaft. --Chaucer.
   [1913 Webster]
.
… with 2 more lines

然而,当从互联网下载数据时,协议很可能是 HTTP,因此 URL 将以http://https://开头。

3.3.4 重定向设置

当你访问一个缩短的网址时,比如以http://bit.ly/http://t.co/开头的网址,你的浏览器会自动将你重定向到正确的位置。然而,使用curl,你需要指定-L--location选项以便被重定向。如果没有,你可能会得到这样的结果:

代码语言:javascript复制
$ curl -s "https://bit.ly/2XBxvwK"
<html>
<head><title>Bitly</title></head>
<body><a href="https://youtu.be/dQw4w9WgXcQ">moved here</a></body>
</html>%

有时候你什么也得不到,就像我们按照上面提到的网址:

代码语言:javascript复制
$ curl -s "https://youtu.be/dQw4w9WgXcQ"

通过指定-I--head选项,curl只获取响应的 HTTP 头,这允许你检查服务器返回的状态代码和其他信息。

代码语言:javascript复制
$ curl -sI "https://youtu.be/dQw4w9WgXcQ" | trim
HTTP/2 303
content-type: application/binary
x-content-type-options: nosniff
cache-control: no-cache, no-store, max-age=0, must-revalidate
pragma: no-cache
expires: Mon, 01 Jan 1990 00:00:00 GMT
date: Thu, 03 Mar 2022 09:41:56 GMT
location: https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=youtu.be
content-length: 0
x-frame-options: SAMEORIGIN
… with 11 more lines

信息中第一行显示了 HTTP 协议和状态码,在本例中是 303。你还可以看到该 URL 重定向到的位置。如果curl没有给你预期的结果,检查标题并获得状态码是一个有用的调试工具。其他常见的 HTTP 状态代码包括 404(未找到)和 403(禁止)。维基百科列出了所有 HTTP 状态码。

总之,curl是一个有用的从互联网下载数据的命令行工具。它的三个最常见的选项是-s忽略进度条、-u指定用户名和密码、-L自动跟随重定向。请参阅其手册页了解更多信息:

代码语言:javascript复制
$ man curl | trim 20
curl(1)                           Curl Manual                          curl(1)

NAME
       curl - transfer a URL

SYNOPSIS
       curl [options / URLs]

DESCRIPTION
       curl  is  a tool to transfer data from or to a server, using one of the
       supported protocols (DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS,  IMAP,
       IMAPS,  LDAP,  LDAPS,  MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP,
       SMB, SMBS, SMTP, SMTPS, TELNET and TFTP). The command  is  designed  to
       work without user interaction.

       curl offers a busload of useful tricks like proxy support, user authen‐
       tication, FTP upload, HTTP post, SSL connections, cookies, file  trans‐
       fer  resume,  Metalink,  and more. As you will see below, the number of
       features will make your head spin!

… with 3986 more lines

3.4 解压文件

如果原始数据集非常大,或者它是许多文件的集合,则该文件可能是压缩文件。包含许多重复值的数据集(如文本文件中的单词或 JSON 文件中的键)特别适合压缩。

压缩文件常见的文件扩展名有:.tar.gz.zip.rar。要解压缩这些文件,你可以使用命令行工具tarunzipunrar。(当然还有一些不太常见的文件扩展名,这些可能需要其他工具来处理。)

让我们以tar.gz(读作“gzipped tarball”)为例。为了提取名为logs.tar.gz的档案,你将使用以下命令行:

代码语言:javascript复制
$ tar -xzf logs.tar.gz # ➊ 

➊ 压缩的时候将xzf这三个短选项组合在一起是很常见的,就像我在这里做的一样。当然你也可以将它们分别指定为-x -z -f,事实上,许多命令工具都允许你由单个字符组合成选项。

tar因其众多的命令行参数而臭名昭著。在这种情况下,三个选项-x-z-f表明tar将会用gzip作为解压缩算法从压缩文件中提取文件。

但是,由于我们还不熟悉这个压缩文件,所以最好先检查一下它的内容,这可以通过-t选项(而不是-x选项)来完成:

代码语言:javascript复制
$ tar -tzf logs.tar.gz | trim
E1FOSPSAYDNUZI.2020-09-01-00.0dd00628
E1FOSPSAYDNUZI.2020-09-01-00.b717c457
E1FOSPSAYDNUZI.2020-09-01-01.05f904a4
E1FOSPSAYDNUZI.2020-09-01-02.36588daf
E1FOSPSAYDNUZI.2020-09-01-02.6cea8b1d
E1FOSPSAYDNUZI.2020-09-01-02.be4bc86d
E1FOSPSAYDNUZI.2020-09-01-03.16f3fa32
E1FOSPSAYDNUZI.2020-09-01-03.1c0a370f
E1FOSPSAYDNUZI.2020-09-01-03.76df64bf
E1FOSPSAYDNUZI.2020-09-01-04.0a1ade1b
… with 2427 more lines

这个压缩文件中包含了很多文件,并且它们不在一个目录中,因此为了保持当前目录的整洁,最好首先使用mkdir创建一个新目录,然后使用-C选项提取其中的文件。

代码语言:javascript复制
$ mkdir logs

$ tar -xzf logs.tar.gz -C logs

让我们来看一下文件的数量及其部分内容:

代码语言:javascript复制
$ ls logs | wc -l
2437

$ cat logs/* | trim
#Version: 1.0
#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem…
2020-09-01      00:51:54        SEA19-C1        391     206.55.174.150  GET    …
2020-09-01      00:54:59        CPH50-C2        384     82.211.213.95   GET    …
#Version: 1.0
#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem…
2020-09-01      00:04:28        DFW50-C1        391     2a03:2880:11ff:9::face:…
#Version: 1.0
#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem…
2020-09-01      01:04:14        ATL56-C4        385     2600:1700:2760:da20:548…
… with 10279 more lines

非常好。现在,我知道你想清理和研究这些日志文件,但那是以后在第五章和第七章中讨论的事情。

随着时间的推移,你会习惯这些选项,但我想给你看一个比较方便的替代脚本,它不需要记住不同的命令行工具和它们的选项,这个方便的脚本叫做unpack,它可以解压缩许多不同的格式。unpack查看你想要解压缩的文件的扩展名,并调用适当的命令行工具。现在,为了解压缩这个文件,你可以运行:

代码语言:javascript复制
$ unpack logs.tar.gz

3.5 将 Microsoft Excel 电子表格转换为 CSV 格式

对于许多人来说,Microsoft Excel 提供了一种直观的方式来处理小型数据集并对其执行计算。因此,大量数据被嵌入到 Microsoft Excel 电子表格中。根据文件名的扩展名,这些电子表格要么以专有的二进制格式(.xls)存储,要么以压缩的 XML 文件的集合(.xlsx)存储。但这两种情况下都不利于大多数命令行工具使用这些数据。如果仅仅因为这些有价值的数据集以这种方式存储,我们就不能使用它们了,那这将是一种耻辱。

特别是当你刚开始使用命令行时,你可能会尝试通过在 Microsoft Excel 或开源版本(如 LibreOffice Calc)中打开电子表格,然后手动将其导出为 CSV 格式,从而将电子表格转换为 CSV 格式。虽然这也是一个解决方案,但缺点是它不能很好地扩展到多个文件,并且不能自动化。此外,当你在服务器上工作时,很可能没有这样的应用可用。相信我,命令行是一个更好的解决方案。

幸运的是,我们有一个名为in2csv的命令行工具,它可以将 Microsoft Excel 电子表格转换成 CSV 文件。CSV 指的是逗号分隔的数值,使用 CSV 文件可能会很棘手,因为它缺乏正式的规范。Yakov Shafranovich 将 CSV 格式定义为以下三点:

  1. 每条记录位于单独的一行,由换行符(LF)分隔。例如,下面的 CSV 文件包含了关于忍者神龟的重要信息:
代码语言:javascript复制
$ bat -A tmnt-basic.csv # ➊
───────┬────────────────────────────────────────────────────────────────────────
       │ File: tmnt-basic.csv
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Leonardo,Leo,blue,two·ninjakens␊
   2   │ Raphael,Raph,red,pair·of·sai␊
   3   │ Michelangelo,Mikey·or·Mike,orange,pair·of·nunchaku␊
   4   │ Donatello,Donnie·or·Don,purple,staff␊
───────┴────────────────────────────────────────────────────────────────────────

-A选项使bat显示所有不可打印的字符,如空格、制表符和换行符。

  1. 文件中的最后一条记录可能有也可能没有结束换行符,例如:
代码语言:javascript复制
$ bat -A tmnt-missing-newline.csv
───────┬────────────────────────────────────────────────────────────────────────
       │ File: tmnt-missing-newline.csv
───────┼────────────────────────────────────────────────────────────────────────
   1   │ Leonardo,Leo,blue,two·ninjakens␊
   2   │ Raphael,Raph,red,pair·of·sai␊
   3   │ Michelangelo,Mikey·or·Mike,orange,pair·of·nunchaku␊
   4   │ Donatello,Donnie·or·Don,purple,staff
───────┴────────────────────────────────────────────────────────────────────────
  1. 文件的第一行可能会出现一个标题行,其格式与普通记录行相同。该标题将包含与文件中的字段相对应的名称,并且应该包含与文件其余部分中的记录相同数量的字段。例如:
代码语言:javascript复制
$ bat -A tmnt-with-header.csv
───────┬────────────────────────────────────────────────────────────────────────
       │ File: tmnt-with-header.csv
───────┼────────────────────────────────────────────────────────────────────────
   1   │ name,nickname,mask_color,weapon␊
   2   │ Leonardo,Leo,blue,two·ninjakens␊
   3   │ Raphael,Raph,red,pair·of·sai␊
   4   │ Michelangelo,Mikey·or·Mike,orange,pair·of·nunchaku␊
   5   │ Donatello,Donnie·or·Don,purple,staff␊
───────┴────────────────────────────────────────────────────────────────────────

如你所见,默认情况下,CSV 不太可读。你可以通过管道将数据传输到一个名为csvlook的工具,它会很好地将数据格式化成表格。如果 CSV 数据没有头,比如tmnt-missing-newline.csv,那么你需要添加-H选项,否则第一行将被解释为头。

代码语言:javascript复制
$ csvlook tmnt-with-header.csv
│ name         │ nickname      │ mask_color │ weapon           │
├──────────────┼───────────────┼────────────┼──────────────────┤
│ Leonardo     │ Leo           │ blue       │ two ninjakens    │
│ Raphael      │ Raph          │ red        │ pair of sai      │
│ Michelangelo │ Mikey or Mike │ orange     │ pair of nunchaku │
│ Donatello    │ Donnie or Don │ purple     │ staff            │

$ csvlook tmnt-basic.csv
│ Leonardo     │ Leo           │ blue   │ two ninjakens    │
├──────────────┼───────────────┼────────┼──────────────────┤
│ Raphael      │ Raph          │ red    │ pair of sai      │
│ Michelangelo │ Mikey or Mike │ orange │ pair of nunchaku │
│ Donatello    │ Donnie or Don │ purple │ staff            │

$ csvlook -H tmnt-missing-newline.csv # ➊
│ a            │ b             │ c      │ d                │
├──────────────┼───────────────┼────────┼──────────────────┤
│ Leonardo     │ Leo           │ blue   │ two ninjakens    │
│ Raphael      │ Raph          │ red    │ pair of sai      │
│ Michelangelo │ Mikey or Mike │ orange │ pair of nunchaku │
│ Donatello    │ Donnie or Don │ purple │ staff            │

-H选项表示 CSV 文件中没有标题行。

让我们用一个电子表格来演示一下in2csv,这个表格包含了一年一度的荷兰马拉松广播节目 2000 强中 2000 首最流行的歌曲。要提取它的数据,你可以如下调用in2csv:

代码语言:javascript复制
$ in2csv top2000.xlsx | tee top2000.csv | trim
NR.,ARTIEST,TITEL,JAAR
1,Danny Vera,Roller Coaster,2019
2,Queen,Bohemian Rhapsody,1975
3,Eagles,Hotel California,1977
4,Billy Joel,Piano Man,1974
5,Led Zeppelin,Stairway To Heaven,1971
6,Pearl Jam,Black,1992
7,Boudewijn de Groot,Avond,1997
8,Coldplay,Fix You,2005
9,Pink Floyd,Wish You Were Here,1975
… with 1991 more lines

Danny Vera 是谁?当然,最受欢迎的歌曲应该是波西米亚狂想曲。嗯,至少 Queen 在前 2000 名中出现了很多次,所以我也不应该抱怨:

代码语言:javascript复制
$ csvgrep top2000.csv --columns ARTIEST --regex '^Queen$' | csvlook -I # ➊
│ NR.  │ ARTIEST │ TITEL                           │ JAAR │
├──────┼─────────┼─────────────────────────────────┼──────┤
│ 2    │ Queen   │ Bohemian Rhapsody               │ 1975 │
│ 11   │ Queen   │ Love Of My Life                 │ 1975 │
│ 46   │ Queen   │ Innuendo                        │ 1991 │
│ 55   │ Queen   │ Don't Stop Me Now               │ 1979 │
│ 70   │ Queen   │ Somebody To Love                │ 1976 │
│ 85   │ Queen   │ Who Wants To Live Forever       │ 1986 │
│ 89   │ Queen   │ The Show Must Go On             │ 1991 │
│ 131  │ Queen   │ Killer Queen                    │ 1974 │
… with 24 more lines

--regex选项后的值是一个正则表达式(或 regex)。这是一种定义模式的特殊语法。因为我只想匹配与Queen完全匹配的艺术家,所以我使用插入符号(^)和美元符号($)来匹配列ARTIEST中值的开始和结束。

顺便说一下,工具in2csvcsvgrepcsvlook都是 CSVkit 的一部分,CSVkit 是处理 CSV 数据的命令行工具的集合。

文件的格式是由扩展名自动决定的,本例中是.xlsx。如果你要将数据导入in2csv,你必须明确指定格式。

一个电子表格可以包含多个工作表。默认情况下,in2csv提取第一个工作表。如果要提取不同的工作表,那么需要将工作表的名称传递给--sheet选项。如果你不确定工作表叫什么,你可以使用--names选项查看,它会打印出所有工作表的名称。这里我们看到top2000.xlsx只有一张表,名为Blad1(荷兰语,意思是Sheet1)。

代码语言:javascript复制
$ in2csv --names top2000.xlsx
Blad1

3.6 查询关系数据库

许多公司将他们的数据存储在关系数据库中。就像电子表格一样,如果我们可以从命令行获得这些数据,那就太好了。

关系数据库的例子有 MySQL、PostgreSQL 和 SQLite。这些数据库的接口方式稍微有些不同。有些提供命令行工具或命令行界面,有些则不提供。此外,当涉及到它们的使用和输出时,格式不是很一致。

幸运的是,有一个名为sql2csv的命令行工具专门用来做这个事,它也是 CSVkit 的一部分。它通过一个公共接口与许多不同的数据库协同工作,包括 MySQL、Oracle、PostgreSQL、SQLite、Microsoft SQL Server 和 Sybase。sql2csv的输出,顾名思义,就是 CSV 格式的。

我们可以通过对关系数据库执行SELECT查询来获取数据。(sql2csv也支持INSERTUPDATEDELETE查询,但这不是本章的目的。)

sql2csv需要两个参数:--db,指定数据库 URL,形式一般是:dialect driver://username:password@host:port/database--query,包含SELECT查询。例如,指定一个包含来自 R 的标准数据集的 SQLite 数据库,我可以从表mtcars中选择所有行,并按mpg列对它们进行排序,如下所示:

代码语言:javascript复制
$ sql2csv --db 'sqlite:///r-datasets.db' 
> --query 'SELECT row_names AS car, mpg FROM mtcars ORDER BY mpg' | csvlook
│ car                 │  mpg │
├─────────────────────┼──────┤
│ Cadillac Fleetwood  │ 10.4 │
│ Lincoln Continental │ 10.4 │
│ Camaro Z28          │ 13.3 │
│ Duster 360          │ 14.3 │
│ Chrysler Imperial   │ 14.7 │
│ Maserati Bora       │ 15.0 │
│ Merc 450SLC         │ 15.2 │
│ AMC Javelin         │ 15.2 │
… with 24 more lines

这个例子中 SQLite 数据库是一个本地文件,所以在这里我不需要指定任何用户名、密码或主机。如果你想查询你雇主的数据库,你当然需要知道如何访问它,并且你需要得到权限。

3.7 调用 Web API

在上一节中,我解释了如何从互联网上下载文件。从互联网上拿数据的另一种方式是通过 Web API,它代表应用编程接口,API 数量正在以越来越快的速度增长,这对我们数据科学家来说意味着大量有趣的数据。

Web API 并不意味着要以漂亮的布局呈现,比如网站。相反,大多数 Web API 以结构化格式返回数据,比如 JSON 或 XML。以结构化的形式保存数据的好处是数据可以很容易地被其他工具处理,比如jq。例如,例子中的 API 包含大量关于 George R.R. Martin 虚构的世界的信息,而《权力的游戏》一书和电视剧就发生在这个虚构世界中,它以下面的 JSON 结构返回数据:

代码语言:javascript复制
$ curl -s "https://anapioficeandfire.com/api/characters/583" | jq '.'
{
  "url": "https://anapioficeandfire.com/api/characters/583",
  "name": "Jon Snow",
  "gender": "Male",
  "culture": "Northmen",
  "born": "In 283 AC",
  "died": "", # ➊
  "titles": [
  "Lord Commander of the Night's Watch"
 ],
  "aliases": [
  "Lord Snow",
  "Ned Stark's Bastard",
  "The Snow of Winterfell",
  "The Crow-Come-Over",
  "The 998th Lord Commander of the Night's Watch",
  "The Bastard of Winterfell",
  "The Black Bastard of the Wall",
  "Lord Crow"
 ],
  "father": "",
  "mother": "",
  "spouse": "",
  "allegiances": [
  "https://anapioficeandfire.com/api/houses/362"
 ],
  "books": [
  "https://anapioficeandfire.com/api/books/5"
 ],
  "povBooks": [
  "https://anapioficeandfire.com/api/books/1",
  "https://anapioficeandfire.com/api/books/2",
  "https://anapioficeandfire.com/api/books/3",
  "https://anapioficeandfire.com/api/books/8"
 ],
  "tvSeries": [
  "Season 1",
  "Season 2",
  "Season 3",
  "Season 4",
  "Season 5",
  "Season 6"
 ],
  "playedBy": [
  "Kit Harington"
 ]
}

➊ 剧透:这个数据并不完全是最新的。

数据通过管道传输到命令行工具jq,这只是为了以一种漂亮的方式显示它。jq有更多清理和探索的可能性,我们将在第五章和第七章中探索。

3.7.1 认证

一些 Web API 要求你在请求它们的输出之前进行身份验证(即证明你的身份)。有几种方法可以做到这一点。一些 Web API 使用 API 密匙,而另一些使用 OAuth 协议。在这里,News API,一个独立的标题和新闻文章来源,就是一个很好的例子。让我们看看当你试图在没有 API 键的情况下访问这个 API 时会发生什么:

代码语言:javascript复制
$ curl -s "http://newsapi.org/v2/everything?q=linux" | jq .
{
  "status": "error", "code": "apiKeyMissing",
  "message": "Your API key is missing. Append this to the URL with the apiKey pa
ram, or use the x-api-key HTTP header."
}

嗯,那是意料之中的。顺便说一下,问号后面的部分是我们传递查询参数的地方,这也是你需要指定 API 密匙的地方。但是我想对自己的 API 密匙保密,所以我通过读取文件的方式将信息插入进去。

代码语言:javascript复制
$ curl -s "http://newsapi.org/v2/everything?q=linux&apiKey=$(< /data/.secret/new
sapi.org_apikey)" |
> jq '.' | trim 30
{
  "status": "ok",
  "totalResults": 9178,
  "articles": [
    {
      "source": {
        "id": "the-verge",
        "name": "The Verge"
      },
      "author": "Sean Hollister",
      "title": "Bungie rejects Steam Deck, threatens to ban Destiny 2 players t…
      "description": "Not only is Bungie apparently not making Destiny 2 compat…
      "url": "https://www.theverge.com/22957294/bungie-destiny-2-steam-deck-gam…
      "urlToImage": "https://cdn.vox-cdn.com/thumbor/PTFqHfu8ezNz4QrLf_ugVteHNV…
      "publishedAt": "2022-03-01T23:45:56Z",
      "content": "Image: Bungiernn nn When will Bungie let Destiny 2 come …
    },
    {
      "source": {
        "id": "ars-technica",
        "name": "Ars Technica"
      },
      "author": "Scharon Harding",
      "title": "System76 Linux workstation looks ready for gaming, too",
      "description": "A 144 Hz screen, colorful keyboard, and Ethernet can appe…
      "url": "https://arstechnica.com/gadgets/2022/02/system76-linux-laptop-uni…
      "urlToImage": "https://cdn.arstechnica.net/wp-content/uploads/2022/02/lis…
      "publishedAt": "2022-02-03T16:49:47Z",
      "content": "13 with 13 posters participatingrnSystem76's latest Linux l…
    },
… with 236 more lines

你可以在 News API 的网站获得自己的 API 密匙。

3.7.2 流式 API

一些 Web API 以流的方式返回数据。这意味着一旦你连接到它,数据将继续涌入,直到连接被关闭。一个众所周知的例子是 Twitter “fire hose”,它不断地向世界各地发送所有的推文。幸运的是,大多数命令行工具也以流方式运行。

例如,让我们来看一个 10 秒钟的 Wikimedia 流媒体 API 示例:

代码语言:javascript复制
$ curl -s "https://stream.wikimedia.org/v2/stream/recentchange" |
> sample -s 10 > wikimedia-stream-sample

这个特定的 API 返回对 Wikipedia 和 Wikimedia 的其他属性所做的所有更改。命令行工具sample用于在 10 秒后关闭连接,我们也可以通过按下Ctrl-C发送中断来手动关闭连接。输出被保存到文件wikimedia-stream-sample,让我们用trim来一窥究竟:

代码语言:javascript复制
$ < wikimedia-stream-sample trim
:ok

event: message
id: [{"topic":"eqiad.mediawiki.recentchange","partition":0,"timestamp":16101133…
data: {"$schema":"/mediawiki/recentchange/1.0.0","meta":{"uri":"https://en.wiki…

event: message
id: [{"topic":"eqiad.mediawiki.recentchange","partition":0,"timestamp":16101133…
data: {"$schema":"/mediawiki/recentchange/1.0.0","meta":{"uri":"https://www.wik…

… with 1078 more lines

通过sedjq,我们就可以清理这些数据,从而看到英文版维基百科发生的变化:

代码语言:javascript复制
$ < wikimedia-stream-sample sed -n 's/^data: //p' | # ➊
> jq 'select(.type == "edit" and .server_name == "en.wikipedia.org") | .title' # ➋
"Odion Ighalo"
"Hold Up (song)"
"Talk:Royal Bermuda Yacht Club"
"Jenna Ushkowitz"
"List of films released by Yash Raj Films"
"SP.A"
"Odette (musician)"
"Talk:Pierre Avoi"
"User:Curlymanjaro/sandbox3"
"List of countries by electrification rate"
"Grieg (crater)"
"Gorman, Edmonton"
"Khabza"
"QAnon"
"Khaw Boon Wan"
"Draft:Oggy and the Cockroaches (1975 TV series)"
"Renzo Reggiardo"
"Greer, Arizona"
"National Curriculum for England"
"Mod DB"
"Jordanian Pro League"
"List of foreign Serie A players"

➊ 这个sed表达式表示只打印以data:开头的行,打印分号后的部分,恰好是 JSON。

➋ 这个jq表达式打印具有某个typeserver_name的 JSON 对象的title键。

说到流媒体,你知道你可以使用telnet免费播放《星球大战:第四集——新的希望》吗?

代码语言:javascript复制
$ telnet towel.blinkenlights.nl

过了一会儿,我们看到 Han Solo 先开枪了!

代码语言:javascript复制
                       -===                    `"',
       I'll bet you   ""o o                    O O|)
           have!      _ -/_                  _o/ _
                     || || |*                /| / |
                     \ || ***              //| |  |\
                      \o=***********      // | |  | ||
                      |(#'***\        -==#  | |  | ||
                      |====|*  ')         '  |====| /#
                      |/|| |                  | || |  "
                      ( )( )                  | || |
                      |-||-|                  | || |
                      | || |                  | || |
      ________________[_][__________________/__)(_)_____________________

当然,这可能不是一个好的数据来源,但在训练你的机器学习模型的同时欣赏一部老经典也没什么错。

3.8 总结

恭喜你,你已经完成了 OSEMN 模型的第一步。你已经学习了各种获取数据的方法,从下载到查询关系数据库。在下一章,也是中间章节,我将教你如何创建你自己的命令行工具。如果你迫不及待地想了解数据清理,可以跳过这一步,直接进入第五章(OSEMN 模型的第二步)。

3.9 进一步探索

  • 寻找一个数据集来练习?GitHub 知识库 Awesome Public Datasets 列出了数百个公开可用的高质量数据集
  • 或者你更愿意用 API 来练习?GitHub 库 Public API 列出了很多免费 API。City Bikes 和 The One API 是我的最爱
  • 编写 SQL 查询从关系数据库中获取数据是一项重要的技能。Ben Forta 的《SQL in 10 Minutes a Day》一书的前 15 课讲了 SELECT 语句及其过滤、分组和排序功能

0 人点赞