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