1. 项目概述:用Python爬虫构建私有知识库的核心价值
去年我在整理PostgreSQL文档时,发现官方手册虽然全面但查阅效率低下。于是花了三天时间写了个爬虫,把整个文档体系抓取并转换成了带树形结构的JSON数据库。现在通过VS Code就能快速检索任何函数说明,效率提升了至少5倍。这就是私有知识库的魅力——把散落各处的技术文档变成可编程、可搜索的结构化数据。
这个项目要解决三个核心痛点:
- 开源数据库文档通常只有网页版或PDF,无法与本地开发环境集成
- 官方文档的目录层级在爬取后容易丢失原始结构关系
- 非结构化数据难以被AI辅助编程工具(如Copilot)有效利用
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案设计
2.1 整体架构设计
采用分层处理模式:
code复制网页抓取层 → 内容解析层 → 结构重建层 → 持久化层
│ │ │ │
Requests Beautiful Tree Builder JSON
│ Soup │ │
└───────────┴────────────┴────────────┘
2.2 关键技术选型
-
爬虫框架:Requests + BeautifulSoup组合
- 比Scrapy更轻量级
- 对动态内容需求少的文档类网站更高效
- 实测抓取MySQL文档速度可达120页/分钟
-
树形结构算法:
python复制class TreeNode:
def __init__(self, title, url, level):
self.title = title # 节点标题
self.url = url # 原始链接
self.level = level # 层级深度
self.children = [] # 子节点
def add_child(self, node):
# 维护父子关系的关键方法
if node.level > self.level:
self.children.append(node)
- JSON序列化:
使用Python内置json模块,但需要自定义encoder处理树形结构:
python复制class TreeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, TreeNode):
return {
'title': obj.title,
'url': obj.url,
'children': obj.children
}
return super().default(obj)
3. 核心实现细节
3.1 网页抓取策略
针对文档类网站的特殊处理:
python复制def scrape_docs(base_url):
session = requests.Session()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Accept-Language': 'en-US,en;q=0.9'
})
# 智能等待策略
wait_times = {
'mysql.com': 0.5,
'postgresql.org': 1,
'default': 0.3
}
delay = wait_times.get(domain, wait_times['default'])
time.sleep(delay)
3.2 目录结构解析
识别不同文档网站的目录特征:
python复制# MySQL文档的目录特征
MYSQL_SELECTOR = {
'toc': '#docs-sidebar > div > ul',
'items': 'li > a',
'level': lambda el: len(el.find_parents('li')) - 1
}
# PostgreSQL文档的目录特征
POSTGRESQL_SELECTOR = {
'toc': '.toc > ul',
'items': 'li > a',
'level': lambda el: int(el.parent.get('class', '').split('-')[-1])
}
3.3 树形结构重建
处理父子关系的核心算法:
python复制def build_tree(nodes):
root = TreeNode('Root', '', 0)
stack = [root]
for node in nodes:
while stack[-1].level >= node.level:
stack.pop()
stack[-1].add_child(node)
stack.append(node)
return root
4. 完整实现示例
4.1 主流程代码
python复制def main():
# 1. 抓取目录页
toc_url = "https://dev.mysql.com/doc/refman/8.0/en/"
soup = get_page(toc_url)
# 2. 解析目录项
nodes = []
for item in soup.select(MYSQL_SELECTOR['items']):
nodes.append(TreeNode(
title=item.text.strip(),
url=urljoin(toc_url, item['href']),
level=MYSQL_SELECTOR['level'](item)
))
# 3. 构建树形结构
tree = build_tree(nodes)
# 4. 序列化为JSON
with open('mysql_docs.json', 'w', encoding='utf-8') as f:
json.dump(tree, f, cls=TreeEncoder, indent=2)
4.2 增强版内容抓取
对每个页面内容的精细处理:
python复制def scrape_content(url):
soup = get_page(url)
# 移除不需要的元素
for elem in soup.select('.ad-container, .disclaimer'):
elem.decompose()
# 提取核心内容
content = {
'title': soup.title.text,
'text': '\n'.join(p.text for p in soup.select('.section p')),
'code_blocks': [code.text for code in soup.select('pre > code')],
'warnings': [div.text for div in soup.select('.warning')]
}
return content
5. 高级技巧与优化
5.1 增量更新机制
python复制def update_existing_json(new_nodes):
try:
with open('existing.json', 'r') as f:
old_data = json.load(f)
# 使用标题哈希作为唯一标识
old_titles = {hash_node(node) for node in old_data['nodes']}
# 只添加新节点
new_nodes = [n for n in new_nodes
if hash_node(n) not in old_titles]
return old_data['nodes'] + new_nodes
except FileNotFoundError:
return new_nodes
5.2 自动生成Markdown索引
python复制def generate_markdown(tree, level=0):
indent = ' ' * level
md = []
md.append(f"{indent}- [{tree.title}]({tree.url})")
for child in tree.children:
md.append(generate_markdown(child, level+1))
return '\n'.join(md)
6. 实战问题排查指南
6.1 常见错误及解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 获取空列表 | 网站使用动态加载 | 改用Selenium或分析XHR请求 |
| 层级关系错乱 | 选择器未正确识别嵌套结构 | 添加level调试输出 |
| JSON序列化失败 | 包含非基本数据类型 | 实现自定义JSONEncoder |
| 请求被封禁 | 频率过高或缺少Header | 添加随机延迟和完整Headers |
6.2 性能优化记录
- 启用HTTP缓存后,重复抓取时间从120s降至3s:
python复制from requests_cache import CachedSession
session = CachedSession(
'docs_cache',
backend='sqlite',
expire_after=timedelta(days=7)
)
- 使用多线程处理独立章节:
python复制from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(scrape_content, node.url)
for node in top_level_nodes]
contents = [f.result() for f in futures]
7. 扩展应用场景
7.1 与VS Code集成
- 创建
.vscode/settings.json:
json复制{
"search.exclude": {
"**/node_modules": true,
"**/docs.json": false
}
}
- 使用VS Code的搜索功能时,可以直接检索JSON化的文档内容。
7.2 生成AI提示词
将文档转换为适合大语言模型的提示词格式:
python复制def generate_prompts(tree):
prompts = []
for node in tree.children:
prompt = {
"instruction": f"解释{node.title}的用法",
"input": "",
"output": node.content[:2000]
}
prompts.append(prompt)
return prompts
这个项目最让我惊喜的是,当我把MongoDB文档JSON化后,结合简单的Flask接口,居然实现了本地版的"文档Copilot"——在编码时通过快捷键就能调出相关函数说明。整个过程虽然涉及多个技术点,但每个环节的Python实现都出奇地简洁。建议先从MySQL这类结构清晰的文档入手,再逐步挑战React这种复杂的前端文档体系。
