一只阿木木

我给 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 到 stdout

LANGUAGE=${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:去重检查

对每个项目:

  1. 检查 wiki/entities/tools/{项目名}.md 是否已存在
  2. 如果存在,检查 updated frontmatter 是否是今天
  3. 如果今天已更新,跳过(避免重复处理)
  4. 如果不存在或超过 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 数今日增长异常高的)

注意事项

  1. 不要伪造数据:如果 GitHub 请求失败,直接报错,不要用训练数据里的项目来填充
  2. 不加主观评价:不要说"这个项目非常优秀"、"强烈推荐"等评价词
  3. 来源可追溯:每个页面必须在 sources frontmatter 里记录来源
  4. 单写原则:遵守 wiki-ingest 的单写规则,使用 allocate-address.sh 分配地址
  5. 不修改 .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 开始:

  1. 执行 fetch-trending.sh rust daily
  2. 用 parse-trending.py 解析 HTML
  3. 逐个处理 Trending 项目
  4. 在 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,下面这些都可以用同样的模式实现:

Skill 名称
来源
产出
arXiv-trending
arXiv 最新论文
wiki/entities/papers/
hacker-news-digest
HN 每日 Top
wiki/sources/hn/
npm-weekly
npm 下载量周报
wiki/entities/libraries/
blog-monitor
订阅的技术博客 RSS
wiki/sources/blogs/
release-tracker
关注项目的 GitHub Releases
wiki/entities/releases/
meeting-ingest
会议录音转录文本
wiki/sources/meetings/

每一个都是同样的三层结构:

  1. 数据获取脚本(shell / python)
  2. SKILL.md(告诉 LLM 怎么处理数据)
  3. 参考文档(模板 + 分类映射)

完整代码汇总

最后把这个 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。

我是【一只阿木木】,AI 知识系统架构师,坐标杭州。

扫码加入行动营👇获取更多Obsidian + AI数字大脑实践

Image

关注【一只阿木木】。

我相信:在 AI 时代,每个普通人都该拥有一个自动生长的知识系统

去做,才是真的学。🌊