灵猴市集Linghou Market

新浪财经股票排行

新浪财经sinafinance-stock-rank

新浪财经热搜股票榜提取 用途 读取新浪财经移动端股票搜索面板中的热搜榜或排行列表,返回股票名称、代码、市场、价格、涨跌信息和详情地址。 执行前提 先打开 https://finance.sina.cn/,页面正常加载后再执行脚本。脚本会点击页面中的搜索入口并尝试切换市场标签,然后读取已经渲染出的榜单 DOM。该交互依赖新浪财经移动端页面结构,可能受客户端脚…

股票排行行情
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://finance.sina.cn/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1const rawParams = params && typeof params === "object" ? params : {};2const marketLabels = {3  cn: "A股",4  hk: "港股",5  us: "美股",6  wh: "外汇",7  ft: "期货",8};9const marketTabAliases = {10  cn: ["cn", "hs"],11  hk: ["hk"],12  us: ["us"],13  wh: ["wh", "fx"],14  ft: ["ft"],15};16 17function integerParam(name, fallback, min, max) {18  const raw = rawParams[name];19  const value = raw === undefined || raw === null || raw === "" ? fallback : Number(raw);20  if (!Number.isInteger(value) || value < min || value > max) {21    throw new Error(`${name} must be an integer between ${min} and ${max}`);22  }23  return value;24}25 26function marketParam() {27  const value = String(rawParams.market || "cn").trim().toLowerCase();28  if (!Object.prototype.hasOwnProperty.call(marketLabels, value)) {29    throw new Error(`market must be one of ${Object.keys(marketLabels).join(", ")}`);30  }31  return value;32}33 34function sleep(ms) {35  return new Promise(resolve => setTimeout(resolve, ms));36}37 38function normalizeText(value) {39  return (value || "").replace(/\s+/g, " ").trim();40}41 42function assertSinaFinanceMobilePage() {43  const current = new URL(location.href);44  if (current.hostname !== "finance.sina.cn") {45    throw new Error("Open https://finance.sina.cn/ first, then run this script");46  }47}48 49function detectBlockedPage(doc) {50  const text = normalizeText(`${doc.title || ""} ${doc.body ? doc.body.innerText : ""}`).slice(0, 5000);51  if (/安全验证|验证码|人机验证|访问异常|请输入验证码|访问受限|页面加载失败/.test(text)) {52    return "The page is showing a verification or access-control challenge";53  }54  return "";55}56 57function dispatchActivation(el) {58  if (!el) return;59  for (const type of ["pointerdown", "mousedown", "mouseup", "click", "tap"]) {60    try {61      el.dispatchEvent(new Event(type, { bubbles: true, cancelable: true }));62    } catch {63      // Ignore event types unsupported by the current browser.64    }65  }66  if (typeof el.click === "function") el.click();67}68 69async function openSearchPanel() {70  const searchButton = document.querySelector("#actionSearch");71  if (!searchButton) {72    throw new Error("No #actionSearch element found; open https://finance.sina.cn/ first or check whether the page changed");73  }74  dispatchActivation(searchButton);75  await sleep(1800);76}77 78function findMarketTab(market) {79  const aliases = marketTabAliases[market] || [market];80  for (const alias of aliases) {81    const tab = document.querySelector(`[data-type="${alias}"]`);82    if (tab) return tab;83  }84 85  const label = marketLabels[market];86  const candidates = Array.from(document.querySelectorAll("[data-type], .tab-item, .j-tab, li, a, div"));87  return candidates.find(el => normalizeText(el.textContent) === label) || null;88}89 90async function activateMarketTab(market) {91  const tab = findMarketTab(market);92  if (!tab) {93    if (market === "cn") return marketLabels[market];94    throw new Error(`No market tab found for ${market}; the stock rank panel may not be open or the page structure changed`);95  }96  dispatchActivation(tab);97  await sleep(1800);98  return normalizeText(tab.textContent) || marketLabels[market];99}100 101function normalizeStockUrl(raw) {102  const value = normalizeText(raw);103  if (!value) return "";104  try {105    const url = value.startsWith("//")106      ? new URL(`https:${value}`)107      : new URL(value, "https://finance.sina.cn");108    if (url.protocol !== "http:" && url.protocol !== "https:") return "";109    return url.toString();110  } catch {111    return "";112  }113}114 115function extractStockRows(doc, market, marketName, limit) {116  const rows = [];117  const seen = new Set();118  const candidates = doc.querySelectorAll("#stock-list .j-stock-row, .j-stock-row");119 120  for (const el of candidates) {121    if (rows.length >= limit) break;122    const name = normalizeText(el.querySelector(".j-sname, .stock-name, .name")?.textContent || "");123    const symbol = normalizeText(el.querySelector(".stock-code, .j-symbol, .symbol, .code")?.textContent || "");124    if (!name && !symbol) continue;125    const key = `${name}\n${symbol}`;126    if (seen.has(key)) continue;127    seen.add(key);128 129    rows.push({130      rank: rows.length + 1,131      name,132      symbol,133      market: marketName || marketLabels[market],134      price: normalizeText(el.querySelector(".j-price, .stock-price, .price")?.textContent || ""),135      change: normalizeText(el.querySelector(".j-change, .stock-change, .change")?.textContent || ""),136      url: normalizeStockUrl(el.getAttribute("open-url") || el.querySelector("a[href]")?.getAttribute("href") || ""),137    });138  }139 140  return rows;141}142 143async function waitForStockRows(market, marketName, limit) {144  const deadline = Date.now() + 10000;145  let rows = extractStockRows(document, market, marketName, limit);146  while (Date.now() < deadline && rows.length === 0) {147    const blocked = detectBlockedPage(document);148    if (blocked) throw new Error(`${blocked}; complete it in the browser and run again`);149    await sleep(250);150    rows = extractStockRows(document, market, marketName, limit);151  }152  return rows;153}154 155const market = marketParam();156const limit = integerParam("limit", 20, 1, 50);157assertSinaFinanceMobilePage();158 159const blocked = detectBlockedPage(document);160if (blocked) throw new Error(`${blocked}; complete it in the browser and run again`);161 162await openSearchPanel();163const marketName = await activateMarketTab(market);164const items = await waitForStockRows(market, marketName, limit);165 166if (items.length === 0) {167  throw new Error(`No Sina Finance stock rank rows found for market "${market}"; the panel may be empty, blocked, or its DOM changed`);168}169 170return items.slice(0, limit);