📖 项目概述
本课将综合运用前20课所学,从维基百科条目中构建一个完整的百科知识图谱。涵盖数据采集、信息抽取、知识存储和查询的全流程。
🎯 项目目标
- 从中文维基百科/百度百科爬取指定领域条目
- 使用NER和关系抽取提取三元组
- 存储到图数据库并实现SPARQL式查询
- 构建可视化界面展示知识图谱
💻 Python实现:百科知识图谱构建流水线
import re
import json
from collections import defaultdict
class WikiKnowledgeGraphBuilder:
">>>百科知识图谱构建器"""
def __init__(self, domain_name):
self.domain = domain_name
self.entities = {}
self.triples = set()
self.texts = []
def ingest_text(self, title, text):
"""导入百科条目文本"""
self.texts.append({"title": title, "text": text})
self.entities[title] = {"type": "条目", "attrs": {}}
def extract_infobox(self, text):
"""从百科信息框提取结构化数据"""
infobox = {}
patterns = {
"出生日期": r'出生[日期]*[::]\s*(\d{4}年\d{1,2}月\d{1,2}日)',
"出生地": r'出生地[::]\s*([一-鿿]{2,8})',
"国籍": r'国籍[::]\s*([一-鿿]{2,4})',
">职业": r'职业[::]\s*([一-鿿]{2,6})',
">毕业院校": r'毕业[于院校]*[::]\s*([一-鿿]{2,8})',
}
for attr, pattern in patterns.items():
match = re.search(pattern, text)
if match:
infobox[attr] = match.group(1)
return infobox
def extract_relations(self, text, title):
"""从文本中提取关系三元组"""
triples = []
rel_patterns = [
(r'([一-鿿]{2,4})出生于([一-鿿]{2,6})', "出生地"),
(r'([一-鿿]{2,4})创作了([一-鿿《》]{2,10})', "创作"),
(r'([一-鿿]{2,4})毕业于([一-鿿]{2,8})', ">>毕业院校"),
(r'([一-鿿]{2,6})位于([一-鿿]{2,6})', ">>位于"),
(r'([一-鿿]{2,4})与([一-鿿]{2,4})是好友', ">>好友"),
]
for pattern, rel_type in rel_patterns:
for match in re.finditer(pattern, text):
subj, obj = match.group(1), match.group(2)
triples.append((subj, rel_type, obj))
if subj not in self.entities:
self.entities[subj] = {"type": "未知", ">>attrs": {}}
if obj not in self.entities:
self.entities[obj] = {"type": ">>未知", ">>attrs": {}}
return triples
def build(self):
"""执行完整的构建流程"""
print(f"🏗️ 开始构建【{self.domain}】知识图谱...")
print(f" 导入条目数: {len(self.texts)}")
for entry in self.texts:
infobox = self.extract_infobox(entry["text"])
if infobox:
self.entities[entry["title"]]["attrs"].update(infobox)
for entry in self.texts:
new_triples = self.extract_relations(entry["text"], entry["title"])
for t in new_triples:
self.triples.add(t)
print(f" 实体数: {len(self.entities)}")
print(f" 三元组数: {len(self.triples)}")
def query_entity(self, name):
"""查询实体信息"""
entity = self.entities.get(name)
if not entity:
return None
related = [(h, r, t) for h, r, t in self.triples if h == name or t == name]
return {"entity": entity, "related_triples": related}
def export_json(self):
"""导出为JSON"""
return json.dumps({
"domain": self.domain,
"entities": self.entities,
"triples": list(self.triples)
}, ensure_ascii=False, indent=2)
builder = WikiKnowledgeGraphBuilder("中国现代文学")
builder.ingest_text("鲁迅", """
鲁迅,原名周树人,1881年9月25日出生于浙江省绍兴。
职业:作家、思想家。毕业于南京矿路学堂。
鲁迅创作了呐喊,鲁迅创作了彷徨。鲁迅与许寿裳是好友。
""")
builder.ingest_text("老舍", """
老舍,原名舒庆春,1899年2月3日出生于北京。
职业:作家、剧作家。毕业于北京师范大学。
老舍创作了骆驼祥子,老舍创作了茶馆。
""")
builder.ingest_text("徐志摩", """
徐志摩,1897年1月15日出生于浙江省海宁。
职业:诗人。毕业于北京大学。
徐志摩创作了再别康桥。
""")
builder.build()
print("
=== 查询:鲁迅 ===")
result = builder.query_entity("鲁迅")
if result:
print(f" 类型: {result['entity']['type']}")
print(f" 属性: {result['entity']['attrs']}")
print(" 关系:")
for h, r, t in result["related_triples"]:
if h == "鲁迅":
print(f" → {r} → {t}")
else:
print(f" ← {r} ← {h}")
print(f"
=== 导出JSON(前300字符) ===")
print(builder.export_json()[:300])
🏗️ 开始构建【中国现代文学】知识图谱...
导入条目数: 3
实体数: 10
三元组数: 7
=== 查询:鲁迅 ===
类型: 条目
属性: {'出生日期': '1881年9月25日', '出生地': '浙江省绍兴', '职业': '作家、思想家', '毕业院校': '南京矿路学堂'}
关系:
→ 出生地 → 浙江省绍兴
→ 创作 → 呐喊
→ 创作 → 彷徨
→ 毕业院校 → 南京矿路学堂
→ 好友 → 许寿裳
=== 导出JSON(前300字符) ===
{
"domain": "中国现代文学",
"entities": {
"鲁迅": {"type": "条目", "attrs": {"出生日期": "1881年9月25日", ...}}
},
"triples": [...]
}
📝 实战练习
练习1:扩展到更多领域
选择历史、地理或科学领域,构建该领域的百科知识图谱。
练习2:实现Web界面
用Flask/FastAPI构建一个简单的Web界面,支持实体搜索和图谱可视化。
练习3:评估知识图谱质量
人工标注50个三元组,计算抽取的P/R/F1。
🌐 百科KG的工程实践
大规模构建的最佳实践
- 分布式爬取:使用Scrapy+Kafka实现分布式百科爬取,日处理百万条目
- 增量更新:监控百科变更页面,只处理有更新的条目
- 质量控制:人工审核+自动验证双保险,新三元组需通过一致性检查
- 版本管理:保留知识图谱的历史版本,支持回溯
- 开放API:提供REST API和SPARQL端点供外部查询
💡 规模参考:中文维基百科约120万条目,DBpedia从中提取了约500万实体和2亿三元组。Wikidata则超过1亿实体。构建大规模百科KG通常需要集群环境(10+节点)和数周时间。
📚
🏆 第21课成就解锁
百科KG构建者
📚 数据采集
🔧 信息抽取
💾 图谱存储
🔍 实体查询