欢迎光临
我们一直在努力

Python爬虫实战:构建私有知识库 - 一键抓取开源数据库文档并 JSON 化树形目录!

㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~ ㊙️本期爬虫难度指数:⭐ (入门级) 🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:

      • 🌟 开篇语
      • 0️⃣ 前言(Preface)
      • 1️⃣ 摘要(Abstract)
      • 2️⃣ 背景与需求(Why)
      • 3️⃣ 合规与注意事项(必写)
      • 4️⃣ 技术选型与整体流程(What/How)
      • 5️⃣ 环境准备与依赖安装(可复现)
      • 6️⃣ 核心实现:请求层(Fetcher)
      • 7️⃣ 核心实现:解析层(Parser)
      • 8️⃣ 数据存储与导出(Storage)
      • 9️⃣ 运行方式与结果展示(必写)
      • 🔟 常见问题与排错(排雷指南)
      • 1️⃣1️⃣ 进阶优化(打造企业级 RAG 语料)
      • 1️⃣2️⃣ 总结与延伸阅读
      • 🌟 文末
        • ✅ 专栏持续更新中|建议收藏 + 订阅
        • ✅ 互动征集
        • ✅ 免责声明

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。 运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO 欢迎大家常来逛逛,一起学习,一起进步~🌟

  我长期专注 Python 爬虫工程化实战,主理专栏 《Python爬虫实战》:从采集策略到反爬对抗,从数据清洗到分布式调度,持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”,让数据价值真正做到——抓得到、洗得净、用得上。

  📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间:如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅专栏👉《Python爬虫实战》👈,一次订阅后,专栏内的所有文章可永久免费阅读,持续更新中。    💕订阅后更新会优先推送,按目录学习更高效💯~

0️⃣ 前言(Preface)

👋 大家好!今天我们要挑战一个极其有趣且实用的项目:使用 Python 的 requests 和 BeautifulSoup,深入某个开源数据库(如类 Redis/PostgreSQL 的在线文档)的侧边栏导航,把它的“层级目录树”完整扒下来,并自动提取每个章节的摘要,最终产出一份完美的结构化 JSON 文件。

