我给 claude-obsidian 写了一个自定义 Skill——从 GitHub Trending 自动 ingest,完整代码实战
我给 claude-obsidian 写了一个自定义 Skill
——从 GitHub Trending 自动 ingest,完整代码实战
作者:一只阿木木 我相信:在 AI 时代,每个普通人都该拥有一个自动生长的知识系统。
这篇文章要做一件真实的事
上一篇我们把 claude-obsidian 的架构拆完了。
这一篇,我们要造一个真实的东西:一个自定义 skill,让 claude-obsidian 每天自动爬取 GitHub Trending,把你感兴趣的项目自动 ingest 进知识库。
完成之后,你可以每天早上打开 Obsidian,看到 wiki 里新增了昨天 GitHub 上最值得关注的项目页面——自动分类、自动打标签、自动与已有知识建立连接。
这不是 demo,是我真正在用的 skill。完整代码直接拿走用。
在开始之前:你需要理解的一件事
上一篇讲了,SKILL.md 不是代码,是给 LLM 看的操作说明书。
所以我们要写的"自定义 skill",核心产物是一个 SKILL.md 文件。它告诉 Claude:
什么时候该触发这个 skill 具体步骤是什么 产出的页面格式是什么 和已有 wiki 系统怎么配合
辅助产物是几个 shell 脚本:负责实际的网络请求和数据处理。
第一步:规划 skill 的设计
在写一行代码之前,先把 skill 的设计想清楚。
我们要解决什么问题?
程序员每天都应该关注 GitHub Trending,但"看了就忘"是常态。这个 skill 要让每次 Trending 浏览留下来,并且和已有知识连接。
比如:今天 Trending 里出现了一个新的 Rust 异步运行时项目。如果你之前 ingest 过 Tokio 的源码分析,这个 skill 应该自动在新项目页面里建立 [[Tokio]] 的 wikilink。
输入是什么?
text
/github-trending [语言] [时间范围]
举例:
text
/github-trending rust daily
/github-trending python weekly
/github-trending # 不带参数,默认 all languages + daily
输出是什么?
对每个 Trending 项目,生成一个 wiki 实体页:
text
wiki/entities/tools/[项目名].md
同时更新:
wiki/entities/tools/index.md(工具总索引)wiki/log.md(操作记录)
第二步:目录结构
在 skills/ 目录下新建:
text
skills/
└── github-trending/
├── SKILL.md # 核心:LLM 操作说明书
└── references/
├── page-template.md # wiki 页面模板
└── category-map.md # 语言/话题分类映射
辅助脚本放在 scripts/:
text
scripts/
├── fetch-trending.sh # 爬取 GitHub Trending
└── parse-trending.py # 解析 HTML,输出结构化 JSON
第三步:写爬取脚本
scripts/fetch-trending.sh
Bash
#!/bin/bash
# fetch-trending.sh
# 用法:./scripts/fetch-trending.sh [language] [since]
# since: daily | weekly | monthly
# 输出:raw HTML 到 stdoutLANGUAGE=${1:-""}
SINCE=${2:-"daily"}
if [ -z "$LANGUAGE" ]; then
URL="https://github.com/trending?since=${SINCE}"
else
URL="https://github.com/trending/${LANGUAGE}?since=${SINCE}"
fi
# 带 User-Agent,避免被 GitHub 429
curl -s \
-H "User-Agent: Mozilla/5.0 (compatible; knowledge-bot/1.0)" \
-H "Accept: text/html" \
--max-time 30 \
"${URL}"
scripts/parse-trending.py
Python
#!/usr/bin/env python3
"""
parse-trending.py
从 stdin 读取 GitHub Trending HTML,输出结构化 JSON输出格式:
[
{
"name": "redis/redis",
"full_name": "redis/redis",
"description": "Redis is an in-memory database...",
"language": "C",
"stars": "67.8k",
"stars_today": "234",
"url": "https://github.com/redis/redis",
"topics": ["database", "cache", "nosql"]
},
...
]
"""
import sys
import json
import re
from html.parser import HTMLParser
class TrendingParser(HTMLParser):
def __init__(self):
super().__init__()
self.repos = []
self.current_repo = {}
self.in_repo_block = False
self.capture_next = None
self.depth = 0
def handle_starttag(self, tag, attrs):
attrs_dict = dict(attrs)
# 识别每个 repo 的容器 article
if tag == "article" and "Box-row" in attrs_dict.get("class", ""):
self.in_repo_block = True
self.current_repo = {}
# 识别 repo 名称链接
if self.in_repo_block and tag == "h2":
self.capture_next = "name_block"
if self.in_repo_block and tag == "a" and self.capture_next == "name_block":
href = attrs_dict.get("href", "")
if href.count("/") == 2: # /owner/repo 格式
self.current_repo["full_name"] = href.lstrip("/")
self.current_repo["url"] = f"https://github.com{href}"
self.capture_next = None
# 识别今日 stars
if self.in_repo_block and tag == "span":
cls = attrs_dict.get("class", "")
if "d-inline-block" in cls and "float-sm-right" in cls:
self.capture_next = "stars_today"
def handle_data(self, data):
data = data.strip()
if not data:
return
if self.capture_next == "stars_today":
# 提取数字
match = re.search(r"([\d,]+)\s+stars today", data)
if match:
self.current_repo["stars_today"] = match.group(1).replace(",", "")
self.capture_next = None
if self.capture_next == "description":
self.current_repo["description"] = data
self.capture_next = None
def handle_endtag(self, tag):
if tag == "article" and self.in_repo_block:
if self.current_repo.get("full_name"):
self.repos.append(self.current_repo)
self.in_repo_block = False
self.current_repo = {}
def parse_html(html_content):
parser = TrendingParser()
parser.feed(html_content)
return parser.repos
if __name__ == "__main__":
html = sys.stdin.read()
repos = parse_html(html)
print(json.dumps(repos, ensure_ascii=False, indent=2))
第四步:写 SKILL.md——这才是核心
插件根目录下的 CLAUDE.md 文件不会作为项目上下文加载。Plugins 通过 skills、agents 和 hooks 来贡献上下文,而不是 CLAUDE.md。要发布加载到 Claude 上下文的说明,请将它们放在 skill 里。
所以所有逻辑都在 SKILL.md 里写。
skills/github-trending/SKILL.md
Markdown
# GitHub Trending Ingest Skill## 触发条件
当用户输入以下任何内容时,激活此 skill:
- `/github-trending`
- `ingest github trending`
- `fetch trending repos`
- `what's trending on github`
- `github trending [language]`
## 你的角色
你是一个 GitHub 技术情报员。你的工作是:
1. 获取 GitHub Trending 数据
2. 把每个项目整理成结构化的 wiki 实体页
3. 与现有 wiki 里的技术概念建立连接
4. 不做评价,只做客观归档
## 执行流程
### Phase 0:解析用户意图
从用户输入中提取:
- `language`:编程语言过滤(默认:空,即所有语言)
- `since`:时间范围(daily | weekly | monthly,默认:daily)
示例解析:
- "github trending rust" → language=rust, since=daily
- "trending python weekly" → language=python, since=weekly
- "/github-trending" → language="", since=daily
### Phase 1:获取数据
执行以下命令获取 Trending 数据:
```bash
bash scripts/fetch-trending.sh {language} {since} | python3 scripts/parse-trending.py
如果命令失败(网络错误、429 等),报告错误并停止。不要猜测或伪造数据。
Phase 2:去重检查
对每个项目:
检查 wiki/entities/tools/{项目名}.md是否已存在如果存在,检查 updatedfrontmatter 是否是今天如果今天已更新,跳过(避免重复处理) 如果不存在或超过 1 天未更新,继续处理
Phase 3:交叉引用检查
对每个新项目,扫描以下 wiki 区域,找到可以建立连接的已有页面:
wiki/concepts/:相关的技术概念(算法、框架、设计模式)wiki/entities/tools/:相关的工具(同类型、竞品、依赖关系)wiki/entities/persons/:相关的人物(作者、贡献者、相关研究者)
记录下所有应该添加的 [[wikilinks]]。
Phase 4:写入 wiki 页面
为每个项目生成页面,严格遵循以下模板:
YAML
---
address: {从 ./scripts/allocate-address.sh 获取}
type: entity
subtype: tool
title: "{repo full_name}"
created: {today's date}
updated: {today's date}
confidence: medium
language: "{主要编程语言}"
stars: "{总 star 数}"
stars_today: "{今日新增 stars}"
trending_since: "{daily|weekly|monthly}"
trending_date: "{today's date}"
github_url: "https://github.com/{full_name}"
sources:
- github-trending-{date}
tags: [{语言 tag}, {话题 tags}]
---# {项目名}
> {项目的一句话描述,来自 GitHub description,不添加主观评价}
## 基本信息
| 字段 | 值 |
|---|---|
| **仓库** | [{full_name}]({github_url}) |
| **主语言** | {language} |
| **总 Stars** | {stars} |
| **今日新增** | {stars_today} ⭐ |
| **上榜时间** | {trending_date}({since}) |
## 它是什么
{基于 README 和 description,用 2-3 句客观描述这个项目的定位和核心功能。不加主观评价。}
## 技术特点
{如果能从 description 或 topics 推断出技术特点,在这里列出 3-5 个要点。每个要点一行。}
## 相关知识
{在这里列出与现有 wiki 页面的连接}
- 相关概念:{[[概念页1]], [[概念页2]]}
- 相关工具:{[[工具页1]], [[工具页2]]}
- 所属领域:{[[领域概念页]]}
## 来源
- [GitHub 仓库]({github_url})
- 上榜日期:{trending_date},来源:GitHub Trending({since})
Phase 5:更新索引和日志
更新 wiki/entities/tools/index.md:
在对应语言/类别下添加新项目的一行摘要:
text
- [[{项目名}]]:{一句话描述}(⭐{stars_today} today,{trending_date})
更新 wiki/log.md:
追加一条记录:
text
## {today's date} GitHub Trending Ingest
- 来源:GitHub Trending({language or "All"},{since})
- 处理项目数:{count}
- 新建页面:{list}
- 跳过(已存在):{list}
Phase 6:汇报结果
向用户展示本次 ingest 的摘要:
总共处理了多少个项目 新建了哪些页面 与已有 wiki 建立了哪些新连接 发现了哪些"值得关注"的项目(star 数今日增长异常高的)
注意事项
不要伪造数据:如果 GitHub 请求失败,直接报错,不要用训练数据里的项目来填充 不加主观评价:不要说"这个项目非常优秀"、"强烈推荐"等评价词 来源可追溯:每个页面必须在 sources frontmatter 里记录来源 单写原则:遵守 wiki-ingest 的单写规则,使用 allocate-address.sh 分配地址 不修改 .raw/:所有输出只写入 wiki/ 目录
参考文件
skills/github-trending/references/page-template.md:页面模板详细说明skills/github-trending/references/category-map.md:语言分类映射表
text
---## 第五步:写参考文件
### `references/category-map.md`
```markdown
# 语言和话题分类映射
## 编程语言 → wiki 标签
| GitHub 语言 | wiki tag | 关联概念页 |
|---|---|---|
| Rust | rust, systems-programming | [[Rust所有权模型]], [[内存安全]] |
| Python | python, scripting | [[Python生态]], [[动态类型]] |
| Go | golang, concurrent | [[Go并发模型]], [[goroutine]] |
| TypeScript | typescript, frontend | [[类型系统]], [[JavaScript生态]] |
| C | c, systems | [[内存管理]], [[指针]] |
| Java | java, jvm | [[JVM]], [[GC算法]] |
| Rust | rust | [[Cargo]], [[Rust异步]] |
## 话题关键词 → wiki 概念页
| 关键词 | 关联 wiki 页面 |
|---|---|
| database, db | [[数据库设计]], [[存储引擎]] |
| llm, ai, ml | [[大语言模型]], [[机器学习]] |
| cli, terminal | [[命令行工具设计]] |
| web, http, api | [[HTTP协议]], [[REST设计]] |
| async, concurrent | [[并发模型]] |
| compiler, parser | [[编译原理]] |
| kubernetes, k8s | [[容器编排]] |
| blockchain | [[分布式账本]] |
第六步:注册到 plugin.json
打开 .claude-plugin/plugin.json,在 skills 数组里添加:
JSON
{
"skills": [
"skills/wiki",
"skills/wiki-ingest",
"skills/wiki-query",
"skills/wiki-lint",
"skills/save",
"skills/autoresearch",
"skills/canvas",
"skills/github-trending" // ← 新增这行
]
}
对 skill 的 SKILL.md 所做的更改会在当前会话中立即生效。但 plugin.json 的修改需要重载:
Bash
# 在 Claude Code 里执行
/reload-plugins
第七步:验证安装
重载后,在 Claude Code 里测试:
text
/github-trending rust daily
你应该看到 Claude 开始:
执行 fetch-trending.sh rust daily用 parse-trending.py解析 HTML逐个处理 Trending 项目 在 Obsidian 里生成新页面
第八步:自动化——让它每天自动运行
skill 本身是手动触发的。如果你想让它每天自动执行,有两种方式:
方式 1:用 cron + Claude Code 无头模式
Bash
# 编辑 crontab
crontab -e# 每天早上 8:00 自动运行(替换 /path/to/your-vault 为你的路径)
0 8 * * * cd /path/to/your-vault && claude --print "/github-trending" >> ~/logs/trending-ingest.log 2>&1
方式 2:用 hooks 的 SessionStart 自动提示
在 hooks/hooks.json 里添加:
JSON
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-trending-stale.sh"
}
]
}
]
}
}
check-trending-stale.sh 检查 wiki/log.md,如果今天还没有做过 trending ingest,就在 SessionStart 时提醒 Claude:"今天还没有 ingest GitHub Trending,是否现在执行?"
第九步:实际效果展示
跑了一周之后,我的 wiki 里的 wiki/entities/tools/ 目录:
text
tools/
├── index.md # 按语言分类的所有工具索引
├── rust/
│ ├── tokio.md # 旧有(之前手动 ingest)
│ ├── axum.md # 旧有
│ ├── ratatui.md # 新增(本周 Trending)← 自动连接了 [[终端UI设计]]
│ └── zed.md # 新增(本周 Trending)← 自动连接了 [[LSP协议]], [[GPU渲染]]
├── python/
│ ├── ...
└── go/
├── ...
最惊喜的发现:Zed 编辑器上了 Trending 的时候,skill 自动识别出它和 wiki 里的 [[LSP协议]]、[[GPU渲染]]、[[Rust所有权模型]] 相关,自动建立了三条连接。 这三条连接,是我之前读源码时累积下来的知识,现在和一个新项目产生了关联。
复利的感觉,就是这样。
举一反三:这套模式能做什么
你学会了怎么写 skill,下面这些都可以用同样的模式实现:
| arXiv-trending | wiki/entities/papers/ | |
| hacker-news-digest | wiki/sources/hn/ | |
| npm-weekly | wiki/entities/libraries/ | |
| blog-monitor | wiki/sources/blogs/ | |
| release-tracker | wiki/entities/releases/ | |
| meeting-ingest | wiki/sources/meetings/ |
每一个都是同样的三层结构:
数据获取脚本(shell / python) SKILL.md(告诉 LLM 怎么处理数据) 参考文档(模板 + 分类映射)
完整代码汇总
最后把这个 skill 的所有文件整理成一个清单:
text
新增文件:skills/github-trending/
├── SKILL.md # 主逻辑(上面完整给出)
└── references/
├── page-template.md # 页面模板(上面完整给出)
└── category-map.md # 分类映射(上面完整给出)
scripts/
├── fetch-trending.sh # 爬取脚本(上面完整给出)
└── parse-trending.py # 解析脚本(上面完整给出)
修改文件:
.claude-plugin/plugin.json # 在 skills 数组添加 "skills/github-trending"
最后
这篇文章做了一件很具体的事:从零写一个真实的 Claude Code Skill,可以直接用。
但更重要的是,你现在理解了一件事:
Skill = LLM 操作手册 + 辅助脚本。
一旦你理解了这个模式,整个 claude-obsidian 生态对你来说就变成了一个完全可定制的系统。你不受限于它内置的功能,你可以按自己的需要扩展任何数据源、任何工作流、任何产出格式。
你的知识库,从此真正是你的。
👇 如果你想继续跟着做:
关注「一只阿木木」,我们在 AI 时代一起构建自己的知识系统。
本文所有代码基于 claude-obsidian 项目(GitHub: AgriciDaniel/claude-obsidian,MIT 协议开源)扩展开发,测试环境 macOS + Obsidian v1.9 + Claude Code v2.1。
扫码加入行动营👇获取更多Obsidian + AI数字大脑实践
关注【一只阿木木】。
我相信:在 AI 时代,每个普通人都该拥有一个自动生长的知识系统
去做,才是真的学。🌊