灵猴市集Linghou Market

掘金热门文章

掘金juejin-hot-articles

掘金热门文章 用途 读取掘金指定分类的热门文章榜单,返回文章标题、摘要、阅读数、点赞数、评论数、热度排名、作者和文章链接。 执行前提 请在 juejin.cn 页面执行。该脚本读取公开文章榜单接口;通常不要求登录,但请求频率过高、网络限制或接口临时变更可能导致失败。 参数 category:可选,分类 slug 或数字分类 ID。slug 支持 backe…

文章热门内容提取
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://*.juejin.cn/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1const input = typeof params === 'object' && params !== null ? params : {};2 3const categoryAliases = {4  backend: '6809637769959178254',5  frontend: '6809637767543259144',6  android: '6809635626879549454',7  ios: '6809635626661445640',8  ai: '6809637773935378440',9};10 11function readLimit(value, fallback, max) {12  const raw = value == null || value === '' ? fallback : value;13  const n = typeof raw === 'number'14    ? raw15    : typeof raw === 'string' && /^[1-9]\d*$/.test(raw)16      ? Number(raw)17      : NaN;18  if (!Number.isSafeInteger(n) || n < 1 || n > max) {19    throw new Error(`limit must be an integer from 1 to ${max}`);20  }21  return n;22}23 24function resolveCategory(value) {25  if (value == null || value === '') {26    return categoryAliases.backend;27  }28  const raw = String(value).trim();29  if (/^\d{16,20}$/.test(raw)) {30    return raw;31  }32  const slug = raw.toLowerCase();33  if (categoryAliases[slug]) {34    return categoryAliases[slug];35  }36  throw new Error(`category must be one of: ${Object.keys(categoryAliases).join(', ')}, or a numeric category id`);37}38 39function readOptionalNumber(value) {40  if (value == null || value === '') {41    return null;42  }43  const n = Number(value);44  if (!Number.isFinite(n)) {45    return null;46  }47  return n;48}49 50function readArticleId(value) {51  const id = String(value || '').trim();52  if (!/^\d{16,20}$/.test(id)) {53    throw new Error('API returned a malformed article id');54  }55  return id;56}57 58function mapArticle(row, rank) {59  const content = row?.content || {};60  const counter = row?.content_counter || {};61  const author = row?.author || {};62  const articleId = readArticleId(content.content_id);63  return {64    rank,65    article_id: articleId,66    title: String(content.title || '').trim(),67    brief: String(content.brief || '').trim(),68    views: readOptionalNumber(counter.view),69    likes: readOptionalNumber(counter.like),70    comments: readOptionalNumber(counter.comment_count),71    hot_rank: readOptionalNumber(counter.hot_rank),72    author: String(author.name || '').trim(),73    url: `https://juejin.cn/post/${articleId}`,74  };75}76 77async function fetchJson(url) {78  const response = await fetch(url, {79    method: 'GET',80    credentials: 'include',81    headers: { accept: 'application/json' },82  });83  if (response.status === 429) {84    throw new Error('request was rate limited');85  }86  if (!response.ok) {87    throw new Error(`request failed with HTTP ${response.status}`);88  }89  const payload = await response.json();90  if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.prototype.hasOwnProperty.call(payload, 'err_no')) {91    throw new Error('API returned a malformed response');92  }93  if (payload.err_no !== 0) {94    throw new Error(`API returned err_no ${payload.err_no}: ${payload.err_msg || ''}`);95  }96  return payload;97}98 99const limit = readLimit(input.limit, 20, 50);100const categoryId = resolveCategory(input.category);101const url = new URL('https://api.juejin.cn/content_api/v1/content/article_rank');102url.searchParams.set('category_id', categoryId);103url.searchParams.set('type', 'hot');104 105const payload = await fetchJson(url.toString());106const rows = Array.isArray(payload.data) ? payload.data : [];107if (rows.length === 0) {108  throw new Error('API returned no articles');109}110 111return rows.slice(0, limit).map((row, index) => mapArticle(row, index + 1));