Python爬虫实战:解构 CLI 工具命令参考文档树!

㊗️本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~

㊙️本期爬虫难度指数:⭐⭐⭐ (进阶)

🉐福利: 一次订阅后,专栏内的所有文章可永久免费看,持续更新中,保底1000+(篇)硬核实战内容。

全文目录:

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

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

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

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

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

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

💕订阅后更新会优先推送,按目录学习更高效💯~

0️⃣ 前言(Preface)

各位极客好!今天我们要挑战的是技术文档中最硬核的部分------CLI(命令行界面)命令参考页 。我们将编写一个 Python 爬虫,顺着官方文档的目录树,把每一个子命令、参数和珍贵的示例代码"抠"出来,组装成结构化数据。

👉 读完这篇你将获得:

  1. 掌握针对深度嵌套的"文档目录树(Document Tree)"的遍历抓取技巧。
  2. 学会从 <pre><code> 标签和参数描述列表中精准提取结构化文本。
  3. 最终产出:一份可以直接用于开发终端自动补全插件的 cli_commands_reference.csv 数据库。

1️⃣ 摘要(Abstract)

本文以通用型 CLI 官方文档为目标站点,采用 requests + BeautifulSoup 的静态抓取方案。爬虫通过先解析左侧导航树获取所有层级的子命令链接,随后进入详情页,利用正则与 CSS 选择器双管齐下,提取命令描述、参数表(Flags)及示例代码,最终利用 Pandas 完成数据的清洗与落盘。

👉 核心收获:

  • 实战:处理带有连字符和尖括号的终端代码块。
  • 难点:如何从不规则的 HTML 列表中提取 --flag 及其说明。
  • 成果:建立高质量的本地化开发辅助语料库。

2️⃣ 背景与需求(Why)

  • 为什么要爬:

    现代 CLI 工具(如 aws-cli, kubectl, git)拥有成百上千个子命令和参数组合。将这些数据结构化,不仅便于本地极速检索,更是开发 Terminal Auto-completion(终端自动补全) 工具的必备前置步骤。

  • 目标站点结构: 某知名开源 CLI 官网(模拟:左侧/顶部有层级目录,点击进入具体命令的语法说明页)。

  • 目标字段清单:

    • Command (主命令 - 如 docker)
    • Sub_Command (子命令 - 如 run, image ls)
    • Description (说明/摘要)
    • Parameters (参数选项 - 组合为 JSON/字符串,如 -d, --detach: Run container in background)
    • Examples (示例代码块 - 原汁原味的 shell 代码)

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

技术向善,尊重开源社区的基础设施。

  • Robots.txt: 绝大多数开源文档都是完全公开并欢迎抓取的,但仍需遵守 robots.txt 中对抓取频率的建议。
  • 频率控制: 文档站通常没有复杂的反爬,但为了不给服务器增加负担(很多是托管在 GitHub Pages 或 Netlify 上),请务必在每次请求间加入 time.sleep(1)
  • 数据用途: 采集到的内容仅限个人学习、内部知识库建设或开源工具开发,保持技术中立。

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

  • 技术选型:

    大多数官方 CLI 文档由 Markdown 生成,是标准的静态 HTML。

    最强组合: requests (发请求) + BeautifulSoup (解构 DOM) + re (清洗多余空格)。

  • 整体流程:

    text 复制代码
    [请求命令文档树入口] ➔ 递归提取所有【子命令 URL】
         ↳ 遍历每个 URL 进入【详情页】 
              ↳ 提取命令概述 (Description)
              ↳ 提取参数表格/列表 (Options/Flags)
              ↳ 提取代码块 (Examples) ➔ 清洗存储

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

  • Python 版本: 3.9+

  • 项目结构:

    text 复制代码
    cli_scraper/
    ├── spider_core.py                # 爬虫核心逻辑
    └── cli_commands_reference.csv    # 最终结果
  • 依赖安装:

    bash 复制代码
    pip install requests beautifulsoup4 pandas

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

我们封装一个带会话保持的请求器。技术文档的链接经常是相对路径,所以需要格外注意 URL 的拼接。

