灵猴市集Linghou Market

掘金推荐文章

掘金juejin-recommend-articles

掘金推荐文章 用途 读取掘金首页推荐文章流,返回文章标题、摘要、阅读数、点赞数、评论数、作者、标签、文章链接和下一页游标。 执行前提 请在 juejin.cn 页面执行。该脚本读取公开推荐接口;通常不要求登录,但推荐流可能随时间、地区、风控策略或接口变更而变化。 参数 limit:可选,返回数量,整数,范围 1-100,默认 20。 cursor:可选,分…

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

匹配范围

*://*.juejin.cn/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1const input = typeof params === 'object' && params !== null ? params : {};2 3function readLimit(value, fallback, max) {4  const raw = value == null || value === '' ? fallback : value;5  const n = typeof raw === 'number'6    ? raw7    : typeof raw === 'string' && /^[1-9]\d*$/.test(raw)8      ? Number(raw)9      : NaN;10  if (!Number.isSafeInteger(n) || n < 1 || n > max) {11    throw new Error(`limit must be an integer from 1 to ${max}`);12  }13  return n;14}15 16function readCursor(value, fallback) {17  const raw = value == null || value === '' ? fallback : value;18  if (typeof raw === 'number' && Number.isSafeInteger(raw) && raw >= 0) {19    return String(raw);20  }21  if (typeof raw === 'string' && /^(0|[1-9]\d*)$/.test(raw)) {22    return raw;23  }24  throw new Error('cursor must be a non-negative integer string');25}26 27function readResponseCursor(value) {28  if (value == null || value === '') {29    return '';30  }31  return readCursor(value, '0');32}33 34function readOptionalNumber(value) {35  if (value == null || value === '') {36    return null;37  }38  const n = Number(value);39  if (!Number.isFinite(n)) {40    return null;41  }42  return n;43}44 45function readArticleId(value) {46  const id = String(value || '').trim();47  if (!/^\d{16,20}$/.test(id)) {48    throw new Error('API returned a malformed article id');49  }50  return id;51}52 53function mapArticle(row, rank, nextCursor, hasMore) {54  const info = row?.item_info || {};55  const article = info.article_info || {};56  const author = info.author_user_info || {};57  const tags = Array.isArray(info.tags)58    ? info.tags.map((tag) => tag?.tag_name).filter(Boolean).slice(0, 6).join(', ')59    : '';60  const articleId = readArticleId(article.article_id);61  return {62    rank,63    article_id: articleId,64    title: String(article.title || '').trim(),65    brief: String(article.brief_content || '').trim(),66    views: readOptionalNumber(article.view_count),67    likes: readOptionalNumber(article.digg_count),68    comments: readOptionalNumber(article.comment_count),69    author: String(author.user_name || '').trim(),70    tags,71    url: `https://juejin.cn/post/${articleId}`,72    next_cursor: nextCursor,73    has_more: hasMore,74  };75}76 77async function fetchJson(url, body) {78  const response = await fetch(url, {79    method: 'POST',80    credentials: 'include',81    headers: {82      accept: 'application/json',83      'content-type': 'application/json',84    },85    body: JSON.stringify(body),86  });87  if (response.status === 429) {88    throw new Error('request was rate limited');89  }90  if (!response.ok) {91    throw new Error(`request failed with HTTP ${response.status}`);92  }93  const payload = await response.json();94  if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.prototype.hasOwnProperty.call(payload, 'err_no')) {95    throw new Error('API returned a malformed response');96  }97  if (payload.err_no !== 0) {98    throw new Error(`API returned err_no ${payload.err_no}: ${payload.err_msg || ''}`);99  }100  return payload;101}102 103const limit = readLimit(input.limit, 20, 100);104const cursor = readCursor(input.cursor, '0');105const payload = await fetchJson('https://api.juejin.cn/recommend_api/v1/article/recommend_all_feed', {106  id_type: 2,107  client_type: 2608,108  sort_type: 200,109  limit,110  cursor,111});112 113const rows = Array.isArray(payload.data) ? payload.data : [];114if (rows.length === 0) {115  throw new Error('API returned no articles');116}117const nextCursor = readResponseCursor(payload.cursor);118if (payload.has_more != null && typeof payload.has_more !== 'boolean') {119  throw new Error('API returned a malformed has_more flag');120}121if (payload.has_more === true && !nextCursor) {122  throw new Error('API returned has_more without a next cursor');123}124const hasMore = payload.has_more == null ? '' : String(payload.has_more);125 126return rows.slice(0, limit).map((row, index) => mapArticle(row, index + 1, nextCursor, hasMore));