目录

last30days-skill:AI 全网研究助手从入门到精通

last30days-skill:AI 全网研究助手从入门到精通

预计阅读时间:25 分钟 | 难度:⭐⭐⭐


目标读者:对 AI 辅助研究、信息聚合、趋势追踪感兴趣的用户。包括:研究人员、投资者、产品经理、开发者、内容创作者 核心问题:如何让 AI Agent 自主研究任意话题,聚合全网最新信息,生成有理有据的深度报告? 难度:⭐⭐⭐(中级) 预计阅读时间:60 分钟


一、原理分析:为什么需要 last30days-skill

1.1 AI 的知识截止日期困境

大语言模型(LLM)存在一个根本性局限:知识有时间边界。GPT-4、Claude 3.5 的训练数据有明确的截止日期,无法回答此后发生的事件、新发布的工具、最近的社区讨论。

这在以下场景会造成严重问题:

场景问题
投资决策需要知道某公司最近的动态、市场情绪
技术选型需要了解某工具的真实用户反馈
竞品分析需要追踪对手的产品更新和社区反应
趋势研究需要发现正在爆发的新话题/工具/方法

传统解决方法的缺陷

  1. 搜索引擎:返回大量噪音,缺乏社区智慧的聚合
  2. RSS 订阅:需要手动整理,无法自动综合
  3. 社交媒体浏览:耗时且容易遗漏重要来源

1.2 last30days-skill 的核心思想

核心思路:让 AI Agent 拥有「上网冲浪做研究」的能力。


### 1.3 解决的核心问题

| 问题 | 解决方案 |
|------|---------|
| AI 知识陈旧 | 实时搜索 30 天内的最新讨论 |
| 信息孤岛 | 跨 8 个平台聚合,发现跨平台趋势 |
| 搜索噪音 | 多维度评分算法排序,过滤低质量内容 |
| 缺乏背景 | 发现@handle、subreddit 等社区生态 |
| 预测盲区 | Polymarket 预测市场提供「真金白银」的情绪指标 |

---

## 二、架构分析:技术深度解析

### 2.1 两阶段搜索架构

last30days-skill 采用了**两阶段搜索架构**,这是其与普通搜索工具的核心差异。

#### 第一阶段:广度发现

**Reddit 搜索**(via ScrapeCreators):

```text

**X/Twitter 搜索**(三条路径,按优先级):

| 路径 | API | 认证方式 | 特点 |
|------|-----|---------|------|
| 1. 捆绑 Bird 客户端 | Twitter GraphQL | AUTH_TOKEN + CT0 Cookie | 本地运行,无需浏览器 |
| 2. xAI 后备 | api.x.ai | XAI_API_KEY | 无需 Cookie,但需 xAI 账号 |
| 3. OpenAI 后备 | api.openai.com | OPENAI_API_KEY | 仅作为最后备选 |

**YouTube 搜索**:

```text

**Hacker News**(via Algolia):

```text

**Polymarket 预测市场**(via Gamma):

```text

#### 第二阶段:智能补充搜索

这是 v2.0 引入的关键创新。

**问题**:第一阶段搜索可能遗漏「当事人自己的帖子」(当事人账号发帖时不带话题关键词)

**解决方案**:

```text

### 2.2 多维度评分算法

**评分公式**(v2.9+):

```text

| 系数 | 指标 | 权重理由 |
|------|------|---------|
| 0.50 | score( upvotes/points) | 主要参与度指标 |
| 0.35 | comments | 讨论深度 |
| 0.05 | ratio(顶评/总评) | 社区质量信号 |
| 0.10 | top_comment_score | 优质回复的放大效应 |

**为什么用 log1p 而非原始值?**

- 避免大 V 帖子主导(10k upvotes 和 100 upvotes 差距被压缩)
- 让小众社区的高质量讨论也能浮现

**Polymarket 专用评分**(5 因子加权):

| 因子 | 权重 | 说明 |
|------|------|------|
| text_relevance | 30% | 与查询的相关性 |
| 24h_volume | 30% | 流动性/市场活跃度 |
| liquidity_depth | 15% | 市场深度 |
| price_movement_velocity | 15% | 价格变动速度 |
| outcome_competitiveness | 10% | 竞争度(接近 50% 说明争议大) |

### 2.3 X Handle 解析机制

**问题**:搜索「Dor Brothers」(一个电影制作团队),关键词搜索找不到他们的帖子,因为他们发帖时不会说「Dor Brothers」。

**解决方案**:

```text

### 2.4 目录结构

```text