python 复制代码
import requests
import time
from urllib.parse import urljoin

def create_session():
    session = requests.Session()
    session.headers.update({
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    })
    return session

def fetch_html(session, url, retries=3):
    """带重试机制的页面获取器"""
    for attempt in range(retries):
        try:
            resp = session.get(url, timeout=10)
            if resp.status_code == 200:
                resp.encoding = 'utf-8'
                return resp.text
            elif resp.status_code == 404:
                print(f"⚠️ 404 页面丢失: {url}")
                return None
        except Exception as e:
            print(f"❌ 请求失败 ({attempt+1}/{retries}): {e}")
            time.sleep(2)
    return None

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

这一部分是重中之重!CLI 详情页里,"参数"往往以表格(<table>)或定义列表(<dl><dt><dd>)的形式存在,而"示例"通常在 <pre><code> 中。

python 复制代码
from bs4 import BeautifulSoup
import re

def parse_command_tree(html, base_url):
    """
    解析左侧的导航树,获取所有子命令的链接
    """
    soup = BeautifulSoup(html, 'html.parser')
    cmd_links = []
    
    # 假设侧边栏有一个特定的 class,例如 'sidebar-menu' 或 'nav-links'
    nav_area = soup.find('nav', class_=re.compile(r'sidebar|nav|menu', re.I))
    if not nav_area:
        return []
        
    for a_tag in nav_area.find_all('a'):
        href = a_tag.get('href')
        text = a_tag.get_text(strip=True)
        # 简单过滤:剔除不是以字母开头的无效链接
        if href and not href.startswith('#') and re.match(r'^[a-zA-Z]', text):
            full_url = urljoin(base_url, href)
            cmd_links.append({"Sub_Command": text, "URL": full_url})
            
    return cmd_links

def parse_command_detail(html, url, sub_command):
    """
    深度解析命令详情页
    """
    soup = BeautifulSoup(html, 'html.parser')
    data = {
        'Command': "mycli",  # 这里填入你的主命令名,如 docker/kubectl
        'Sub_Command': sub_command,
        'Source_URL': url
    }
    
    try:
        # 1. 提取说明 (Description) - 通常在 H1 后面的第一个 P 标签
        h1_tag = soup.find('h1')
        desc_p = h1_tag.find_next_sibling('p') if h1_tag else None
        data['Description'] = desc_p.get_text(strip=True) if desc_p else "暂无说明"
        
        # 2. 提取参数 (Options / Flags)
        # 参数通常包含在一个标题为 "Options" 或 "Flags" 的后续元素中
        flags_section = soup.find(lambda tag: tag.name in ['h2', 'h3'] and 'Options' in tag.text)
        params_list = []
        
        if flags_section:
            # 找它紧跟着的列表或表格
            next_node = flags_section.find_next_sibling(['ul', 'table', 'dl'])
            if next_node:
                if next_node.name == 'ul':
                    for li in next_node.find_all('li'):
                        params_list.append(li.get_text(separator=': ', strip=True))
                elif next_node.name == 'table':
                    for tr in next_node.find_all('tr')[1:]: # 跳过表头
                        tds = tr.find_all('td')
                        if len(tds) >= 2:
                            flag = tds[0].get_text(strip=True)
                            desc = tds[1].get_text(strip=True)
                            params_list.append(f"{flag}: {desc}")
                            
        data['Parameters'] = " | ".join(params_list) if params_list else "None"
        
        # 3. 提取代码块示例 (Examples)
        # 查找带有 'language-bash' 或 'language-sh' 的 pre/code 标签
        examples = []
        for code_block in soup.find_all('code', class_=re.compile(r'bash|sh|cli')):
            # 如果 code 被 pre 包裹,我们需要提取整个文本
            pre_tag = code_block.find_parent('pre')
            text_block = pre_tag.get_text() if pre_tag else code_block.get_text()
            # 简单清洗:去除多余空白,保留换行
            clean_code = "\n".join([line for line in text_block.split('\n') if line.strip()])
            examples.append(clean_code)
            
        data['Examples'] = "\n\n".join(examples) if examples else "No examples provided"

    except Exception as e:
        print(f"⚠️ 解析详情页异常 {url}: {e}")
        return None
        
    return data

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

