匹配范围
*://xueqiu.com/**://*.xueqiu.com/*xueqiu-stock-search雪球股票搜索 用途 按股票代码或名称搜索雪球股票,返回匹配股票的代码、名称、交易所、价格、涨跌幅、行业、交易状态和雪球页面地址。 执行前提 先打开 https://xueqiu.com/ 或雪球站内页面。请求会使用当前浏览器 Cookie;接口可能受登录态、风控、Cookie 状态和访问频率影响,脚本不会绕过登录、验证码或风控。 参数 query:必填,股…
*://xueqiu.com/**://*.xueqiu.com/*dom.read1const rawParams = typeof params === "object" && params !== null ? params : {};2 3function assertXueqiuPage() {4 if (!/(^|\.)xueqiu\.com$/i.test(location.hostname)) {5 throw new Error("请先打开 xueqiu.com 页面再执行脚本");6 }7}8 9function readQuery() {10 const query = String(rawParams.query ?? "").trim();11 if (!query) {12 throw new Error("query 为必填参数,请输入股票代码或名称");13 }14 return query;15}16 17function readLimit() {18 const value = rawParams.limit ?? 10;19 const limit = Number(value);20 if (!Number.isInteger(limit) || limit < 1 || limit > 50) {21 throw new Error("limit 必须是 1 到 50 的整数");22 }23 return limit;24}25 26function firstValue() {27 for (const value of arguments) {28 if (value !== undefined && value !== null && value !== "") return value;29 }30 return null;31}32 33function numericOrText(value) {34 if (value === undefined || value === null || value === "") return null;35 const number = Number(value);36 return Number.isFinite(number) ? number : String(value);37}38 39function normalizeSymbol(stock) {40 const rawCode = String(firstValue(stock.code, stock.symbol, "")).trim().toUpperCase();41 if (!rawCode) return "";42 if (/^(SH|SZ|BJ)[0-9A-Z.]+$/i.test(rawCode)) return rawCode;43 44 const exchange = String(firstValue(stock.exchange, stock.market, "")).trim().toUpperCase();45 const prefixMap = {46 SH: "SH",47 SSE: "SH",48 SHSE: "SH",49 XSHG: "SH",50 SZ: "SZ",51 SZSE: "SZ",52 XSHE: "SZ",53 BJ: "BJ",54 BSE: "BJ",55 XBEI: "BJ",56 };57 const prefix = prefixMap[exchange];58 if (prefix && /^[0-9]{6}$/.test(rawCode)) return `${prefix}${rawCode}`;59 return rawCode;60}61 62async function readJson(response) {63 try {64 return await response.json();65 } catch {66 return null;67 }68}69 70function errorDetail(payload, fallback) {71 return firstValue(payload?.error_description, payload?.errorMessage, payload?.message, fallback);72}73 74assertXueqiuPage();75 76const query = readQuery();77const limit = readLimit();78const url = `https://xueqiu.com/stock/search.json?code=${encodeURIComponent(query)}&size=${encodeURIComponent(limit)}`;79 80const response = await fetch(url, {81 headers: { Accept: "application/json" },82 credentials: "include",83});84 85const payload = await readJson(response);86 87if (!response.ok) {88 throw new Error(`请求雪球股票搜索失败:${errorDetail(payload, `HTTP ${response.status}`)}`);89}90 91if (payload?.error_code || payload?.error_description) {92 throw new Error(`请求雪球股票搜索失败:${errorDetail(payload, payload.error_code)}`);93}94 95const stocks = Array.isArray(payload?.stocks) ? payload.stocks : [];96if (!stocks.length) {97 throw new Error(`没有找到与“${query}”匹配的股票`);98}99 100return stocks.slice(0, limit).map((stock, index) => {101 const symbol = normalizeSymbol(stock);102 return {103 rank: index + 1,104 symbol,105 name: firstValue(stock.name, stock.stock_name, stock.title),106 exchange: firstValue(stock.exchange, stock.market),107 price: numericOrText(firstValue(stock.current, stock.price, stock.last)),108 changePercent: numericOrText(firstValue(stock.percentage, stock.percent, stock.change_percent, stock.changePercent)),109 industry: firstValue(stock.industry, stock.industry_name, stock.industryName),110 marketStatus: firstValue(stock.market_status, stock.marketStatus, stock.status),111 url: symbol ? `https://xueqiu.com/S/${symbol}` : null,112 };113});