---

## 三、使用说明:完整指南

### 3.1 安装

#### 方式一:Claude Code Marketplace(推荐)

```bash
/plugin marketplace add mvanhorn/last30days-skill
/plugin install last30days@last30days-skill
```bash
clawhub install last30days-official
```bash
# 克隆到 Claude Code skills 目录
git clone https://github.com/mvanhorn/last30days-skill.git ~/.claude/skills/last30days

# 或 Codex CLI
git clone https://github.com/mvanhorn/last30days-skill.git ~/.agents/skills/last30days
```bash
mkdir -p ~/.config/last30days
cat > ~/.config/last30days/.env << 'EOF'
# Reddit + TikTok + Instagram(一个密钥覆盖三个平台)
# 获取地址: https://scrapecreators.com
SCRAPECREATORS_API_KEY=your_key_here

# X/Twitter 搜索(推荐方式)
# 登录 x.com,打开浏览器 DevTools,复制 auth_token 和 ct0 Cookie
AUTH_TOKEN=your_auth_token
CT0=your_ct0_token

# xAI 后备(可选,没有 Cookie 时使用)
XAI_API_KEY=xai-your_key

# Bluesky(可选)
BSKY_HANDLE=your.bsky.social
BSKY_APP_PASSWORD=xxxx-xxxx-xxxx

# Web 搜索 API(可选,增加网络搜索能力)
PARALLEL_API_KEY=your_key          # Parallel AI(推荐)
BRAVE_API_KEY=your_key              # Brave Search(免费额度 2000/月)
OPENROUTER_API_KEY=your_key         # OpenRouter/Perplexity Sonar Pro
EOF
chmod 600 ~/.config/last30days/.env
```bash
# 在项目根目录创建
mkdir -p .claude
cat > .claude/last30days.env << 'EOF'
# 这里填项目特定的 API 密钥
SCRAPECREATORS_API_KEY=project_specific_key
EOF
```bash
python3 scripts/last30days.py --diagnose
```text
=== Source Availability ===
✓ ScrapeCreators API: configured
✓ X Search (Bird): working
✓ Bluesky: configured
✓ YouTube (yt-dlp): found in PATH
✓ HN Algolia: available (no key needed)
✓ Polymarket Gamma: available (no key needed)
```bash
/last30days [主题]
```bash
/last30days Claude Code best practices
/last30days AI Agent frameworks 2026
/last30days 最新的开源LLM进展
```bash
/last30days [主题] for [目标工具]
```bash
/last30days prompting techniques for ChatGPT
/last30days nano banana pro prompting
/last30days remotion animations for Claude Code
```bash
cp variants/open/SKILL.md ~/.claude/skills/last30days/SKILL.md
```bash
# 添加关注主题
last30 watch 竞争对手公司 每周
last30 watch AI视频工具 每月
last30 watch Y Combinator 热门公司 每年4月和9月
```bash
# 手动运行所有关注主题
last30 run all

# 查看累积知识
last30 what have you found about AI video?
```bash
# crontab 示例:每周一早上 9 点运行
0 9 * * 1 cd ~/projects && last30 run all >> ~/logs/last30.log 2>&1

# 或 macOS launchd
```text
Query: /last30days prompting techniques for legal questions in ChatGPT

Research Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

The dominant theme is hallucination prevention...

Key patterns discovered:
1. Hallucination prevention clauses
2. Role assignment (paralegal, issue-spotter)
3. Structured output requirements
4. Epistemic humility enforcement
5. Scope limitation

