GitHub趋势数据获取Skill github-trending

这个技能用于获取和展示GitHub上的趋势仓库和开发者数据,支持通过网页抓取或官方搜索API实现,适用于构建趋势仪表板、发现热门项目、跟踪GitHub趋势和数据分析。关键词包括:GitHub趋势、趋势仓库、开发者数据、网页抓取、API集成、数据获取、仪表板构建、热门项目发现。

后端开发 0 次安装 0 次浏览 更新于 3/22/2026

名称:github-trending 描述:获取并显示GitHub趋势仓库和开发者数据。用于构建展示趋势仓库的仪表板、发现热门项目或跟踪GitHub趋势。触发词包括GitHub趋势、趋势仓库、热门仓库、GitHub发现。

GitHub趋势数据

访问GitHub趋势仓库和开发者数据。

重要说明

GitHub不提供官方的趋势API。 必须直接抓取github.com/trending页面或使用GitHub搜索API作为替代方案。

方法一:直接网页抓取(推荐)

使用Cheerio抓取github.com/trending

import * as cheerio from 'cheerio';

interface TrendingRepo {
  owner: string;
  name: string;
  fullName: string;
  url: string;
  description: string;
  language: string;
  languageColor: string;
  stars: number;
  forks: number;
  starsToday: number;
}

async function scrapeTrending(options: {
  language?: string;
  since?: 'daily' | 'weekly' | 'monthly';
} = {}): Promise<TrendingRepo[]> {
  // 构建URL:github.com/trending 或 github.com/trending/typescript?since=weekly
  let url = 'https://github.com/trending';
  if (options.language) {
    url += `/${encodeURIComponent(options.language)}`;
  }
  if (options.since) {
    url += `?since=${options.since}`;
  }

  const response = await fetch(url, {
    headers: {
      'User-Agent': 'Mozilla/5.0 (compatible; TrendingBot/1.0)',
    },
  });
  
  if (!response.ok) {
    throw new Error(`获取趋势数据失败: ${response.status}`);
  }

  const html = await response.text();
  const $ = cheerio.load(html);
  const repos: TrendingRepo[] = [];

  // 每个趋势仓库位于article.Box-row元素中
  $('article.Box-row').each((_, element) => {
    const $el = $(element);
    
    // 获取仓库链接(例如,/owner/repo)
    const repoLink = $el.find('h2 a').attr('href')?.trim() || '';
    const [, owner, name] = repoLink.split('/');
    
    // 获取描述
    const description = $el.find('p.col-9').text().trim();
    
    // 获取语言
    const language = $el.find('[itemprop="programmingLanguage"]').text().trim();
    
    // 从彩色点获取语言颜色
    const langColorStyle = $el.find('.repo-language-color').attr('style') || '';
    const langColorMatch = langColorStyle.match(/background-color:\s*([^;]+)/);
    const languageColor = langColorMatch ? langColorMatch[1].trim() : '';
    
    // 获取星星数(总计)
    const starsText = $el.find('a[href$="/stargazers"]').text().trim();
    const stars = parseNumber(starsText);
    
    // 获取分支数
    const forksText = $el.find('a[href$="/forks"]').text().trim();
    const forks = parseNumber(forksText);
    
    // 获取今日/本周/本月新增星星数
    const starsTodayText = $el.find('.float-sm-right, .d-inline-block.float-sm-right').text().trim();
    const starsToday = parseNumber(starsTodayText);

    if (owner && name) {
      repos.push({
        owner,
        name,
        fullName: `${owner}/${name}`,
        url: `https://github.com${repoLink}`,
        description,
        language,
        languageColor,
        stars,
        forks,
        starsToday,
      });
    }
  });

  return repos;
}

function parseNumber(text: string): number {
  const clean = text.replace(/,/g, '').trim();
  if (clean.includes('k')) {
    return Math.round(parseFloat(clean) * 1000);
  }
  return parseInt(clean) || 0;
}

方法二:GitHub搜索API(官方替代方案)

使用GitHub的搜索API查找近期创建且星标数高的仓库:

interface GitHubSearchResult {
  total_count: number;
  items: GitHubRepo[];
}

interface GitHubRepo {
  full_name: string;
  html_url: string;
  description: string;
  language: string;
  stargazers_count: number;
  forks_count: number;
  created_at: string;
}

