Extract GitHub Trending Repositories

Site github.comTask get-github-trending-repositoriesVersion v10Updated Sep 16, 2026Category developer-tools

Read GitHub Trending repositories for daily, weekly, or monthly periods with optional programming-language, spoken-language, page, and cross-page comparison filters, verify URL parameter retention, and distinguish real pagination from duplicated or unavailable pages. This skill was captured from a live agent session on github.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Given {period} (daily, weekly, or monthly), optional {language}, optional {spoken-language-code}, and one or more numeric {page} values, read GitHub Trending without mutations. Return each repository's page rank, owner, name, canonical URL, description, primary language, total stars, total forks, period-specific stars gained, language color, built-by contributors, all requested filters, final URL, and diagnostics. Verify whether GitHub retains every requested query parameter after navigation. When multiple pages are requested, compare canonical repository sets, deduplicate only when producing a stitched collection, and report distinct, duplicate, empty, blocked, or errored pages without assuming that a requested combination has results.

Use Cases

  • Listing current all-language or language-specific trending repositories.
  • Reading daily, weekly, or monthly Trending pages.
  • Applying GitHub's spoken-language filter.
  • Testing a specific numeric page, including page 0 or pages beyond the normal range.
  • Verifying that since, page, spoken_language_code, and the language path survive navigation, including case-preserving or case-normalized paths.
  • Testing whether pagination is genuine by comparing page 1 with a target or probe page.
  • Returning normalized integer counts when GitHub displays K, M, or B abbreviations.
  • Distinguishing an explicit empty result from a login wall, rate limit, CAPTCHA, changed layout, or duplicate page.

Automation Flow

  1. Normalize {period}, optional {language}, optional {spoken-language-code}, target {page}, and optional comparison/probe pages. Default to page 1; preserve the caller's language slug and URL-encode path components. Do not assume the filter combination has results.
  2. Construct the direct URL without visiting the homepage or using controls: https://github.com/trending/{language}?since={period}&page={page}&spoken_language_code={spoken-language-code} Omit the language path and spoken-language parameter only when those inputs are absent. A page-zero probe is allowed when requested: use page=0 exactly and classify its observed result.
  3. Navigate directly to the target URL and run the evaluator below in the same browser call. For duplicate-page testing, also navigate directly to comparison pages such as page 1 and run the same evaluator. Batch navigations and evaluations when supported; do not click filters, pagination controls, or repository links.
  4. Use the final live URL, not merely the requested URL, as provenance. Parse its path and query parameters. Set filter_retained only when the final URL retains the requested since, exact requested page string/value, optional spoken-language code, and the requested language identity case-insensitively; report both requested and observed values so case normalization is visible.
  5. Treat a page with repository cards or explicit empty-state text as successful. A page with neither is blocked or structurally changed unless diagnostics establish otherwise. Preserve the normalized empty message and return count 0 rather than fabricating repositories.
  6. Compare canonical repository URL sets between requested pages. Identical sets are duplicate_page; disjoint or partially different sets are distinct (with overlap details). A page-zero or high-page result is classified from its final URL and extracted content, never from the page number alone.
  7. Preserve one result per requested page, including final URL, observed filters, requested page, count, repositories, empty state, retention status, pagination status, and diagnostics. If producing a stitched collection, deduplicate by (period, canonical repository URL) while retaining first-seen page and rank. Page-local ranks remain page-local.
  8. Keep the operation read-only. Never star, fork, watch, or otherwise mutate repositories.

Run this deterministic extractor once on each loaded page:

