Files
agent-park/docs/data-ingestion-flow.md
T
mzaxdandClaude cae5686d2d feat: 添加 GitHub 统计数据展示功能
新增 GitHub 统计卡片和徽章组件,在项目卡片、详情页和侧边栏中展示 GitHub stars、forks 等统计数据

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-30 14:14:02 +08:00

35 KiB
Raw Blame History

数据新增流程设计文档

本文档详细描述了 AI 项目导航站的数据获取、处理和维护的完整流程

目录


1. 概述

1.1 流程全景

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│ Discovery  │ -> │   Fetch     │ -> │   Parse     │ -> │  Validate   │
│  发现项目    │    │  深度抓取    │    │  解析标准化   │    │  质量验证    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
       ↓                  ↓                  ↓                  ↓
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  多源种子    │    │  完整元数据   │    │  结构化数据   │    │  质量评分    │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
                                                                 ↓
                                                    ┌─────────────┐
                                                    │    Store    │
                                                    │  入库存储    │
                                                    └─────────────┘
                                                            ↓
                                                    ┌─────────────┐
                                                    │   Maintain  │
                                                    │  持续维护    │
                                                    └─────────────┘

1.2 核心目标

  • 完整性:获取项目的多维度信息(基础、统计、内容、关系、更新、社区)
  • 准确性:通过多源交叉验证和质量评分确保数据可靠
  • 时效性:定期更新活跃项目,及时下架失效项目
  • 可扩展性:模块化架构便于添加新数据源

2. 数据源发现

2.1 主要数据平台

平台 数据类型 API 能力 数据量级
GitHub 代码项目 REST API + GraphQL ★★★★★
Hugging Face 模型/数据集/空间 REST API ★★★★☆
Papers with Code 论文+代码 Web Scraping ★★★☆☆
arXiv 论文预印本 REST API ★★★★☆
Product Hunt AI产品 无公开API ★★☆☆☆
AI导航站 聚合列表 Web Scraping ★★☆☆☆

2.2 项目发现渠道

2.2.1 趋势榜单

GitHub Trending:
https://github.com/trending
- 参数:since=daily/weekly/monthly, language={python,typescript,...}
- 获取:spike_count、stars、forks、description

Hugging Face Trending:
https://huggingface.co/api/models
- 参数:sort=downloads/likes, trending=true
- 获取:modelId、downloads、likes、pipeline_tag

2.2.2 社区讨论

  • Reddit: r/MachineLearning、r/artificial、r/LocalLLaMA 高赞帖
  • Hacker News: AI相关front page故事
  • Twitter/X: AI influencerAndrew Ng、Yann LeCun等)转发
  • Discord/Slack: AI社区热帖

2.2.3 论文与代码关联

Papers with Code:
- Tasks分类:https://paperswithcode.com/tasks
- Leaderboards: https://paperswithcode.com/leaderboards
- 关联GitHub仓库

2.2.4 聚合站点

  • FutureTools、There's An AI For That、AI Valley
  • 需注意:这些站点数据源自上游,去重更重要

2.3 发现优先级

P0: GitHub Trending + Hugging Face Trending(每日)
P1: Papers with Code新入库论文(每周)
P2: Reddit/HN高赞讨论(每周)
P3: Product Hunt AI产品(每月)
P4: AI导航站爬取(按需)

3. 深度数据提取

3.1 提取维度架构

项目元数据模型:
  基础层:
    - 名称: name, nameEn
    - 描述: description, descriptionEn (100-500字)
    - 主页: homepage_url
    - 许可证: license

  统计层:
    - GitHub: stars, forks, watchers, open_issues
    - Hugging Face: downloads, likes, discussions
    - 变化趋势: stars_delta_7d, stars_delta_30d

  内容层:
    - README: 完整Markdown内容 (content/contentEn)
    - 文档: docs_url, wiki_url
    - 演示: demo_url, video_url

  技术层:
    - 编程语言: languages (按代码量排序)
    - 依赖项: dependencies (package.json, requirements.txt)
    - 框架: frameworks (PyTorch, TensorFlow, LangChain...)
    - 模型类型: LLM, Diffusion, Computer Vision...

  关系层:
    - 作者: author, author_url
    - 组织: organization, org_url
    - 相关项目: similar_projects, forks_from

  更新层:
    - 首次发布: created_at
    - 最后更新: updated_at
    - 最后提交: pushed_at
    - 版本历史: releases, tags

  社区层:
    - 贡献者: contributors_count, top_contributors
    - Issue活动: open_issues, closed_issues, issue_response_time
    - 讨论质量: discussions_count, avg_engagement

