灵猴市集Linghou Market

什么值得买商品搜索

什么值得买smzdm-search-deals

什么值得买搜索好价提取 用途 读取什么值得买搜索结果页中的好价条目,返回标题、价格、商城、更新时间、互动计数和详情地址。 执行前提 先打开 https://search.smzdm.com/?c=home&s=<keyword>&v=b 对应的搜索结果页,再用同一个 keyword 参数执行。搜索页可能受 Cookie、登录状态、访问频率或验证页影响;若页…

搜索优惠商品
版本1.0.0
扫描passed
更新2026-06-28
超时30000ms

匹配范围

*://search.smzdm.com/*

排除范围

未声明

能力声明

dom.read

代码

源码已展开
1const rawParams = params && typeof params === "object" ? params : {};2 3function integerParam(name, fallback, min, max) {4  const raw = rawParams[name];5  const value = raw === undefined || raw === null || raw === "" ? fallback : Number(raw);6  if (!Number.isInteger(value) || value < min || value > max) {7    throw new Error(`${name} must be an integer between ${min} and ${max}`);8  }9  return value;10}11 12function requiredKeyword() {13  const raw = rawParams.keyword ?? rawParams.query;14  const keyword = raw === undefined || raw === null ? "" : String(raw).trim();15  if (!keyword) throw new Error("keyword is required");16  return keyword;17}18 19function sleep(ms) {20  return new Promise(resolve => setTimeout(resolve, ms));21}22 23function normalizeText(value) {24  return (value || "").replace(/\s+/g, " ").trim();25}26 27function normalizeCount(text) {28  const raw = normalizeText(text).replace(/,/g, "");29  const match = raw.match(/(\d+(?:\.\d+)?)\s*([万kK]?)/);30  if (!match) return 0;31  const base = Number(match[1]);32  if (!Number.isFinite(base)) return 0;33  if (match[2] === "万") return Math.round(base * 10000);34  if (match[2] === "k" || match[2] === "K") return Math.round(base * 1000);35  return Math.round(base);36}37 38function trustedDealUrl(raw) {39  const text = normalizeText(raw);40  if (!text) return "";41  try {42    const url = text.startsWith("/")43      ? new URL(text, "https://www.smzdm.com")44      : new URL(text, location.href);45    const hostname = url.hostname.toLowerCase();46    if (url.protocol !== "https:") return "";47    if (hostname !== "www.smzdm.com" && hostname !== "post.smzdm.com") return "";48    return url.toString();49  } catch {50    return "";51  }52}53 54function directText(el) {55  if (!el) return "";56  return Array.from(el.childNodes)57    .filter(node => node.nodeType === Node.TEXT_NODE)58    .map(node => normalizeText(node.textContent))59    .filter(Boolean)60    .join(" ");61}62 63function extractDeals(doc, limit) {64  const rows = doc.querySelectorAll("li.feed-row-wide");65  const results = [];66 67  for (const row of rows) {68    if (results.length >= limit) break;69    const titleEl =70      row.querySelector("h5.feed-block-title > a") ||71      row.querySelector("h5 > a") ||72      row.querySelector("a[href*='/p/']");73    if (!titleEl) continue;74 75    const title = normalizeText(titleEl.getAttribute("title") || titleEl.textContent);76    const url = trustedDealUrl(titleEl.getAttribute("href") || titleEl.href || "");77    if (!title || !url) continue;78 79    const extrasEl =80      row.querySelector(".z-feed-foot-r .feed-block-extras") ||81      row.querySelector(".feed-block-extras");82    const mallEl =83      row.querySelector(".z-feed-foot-r .feed-block-extras span") ||84      row.querySelector(".z-feed-foot-r span");85    const zhiEl = row.querySelector(".price-btn-up .unvoted-wrap span");86    const buzhiEl = row.querySelector(".price-btn-down .unvoted-wrap span");87    const favEl = row.querySelector(".feed-btn-fav span");88    const commentEl = row.querySelector(".feed-btn-comment");89 90    results.push({91      rank: results.length + 1,92      title,93      price: normalizeText(row.querySelector(".z-highlight")?.textContent || ""),94      mall: normalizeText(mallEl?.textContent || ""),95      updated_at: directText(extrasEl),96      zhi_count: normalizeCount(zhiEl?.textContent || ""),97      buzhi_count: normalizeCount(buzhiEl?.textContent || ""),98      favorite_count: normalizeCount(favEl?.textContent || ""),99      comments: normalizeCount(commentEl?.textContent || commentEl?.getAttribute("title") || ""),100      url,101    });102  }103 104  return results;105}106 107async function waitForDeals(doc, limit, timeoutMs) {108  const deadline = Date.now() + timeoutMs;109  let items = extractDeals(doc, limit);110  while (Date.now() < deadline && items.length === 0) {111    await sleep(250);112    items = extractDeals(doc, limit);113  }114  return items;115}116 117function assertCurrentPage(targetUrl, keyword) {118  const current = new URL(location.href);119  if (current.hostname !== "search.smzdm.com" || current.searchParams.get("s") !== keyword) {120    throw new Error(`Open ${targetUrl} first, then run this script`);121  }122}123 124const keyword = requiredKeyword();125const limit = integerParam("limit", 20, 1, 100);126const timeoutMs = integerParam("timeoutMs", 8000, 1000, 30000);127const targetUrl = `https://search.smzdm.com/?c=home&s=${encodeURIComponent(keyword)}&v=b`;128assertCurrentPage(targetUrl, keyword);129 130const items = await waitForDeals(document, limit, timeoutMs);131 132if (items.length === 0) {133  throw new Error(`No deal results found on ${targetUrl}`);134}135 136return items.slice(0, limit);