由于我们的代码块里包含了大量换行符和特殊符号(如 >, |, -),使用 CSV 存储时必须非常小心。pandas 会自动帮我们处理包含换行符的单元格(通过加上双引号)。

python 复制代码
import pandas as pd
import os

def save_to_csv(data_list, filename="cli_commands_reference.csv"):
    if not data_list:
        return
        
    df = pd.DataFrame(data_list)
    # 调整列的顺序
    columns_order = ['Command', 'Sub_Command', 'Description', 'Parameters', 'Examples', 'Source_URL']
    df = df[columns_order]
    
    file_exists = os.path.exists(filename)
    df.to_csv(
        filename, 
        mode='a', 
        index=False, 
        header=not file_exists, 
        encoding='utf-8-sig' # 防止乱码
    )
    print(f"💾 成功追加 {len(data_list)} 个命令数据到 CSV。")

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

将爬虫组装起来,跑起来吧!

python 复制代码
# spider_core.py的主入口

def main():
    # 模拟一个官方文档的主页 URL
    BASE_URL = "https://example-cli.com/docs/reference/"
    print(f"🚀 CLI 命令行文档爬虫启动!目标:{BASE_URL}")
    
    session = create_session()
    
    # 1. 获取目录树
    print("🌲 正在解析命令文档树...")
    index_html = fetch_html(session, BASE_URL)
    if not index_html:
        return
        
    cmd_links = parse_command_tree(index_html, BASE_URL)
    # 去重
    unique_links = {item['URL']: item['Sub_Command'] for item in cmd_links}
    print(f"📌 成功发现 {len(unique_links)} 个子命令文档。")
    
    # 2. 遍历抓取详情页
    batch_data = []
    count = 1
    
    for url, sub_cmd in unique_links.items():
        print(f"\n⏳ [{count}/{len(unique_links)}] 正在提取命令: {sub_cmd}")
        detail_html = fetch_html(session, url)
        
        if detail_html:
            parsed_data = parse_command_detail(detail_html, url, sub_cmd)
            if parsed_data:
                batch_data.append(parsed_data)
                print(f"   ✅ 提取成功,发现 {len(parsed_data['Parameters'].split('|'))} 个参数。")
                
        # 🟢 礼貌延时
        time.sleep(1.5)
        count += 1
        
        # 每 10 条落盘一次,防止意外崩溃
        if len(batch_data) >= 10:
            save_to_csv(batch_data)
            batch_data = []
            
    # 保存剩余数据
    if batch_data:
        save_to_csv(batch_data)
        
    print("\n🎉 全部命令解析完毕!请查看 cli_commands_reference.csv")

if __name__ == "__main__":
    # main() # 运行前请替换真实的 BASE_URL
    pass

📊 示例结果展示 (cli_commands_reference.csv):

Command Sub_Command Description Parameters Examples Source_URL
mycli run 创建并运行一个新的容器 `-d, --detach: Run container in background and print container ID -p, --publish list: Publish a container's port(s) to the host` mycli run -d -p 80:80 nginx mycli run -it ubuntu bash
mycli logs 获取容器的日志输出 `-f, --follow: Follow log output --tail string: Number of lines to show from the end of the logs` mycli logs -f my_container mycli logs --tail 50 my_container