3.2 GitHub 深度提取方案

3.2.1 API 调用策略

// 推荐使用 GraphQL 一次性获取,减少请求次数
const query = `
  query($owner: String!, $name: String!) {
    repository(owner: $owner, name: $name) {
      # 基础信息
      name
      description
      homepageUrl
      licenseInfo { key name }
      url

      # 统计数据
      stargazers { totalCount }
      forks { totalCount }
      watchers { totalCount }
      openIssues: issues(states: OPEN) { totalCount }

      # 内容
      readme: object(expression: "HEAD:README.md") {
        ... on Blob { text }
      }

      # 技术
      languages(orderBy: {field: SIZE, direction: DESC}, first: 10) {
        edges { node { name } size }
      }

      # 更新时间
      createdAt
      updatedAt
      pushedAt

      # 发布版本
      releases(last: 5, orderBy: {field: CREATED_AT, direction: DESC}) {
        nodes { tagName name publishedAt }
      }

      # 贡献者
      contributors: mentionableUsers(first: 20) {
        nodes { login name url }
      }

      # Topics (标签)
      repositoryTopics(first: 20) {
        nodes { topic { name } }
      }

      # 依赖关系
      defaultBranchRef {
        target {
          ... on Commit {
            history(first: 1) {
              nodes {
                ... on Commit {
                  file(path: "package.json") {
                    ... on Blob { text }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
`;

3.2.2 REST API 补充

// 获取 issue 活跃度
const issuesActivity = await fetch(
  `https://api.github.com/repos/${owner}/${repo}/issues?state=all&per_page=100&sort=comments`
);

// 获取 star 历史(需第三方服务如 star-history.com
const starHistory = await fetch(
  `https://api.star-history.com/svg?repos=${owner}/${repo}&type=Date`
);

// 获取社区健康度
const communityProfile = await fetch(
  `https://api.github.com/repos/${owner}/${repo}/community/profile`
);

3.3 Hugging Face 深度提取方案

3.3.1 模型 API

const model = await fetch(`https://huggingface.co/api/models/${modelId}`);

// 返回结构
{
  modelId: "meta-llama/Llama-2-7b",
  author: "meta-llama",
  downloads: 5000000,
  likes: 12000,
  lastModified: "2024-01-15T00:00:00.000Z",

  // 标签体系
  tags: ["transformers", "pytorch", "llm", "arxiv:2307.12345"],
  pipeline_tag: "text-generation",

  // README 中的 YAML Frontmatter
  cardData: {
    license: "llama2",
    tags: ["llm", "generative"],
    datasets: ["commoncrawl"],
    metrics: ["perplexity"],
    model_index: {
      "text-generation": [
        { name: "Llama-2-7b", model: "?" }
      ]
    }
  }
}

3.3.2 README 解析

Hugging Face 的 README 通常包含结构化的 YAML 元数据:

---
license: llama2
tags:
- llm
- generative
- text generation
datasets:
- commoncrawl
- c4
metrics:
- perplexity
---

# Llama 2 7B

[Markdown 内容...]

需要解析并合并这些元数据。

3.4 Papers with Code 提取

// 该平台无公开API,需网页抓取
const paperPage = await fetch(`https://paperswithcode.com/paper/${paperSlug}`);

// 提取字段
{
  title: "Attention Is All You Need",
  titleEn: "Attention Is All You Need",
  authors: ["Ashish Vaswani", ...],
  published: "2017-06-12",
  arxiv_id: "1706.03762",
  pdf_url: "https://arxiv.org/pdf/1706.03762.pdf",

  // 代码实现
  frameworks: ["PyTorch", "TensorFlow"],
  implementations: [
    { name: "Tensor2Tensor", github: "tensorflow/tensor2tensor", stars: 12000 },
    { name: "Harvard NLP", github: "harvardnlp/annotated-transformer", stars: 5000 }
  ],

  // 任务与指标
  tasks: ["machine-translation", "language-modeling"],
  benchmarks: ["WMT 2014 En-De", "WMT 2014 En-Fr"],
  sota_scores: { "BLEU": 28.4 }
}

4. 数据标准化处理

4.1 统一数据结构

所有数据源最终转换为 ProjectInputSchema 格式:

