灵猴市集Linghou Market

36氪热榜新闻

36氪36kr-hot-news

36氪热榜文章提取 用途 读取 36氪热榜页面中已经渲染出的文章链接,返回排名、标题和文章地址。 执行前提 先按参数打开对应热榜页面后再执行。type 为 catalog 时打开 https://www.36kr.com/hot-list/catalog;type 为 renqi、zonghe 或 shoucang 时打开 https://www.36kr…

新闻创投热榜
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://*.36kr.com/hot-list/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1const rawParams = params && typeof params === "object" ? params : {};2const typeLabels = {3  catalog: "热门资讯",4  renqi: "人气榜",5  zonghe: "综合榜",6  shoucang: "收藏榜",7};8 9function integerParam(name, fallback, min, max) {10  const raw = rawParams[name];11  const value = raw === undefined || raw === null || raw === "" ? fallback : Number(raw);12  if (!Number.isInteger(value) || value < min || value > max) {13    throw new Error(`${name} must be an integer between ${min} and ${max}`);14  }15  return value;16}17 18function stringParam(name, fallback) {19  const raw = rawParams[name];20  if (raw === undefined || raw === null || raw === "") return fallback;21  return String(raw).trim();22}23 24function shanghaiDate() {25  return new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);26}27 28function normalizeDate(raw) {29  const value = raw || shanghaiDate();30  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {31    throw new Error("date must use YYYY-MM-DD");32  }33  return value;34}35 36function buildHotUrl(type, date) {37  if (type === "catalog") return "https://www.36kr.com/hot-list/catalog";38  return `https://www.36kr.com/hot-list/${type}/${date}/1`;39}40 41function sleep(ms) {42  return new Promise(resolve => setTimeout(resolve, ms));43}44 45function normalizeText(value) {46  return (value || "").replace(/\s+/g, " ").trim();47}48 49function normalizeArticleUrl(href) {50  if (!href) return "";51  try {52    const url = new URL(href, "https://36kr.com");53    if (!/(\.|^)36kr\.com$/.test(url.hostname)) return "";54    if (!/\/p\/\d+/.test(url.pathname)) return "";55    return url.toString();56  } catch {57    return "";58  }59}60 61function extractItems(doc) {62  const seen = new Set();63  const results = [];64  const links = doc.querySelectorAll("a[href*='/p/'], a[href*=\"/p/\"]");65 66  for (const link of links) {67    const title = normalizeText(link.getAttribute("title") || link.textContent);68    const url = normalizeArticleUrl(link.getAttribute("href") || link.href || "");69    if (!title || title.length < 5 || !url) continue;70    const key = `${title}\n${url}`;71    if (seen.has(key) || seen.has(url) || seen.has(title)) continue;72    seen.add(key);73    seen.add(url);74    seen.add(title);75    results.push({ title, url });76  }77 78  return results;79}80 81async function waitForItems(doc, timeoutMs) {82  const deadline = Date.now() + timeoutMs;83  let items = extractItems(doc);84  while (Date.now() < deadline && items.length === 0) {85    await sleep(250);86    items = extractItems(doc);87  }88  return items;89}90 91function assertCurrentPage(targetUrl) {92  const current = new URL(location.href);93  const target = new URL(targetUrl);94  const currentPath = current.pathname.replace(/\/$/, "");95  const targetPath = target.pathname.replace(/\/$/, "");96  if (current.origin !== target.origin || currentPath !== targetPath) {97    throw new Error(`Open ${targetUrl} first, then run this script`);98  }99}100 101const limit = integerParam("limit", 20, 1, 50);102const timeoutMs = integerParam("timeoutMs", 8000, 1000, 30000);103const type = stringParam("type", "catalog");104if (!Object.prototype.hasOwnProperty.call(typeLabels, type)) {105  throw new Error(`type must be one of ${Object.keys(typeLabels).join(", ")}`);106}107 108const date = normalizeDate(stringParam("date", ""));109const targetUrl = buildHotUrl(type, date);110assertCurrentPage(targetUrl);111 112const items = await waitForItems(document, timeoutMs);113 114if (items.length === 0) {115  throw new Error(`No hot-list items found on ${targetUrl}`);116}117 118return items.slice(0, limit).map((item, index) => ({119  rank: index + 1,120  title: item.title,121  url: item.url,122}));