🔟 常见问题与排错(Troubleshooting)🛠️

  1. 抓下来的代码块里全是 HTML 标签(如 <span>)?

    • 原因 :文档站点使用了语法高亮库(如 Prism.js),它会把 echo "hello" 渲染成 <span class="keyword">echo</span> "hello"
    • 解法 :不用怕!BeautifulSoup 的 get_text(strip=True) 机制会自动剥离内部所有标签,只返回纯净的文本字符串。
  2. Parameters 参数抓成了空或乱七八糟的段落?

    • 诊断 :各个文档对参数的书写极其不标准。有的放表格里,有的放 <ul> 里,还有的甚至直接用加粗标签 <strong>--flag</strong> 混在段落中。
    • 破局方案 :在 parse_command_detail 中增加正则表达式回退机制!如果找不到表格,就用 re.findall(r'(--[a-zA-Z0-9\-]+)[^<]+', html) 直接在全文中暴力搜索所有的 flag 名称!
  3. HTML 符号被转义了(比如代码块里出现了 &lt;&gt;)?

    • 解法 :在保存数据前,引入 Python 的 html 模块,使用 html.unescape(text) 把它们还原成 <>

1️⃣1️⃣ 进阶优化(Optional)🚀

当你的数据都整理好之后,不妨再往前迈一步!

  • JSON 结构化 :很多开发者更喜欢用 JSON 格式的数据。你可以修改存储逻辑,把 Parameters 解析为真实的键值对字典并导出为 .json,方便其他程序调用。

  • 数据可视化 (Data Visualization)

    你想知道哪个子命令最复杂吗?我们可以用 matplotlib 画一个参数数量排行榜!
    (💡 注意:所有的图表元素都遵循英文标准)

    python 复制代码
    import matplotlib.pyplot as plt
    import pandas as pd
    
    def plot_command_complexity():
        df = pd.read_csv("cli_commands_reference.csv")
        # 计算参数数量
        df['Param_Count'] = df['Parameters'].apply(lambda x: len(str(x).split('|')) if x != 'None' else 0)
        top_cmds = df.nlargest(10, 'Param_Count')
        
        plt.figure(figsize=(10, 6))
        plt.bar(top_cmds['Sub_Command'], top_cmds['Param_Count'], color='#007acc')
        plt.title("Top 10 Most Complex Sub-commands by Number of Flags")
        plt.xlabel("Sub-command")
        plt.ylabel("Number of Parameters/Flags")
        plt.xticks(rotation=45)
        plt.tight_layout()
        plt.show()

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

干得漂亮!👏 我们今天不仅写完了一个针对结构化文档的爬虫,更是把分散在网页深处的知识提纯成了高效的生产力工具。

现在你拥有了这份 CSV,下一步能做什么?

你完全可以用这批数据写一个基于 argparseclick 库的终端 Mock 工具,甚至把它喂给 LangChain,搭建一个能在命令行里直接通过自然语言问答的 "CLI 专家助手"!

在实际操作中,如果你面对目标文档的参数列表实在解析不出规律,千万别一个人死磕,随时把那段 HTML 源码截取发给我,我帮你现场手写专属的解析器!👨‍💻🔥

🌟 文末

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

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

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

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

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

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

✅ 互动征集

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

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


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


✅ 免责声明

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

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

  • 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
  • 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
  • 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
  • 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
相关推荐
人工智能AI技术2 小时前
GitHub Trending榜首:Python Agentic RAG企业级落地指南
人工智能·python
进击的雷神2 小时前
突破增量抓取困境:基于数据库状态判断的高效新闻爬虫设计
数据库·爬虫·spiderflow
进击的雷神2 小时前
多展会框架复用、Next.js结构统一、北非网络优化、参数差异化配置——阿尔及利亚展爬虫四大技术难关攻克纪实
javascript·网络·爬虫·python
ZTLJQ2 小时前
网络通信的基石:Python HTTP请求库完全解析
开发语言·python·http
华科大胡子2 小时前
爬虫对抗:ZLibrary反爬机制实战分析
python
进击的小头2 小时前
第17篇:卡尔曼滤波器之概率论初步
python·算法·概率论
是梦终空2 小时前
计算机毕业设计269—基于python+深度学习+YOLOV8的交通标志识别系统(源代码+数据库+报告)
python·深度学习·opencv·毕业设计·torch·课程设计·pyqt5
crossoverJie2 小时前
OpenAI 收购 Python 工具链 uv 和 Ruff
开发语言·人工智能·python·uv
2401_831824962 小时前
RESTful API设计最佳实践(Python版)
jvm·数据库·python