async function getTrendingViaSearch(options: {
  language?: string;
  days?: number;
  minStars?: number;
} = {}): Promise<GitHubRepo[]> {
  const days = options.days || 7;
  const minStars = options.minStars || 100;
  
  // 计算N天前的日期
  const date = new Date();
  date.setDate(date.getDate() - days);
  const since = date.toISOString().split('T')[0];

  // 构建搜索查询
  const queryParts = [
    `created:>${since}`,
    `stars:>=${minStars}`,
  ];
  if (options.language) {
    queryParts.push(`language:${options.language}`);
  }
  const query = queryParts.join(' ');

  const response = await fetch(
    `https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=stars&order=desc&per_page=25`,
    {
      headers: {
        'Accept': 'application/vnd.github.v3+json',
        'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`, // 可选但推荐
        'User-Agent': 'TrendingApp/1.0',
      },
    }
  );

  if (!response.ok) {
    throw new Error(`GitHub API错误: ${response.status}`);
  }

  const data: GitHubSearchResult = await response.json();
  return data.items;
}

注意: 搜索API有速率限制(未认证时每分钟10次请求,带令牌时每分钟30次)。

Next.js API路由(服务器端抓取)

// app/api/trending/route.ts
import { NextRequest } from 'next/server';
import * as cheerio from 'cheerio';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const language = searchParams.get('language') || '';
  const since = searchParams.get('since') || 'daily';

  try {
    let url = 'https://github.com/trending';
    if (language) url += `/${encodeURIComponent(language)}`;
    url += `?since=${since}`;

    const response = await fetch(url, {
      headers: { 'User-Agent': 'Mozilla/5.0 (compatible)' },
      next: { revalidate: 3600 }, // 缓存1小时
    });

    const html = await response.text();
    const repos = parseGitHubTrending(html);
    
    return Response.json(repos);
  } catch (error) {
    console.error('趋势抓取失败:', error);
    return Response.json(
      { error: '获取趋势仓库失败' },
      { status: 500 }
    );
  }
}

function parseGitHubTrending(html: string) {
  const $ = cheerio.load(html);
  const repos: any[] = [];

  $('article.Box-row').each((_, el) => {
    const $el = $(el);
    const repoLink = $el.find('h2 a').attr('href') || '';
    const [, owner, name] = repoLink.split('/');
    
    repos.push({
      owner,
      name,
      fullName: `${owner}/${name}`,
      url: `https://github.com${repoLink}`,
      description: $el.find('p.col-9').text().trim(),
      language: $el.find('[itemprop="programmingLanguage"]').text().trim(),
      stars: parseNumber($el.find('a[href$="/stargazers"]').text()),
      forks: parseNumber($el.find('a[href$="/forks"]').text()),
      starsToday: parseNumber($el.find('.float-sm-right').text()),
    });
  });

  return repos;
}

function parseNumber(text: string): number {
  const clean = text.replace(/,/g, '').trim();
  if (clean.includes('k')) return Math.round(parseFloat(clean) * 1000);
  return parseInt(clean) || 0;
}

React钩子

import { useState, useEffect } from 'react';

function useTrending(options: { language?: string; since?: string } = {}) {
  const [repos, setRepos] = useState([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    async function fetchTrending() {
      setIsLoading(true);
      try {
        const params = new URLSearchParams();
        if (options.language) params.set('language', options.language);
        if (options.since) params.set('since', options.since);

        const response = await fetch(`/api/trending?${params}`);
        if (!response.ok) throw new Error('获取失败');
        setRepos(await response.json());
      } catch (err) {
        setError(err instanceof Error ? err.message : '未知错误');
      } finally {
        setIsLoading(false);
      }
    }

    fetchTrending();
  }, [options.language, options.since]);

  return { repos, isLoading, error };
}

重要考虑因素

  1. 无官方API:GitHub趋势页面没有官方API - 抓取是唯一选项
  2. 速率限制:尊重GitHub服务器 - 积极缓存
  3. HTML结构变化:GitHub可能更改HTML - 监控断点
  4. User-Agent:始终包含User-Agent头部
  5. 仅服务器端:在服务器端进行抓取以避免CORS问题

资源