# Python实战:用Wikipedia-API快速抓取多语言百科数据(附完整代码)
最近在做一个跨语言知识库的项目,需要从不同语言的维基百科中抽取结构化信息。一开始我尝试用`requests`库直接调用MediaWiki API,虽然可行,但处理多语言重定向、页面解析和错误处理时,代码很快就变得臃肿不堪。后来发现了`Wikipedia-API`这个宝藏库,它把那些繁琐的细节都封装好了,让开发者能专注于数据本身。这篇文章,我就结合自己踩过的坑和实际项目经验,分享如何高效、优雅地利用这个库进行多语言数据抓取,并构建可复用的数据处理管道。
对于数据分析师、自然语言处理工程师或是需要构建知识图谱的开发者来说,维基百科是一个无与伦比的免费、高质量多语言语料库。但直接处理原始API响应,尤其是在处理几十种语言、成千上万个页面时,挑战不小。`Wikipedia-API`库提供了一个Pythonic的接口,极大地简化了这一过程。接下来,我会从环境配置、核心功能、高级技巧到实战项目,一步步拆解,并提供可直接运行的代码片段。
## 1. 环境准备与库的核心优势
在开始编写任何代码之前,我们需要先搭建好环境。`Wikipedia-API`库对Python版本有要求,因为它使用了`IntEnum`等特性,所以需要Python 3.4或更高版本。安装过程非常简单,使用pip即可。
```bash
pip install wikipedia-api
```
安装完成后,我们可以通过一个简单的导入语句来验证是否成功。
```python
import wikipediaapi
print(wikipediaapi.__version__)
```
这个库之所以比直接调用原始API更高效,主要体现在几个方面:
* **面向对象的封装**:它将维基百科的页面、章节、链接、分类等概念都封装成了Python对象(如`WikipediaPage`, `WikipediaPageSection`),操作起来非常直观。
* **自动处理语言链接**:获取一个页面的多语言版本变得异常简单,无需手动拼接URL或处理跨语言的重定向。
* **灵活的文本提取格式**:支持以维基文本(WikiText)或HTML格式获取页面内容,方便不同场景下的解析需求。
* **内置请求缓存与错误处理**:库内部处理了网络请求、速率限制(虽然维基百科API本身有调用限制,但库提供了更友好的接口)和部分错误,让代码更健壮。
* **清晰的文档结构**:通过`sections`属性可以轻松获取页面的层级化章节信息,这对于抽取特定部分的内容(如“历史”、“概述”)非常有帮助。
> 提示:虽然库本身处理了部分网络问题,但在生产环境中进行大规模抓取时,仍然建议实现自己的重试逻辑和延迟策略,以尊重维基百科的服务器,并提高程序的稳定性。
## 2. 核心功能实战:从单页面到多语言网络
让我们从最基本的操作开始,逐步深入到更复杂的多语言数据抓取场景。假设我们的目标是获取“人工智能”这个主题在不同语言维基百科中的描述。
### 2.1 初始化与获取单页面内容
首先,我们需要创建一个`Wikipedia`对象,并指定目标语言。
```python
import wikipediaapi
# 初始化英文维基百科客户端
wiki_en = wikipediaapi.Wikipedia(
language='en',
user_agent='MyResearchProject/1.0 (your_email@example.com)' # 建议设置用户代理
)
# 获取“Artificial intelligence”页面
page_ai = wiki_en.page('Artificial_intelligence')
# 检查页面是否存在
if page_ai.exists():
print(f"页面标题: {page_ai.title}")
print(f"页面摘要 (前200字符): {page_ai.summary[:200]}...")
print(f"页面完整URL: {page_ai.fullurl}")
else:
print("指定页面不存在。")
```
这里有几个关键点:
* `user_agent`参数虽然不是强制的,但**强烈建议设置**。一个好的用户代理字符串(包含你的项目名和联系方式)是遵循维基百科API使用规范的表现,在遇到问题时也便于管理员联系你。
* `exists()`方法是判断页面是否可用的最可靠方式,应总是在尝试访问页面内容前调用。
* `summary`属性提供了页面的简短摘要,通常就是文章的第一段,对于快速了解主题非常有用。
### 2.2 深入页面结构:章节与完整文本
对于研究或深度分析,我们往往需要更细粒度的内容。`Wikipedia-API`允许我们获取完整的页面文本和结构化的章节信息。
```python
# 获取完整的页面文本(维基文本格式)
wiki_wiki = wikipediaapi.Wikipedia(
language='en',
extract_format=wikipediaapi.ExtractFormat.WIKI # 指定格式为WikiText
)
page_ai_full = wiki_wiki.page('Artificial_intelligence')
full_text = page_ai_full.text
print(f"完整文本长度: {len(full_text)} 字符")
# 遍历并打印所有顶级章节标题
def print_section_titles(sections, level=0):
for section in sections:
indent = " " * level
print(f"{indent}- {section.title}")
# 递归打印子章节
print_section_titles(section.sections, level + 1)
print("\n页面章节结构:")
print_section_titles(page_ai_full.sections)
```
有时,你可能需要HTML格式的内容以便于用BeautifulSoup等库进行二次解析。只需在初始化时更改`extract_format`参数:
```python
wiki_html = wikipediaapi.Wikipedia(
language='en',
extract_format=wikipediaapi.ExtractFormat.HTML
)
page_ai_html = wiki_html.page('Artificial_intelligence')
# page_ai_html.text 现在包含的是HTML格式的文本
```
### 2.3 解锁多语言能力:跨语言信息抓取
这是`Wikipedia-API`库最强大的功能之一。通过`langlinks`属性,你可以轻松访问一个页面在所有其他语言维基百科中的对应版本。
```python
# 获取英文“Artificial intelligence”页面的所有语言链接
langlinks = page_ai.langlinks
print(f"该页面共有 {len(langlinks)} 种语言版本。")
# 获取特定语言版本,例如中文简体(zh)和日语(ja)
if 'zh' in langlinks:
page_ai_zh = langlinks['zh']
print(f"\n中文标题: {page_ai_zh.title}")
print(f"中文摘要: {page_ai_zh.summary[:150]}...")
if 'ja' in langlinks:
page_ai_ja = langlinks['ja']
print(f"\n日文标题: {page_ai_ja.title}")
# 可以进一步获取日文页面的章节、链接等
```
我们可以将这个过程封装成一个函数,用于批量获取一个主题在多个语言中的摘要,并存储起来进行比较分析。
```python
def fetch_summaries_in_languages(topic, base_lang='en', target_langs=['zh', 'fr', 'de', 'es', 'ru']):
"""
获取一个主题在多种语言下的摘要。
Args:
topic (str): 主题词,在base_lang维基百科中存在的页面标题。
base_lang (str): 作为起点的语言代码。
target_langs (list): 需要获取摘要的目标语言代码列表。
Returns:
dict: 键为语言代码,值为该语言页面摘要的字典。如果某种语言版本不存在,则值为None。
"""
wiki_base = wikipediaapi.Wikipedia(language=base_lang)
base_page = wiki_base.page(topic)
if not base_page.exists():
print(f"在{base_lang}维基百科中未找到主题 '{topic}'。")
return {}
summaries = {}
summaries[base_lang] = base_page.summary
for lang in target_langs:
if lang in base_page.langlinks:
lang_page = base_page.langlinks[lang]
summaries[lang] = lang_page.summary
else:
summaries[lang] = None
print(f"警告:未找到{topic}的{lang}语言版本。")
return summaries
# 使用示例
ai_summaries = fetch_summaries_in_languages('Artificial_intelligence', target_langs=['zh', 'fr', 'ja', 'ar'])
for lang, summary in ai_summaries.items():
if summary:
print(f"\n--- {lang.upper()} 摘要 (前100字符) ---")
print(summary[:100])
```
## 3. 高级技巧与批量处理策略
当我们需要处理成百上千个页面时,简单的顺序请求效率低下且容易触发API限制。我们需要更聪明的策略。
### 3.1 利用分类获取相关页面群
维基百科的页面组织在庞大的分类树中。我们可以通过一个分类页,获取其下的所有成员页面,这是进行领域特定数据收集的绝佳入口。
```python
def get_pages_in_category(category_name, language='en', max_pages=50):
"""
获取一个分类下的所有页面(非子分类)。
Args:
category_name (str): 分类名称,如'Category:Machine_learning'。
language (str): 语言代码。
max_pages (int): 限制获取的页面数量,防止过多。
Returns:
list: 页面标题列表。
"""
wiki = wikipediaapi.Wikipedia(language=language)
# 注意:分类页面需要以'Category:'为前缀
if not category_name.startswith('Category:'):
category_name = 'Category:' + category_name
category_page = wiki.page(category_name)
if not category_page.exists():
print(f"分类 '{category_name}' 不存在。")
return []
page_titles = []
for member in category_page.categorymembers.values():
# 筛选出普通文章页面(命名空间为0),排除子分类或其他类型
if member.ns == wikipediaapi.Namespace.MAIN: # 0代表主命名空间(文章)
page_titles.append(member.title)
if len(page_titles) >= max_pages:
break
return page_titles
# 获取“机器学习”分类下的前20篇文章
ml_pages = get_pages_in_category('Machine_learning', max_pages=20)
print(f"找到 {len(ml_pages)} 个相关页面:")
for title in ml_pages[:5]: # 打印前5个
print(f" - {title}")
```
### 3.2 构建健壮的批量抓取管道
直接用一个`for`循环遍历页面标题列表进行请求是不可取的。我们需要加入错误处理、延迟控制,并考虑将结果持久化。
```python
import time
import json
from typing import Dict, Any
def robust_page_fetcher(page_titles: list, language: str = 'en', delay: float = 1.0) -> Dict[str, Any]:
"""
健壮的页面批量抓取函数。
Args:
page_titles: 页面标题列表。
language: 语言代码。
delay: 每次请求之间的延迟(秒),用于礼貌爬取。
Returns:
一个字典,键为页面标题,值为包含页面信息的字典。如果抓取失败,值为错误信息。
"""
wiki = wikipediaapi.Wikipedia(language=language)
results = {}
for i, title in enumerate(page_titles):
print(f"正在处理 ({i+1}/{len(page_titles)}): {title}")
try:
page = wiki.page(title)
time.sleep(delay) # 关键:请求间延迟
if page.exists():
page_data = {
'title': page.title,
'summary': page.summary,
'url': page.fullurl,
'text_length': len(page.text) if hasattr(page, 'text') else 0,
'langlinks_count': len(page.langlinks) if hasattr(page, 'langlinks') else 0,
# 可以根据需要添加更多字段,如categories, links等
}
results[title] = page_data
else:
results[title] = {'error': 'Page does not exist'}
except Exception as e:
# 捕获网络超时、连接错误等异常
results[title] = {'error': str(e)}
print(f" 抓取 '{title}' 时出错: {e}")
# 遇到错误时等待稍长时间
time.sleep(delay * 2)
return results
# 使用示例:抓取几个页面
titles_to_fetch = ['Python_(programming_language)', 'Deep_learning', 'Natural_language_processing']
fetched_data = robust_page_fetcher(titles_to_fetch, delay=0.5)
# 将结果保存为JSON文件
with open('wikipedia_pages.json', 'w', encoding='utf-8') as f:
json.dump(fetched_data, f, ensure_ascii=False, indent=2)
print("数据已保存至 wikipedia_pages.json")
```
这个函数包含了几个重要实践:
1. **延迟 (`time.sleep`)**:这是遵守维基百科机器人政策的核心。即使API没有严格的速率限制,添加延迟也是负责任的爬虫行为。
2. **全面的错误处理**:使用`try-except`块捕获可能发生的任何异常,防止单个页面失败导致整个程序崩溃。
3. **结果结构化存储**:将抓取到的信息组织成结构化的字典,并方便地导出为JSON等格式,便于后续分析。
### 3.3 数据增强:获取页面链接与分类信息
为了构建知识图谱或进行网络分析,我们经常需要页面之间的关联关系。`links`和`categories`属性提供了这些信息。
```python
def get_page_network(page_title, language='en', depth=1):
"""
获取一个页面的直接链接和分类(一度网络)。
Args:
page_title: 中心页面标题。
language: 语言代码。
depth: 目前只支持1(直接关联)。更深的遍历需要递归。
Returns:
包含中心页面、链接页面和分类页面的信息字典。
"""
wiki = wikipediaapi.Wikipedia(language=language)
center_page = wiki.page(page_title)
if not center_page.exists():
return None
network_data = {
'center': center_page.title,
'links': [],
'categories': []
}
# 获取链接(只取前20个作为示例)
if hasattr(center_page, 'links'):
for link_title, link_page in list(center_page.links.items())[:20]:
network_data['links'].append({
'title': link_page.title,
'url': link_page.fullurl
})
# 获取分类
if hasattr(center_page, 'categories'):
for cat_title, cat_page in list(center_page.categories.items())[:10]:
network_data['categories'].append({
'title': cat_page.title,
'url': cat_page.fullurl
})
return network_data
# 分析“神经网络”的关联页面
nn_network = get_page_network('Artificial_neural_network')
if nn_network:
print(f"中心页面: {nn_network['center']}")
print(f"直接链接数 (示例): {len(nn_network['links'])}")
print(f"所属分类数 (示例): {len(nn_network['categories'])}")
print("\n前3个链接:")
for link in nn_network['links'][:3]:
print(f" - {link['title']}")
```
## 4. 实战项目:构建一个多语言术语对照表
让我们综合运用以上所有技巧,完成一个实际的小项目:给定一个英文技术术语列表,自动生成其中文、日文、法文和西班牙文的对照表,并附上各语言版本的简要定义。
这个项目模拟了为国际化产品构建术语库,或为研究收集跨语言概念定义的场景。
```python
import csv
import concurrent.futures
from functools import partial
def fetch_term_data(term, base_lang='en', target_langs=['zh', 'ja', 'fr', 'es']):
"""
为单个术语获取多语言数据。
设计为可在线程池中执行。
"""
wiki_base = wikipediaapi.Wikipedia(
language=base_lang,
user_agent='MultilingualGlossaryBuilder/1.0'
)
base_page = wiki_base.page(term)
term_data = {
'base_term': term,
'base_lang': base_lang,
'base_summary': None,
'translations': {}
}
if base_page.exists():
term_data['base_summary'] = base_page.summary[:300] # 取前300字符作为定义
for lang in target_langs:
if lang in base_page.langlinks:
lang_page = base_page.langlinks[lang]
term_data['translations'][lang] = {
'term': lang_page.title,
'summary': lang_page.summary[:300] if lang_page.summary else None
}
else:
term_data['translations'][lang] = {'term': None, 'summary': None}
else:
# 如果基础术语页面不存在,所有翻译也为空
for lang in target_langs:
term_data['translations'][lang] = {'term': None, 'summary': None}
# 添加礼貌延迟
time.sleep(0.3)
return term_data
def build_multilingual_glossary(term_list, output_file='glossary.csv', max_workers=3):
"""
主函数:构建多语言术语表。
使用线程池并发抓取以提高效率(需谨慎控制并发数)。
"""
all_term_data = []
# 使用线程池进行并发抓取,但严格控制工作线程数,避免对服务器造成压力
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
# 使用partial固定部分参数
fetch_func = partial(fetch_term_data, base_lang='en', target_langs=['zh', 'ja', 'fr', 'es'])
# 提交所有任务
future_to_term = {executor.submit(fetch_func, term): term for term in term_list}
for future in concurrent.futures.as_completed(future_to_term):
term = future_to_term[future]
try:
data = future.result()
all_term_data.append(data)
print(f"已完成: {data['base_term']}")
except Exception as exc:
print(f"术语 '{term}' 生成时产生异常: {exc}")
# 记录一个错误数据
all_term_data.append({
'base_term': term,
'base_lang': 'en',
'base_summary': f'ERROR: {exc}',
'translations': {lang: {'term': None, 'summary': None} for lang in ['zh', 'ja', 'fr', 'es']}
})
# 写入CSV文件
fieldnames = [
'英文术语', '英文定义',
'中文术语', '中文定义',
'日文术语', '日文定义',
'法文术语', '法文定义',
'西班牙文术语', '西班牙文定义'
]
with open(output_file, 'w', newline='', encoding='utf-8-sig') as csvfile: # utf-8-sig支持Excel直接打开
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for data in all_term_data:
row = {
'英文术语': data['base_term'],
'英文定义': data['base_summary'] or '未找到',
}
for lang_code, lang_name in [('zh','中文'), ('ja','日文'), ('fr','法文'), ('es','西班牙文')]:
trans = data['translations'].get(lang_code, {})
row[f'{lang_name}术语'] = trans.get('term', '未找到')
row[f'{lang_name}定义'] = trans.get('summary', '未找到')
writer.writerow(row)
print(f"\n术语表已成功生成并保存至: {output_file}")
return all_term_data
# 定义要查询的技术术语列表
tech_terms = [
'Blockchain',
'Machine_learning',
'Cloud_computing',
'Internet_of_things',
'Artificial_intelligence',
'Quantum_computing',
'DevOps',
'Microservices'
]
# 运行项目(注意:首次运行可能需要一些时间)
if __name__ == '__main__':
print("开始构建多语言技术术语表...")
glossary_data = build_multilingual_glossary(tech_terms, max_workers=2) # 保守的并发数
print("项目完成!")
```
这个实战项目展示了如何将`Wikipedia-API`用于一个具体的、有价值的任务。它包含了错误处理、并发控制(适度使用)、数据持久化等生产级代码的要素。生成的CSV文件可以直接用Excel或Google Sheets打开,方便产品经理、翻译或研究人员使用。
在实际使用中,你可能会遇到一些页面不存在、摘要过长或格式不一致的情况。这时,代码中的错误处理和数据清洗逻辑就派上了用场。例如,对于“未找到”的术语,你可能需要后续手动处理,或者尝试使用更通用的术语变体。
通过`Wikipedia-API`,我们能够以编程方式高效访问这座人类知识的宝库。无论是进行学术研究、构建多语言应用,还是简单地满足好奇心,它都是一个强大而优雅的工具。记住,能力越大责任越大,在使用时务必遵守维基百科的机器人使用指南,添加合理的延迟,并设置清晰的用户代理。