# 第 3 章 Web 爬网和交互式可视化
到目前为止,在本书中,我们一直专注于使用 Jupyter 构建可重现的数据分析管道和预测模型。 在本课中,我们将继续探讨这些主题,但是这里的主要重点是数据采集。 特别是,我们将向您展示如何使用 HTTP 请求从网络上获取数据。 这将涉及通过请求和解析 HTML 来抓取网页。 然后,我们将使用交互式可视化技术来总结本课程,以探索我们收集的数据。
在线可用的数据量巨大,并且相对容易获取。 它也在不断增长,并且变得越来越重要。 这种持续增长的部分原因是全球不断从报纸,杂志和电视转向在线内容的结果。 借助可随时随地在手机上使用自定义新闻源以及 Facebook,Reddit,Twitter 和 YouTube 等实时新闻来源,很难想象历史上其他替代方法的使用时间会更长。 令人惊讶的是,这仅占在线可用数据量越来越大的一部分。
随着这种向使用 HTTP 服务(博客,新闻网站,Netflix 等)消费内容的全球转变,使用数据驱动的分析有很多机会。 例如,Netflix 会查看用户观看的电影并预测他们会喜欢什么。 此预测用于确定出现的建议电影。 但是,在本课程中,我们不会像这样面对“面向业务”的数据,而是会看到客户端如何利用互联网作为数据库。 如此庞大的数据量从未如此轻松地获得过。 我们将使用网页抓取技术来收集数据,然后在 Jupyter 中使用交互式可视化工具对其进行探索。
交互式可视化是数据表示的一种可视形式,可以帮助用户使用图形或图表理解数据。 交互式可视化帮助开发人员或分析人员以简单的形式显示数据,非技术人员也可以理解。
# 课程目标
在本课程中,您将:
* 分析 HTTP 请求的工作方式
* 从网页抓取表格数据
* 建立和转换 Pandas DataFrames
* 创建交互式可视化
# 抓取网页数据
本着利用互联网作为数据库的精神,我们可以考虑通过抓取内容或与 Web API 接口从网页获取数据。 通常,抓取内容意味着使计算机读取旨在以人类可读格式显示的数据。 这与 Web API 有所不同,Web API 以机器可读格式(最常见的是 JSON)传递数据。
在本主题中,我们将集中在网页抓取上。 确切的执行过程将取决于页面和所需的内容。 但是,正如我们将看到的,只要我们对基本概念和工具有所了解,就很容易从 HTML 页面中抓取所需的任何内容。 在本主题中,我们将以 Wikipedia 为例,并从文章中获取表格内容。 然后,我们将应用相同的技术从完全独立的域中的页面中抓取数据。 但是首先,我们将花一些时间介绍 HTTP 请求。
## 子主题 A:HTTP 请求简介
超文本传输协议(简称 HTTP)是 Internet 数据通信的基础。 它定义了如何请求页面以及响应的外观。 例如,客户可以请求亚马逊的笔记本电脑页面出售,谷歌搜索本地餐馆或他们的 Facebook feed。 该请求连同 URL 一起将包含用户代理和 **请求标头**内容中的可用浏览 cookie。 用户代理告诉服务器客户端正在使用哪种浏览器和设备,通常用于提供最用户友好的网页响应版本。 也许他们最近已经登录到网页了; 此类信息将存储在 Cookie 中,该 Cookie 可用于自动登录用户。
借助 Web 浏览器,HTTP 请求和响应的这些详细信息已在后台处理。 对我们来说幸运的是,今天当使用高级语言(例如 Python)发出请求时,情况也是如此。 出于许多目的,可以很大程度上忽略请求标头的内容。 除非另有说明,否则在请求 URL 时会在 Python 中自动生成它们。 尽管如此,出于故障排除和理解我们的请求所产生的响应的目的,对 HTTP 有基本的了解还是很有用的。
HTTP 方法有很多类型,例如 GET,HEAD,POST 和 PUT。 前两个用于请求将数据从服务器发送到客户端,而后两个用于将数据发送到服务器。
下表中总结了这些 HTTP 方法:
|
HTTP 方法
|
描述
|
| --- | --- |
| 得到 | 从指定的 URL 检索信息 |
| 头 | 从指定 URL 的 HTTP 标头中检索元信息 |
| 邮政 | 发送附加信息以附加到指定 URL 的资源 |
| 放 | 发送附加信息以替换指定 URL 上的资源 |
每次我们在浏览器中输入网页地址并按**输入**时,都会发送 GET 请求。 对于 Web 抓取,通常这是我们感兴趣的唯一 HTTP 方法,也是在本课程中将使用的唯一方法。
发送请求后,可以从服务器返回各种响应类型。 这些被标记为,具有 100 级到 500 级代码,其中代码的第一位数字表示响应类别。 这些可以描述如下:
* **1xx**:信息响应,例如,服务器正在处理请求。 看到这种情况并不常见。
* **2xx**:例如,页面已正确加载成功。
* **3xx**:重定向,例如,请求的资源已移动,并且我们已重定向到新的 URL。
* **4xx**:客户端错误,例如,请求的资源不存在。
* **5xx**:服务器错误,例如,网站服务器接收的流量过多,无法满足请求。
为了进行网页抓取,我们通常只关心响应类,即响应代码的第一位。 但是,每个类中都存在响应的子类别,这些子类别提供了所发生事件的更多粒度。 例如,401 代码表示*未经授权*响应,而 404 代码表示*未找到页面*响应。 这种区别是值得注意的,因为 404 表示我们请求的页面不存在,而 401 则表示我们需要登录才能查看特定资源。
让我们看看如何在 Python 中完成 HTTP 请求,并使用 Jupyter Notebook 探索其中一些主题。
## 子主题 B:在 Jupyter 笔记本中发出 HTTP 请求
既然我们已经讨论了 HTTP 请求如何工作以及应该期望哪种类型的响应,让我们看看如何在 Python 中完成此工作。 我们将使用一个名为 **Requests** 的库,该库恰好是 Python 上下载次数最多的外部库。 可以使用 Python 的内置工具,例如`urllib`来发出 HTTP 请求,但是请求要直观得多,事实上,在官方 Python 文档中,建议使用 urllib。
请求是用于进行简单和高级 Web 请求的绝佳选择。 它允许对标头,cookie 和授权进行各种自定义。 它跟踪重定向并提供用于返回特定页面内容(例如 JSON)的方法。 此外,还有一整套高级功能。 但是,它不允许呈现 JavaScript。
### 注意
通常,服务器会返回包含 JavaScript 代码段的 HTML,这些 HTML 代码段会在加载时自动在浏览器中运行。 使用“请求”使用 Python 请求内容时,此 JavaScript 代码是可见的,但不会运行。 因此,将丢失通过此操作将要更改或创建的任何元素。 通常,这并不影响获取所需信息的能力,但是在某些情况下,我们可能需要呈现 JavaScript 才能正确刮取页面。 为此,我们可以使用 Selenium 之类的库。 它具有与 Requests 库类似的 API,但提供了使用 Web 驱动程序呈现 JavaScript 的支持。
让我们深入研究以下部分,在 Jupyter Notebook 中使用带有 Python 的 Requests 库。
### 在 Jupyter Notebook 中使用 Python 处理 HTTP 请求
1. 通过执行`jupyter notebook`从项目目录启动`NotebookApp`。 导航到`lesson-3`目录并打开`lesson-3-workbook.ipynb`文件。 找到顶部附近的单元格,然后将其装入。
我们将请求一个网页,然后检查响应对象。 有许多不同的库可用于发出请求,也有许多选择方法来精确地处理每个请求。 我们仅使用 Requests 库,因为它提供了出色的文档,高级功能和简单的 API。
2. 向下滚动至`Subtopic A: Introduction to HTTP requests`,然后运行该部分中的第一个单元格以导入请求库。 然后,通过运行包含以下代码的单元格来准备请求:
```py
url = 'https://jupyter.org/'
req = requests.Request('GET', url)
req.headers['User-Agent'] = 'Mozilla/5.0'
req = req.prepare()
```
我们使用`Request`类来准备对 jupyter.org 主页的 GET 请求。 通过将用户代理指定为`Mozilla/5.0`,我们要求提供适合标准台式机浏览器的响应。 最后,我们准备请求。
3. 通过运行包含`req?`的单元格,为“准备的请求”`req`打印文档字符串:
![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook](img/image3_01.jpg)
查看其用法,我们看到如何使用会话发送请求。 这类似于打开 Web 浏览器(开始会话)然后请求 URL。
4. 通过运行以下代码,发出请求并将响应存储在名为`page`的变量中:
```py
with requests.Session() as sess:
page = sess.send(req)
```
此代码返回 HTTP 响应,该变量由`page`引用。 通过使用`with`语句,我们初始化了一个范围限于缩进代码块的会话。 这意味着我们不必担心显式关闭会话,因为它是自动完成的。
5. 运行笔记本中的下两个单元格以调查响应。 `page`的字符串表示应指示 200 状态码响应。 这应该与`status_code`属性一致。
6. 将响应文本保存到`page_html`变量中,并使用`page_html[:1000]`看一下字符串的开头:
![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook](img/image3_03.jpg)
如预期的那样,响应为 HTML。 我们可以在 BeautifulSoup 的帮助下更好地格式化此输出,该库将在本节后面的内容中广泛用于 HTML 解析。
7. 通过运行以下命令来打印格式化的 HTML 的标题:
```py
from bs4 import BeautifulSoup
print(BeautifulSoup(page_html, 'html.parser').prettify()[:1000])
```
我们导入 BeautifulSoup,然后打印*漂亮的*输出,其中的换行根据 HTML 结构中的层次结构缩进。
8. 我们可以更进一步,使用 IPython 显示模块在 Jupyter 中实际显示 HTML。 通过运行以下代码来执行此操作:
```py
from IPython.display import HTML
HTML(page_html)
```
![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook](img/image3_05.jpg)
鉴于没有运行 JavaScript 代码且未加载任何外部资源,在这里,我们将尽可能地看到呈现的 HTML。 例如,不会呈现在 jupyter.org 服务器上的图像,而是显示`alt`文本:**编程图标圆圈**,**jupyter 徽标**等,依此类推。
9. 让我们将其与实时网站进行比较,可以使用 IFrame 在 Jupyter 中打开它。 通过运行以下代码来执行此操作:
```py
from IPython.display import IFrame
IFrame(src=url, height=800, width=800)
```
![Handling HTTP requests Jupyter NotebooksHTTP requests, handling with Pythonwith Python in a Jupyter Notebook](img/image3_07.jpg)
在这里,我们看到了完整的网站,包括 JavaScript 和外部资源。 实际上,我们甚至可以单击超链接并将这些页面加载到 IFrame 中,就像常规浏览会话一样。
10. 在使用 iFrame 后,最好关闭它。 这样可以防止它耗尽内存和处理能力。 可以通过从 Jupyter Notebook 的“单元格”菜单中选择单元格并单击“当前输出 | 清除”来关闭它。
回想一下我们如何使用准备好的请求和会话在 Python 中以字符串形式请求此内容。 通常,这是使用速记方法来完成的。 缺点是我们没有太多的请求标头自定义,但这通常很好。
11. 通过运行以下代码来向`http://www.python.org/`发出请求:
```py
url = 'http://www.python.org/'page = requests.get(url)
page
```
页面的字符串表示形式(如单元格下方所示)应指示 200 状态代码,指示成功响应。
12. 运行接下来的两个单元格。 在这里,我们打印页面的`url`和`history`属性。
返回的 URL 不是我们输入的; 注意区别吗? 我们从[输入 URL](http://www.python.org/) 重定向到[该页面的安全版本](https://www.python.org/)。 在协议的 URL 开头,附加的`s`表示差异。 任何重定向都存储在`history`属性中; 在这种情况下,我们在此处找到一个页面,其状态码为 301(永久重定向),与所请求的原始 URL 相对应。
现在,我们可以轻松地发出请求了,我们将注意力转向解析 HTML。 这可能是一门艺术,因为通常有多种处理方法,而最好的方法通常取决于所讨论的特定 HTML 的细节。
## 子主题 C:在 Jupyter Notebook 中解析 HTML
从网页上抓取数据时,发出请求后,我们必须从响应内容中提取数据。 如果内容是 HTML,则最简单的方法是使用高级解析库(例如 Beautiful Soup)。 这并不是说它是的唯一方法; 原则上,可以使用正则表达式或 Python 字符串方法(例如`split`)来选择数据,但是采用这些选项中的任何一个都会浪费时间,并且很容易导致错误。 因此,通常对此并不满意,建议使用可信赖的解析工具。
为了了解如何从 HTML 中提取内容,了解 HTML 的基础很重要。 首先,HTML 代表**超文本标记语言**。 类似于 Markdown 或 XML(**可扩展标记语言**),它只是一种用于标记文本的语言。 在 HTML 中,显示文本包含在 HTML 元素的内容部分中,其中元素属性指定该元素在页面上的显示方式。
![Subtopic C: Parsing HTML in the Jupyter Notebook](img/image3_09.jpg)
如上图所示,查看 HTML 元素的剖析,我们看到起始标签和结束标签之间包含的内容。 在此示例中,段落的标签为``; 其他常见的标签类型是`