interface ProjectInput {
  // 基础信息(必填)
  name: string;           // 中文名称(如无则翻译)
  nameEn: string;         // 英文名称
  description: string;    // 中文描述(100-500字)
  descriptionEn: string;  // 英文描述
  slug: string;           // URL友好标识符

  // 内容(必填)
  content: string;        // 中文READMEMarkdown
  contentEn: string;      // 英文README

  // 状态
  status: "ACTIVE" | "ARCHIVED";
  source: "GITHUB" | "HUGGING_FACE" | "PAPERS_WITH_CODE" | "MANUAL";

  // 关联(必填)
  tags: string[];         // 1-10个标签
  externalLinks: ExternalLink[];  // 1-10个链接
}

4.2 标签智能生成

4.2.1 标签分类体系

技术栈标签:
  - 来源: GitHub languages, HF tags
  - 示例: Python, TypeScript, PyTorch, TensorFlow

应用领域标签:
  - 来源: README关键词, HF pipeline_tag, PwC tasks
  - 示例: Computer Vision, NLP, Reinforcement Learning

模型类型标签:
  - 来源: README, paper tags
  - 示例: LLM, Diffusion, GAN, Transformer

框架标签:
  - 来源: dependencies, README
  - 示例: LangChain, Gradio, Streamlit, FastAPI

商业状态标签:
  - 来源: license, homepage
  - 示例: Open Source, Commercial, Research Only

4.2.2 标签提取算法

async function extractTags(project) {
  const tags = new Set();

  // 1. 从平台标签直接获取
  if (project.githubTopics) {
    project.githubTopics.forEach(t => tags.add(normalizeTag(t)));
  }

  // 2. 从 HF pipeline_tag 获取
  if (project.pipelineTag) {
    tags.add(normalizeTag(project.pipelineTag));
  }

  // 3. NLP 关键词提取(使用NER
  const keywords = await extractKeywords(project.descriptionEn);
  keywords.forEach(kw => {
    if (isTechnicalTerm(kw)) tags.add(normalizeTag(kw));
  });

  // 4. 编程语言映射
  if (project.languages) {
    Object.keys(project.languages).forEach(lang => {
      tags.add(normalizeTag(lang));
    });
  }

  // 5. 去重与标准化
  return Array.from(tags)
    .filter(t => t.length >= 2 && t.length <= 30)
    .map(t => applyTagAlias(t));  // "LLM" -> "Large Language Model"
}

4.2.3 标签标准化规则

const tagAliases = {
  "LLM": "Large Language Model",
  "llm": "Large Language Model",
  "GPT": "Generative Pre-trained Transformer",
  "CV": "Computer Vision",
  "NLP": "Natural Language Processing"
};

const tagSynonyms = {
  "diffusion": ["stable-diffusion", "ddpm", "score-based"],
  "transformer": ["attention", "self-attention"],
  "fine-tuning": ["finetuning", "fine_tuning"]
};

4.3 多语言内容生成

4.3.1 翻译策略

async function translateProject(project, sourceLang, targetLang) {
  // 1. 名称翻译(保留专有名词)
  const translatedName = await translateText(project.name, {
    preserveTerms: ["Transformer", "Diffusion", "LLaMA"],
    format: "title"
  });

  // 2. 描述翻译
  const translatedDesc = await translateText(project.description, {
    maxLength: 500,
    preserveFormatting: true
  });

  // 3. README 分段翻译
  const translatedContent = await translateMarkdown(project.content, {
    skipCodeBlocks: true,
    preserveLinks: true,
    preserveImages: true
  });

  return {
    name: targetLang === 'zh' ? translatedName : project.name,
    nameEn: targetLang === 'en' ? translatedName : project.name,
    // ...
  };
}

4.3.2 翻译质量检查