Research Stats:
• 10 Reddit threads (1,200+ upvotes)
• 15 X posts (5,000+ likes)
• 8 YouTube videos (200K+ views)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```text
Query: /last30days anthropic odds

Research Output:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Key findings:
• Pentagon standoff: 22% ban probability ( Polymarket)
• Best AI model (Feb): 98% Anthropic ( Polymarket)
• IPO before OpenAI: 64% YES
• $500B+ valuation: 87% YES

Research Stats:
• 25 X posts (218 likes)
• 13 YouTube videos (719K views)
• 6 HN stories (48 points)
• 11 Polymarket markets

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```text
┌─────────────────────────────────────────────────────────────┐
│                    last30days 核心架构                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  SKILL.md ← Claude Code 的指令入口                           │
│    ↓                                                        │
│  last30days.py ← 主脚本,协调各模块                          │
│    ↓                                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    搜索层                             │   │
│  │  • RedditSearch (ScrapeCreators)                     │   │
│  │  • TwitterSearch (Bird/xAI)                          │   │
│  │  • YouTubeSearch (yt-dlp)                           │   │
│  │  • HNSearch (Algolia)                               │   │
│  │  • PolymarketSearch (Gamma)                          │   │
│  │  • BlueskySearch (AT Protocol)                       │   │
│  │  • WebSearch (Parallel/Brave/OpenRouter)             │   │
│  └─────────────────────────────────────────────────────┘   │
│    ↓                                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    评分层                             │   │
│  │  • TextSimilarityEngine (双向匹配 + 同义词扩展)        │   │
│  │  • EngagementScorer (参与度归一化)                    │   │
│  │  • CrossPlatformDetector (跨平台热点检测)             │   │
│  │  • PolymarketScorer (5因子加权)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│    ↓                                                        │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    合成层                             │   │
│  │  • LLMSynthesizer (调用 LLM 生成报告)                 │   │
│  │  • CitationManager (引用管理)                         │   │
│  │  • FormatRenderer (Markdown/JSON 输出)                │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```python
# scripts/lib/sources/mysource.py
from typing import List, Dict, Any
from .base import SearchSource

class MySource(SearchSource):
    """自定义搜索源示例"""
    
    name = "mysource"
    api_endpoint = "https://api.mysource.com/search"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
    
    def search(
        self, 
        query: str, 
        days: int = 30,
        max_results: int = 20
    ) -> List[Dict[str, Any]]:
        """执行搜索并返回结果"""
        # 1. 调用 API
        response = self._call_api(query, days, max_results)
        
        # 2. 解析响应
        results = self._parse(response)
        
        # 3. 标准化格式
        return [self._normalize(r) for r in results]
    
    def _normalize(self, raw: Dict) -> Dict[str, Any]:
        """标准化为统一格式"""
        return {
            "source": self.name,
            "title": raw.get("title"),
            "url": raw.get("url"),
            "published_at": raw.get("published_at"),
            "engagement": {
                "score": raw.get("upvotes", 0),
                "comments": raw.get("comment_count", 0),
            },
            "author": raw.get("author"),
            "raw": raw,  # 保留原始数据供后续处理
        }
```python
# scripts/last30days.py

from .sources.mysource import MySource

# 在搜索函数中注册
def get_all_sources(config: Config) -> Dict[str, SearchSource]:
    return {
        # 从配置加载已有源...
        "mysource": MySource(config.get("MYSOURCE_API_KEY")),
    }
```python
# 在 scorer.py 中
class MultiSignalScorer:
    def __init__(self):
        self.source_weights = {
            # 从配置加载已有权重...
            "mysource": 0.15,  # 自定义源的权重
        }
    
    def score(self, results: List[Dict]) -> List[Dict]:
        for result in results:
            source = result["source"]
            base = result.get("engagement", {}).get("score", 0)
            
            # 应用源特定权重
            weight = self.source_weights.get(source, 0.10)
            result["final_score"] = base * weight
            
        return sorted(results, key=lambda x: x["final_score"], reverse=True)
```python
# scripts/lib/scorer.py

class CustomScorer:
    """自定义评分器示例"""
    
    def __init__(
        self,
        score_weight: float = 0.50,
        comment_weight: float = 0.35,
        ratio_weight: float = 0.05,
        top_comment_weight: float = 0.10,
    ):
        self.weights = {
            "score": score_weight,
            "comment": comment_weight,
            "ratio": ratio_weight,
            "top_comment": top_comment_weight,
        }
    
    def score(self, item: Dict) -> float:
        import math
        
        score = item.get("engagement", {}).get("score", 0)
        comments = item.get("engagement", {}).get("comments", 0)
        top_comment = item.get("engagement", {}).get("top_comment_score", 0)
        ratio = top_comment / (score + 1)  # 避免除零
        
        return (
            self.weights["score"] * math.log1p(score) +
            self.weights["comment"] * math.log1p(comments) +
            self.weights["ratio"] * (ratio * 10) +
            self.weights["top_comment"] * math.log1p(top_comment)
        )
