㊗️本期内容已收录至专栏《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 爬虫,顺着官方文档的目录树,把每一个子命令、参数和珍贵的示例代码"抠"出来,组装成结构化数据。
👉 读完这篇你将获得:
- 掌握针对深度嵌套的"文档目录树(Document Tree)"的遍历抓取技巧。
- 学会从
<pre><code>标签和参数描述列表中精准提取结构化文本。 - 最终产出:一份可以直接用于开发终端自动补全插件的
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+
-
项目结构:
textcli_scraper/ ├── spider_core.py # 爬虫核心逻辑 └── cli_commands_reference.csv # 最终结果 -
依赖安装:
bashpip 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)🛠️
-
抓下来的代码块里全是 HTML 标签(如
<span>)?- 原因 :文档站点使用了语法高亮库(如 Prism.js),它会把
echo "hello"渲染成<span class="keyword">echo</span> "hello"。 - 解法 :不用怕!BeautifulSoup 的
get_text(strip=True)机制会自动剥离内部所有标签,只返回纯净的文本字符串。
- 原因 :文档站点使用了语法高亮库(如 Prism.js),它会把
-
Parameters 参数抓成了空或乱七八糟的段落?
- 诊断 :各个文档对参数的书写极其不标准。有的放表格里,有的放
<ul>里,还有的甚至直接用加粗标签<strong>--flag</strong>混在段落中。 - 破局方案 :在
parse_command_detail中增加正则表达式回退机制!如果找不到表格,就用re.findall(r'(--[a-zA-Z0-9\-]+)[^<]+', html)直接在全文中暴力搜索所有的 flag 名称!
- 诊断 :各个文档对参数的书写极其不标准。有的放表格里,有的放
-
HTML 符号被转义了(比如代码块里出现了
<和>)?- 解法 :在保存数据前,引入 Python 的
html模块,使用html.unescape(text)把它们还原成<和>。
- 解法 :在保存数据前,引入 Python 的
1️⃣1️⃣ 进阶优化(Optional)🚀
当你的数据都整理好之后,不妨再往前迈一步!
-
JSON 结构化 :很多开发者更喜欢用 JSON 格式的数据。你可以修改存储逻辑,把
Parameters解析为真实的键值对字典并导出为.json,方便其他程序调用。 -
数据可视化 (Data Visualization) :
你想知道哪个子命令最复杂吗?我们可以用
matplotlib画一个参数数量排行榜!
(💡 注意:所有的图表元素都遵循英文标准)pythonimport 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,下一步能做什么?
你完全可以用这批数据写一个基于 argparse 或 click 库的终端 Mock 工具,甚至把它喂给 LangChain,搭建一个能在命令行里直接通过自然语言问答的 "CLI 专家助手"!
在实际操作中,如果你面对目标文档的参数列表实在解析不出规律,千万别一个人死磕,随时把那段 HTML 源码截取发给我,我帮你现场手写专属的解析器!👨💻🔥
🌟 文末
好啦~以上就是本期的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
✅ 专栏持续更新中|建议收藏 + 订阅
墙裂推荐订阅专栏 👉 《Python爬虫实战》,本专栏秉承着以"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一期内容都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴 :强烈建议先订阅专栏 《Python爬虫实战》,再按目录大纲顺序学习,效率十倍上升~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战?
评论区留言告诉我你的需求,我会优先安排实现(更新)哒~
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
✅ 免责声明
本文爬虫思路、相关技术和代码仅用于学习参考,对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。
使用或者参考本项目即表示您已阅读并同意以下条款:
- 合法使用: 不得将本项目用于任何违法、违规或侵犯他人权益的行为,包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。
- 风险自负: 任何因使用本项目而产生的法律责任、技术风险或经济损失,由使用者自行承担,项目作者不承担任何形式的责任。
- 禁止滥用: 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。
- 使用或者参考本项目即视为同意上述条款,即 "谁使用,谁负责" 。如不同意,请立即停止使用并删除本项目。!!!