function validateTranslation(original, translated) {
  const checks = {
    lengthRatio: translated.length / original.length,
    // 异常检测:中译英应在0.6-1.5倍之间
    hasPreservedTerms: original.match(/[A-Z]{2,}/g).every(term =>
      translated.includes(term)
    ),
    noBrokenFormatting: !translated.includes('```') || translated.match(/```/g).length % 2 === 0,
    noImageLoss: (original.match(/!\[.*\]\(.*\)/g) || []).length ===
                 (translated.match(/!\[.*\]\(.*\)/g) || []).length
  };

  return Object.values(checks).every(v => v === true);
}

4.4 Slug 生成规则

function generateSlug(name, nameEn, existingSlugs) {
  // 1. 优先使用英文
  let slug = nameEn
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');

  // 2. 检查冲突
  let finalSlug = slug;
  let counter = 1;
  while (existingSlugs.includes(finalSlug)) {
    finalSlug = `${slug}-${counter}`;
    counter++;
  }

  return finalSlug;
}

5. 质量控制机制

5.1 质量评分模型

function calculateQualityScore(project) {
  const scores = {
    completeness: 0,    // 完整性 (0-30)
    freshness: 0,       // 时效性 (0-25)
    activity: 0,        // 活跃度 (0-25)
    authority: 0,       // 权威性 (0-10)
    usability: 0        // 可用性 (0-10)
  };

  // 1. 完整性评分 (30分)
  if (project.name && project.description) scores.completeness += 10;
  if (project.content && project.content.length > 500) scores.completeness += 10;
  if (project.externalLinks.length >= 2) scores.completeness += 5;
  if (project.tags.length >= 3) scores.completeness += 5;

  // 2. 时效性评分 (25分)
  const daysSinceUpdate = (Date.now() - new Date(project.updatedAt)) / (1000 * 60 * 60 * 24);
  if (daysSinceUpdate < 30) scores.freshness = 25;
  else if (daysSinceUpdate < 90) scores.freshness = 20;
  else if (daysSinceUpdate < 180) scores.freshness = 15;
  else if (daysSinceUpdate < 365) scores.freshness = 10;
  else scores.freshness = 5;

  // 3. 活跃度评分 (25分)
  const stars = project.stars || 0;
  if (stars > 10000) scores.activity += 10;
  else if (stars > 1000) scores.activity += 7;
  else if (stars > 100) scores.activity += 5;
  else if (stars > 10) scores.activity += 3;

  const recentCommits = project.recentCommits || 0;
  if (recentCommits > 10) scores.activity += 15;
  else if (recentCommits > 5) scores.activity += 10;
  else if (recentCommits > 0) scores.activity += 5;

  // 4. 权威性评分 (10分)
  if (project.isOfficialOrg) scores.authority += 5;
  if (project.hasPaperBacking) scores.authority += 3;
  if (project.stars > 5000) scores.authority += 2;

  // 5. 可用性评分 (10分)
  if (project.hasInstallationGuide) scores.usability += 4;
  if (project.hasDemo) scores.usability += 3;
  if (project.hasDocumentation) scores.usability += 3;

  // 总分
  const totalScore = Object.values(scores).reduce((a, b) => a + b, 0);

  return {
    totalScore,
    breakdown: scores,
    quality: totalScore >= 70 ? 'HIGH' : totalScore >= 40 ? 'MEDIUM' : 'LOW'
  };
}

5.2 垃圾项目检测

function detectSpamProject(project) {
  const signals = [];

  // 1. 描述异常相似
  if (isDescriptionTemplate(project.description)) {
    signals.push('template_description');
  }

  // 2. Star 增长异常
  const starGrowthRate = project.stars / project.daysSinceCreated;
  if (starGrowthRate > 1000 && project.daysSinceCreated < 7) {
    signals.push('suspicious_star_growth');
  }

  // 3. 内容过短
  if (project.content.length < 100) {
    signals.push('minimal_content');
  }

  // 4. 缺少基本链接
  if (!project.externalLinks.some(l => l.type === 'GITHUB' || l.type === 'WEBSITE')) {
    signals.push('missing_repository');
  }

  // 5. 关键词堆砌
  const keywordDensity = calculateKeywordDensity(project.description);
  if (keywordDensity > 0.3) {
    signals.push('keyword_stuffing');
  }

  return {
    isSpam: signals.length >= 3,
    signals,
    confidence: signals.length / 5
  };
}

5.3 去重策略

利用现有 Webhook 的多级去重机制:

async function deduplicateProject(newProject) {
  const { githubUrl, websiteUrl, slug } = newProject;

  // P0: GitHub URL 精确匹配
  const githubMatch = await prisma.externalLink.findUnique({
    where: { url_type: { url: githubUrl, type: 'GITHUB' } },
    include: { project: true }
  });
  if (githubMatch) {
    return { exists: true, project: githubMatch.project, reason: 'GITHUB_URL' };
  }

  // P1: Website URL 精确匹配
  if (websiteUrl) {
    const websiteMatch = await prisma.externalLink.findUnique({
      where: { url_type: { url: websiteUrl, type: 'WEBSITE' } },
      include: { project: true }
    });
    if (websiteMatch) {
      return { exists: true, project: websiteMatch.project, reason: 'WEBSITE_URL' };
    }
  }

  // P2: Slug 匹配
  const slugMatch = await prisma.project.findUnique({
    where: { slug }
  });
  if (slugMatch) {
    return { exists: true, project: slugMatch, reason: 'SLUG' };
  }

  return { exists: false };
}

6. 数据维护策略

6.1 增量更新机制

// 更新优先级
const UPDATE_PRIORITIES = {
  HIGH: { interval: '7d', condition: 'stars > 1000 && updated < 7d ago' },
  MEDIUM: { interval: '30d', condition: 'stars > 100 && updated < 30d ago' },
  LOW: { interval: '90d', condition: 'stars <= 100' }
};

async function scheduleUpdate(project) {
  const priority = determineUpdatePriority(project);

  // 使用 BullMQ 队列
  await updateQueue.add('refresh-project', {
    projectId: project.id,
    source: project.source
  }, {
    delay: parseInterval(priority.interval),
    attempts: 3,
    backoff: { type: 'exponential', delay: 5000 }
  });
}

6.2 生命周期管理

async function manageProjectLifecycle(project) {
  const daysSinceUpdate = (Date.now() - new Date(project.updatedAt)) / (1000 * 60 * 60 * 24);

  // 1. 活跃项目(90天内更新)
  if (daysSinceUpdate < 90) {
    await prisma.project.update({
      where: { id: project.id },
      data: { status: 'ACTIVE' }
    });
  }

  // 2. 不活跃项目(90-365天)
  else if (daysSinceUpdate < 365) {
    // 检查是否仍在维护
    const stillActive = await checkMaintenanceStatus(project);
    if (!stillActive) {
      await prisma.project.update({
        where: { id: project.id },
        data: { status: 'ARCHIVED' }
      });
    }
  }

  // 3. 长期未更新(超过365天)
  else {
    await prisma.project.update({
      where: { id: project.id },
      data: { status: 'ARCHIVED' }
    });
  }
}

6.3 死链检测

async function checkExternalLinks() {
  const links = await prisma.externalLink.findMany();

  for (const link of links) {
    try {
      const response = await fetch(link.url, {
        method: 'HEAD',
        timeout: 5000
      });

      if (response.status === 404) {
        // 标记失效
        await prisma.externalLink.update({
          where: { id: link.id },
          data: { valid: false }
        });
      } else if (response.status >= 400) {
        // 标记异常
        await prisma.externalLink.update({
          where: { id: link.id },
          data: { valid: false, lastError: response.status }
        });
      }
    } catch (error) {
      // 网络错误,标记待重检
      await prisma.externalLink.update({
        where: { id: link.id },
        data: { lastCheckFailed: true }
      });
    }
  }

  // 移除长期失效的链接
  await prisma.externalLink.deleteMany({
    where: {
      valid: false,
      updatedAt: { lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }
    }
  });
}

6.4 热度衰减算法

function calculateTrendingScore(project) {
  const BASE_SCORE = project.stars || 0;

  // 时间衰减(半衰期30天)
  const daysSinceUpdate = (Date.now() - new Date(project.updatedAt)) / (1000 * 60 * 60 * 24);
  const timeDecay = Math.pow(0.5, daysSinceUpdate / 30);

  // 增长加权(最近7天的star增长)
  const recentGrowth = (project.stars - project.stars7dAgo) || 0;
  const growthBonus = recentGrowth * 2;

  // 社区活跃度
  const activityBonus = (project.recentCommits || 0) * 10 +
                        (project.issuesClosedLastWeek || 0) * 5;

  return (BASE_SCORE * timeDecay) + growthBonus + activityBonus;
}

7. 技术实现架构

7.1 系统架构

┌─────────────────────────────────────────────────────────────┐
│                         调度层                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  定时任务     │  │  事件触发     │  │  手动触发     │      │
│  │  (cron)      │  │  (webhook)   │  │  (admin)     │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                         采集层                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │ GitHub       │  │ Hugging Face │  │ Papers w/    │      │
│  │ Adapter      │  │ Adapter      │  │ Code Adapter │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                         解析层                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  数据标准化   │  │  标签生成     │  │  多语言翻译   │      │
│  │  (normalizer) │  │  (tagger)    │  │  (translator)│      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                         验证层                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  质量评分     │  │  去重检测     │  │  垃圾过滤     │      │
│  │  (scorer)    │  │  (deduper)   │  │  (spam-filter)│     │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                         存储层                               │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │  Webhook     │  │   Prisma     │  │  PostgreSQL  │      │
│  │  API         │  │   ORM        │  │   Database   │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────┐
│                       任务队列                               │
│              ┌──────────────────────────────┐               │
│              │      BullMQ Queue            │               │
│              │  - 采集任务                   │               │
│              │  - 更新任务                   │               │
│              │  - 死链检测                   │               │
│              └──────────────────────────────┘               │
└─────────────────────────────────────────────────────────────┘

7.2 目录结构

src/
├── lib/
│   ├── scrapers/           # 数据采集器
│   │   ├── base.ts         # 基础采集器接口
│   │   ├── github.ts       # GitHub采集器
│   │   ├── huggingface.ts  # HF采集器
│   │   └── paperswithcode.ts
│   │
│   ├── processors/         # 数据处理器
│   │   ├── normalizer.ts   # 数据标准化
│   │   ├── tagger.ts       # 标签生成
│   │   ├── translator.ts   # 多语言翻译
│   │   └── slugify.ts      # Slug生成
│   │
│   ├── validators/         # 数据验证器
│   │   ├── scorer.ts       # 质量评分
│   │   ├── deduper.ts      # 去重检测
│   │   └── spam-filter.ts  # 垃圾过滤
│   │
│   └── queue/              # 任务队列
│       ├── producer.ts     # 任务生产者
│       ├── consumer.ts     # 任务消费者
│       └── jobs/           # 任务定义
│           ├── fetch-project.ts
│           ├── refresh-project.ts
│           └── check-links.ts
│
├── app/
│   └── api/
│       └── admin/          # 管理API
│           ├── ingest/
│           │   └── route.ts      # 手动触发采集
│           └── maintenance/
│               └── route.ts      # 手动触发维护
│
└── scripts/
    ├── ingest-trending.ts      # 采集trending项目
    ├── refresh-all.ts          # 刷新所有项目
    └── health-check.ts         # 系统健康检查

7.3 核心接口定义

7.3.1 采集器接口

// src/lib/scrapers/base.ts
export interface ProjectScraper {
  // 识别平台
  platform: ProjectSource;

  // 从URL识别是否属于该平台
  canHandle(url: string): boolean;

  // 获取项目基础信息
  fetchBasic(url: string): Promise<BasicProjectInfo>;

  // 获取项目完整信息
  fetchFull(url: string): Promise<FullProjectInfo>;

  // 获取趋势列表
  fetchTrending(options?: TrendingOptions): Promise<ProjectUrl[]>;
}

export interface BasicProjectInfo {
  name: string;
  description: string;
  homepage?: string;
  repository: string;
  stars?: number;
}

export interface FullProjectInfo extends BasicProjectInfo {
  content: string;
  languages: Record<string, number>;
  tags: string[];
  contributors: number;
  lastUpdated: Date;
  // ...
}

7.3.2 处理器接口

// src/lib/processors/normalizer.ts
export async function normalizeProject(
  rawProject: FullProjectInfo,
  source: ProjectSource
): Promise<ProjectInput> {
  // 1. 基础字段映射
  const base = {
    name: rawProject.name,
    nameEn: rawProject.name,
    description: rawProject.description,
    descriptionEn: rawProject.description,
    // ...
  };

  // 2. 内容处理
  const content = processMarkdown(rawProject.content);

  // 3. 标签生成
  const tags = await extractTags(rawProject);

  // 4. Slug生成
  const slug = generateSlug(base.name, base.nameEn);

  // 5. 多语言翻译
  const translated = await translateIfNeeded(base, content);

  return {
    ...base,
    ...translated,
    slug,
    tags,
    content,
    source,
    externalLinks: buildExternalLinks(rawProject),
    status: 'ACTIVE'
  };
}

7.4 任务队列配置

// src/lib/queue/producer.ts
import { Queue } from 'bullmq';
import Redis from 'ioredis';

const connection = new Redis({
  host: process.env.REDIS_HOST,
  port: 6379,
  maxRetriesPerRequest: 3
});

export const ingestQueue = new Queue('project-ingestion', { connection });

export async function scheduleIngest(url: string) {
  await ingestQueue.add('ingest-project', { url }, {
    attempts: 3,
    backoff: { type: 'exponential', delay: 5000 },
    removeOnComplete: { count: 1000 },
    removeOnFail: { count: 5000 }
  });
}

export async function scheduleBulkIngest(urls: string[]) {
  const jobs = urls.map(url => ({
    name: 'ingest-project',
    data: { url }
  }));

  await ingestQueue.addBulk(jobs);
}
// src/lib/queue/consumer.ts
import { Worker } from 'bullmq';
import { scrapeProject } from '../scrapers';
import { normalizeProject } from '../processors/normalizer';
import { validateProject } from '../validators';
import { prisma } from '../prisma';

const worker = new Worker('project-ingestion', async (job) => {
  const { url } = job.data;

  // 1. 识别平台并采集
  const scraper = identifyScraper(url);
  const rawProject = await scraper.fetchFull(url);

  // 2. 标准化处理
  const normalized = await normalizeProject(rawProject, scraper.platform);

  // 3. 质量验证
  const validation = await validateProject(normalized);

  if (!validation.passed) {
    throw new Error(`Validation failed: ${validation.reasons.join(', ')}`);
  }

  // 4. 去重检测
  const existing = await checkDuplicate(normalized);
  if (existing.exists) {
    return { action: 'skipped', reason: 'duplicate', projectId: existing.project.id };
  }

  // 5. 写入数据库(通过Webhook API
  const response = await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/webhook/projects`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      apiKey: process.env.WEBHOOK_API_KEY,
      projects: [normalized]
    })
  });

  if (!response.ok) {
    throw new Error(`Failed to store project: ${response.statusText}`);
  }

  return { action: 'created', projectId: result.id };
}, { connection });