(() => {
 const clean = s => (s || '').replace(/\s+/g, ' ').trim();
 const metric = value => {
  const m = clean(value).match(/[\d,.]+\s*[KMB]?/i);
  if (!m) return null;
  const raw = m[0].replace(/,/g, '').replace(/\s/g, '');
  const n = Number.parseFloat(raw);
  if (Number.isNaN(n)) return null;
  if (/b$/i.test(raw)) return Math.round(n * 1e9);
  if (/m$/i.test(raw)) return Math.round(n * 1e6);
  if (/k$/i.test(raw)) return Math.round(n * 1e3);
  return Math.round(n);
 };
 const url = location.href;
 const qs = new URL(url).searchParams;
 const pathMatch = location.pathname.match(/^\/trending(?:\/([^/?#]+))?/i);
 const observedLanguage = pathMatch?.[1] ? decodeURIComponent(pathMatch[1]) : null;
 const pageRaw = qs.get('page');
 const page = Number(pageRaw || '1');
 const period = qs.get('since');
 const spoken = qs.get('spoken_language_code');
 const rows = [...document.querySelectorAll('article.Box-row')];
 const repositories = rows.map((row, index) => {
  const link = row.querySelector('h2 a[href^="/"]');
  const href = link?.getAttribute('href') || '';
  const parts = href.split('/').filter(Boolean);
  const periodText = [...row.querySelectorAll('span')].map(e => clean(e.textContent)).find(t => /stars?\s+(today|this week|this month)/i.test(t)) || null;
  const periodMatch = periodText?.match(/([\d,.]+(?:\s*[KMB])?)\s+stars?\s+(today|this week|this month)/i) || null;
  const starLink = row.querySelector('a[href$="/stargazers"]');
  const forkLink = row.querySelector('a[href$="/forks"]');
  return {
   rank: index + 1,
   owner: parts[0] || null,
   repository: parts[1] || null,
   url: href ? new URL(href, location.origin).href : null,
   description: clean(row.querySelector('p')?.textContent) || null,
   language: clean(row.querySelector('[itemprop="programmingLanguage"]')?.textContent) || null,
   language_color: row.querySelector('.repo-language-color')?.getAttribute('style')?.match(/#[0-9A-Fa-f]{6}/)?.[0] || null,
   stars_total: metric(starLink?.textContent),
   forks_total: metric(forkLink?.textContent),
   stars_period: periodMatch ? metric(periodMatch[1]) : null,
   period_label: periodMatch ? `stars ${periodMatch[2].toLowerCase()}` : null,
   built_by: [...row.querySelectorAll('img[alt^="@"]').values()].map(img => clean(img.alt.slice(1))).filter(Boolean)
  };
 });
 const empty_state = [...document.querySelectorAll('h1,h2,h3,p,[role="status"]')].map(e => clean(e.textContent)).find(t => /there(?:'|’)s|there are not|don(?:'|’)t have|no trending repositories|no repositories found|nothing trending/i.test(t) && /trending|repositories/i.test(t)) || null;
 const diagnostics = [...document.querySelectorAll('[role="alert"], .flash, .js-flash-alert')].map(e => clean(e.textContent)).filter(Boolean).slice(0, 20);
 const body = clean(document.body?.innerText);
 const blocked = !repositories.length && !empty_state && /sign in|captcha|rate limit|access denied|not found|error/i.test(body);
 const filters = {language: observedLanguage, since: period, page: pageRaw, spoken_language_code: spoken};
 const paginationLinks = [...document.querySelectorAll('a[href]')].map(a => a.href).filter(h => /[?&]page=\d+/i.test(h)).slice(0, 20);
 return {
  ok: !blocked && (repositories.length > 0 || Boolean(empty_state)),
  url,
  title: document.title,
  period,
  language: observedLanguage,
  spoken_language_code: spoken,
  requested_page: page,
  count: repositories.length,
  repositories,
  empty_state,
  filters,
  filter_retained: Boolean(period !== null && pageRaw !== null && (observedLanguage === null || /^\/trending\/[^/?#]+$/i.test(location.pathname))),
  pagination_links: paginationLinks,
  pagination_status: blocked ? 'blocked' : repositories.length ? 'unknown' : empty_state ? 'empty' : 'error',
  diagnostics
 };
})()

After extraction, strengthen filter_retained in the caller using the original requested values: compare since, page number/string semantics, spoken-language code exactly, and language path case-insensitively. Do not mark retention true merely because some query parameters exist.

Possible Friction Points

  • Direct Trending URLs are /trending?since={period}&page={page} and /trending/{language}?since={period}&page={page}; homepage navigation and filter clicks are unnecessary.
  • The optional spoken-language query parameter is exactly spoken_language_code={spoken-language-code}. Preserve it in output even when the result is empty.
  • GitHub may preserve or normalize the case of the language path. Compare language identity case-insensitively, but return the exact final URL and observed path.
  • Repository cards are conventionally article.Box-row; identity comes from the h2 a href, while total stars and forks come from links ending in /stargazers and /forks.
  • The period-specific metric is separate from total stars and is rendered as stars today, stars this week, or stars this month.
  • Counts may use commas or K/M/B suffixes; normalize only values actually present.
  • Built-by contributors use image alt text beginning with @; remove the prefix while preserving DOM order.
  • A page with no cards is not automatically empty. Require explicit empty-state text and return it; report login walls, rate limits, CAPTCHA, access denial, and structural changes as failures.
  • Numeric pagination uses the page query parameter. Page 0 and large page values may be empty, normalized, redirected, duplicated, or otherwise unsupported; classify only from observed content and final URL.
  • Successful navigation and retained parameters do not prove that pagination is genuine. Compare canonical repository URL sets. Identical sets must be reported as duplicate_page.
  • Page-local rank is not a global rank; rank deltas are meaningful only for repositories present on both compared pages.
  • CSS classes can change. Prefer repository hrefs, itemprop="programmingLanguage", metric-link suffixes, semantic status elements, and the live URL.
  • Keep the workflow read-only: never star, fork, watch, or otherwise mutate repositories.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=github.com&task=get-github-trending-repositories