🔥 读完这篇你能获得:

  • 掌握**递归解析 HTML 嵌套列表(<ul>/<li>)**的高级技巧,轻松构建树形数据结构。
  • 学会如何处理父子页面的关系,精准提取跨页面的连贯信息。
  • 获得一份可以直接喂给 LLM(大语言模型)作为 RAG 知识库索引的高质量数据集。
  • 1️⃣ 摘要(Abstract)

    本文将演示如何针对由 Sphinx/MkDocs/Docusaurus 等框架生成的静态数据库文档站点,开发一个层级目录爬虫。我们将优先抓取全局侧边栏(Sidebar)获取章节的名称、层级与链接,随后并发访问各详情页提取首段简介,最终输出为标准的 db_docs_tree.json 文件。

    2️⃣ 背景与需求(Why)

    🤔 为什么要爬文档树? 在学习复杂的开源数据库时,官方文档往往庞大且碎片化。通过爬虫将目录“树状 JSON 化”,不仅可以生成离线版的高清知识导图,更是目前构建 AI 智能客服(让 AI 知道哪个知识点在哪个章节)的基石。

    🎯 目标字段清单:

    • Chapter_Name (章节名):如 “1.2 安装指南”
    • Path (路径):用于记录它的父级关系,如 “入门 / 安装”
    • Level (层级):树的深度(1级、2级、3级目录…)
    • Link (链接):该章节的绝对 URL
    • Summary (简介):进入该链接后,正文部分的第一段有效文本 <p>

    3️⃣ 合规与注意事项(必写)

    在开源世界遨游,规矩不能忘:👮‍♂️

    • 尊重 robots.txt:官方文档通常是完全开放索引的,但礼貌性检查 robots.txt 依然是专业爬虫工程师的素养。
    • 温和的并发(频率控制):文档站点多由开源社区用爱发电(有些部署在 GitHub Pages 或 ReadTheDocs),请务必加入延时(time.sleep),不要用高并发把人家服务器打宕机了。
    • 遵守开源协议:抓取的数据仅限个人学习或内部知识库使用,若要公开展示,请遵循目标站点的 CC-BY-SA 或 MIT 等开源授权协议(中立表达,合法合规)。

    4️⃣ 技术选型与整体流程(What/How)

    🛠️ 技术选型:静态解析为主 绝大多数文档网站(如 ReadTheDocs)首次加载时侧边栏 HTML 已经存在于源码中。因此,我们不需要沉重的无头浏览器。 选型方案:Requests (请求) + BeautifulSoup (DOM解析) + 递归算法 (组装树)。

    🔄 整体执行流程:

    [Fetch] 请求文档主页 HTML
    ⬇️
    [Parse] 定位 Sidebar(侧边栏),递归解析嵌套的 <ul> 和 <li>
    ⬇️
    [Build] 构建包含层级 (Level) 和路径 (Path) 的扁平字典列表
    ⬇️
    [Fetch & Parse] 遍历列表,访问每个 Link,提取正文首段作为 Summary
    ⬇️
    [Storage] 将丰富后的数据持久化为嵌套或扁平的 JSON 文件

    5️⃣ 环境准备与依赖安装(可复现)

    推荐使用 Python 3.9+,我们需要非常纯粹的基础库组合。

    📦 依赖安装:

    pip install requests beautifulsoup4

    📂 项目结构:

    db_docs_crawler/
    ├── spider.py # 核心代码
    ├── utils.py # 辅助工具(如清洗 HTML 标签)
    └── output/ # 存放最终导出的树形文件
    └── db_docs_tree.json

    6️⃣ 核心实现:请求层(Fetcher)

    文档站点通常没有严格的反爬,但由于可能会一次性请求几百个页面,**网络稳定性(重试机制)**至关重要。

    import requests
    from requests.adapters import HTTPAdapter
    from urllib3.util.retry import Retry

    def get_session():
    """配置具有重试机制的会话"""
    session = requests.Session()
    session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
    })

    # 遇到 429(限流) 和 5xx 错误时,自动进行指数退避重试
    retries = Retry(total=3, backoff_factor=1.5, status_forcelist=[429, 500, 502, 503, 504])
    session.mount('http://', HTTPAdapter(max_retries=retries))
    session.mount('https://', HTTPAdapter(max_retries=retries))
    return session

    client = get_session()

    def fetch_page(url):
    try:
    res = client.get(url, timeout=10)
    res.raise_for_status()
    res.encoding = 'utf-8'
    return res.text
    except Exception as e:
    print(f"❌ 请求失败 [{url}]: {e}")
    return None

    7️⃣ 核心实现:解析层(Parser)

    这是本项目的灵魂所在!文档的侧边栏通常是多重嵌套的 <ul> 列表,我们需要写一个递归函数来遍历它。

    (注:以下代码基于典型的 Sphinx/MkDocs 侧边栏结构编写,实战需视情况微调 DOM 选择器)

    from bs4 import BeautifulSoup
    from urllib.parse import urljoin

    BASE_DOC_URL = "https://docs.mock-database.com/en/latest/"

    def parse_navigation_tree(html, base_url):
    """解析左侧导航树,返回带有层级关系的列表"""
    soup = BeautifulSoup(html, 'html.parser')

    # 假设侧边栏被包裹在 class="wy-menu-vertical" 或 <nav class="sidebar"> 中
    nav_container = soup.select_one('.sidebar-nav') or soup.select_one('nav')
    if not nav_container:
    return []

    chapters = []

    def traverse_ul(ul_element, current_level=1, parent_path=""):
    """递归遍历 UL 标签"""
    if not ul_element: return

    # 遍历当前 UL 下的所有 LI
    for li in ul_element.find_all('li', recursive=False): # 只找第一层儿子,防止重复
    a_tag = li.find('a', recursive=False)
    if not a_tag: continue

    chapter_name = a_tag.text.strip()
    # 拼接相对路径为绝对 URL
    link = urljoin(base_url, a_tag.get('href', ''))

    # 构建当前的完整路径 (如 "Home / Setup / Installation")
    current_path = f"{parent_path} / {chapter_name}" if parent_path else chapter_name

    # 记录当前节点
    # 注意:这里的 link 可能带有锚点 #,比如 page.html#section,我们保留它
    chapters.append({
    "Chapter_Name": chapter_name,
    "Path": current_path,
    "Level": current_level,
    "Link": link,
    "Summary": "" # 占位,稍后填充
    })

    # 如果当前 LI 内部还有 UL,说明有子章节,递归深入!
    nested_ul = li.find('ul', recursive=False)
    if nested_ul:
    traverse_ul(nested_ul, current_level + 1, current_path)

    # 寻找第一层入口 UL 开始递归
    root_ul = nav_container.find('ul')
    traverse_ul(root_ul)

    return chapters

    def extract_summary(html):
    """提取正文页面的第一段作为简介"""
    if not html: return "无法获取页面"
    soup = BeautifulSoup(html, 'html.parser')

    # 典型文档的正文区包裹在 class="document" 或 <main> 中
    content_area = soup.select_one('.document') or soup.select_one('main')
    if content_area:
    # 找到第一个有效的非空段落
    for p in content_area.find_all('p'):
    text = p.text.strip()
    if len(text) > 20: # 过滤掉极短的无意义文本
    return text[:200] + "…" # 截取前 200 字
    return "无有效简介"

    8️⃣ 数据存储与导出(Storage)

    我们将爬取到的扁平化但带有层级(Level)标识的数据结构,存储为 JSON 文件。这比 CSV 更适合表达树形关系。

    import json
    import os

    def save_to_json(data, filename="db_docs_tree.json"):
    if not data:
    print("📭 数据为空,取消保存!")
    return

    os.makedirs("output", exist_ok=True)
    file_path = os.path.join("output", filename)

    # 简单的去重(有些文档锚点不同但指向同一页,按需决定是否去重)
    unique_data = {item['Path']: item for item in data}.values()

    with open(file_path, 'w', encoding='utf-8') as f:
    # ensure_ascii=False 保证中文正常显示,indent=4 美化输出
    json.dump(list(unique_data), f, ensure_ascii=False, indent=4)

    print(f"💾 文档目录树已成功生成并保存至: {file_path}")

    9️⃣ 运行方式与结果展示(必写)

    这是主程序,它将“拉取树”和“拉取摘要”两大步骤完美串联。

    import time

    def main():
    print("🚀 数据库文档章节爬虫启动!")

    # 步骤 1: 抓取主页获取全局导航树
    print(f"🌳 正在解析全局导航树: {BASE_DOC_URL}")
    home_html = fetch_page(BASE_DOC_URL)
    chapters = parse_navigation_tree(home_html, BASE_DOC_URL)
    print(f"✅ 导航解析完成,共发现 {len(chapters)} 个章节节点。")

    # 步骤 2: 遍历节点,抓取详情页摘要 (如果节点过多,实战中可按需截断)
    for i, chapter in enumerate(chapters):
    link = chapter["Link"]

    # 如果带有 '#' 锚点,说明页面内容跟父级是同一页,可避免重复请求
    if '#' in link.split('/')[–1]:
    chapter["Summary"] = "见父级页面"
    continue

    print(f" ├── [{i+1}/{len(chapters)}] 正在提取摘要: Level {chapter['Level']} – {chapter['Chapter_Name']}")
    page_html = fetch_page(link)
    chapter["Summary"] = extract_summary(page_html)

    time.sleep(1) # 频率控制,做个乖宝宝 ☕

    # 步骤 3: 存储
    save_to_json(chapters)

    if __name__ == "__main__":
    # main() # 取消注释即可运行
    pass

    📄 预期输出的 JSON 文件示例 (db_docs_tree.json):

    [
    {
    "Chapter_Name": "Getting Started",
    "Path": "Getting Started",
    "Level": 1,
    "Link": "https://docs.mock-database.com/en/latest/start.html",
    "Summary": "Welcome to the MockDB documentation. This guide will help you install and configure the database…"
    },
    {
    "Chapter_Name": "Installation",
    "Path": "Getting Started / Installation",
    "Level": 2,
    "Link": "https://docs.mock-database.com/en/latest/install.html",
    "Summary": "MockDB can be installed on Linux, macOS, and Windows. We highly recommend using Docker for the quickest setup…"
    }
    ]

    🔟 常见问题与排错(排雷指南)

    在实际爬取不同框架构建的文档时,你极有可能遇到这些坑:💣

  • 相对路径地狱:

    • 现象:获取到的 Link 是 ../setup/install.html,直接访问报 404。
    • 解法:永远使用 urllib.parse.urljoin(current_page_url, extracted_href)!这是处理相对路径的银弹。
  • 动态渲染的文档站(如 Docusaurus v2 某些模式):

    • 现象:网页源码里没有 <ul>,只有 <div id="__docusaurus"></div> 空壳。
    • 解法:此时不必死磕 HTML。按 F12 打开 Network,寻找类似 search-index.json 或 route.js 的接口,很多现代前端框架会在底层打包一份完整的站点 JSON,直接抓那个更香!
  • 循环嵌套导致递归死循环:

    • 现象:代码跑着跑着报 RecursionError 栈溢出。
    • 解法:警惕网页中存在的互相引用(比如顶部导航栏和侧边栏互相指向)。通过控制 recursive=False (只找子节点) 或维护一个 visited_urls 集合来避免死循环。
  • 1️⃣1️⃣ 进阶优化(打造企业级 RAG 语料)

    如果你的终极目的是做知识库,还可以这样秀操作:✨

    • Markdown 还原:利用 html2text 库,把 HTML 正文直接转换为 Markdown 格式存储,这是 LLM 最喜欢的阅读格式。
    • 并发加速 (Asyncio / ThreadPool):如果有上千个章节,可以用 concurrent.futures 开启 10 个线程并行抓取摘要,速度提升 10 倍以上。
    • 接入向量数据库:爬完之后直接调用 OpenAI 的 Embedding API,将 Summary 转成向量存入 Milvus 或 Pinecone,立刻拥有一个属于你的智能文档问答助手!

    1️⃣2️⃣ 总结与延伸阅读

    🎉 搞定!在这个项目中,我们不仅仅是在写爬虫,更是在解析结构、理解逻辑。利用递归算法将 DOM 树无损转换为 JSON 树,这种“降维打击”的能力在数据工程中非常核心。

    下一步,你可以尝试把抓取到的 db_docs_tree.json 导入到 Elasticsearch 中,自己手撸一个比官方更好用的文档全文搜索工具!

    🌟 文末

    好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

    小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~ 三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

    ✅ 专栏持续更新中|建议收藏 + 订阅

    墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以“入门 → 进阶 → 工程化 → 项目落地”的路线持续更新,争取让每一期内容都做到:

    ✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

    📣 想系统提升的小伙伴:强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

    ✅ 互动征集

    想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?

    评论区留言告诉我你的需求,我会优先安排实现(更新)哒~


    ⭐️ 若喜欢我,就请关注我叭~(更新不迷路) ⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力) ⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


    ✅ 免责声明

    本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。

    使用或者参考本项目即表示您已阅读并同意以下条款:

    • 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
    • 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
    • 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
    • 使用或者参考本项目即视为同意上述条款,即 “谁使用,谁负责” 。如不同意,请立即停止使用并删除本项目。!!!

    赞(0)
    未经允许不得转载:171主机测评 » Python爬虫实战:构建私有知识库 - 一键抓取开源数据库文档并 JSON 化树形目录!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址