8. 实施路径

8.1 阶段规划

Phase 1: GitHub MVPWeek 1-2

  • 实现 GitHub 采集器(REST + GraphQL
  • 实现基础标签提取(topics + languages
  • 实现数据标准化流程
  • 集成现有 Webhook API
  • 添加基础质量评分

交付物:能从 GitHub URL 采集完整项目信息

  • 实现 GitHub Trending 解析
  • 配置定时任务(每日凌晨)
  • 实现去重逻辑
  • 添加监控告警

交付物:每日自动采集 trending 项目

Phase 3: Hugging Face 集成(Week 4

  • 实现 HF 采集器
  • 解析 HF YAML 元数据
  • 实现 HF Trending 采集
  • 扩展标签体系(pipeline_tag

交付物:支持 HF 模型/数据集

Phase 4: 智能化增强(Week 5-6

  • 实现 NLP 标签提取
  • 集成翻译 APIDeepL 或 GPT-4
  • 实现质量评分模型
  • 添加垃圾项目检测

交付物:自动化标签生成和翻译

Phase 5: 维护系统(Week 7

  • 实现增量更新机制
  • 实现死链检测
  • 实现热度衰减算法
  • 添加生命周期管理

交付物:数据自动维护

Phase 6: 扩展数据源(Week 8+

  • Papers with Code 集成
  • Reddit/HN 讨论挖掘
  • Product Hunt 集成
  • AI导航站爬取

交付物:多源数据融合

8.2 监控指标

采集指标:
  - 每日新增项目数: target >= 20
  - 采集成功率: target >= 95%
  - API调用次数: 监控配额使用

质量指标:
  - 高质量项目占比: target >= 70%
  - 垃圾项目过滤率: target >= 98%
  - 去重准确率: target >= 99%

维护指标:
  - 死链检测覆盖率: 100%
  - 更新及时性: 活跃项目7天内更新
  - 数据新鲜度: 90%项目在90天内更新

8.3 技术选型

任务队列: BullMQ (基于Redis)
定时任务: node-cron
爬虫框架: axios + cheerio
NLP处理: OpenAI API / Hugging Face Inference API
翻译服务: DeepL API / OpenAI API
监控告警: Sentry + 自定义webhook

9. 附录

9.1 API 密钥配置

# .env.local
GITHUB_TOKEN=ghp_xxxxx
HUGGING_FACE_TOKEN=hf_xxxxx
DEEPL_API_KEY=xxxxx
OPENAI_API_KEY=sk-xxxxx
WEBHOOK_API_KEY=xxxxx
REDIS_HOST=localhost

9.2 参考资源


文档版本: v1.0 最后更新: 2024年 维护者: AI项目导航站团队