作为一名数据爱好者或开发者,你是否曾经对某个词汇在历史长河中的演变趋势感到好奇?比如,“Artificial Intelligence”(人工智能)这个词在 20 世纪初的书籍中是否出现过?或者,“爱因斯坦”是在哪一年开始在文献中频繁被提及的?
手动去翻阅成千上万本书显然是不现实的。幸运的是,Google 提供了一个强大的工具——Google Ngram Viewer,它不仅能可视化这些趋势,其背后的接口还允许我们通过编程方式直接获取数据。
在这篇文章中,我们将深入探讨如何使用 Python 来爬取 Google Ngram Viewer 的数据。我们将不仅仅满足于“拿到数据”,还会深入解释 API 的工作原理、如何构建灵活的查询器,以及如何处理和分析返回的 JSON 数据。让我们开始这场数据探索之旅吧。
目录
什么是 Google Ngram Viewer?
在编写代码之前,我们需要先了解我们要与之交互的对象。Google Ngram Viewer 是一个基于 Google Books 数据的在线搜索引擎。它扫描了 Google 数字图书馆中数百万册出版的图书,统计并绘制出特定单词或短语在给定年份中出现的频率图表。
Ngram 的核心概念
这里的“Ngram”指的是文本的 n 元语法模型。简单来说:
- Unigram(一元组):由一个词组成的序列,例如 "geek"。
- Bigram(二元组):由两个连续词组成的序列,例如 "Isaac Newton"。
- Trigram(三元组):由三个连续词组成的序列,例如 "to be or"。
当你在 Ngram Viewer 中搜索 "Albert Einstein" 时,它实际上是在统计 bigram,并告诉你:在特定年份出版的所有书籍中,这个二元组出现的比例是多少。
搜索结果的背后
当我们在 Google Ngram Viewer 中搜索 "Albert Einstein"(假设时间跨度为 1850-1900)时,界面会展示一个漂亮的折线图。但除了这张图,Google 还通过 HTTP 请求返回了原始数据。我们的目标,就是绕过前端渲染,直接通过 Python 获取这些原始的统计数据。
揭秘 Google Ngram 的 URL 结构
要实现“爬虫”,第一步通常是分析目标网站的 URL 结构。让我们仔细观察一下 Google Ngram 搜索时的 URL 链接。
一个标准的查询 URL 如下所示:
https://books.google.com/ngrams/graph?content=Albert%20Einstein&year_start=1850&year_end=1860&corpus=26&smoothing=0
我们可以将这个 URL 拆解为几个关键部分:
- 基础地址:
https://books.google.com/ngrams/graph - 查询内容 (content):
Albert%20Einstein(%20 是空格的 URL 编码)。 - 起止年份:INLINECODE22b92148 和 INLINECODE7884f721。
- 语料库 (corpus):
corpus=26。这个数字代表了我们搜索的书籍数据集类型。例如,26 通常代表“英语”,其他数字可能代表“英语小说”、“英语科幻”等不同子集。 - 平滑度 (smoothing):
smoothing=0。这决定了曲线的平滑程度,0 代表不平滑,显示每年的原始波动。
技巧:从图表到数据的魔法
如果你直接点击上面的链接,你会看到一张图表。但是,作为一名开发者,我们对静态图片不感兴趣。我们要的是数据。
这里有一个鲜为人知但非常实用的技巧:将 URL 中的 INLINECODEde80dd9a 替换为 INLINECODE1c72c610。
修改后的 URL 如下:
https://books.google.com/ngrams/json?content=Albert%20Einstein&year_start=1850&year_end=1860&corpus=26&smoothing=0
当你访问这个 JSON 版本的 URL 时,Google 会返回包含时间序列数据的原始 JSON 格式文本。这正是我们 Python 程序要“抓取”的内容。
编写 Python 爬虫:从零开始
现在,让我们动手编写代码。我们将使用 Python 中最流行的两个网络请求库:INLINECODEf426c13c 和标准库中的 INLINECODEad026f93。
1. 准备工作
首先,确保你的环境中安装了 requests 库。如果没有,可以通过 pip 安装:
pip install requests
2. 构建核心函数
我们将创建一个名为 get_ngram_data 的函数。这个函数将封装所有复杂的 URL 构建逻辑和请求处理,让我们在调用时只需关心查询的关键词。
请仔细阅读以下代码中的注释,我们添加了详细的解释来帮助你理解每一步:
import requests
import urllib.parse
import json
def get_ngram_data(query, start_year=1800, end_year=2000, corpus=26, smoothing=0):
"""
从 Google Ngram 获取指定短语的时间序列数据。
参数:
query (str): 要搜索的短语,多个短语可用逗号分隔 (例如: "Albert Einstein,Isaac Newton")
start_year (int): 起始年份,默认 1800
end_year (int): 结束年份,默认 2000
corpus (int): 语料库 ID,默认 26 (英语)
smoothing (int): 平滑度,默认 0
返回:
list: 包含元组的列表,每个元组格式为 (短语, [数据序列])
"""
# 第一步:处理 URL 编码
# 我们不能直接把空格或特殊字符放在 URL 里,必须转换为标准格式
# 例如:"geeks for,geeks" 会被转换为 "geeks%20for%2Cgeeks"
encoded_query = urllib.parse.quote(query)
# 第二步:构建完整的 API URL
# 我们使用 f-string 来格式化字符串,确保各参数正确拼接
base_url = ‘https://books.google.com/ngrams/json?content=‘
full_url = (
f"{base_url}{encoded_query}"
f"&year_start={start_year}"
f"&year_end={end_year}"
f"&corpus={corpus}"
f"&smoothing={smoothing}"
)
try:
# 第三步:发送 HTTP GET 请求
response = requests.get(full_url)
# 检查请求是否成功 (状态码 200)
response.raise_for_status()
# 第四步:解析 JSON 数据
data = response.json()
# 如果没有数据,返回提示信息
if not data:
return "No data available for this Ngram."
# 第五步:提取并清洗数据
# Google 返回的数据结构比较复杂,我们需要提取 ‘ngram‘ (名称) 和 ‘timeseries‘ (数据)
results = []
for item in data:
# 每一个 item 是一个字典,包含查询的短语和对应的时间序列数组
ngram_name = item.get(‘ngram‘)
timeseries = item.get(‘timeseries‘, [])
# 将提取的数据存入列表
results.append((ngram_name, timeseries))
return results
except requests.exceptions.RequestException as e:
# 捕获网络请求中的错误(如连接超时、DNS错误等)
return f"An error occurred: {e}"
代码深度解析
在上述代码中,我们做了一些优化以提高鲁棒性:
- URL 编码处理:用户可能会输入像 "A+B" 这样的字符串,或者逗号。
urllib.parse.quote是处理这些特殊字符的最佳实践,它能确保服务器正确解析我们的意图。 - 异常处理:网络请求并不总是成功的。我们添加了 INLINECODEf6fd8afa 块,并在发生 INLINECODE729f0bb3 时返回错误信息,防止程序直接崩溃。
- 数据清洗:直接返回的 JSON 对象可能包含多余的元数据字段,我们只提取了最关键的 INLINECODE025ff475 和 INLINECODEbead212e,使后续处理更简单。
实战演练:单词语义分析
有了上面的函数,让我们进行几个实际的测试。我们来看看如何在 Python 中调用它并查看结果。
案例 1:查询单一短语
让我们查询 "Albert Einstein" 在 1850 年到 1900 年之间的出现频率(注意:爱因斯坦的重要贡献主要在 20 世纪初,所以在这个较早的时间段内数据应该非常少或为零)。
# 定义查询参数
phrase = "Albert Einstein"
start = 1850
end = 1900
# 调用我们的函数
result = get_ngram_data(phrase, start_year=start, end_year=end)
# 打印结果
print(f"查询短语: {phrase}")
print(f"时间范围: {start} - {end}")
print("原始数据:")
print(result)
输出示例:
查询短语: Albert Einstein
时间范围: 1850 - 1900
原始数据:
[(‘Albert Einstein‘, [0.0, 0.0, 0.0, 0.0, 2.17e-09, 1.01e-09, 6.44e-10, ...])]
从结果中我们可以看到,函数返回了一个列表,列表中包含元组。元组的第一个元素是查询的短语,第二个元素是一个浮点数列表,代表每年的频率百分比。e-09 表示这是一个非常小的数字,符合 19 世纪末爱因斯坦尚未成名的史实。
案例 2:对比分析
Google Ngram 最强大的功能之一是支持同时查询多个短语。我们只需用逗号分隔它们即可。让我们对比一下 "Alan Turing", "Albert Einstein" 和 "Isaac Newton" 在 20 世纪的流行度趋势。
# 注意:逗号分隔的短语不要包含额外的空格(除非空格是短语的一部分)
query_string = "Alan Turing,Albert Einstein,Isaac Newton"
results = get_ngram_data(query_string, start_year=1900, end_year=2000)
# 美化输出
for name, data_points in results:
# 获取一些简单的统计信息,例如该时期的最高频率
max_freq = max(data_points)
print(f"短语 ‘{name}‘ 在 20 世纪的最大频率占比: {max_freq:.2e}")
通过这种方式,我们可以快速地进行宏观的数据对比,而不需要手动去 Google Books 页面上一个个截图。
进阶应用:数据可视化与最佳实践
仅仅打印列表中的数字往往不够直观。在实际的数据分析工作中,我们通常会将这些数据可视化。
结合 Matplotlib 进行绘图
让我们展示如何利用我们获取的数据,结合 Python 的 matplotlib 库绘制趋势图。这将是一个完整的端到端数据流程示例。
import matplotlib.pyplot as plt
def plot_ngrams(query_data, start_year, end_year):
"""
将获取的 Ngram 数据绘制成折线图。
"""
plt.figure(figsize=(10, 6))
# 生成年份轴
# 注意:Google 的数据是按年份均匀采样的,默认通常是每年一个数据点
years = range(start_year, end_year + 1)
for name, series in query_data:
# 检查年份序列和数据点长度是否匹配,防止绘图报错
if len(years) == len(series):
plt.plot(years, series, label=name)
else:
print(f"警告:‘{name}‘ 的数据点数量 ({len(series)}) 与年份范围不匹配,跳过绘图。")
plt.title(f"Ngram 频率分析 ({start_year}-{end_year})")
plt.xlabel("年份")
plt.ylabel("频率占比")
plt.legend()
plt.grid(True)
# 显示图表
plt.show()
# 使用示例
query = "computer,internet"
start = 1950
end = 2000
data = get_ngram_data(query, start_year=start, end_year=end)
# 只有当返回的是有效列表时才绘图
if isinstance(data, list):
plot_ngrams(data, start, end)
else:
print(data) # 打印错误信息
常见问题与解决方案
在爬取 Google Ngram 数据的过程中,你可能会遇到一些常见问题,这里提供一些解决思路:
- 请求过于频繁
虽然 Google Ngram 的 JSON 接口相对开放,但如果你在短时间内发送成千上万个请求,IP 仍然可能被暂时限制。解决方案:在循环请求中添加 time.sleep(),或者控制并发数量。
- 语料库 ID 的选择
代码中默认使用的 INLINECODE629a4856 是指“英语”。但如果你想研究其他语言,或者特定类型的文学作品(如仅限小说),你需要修改这个参数。解决方案:查阅 Google Ngram 的官方文档,找到对应语言的 INLINECODE8a7c59d7 ID(例如,中文简体可能是另一个特定的 ID)。
- 数据处理量大
如果你查询的时间跨度很大(例如 1800-2019),返回的时间序列数组会非常长。解决方案:使用 INLINECODE14d4ffe8 或 INLINECODE0473e042 库来处理这些数值型数据,它们在处理大规模数组时比 Python 原生列表更高效。
总结与展望
在这篇文章中,我们像真正的数据工程师一样,从零开始构建了一个能够与 Google Ngram Viewer 交互的 Python 工具。我们没有依赖任何现成的第三方封装包,而是直接分析了 URL 结构,利用 requests 库实现了对 JSON 接口的调用,并进一步探讨了如何清洗和可视化这些数据。
你现在已经掌握了:
- 如何识别 URL 参数并利用 API。
- 如何编写健壮的 Python 函数来处理网络请求和异常。
- 如何处理 JSON 格式的返回数据。
这种技能不仅仅适用于 Google Ngram,它是通用的 Web 数据抓取的基础。你可以尝试将这个函数扩展为一个完整的类,或者增加对更多过滤选项的支持。
希望这篇指南能激发你进一步挖掘海量书籍数据的兴趣。不妨现在就运行一下你的 Python 脚本,看看历史上还有哪些有趣的词汇趋势等待着你去发现!