```python
# scripts/lib/renderer.py

class MarkdownRenderer:
    def __init__(self, template: str = "default"):
        self.template = template
    
    def render(self, results: List[Dict], synthesis: str) -> str:
        sections = []
        
        # 标题
        sections.append(f"# Research Report: {results[0]['query']}\n")
        
        # 关键发现
        sections.append("## Key Findings\n")
        sections.append(synthesis)
        
        # 按来源分组
        sections.append("\n## Source Breakdown\n")
        by_source = self._group_by_source(results)
        for source, items in by_source.items():
            sections.append(f"### {source} ({len(items)} results)\n")
            for item in items[:5]:  # 每源最多 5 条
                sections.append(
                    f"- [{item['title']}]({item['url']}) "
                    f"(score: {item['engagement']['score']})\n"
                )
        
        # 统计数据
        sections.append("\n## Research Stats\n")
        sections.append(self._render_stats(results))
        
        return "".join(sections)
```python
# scripts/integrations/slack.py

import requests
from typing import List, Dict

class SlackIntegration:
    def __init__(self, webhook_url: str):
        self.webhook_url = webhook_url
    
    def send_report(
        self, 
        title: str, 
        summary: str, 
        results: List[Dict]
    ):
        blocks = [
            {
                "type": "header",
                "text": {"type": "plain_text", "text": title}
            },
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": summary}
            },
            {"type": "divider"},
        ]
        
        # 添加 Top 3 结果
        for item in results[:3]:
            blocks.append({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"• <{item['url']}|{item['title']}> ({item['engagement']['score']} points)"
                }
            })
        
        requests.post(
            self.webhook_url,
            json={"blocks": blocks}
        )
```python
# scripts/integrations/notion.py

from notion_client import Client

class NotionIntegration:
    def __init__(self, token: str, database_id: str):
        self.notion = Client(auth=token)
        self.database_id = database_id
    
    def create_research_page(
        self,
        title: str,
        summary: str,
        results: List[Dict],
        tags: List[str]
    ):
        properties = {
            "Title": {"title": [{"text": {"content": title}}]},
            "Tags": {"multi_select": [{"name": t} for t in tags]},
            "Summary": {"rich_text": [{"text": {"content": summary[:2000]}}]},
        }
        
        children = [
            {
                "object": "block",
                "type": "heading_2",
                "heading_2": {"rich_text": [{"text": {"content": "Top Results"}}]}
            }
        ]
        
        for item in results[:10]:
            children.append({
                "object": "block",
                "type": "bulleted_list_item",
                "bulleted_list_item": {
                    "rich_text": [
                        {"text": {"content": f"{item['title']} — "}},
                        {"text": {"link": {"url": item["url"]}, "content": "Link"}}
                    ]
                }
            })
        
        self.notion.pages.create(
            parent={"database_id": self.database_id},
            properties=properties,
            children=children
        )
```bash
# 运行所有测试
pytest tests/ -v

# 运行特定源的测试
pytest tests/test_reddit.py -v

# 运行带覆盖率
pytest tests/ --cov=scripts --cov-report=html
```python
# tests/test_mysource.py

import pytest
from scripts.sources.mysource import MySource

class TestMySource:
    @pytest.fixture
    def source(self):
        return MySource(api_key="test_key")
    
    def test_search_returns_results(self, source):
        results = source.search("test query", days=7, max_results=10)
        assert len(results) <= 10
        assert all("title" in r for r in results)
        assert all("url" in r for r in results)
    
    def test_normalize_standardizes_format(self, source):
        raw = {
            "title": "Test Post",
            "url": "https://example.com/post",
            "published_at": "2026-03-28",
            "upvotes": 100,
            "comment_count": 50,
        }
        normalized = source._normalize(raw)
        
        assert normalized["source"] == "mysource"
        assert normalized["engagement"]["score"] == 100
        assert normalized["engagement"]["comments"] == 50
```bash
sudo "/Applications/Python 3.12/Install Certificates.command"

5.3 局限性

局限说明
覆盖范围依赖各平台的 API 可用性
延迟实时性受限于各平台的爬虫/搜索限制
API 成本ScrapeCreators 等服务有使用限制
Cookie 过期X 搜索依赖 Cookie,需要定期更新

六、与其他工具对比

工具覆盖平台开源实时性适合场景
last30days-skill8 个30 天内深度研究、趋势发现
PerplexityWeb only实时快速问答
Custom GPTs有限依赖配置个人助手
RSS + AI依赖订阅源可定制取决于源被动信息流

七、资源链接

资源链接
GitHubhttps://github.com/mvanhorn/last30days-skill
版本v2.9.5
Claude Code 插件/plugin marketplace add mvanhorn/last30days-skill
ClawHubclawhub.ai/skills/last30days-official
ScrapeCreatorshttps://scrapecreators.com

文档信息

  • 难度:⭐⭐⭐ | 类型:入门到精通 | 更新日期:2026-03-28 | 预计阅读时间:60 分钟