匹配范围
*://www.dongchedi.com/*dongchedi-car-search用途 按关键词搜索懂车帝车系,返回车系 ID、名称、品牌、指导价、经销商价、图片数量和车系页面链接。 执行前提 需要在懂车帝网页中执行,建议先打开 https://www.dongchedi.com/ 或任意懂车帝页面。脚本读取公开页面数据,不需要登录。若页面结构变化、网络风控或搜索结果为空,脚本会返回错误。 参数 keyword:必填,搜索关键词,例如 …
*://www.dongchedi.com/*dom.read1return await (async () => {2 const DCD_BASE = "https://www.dongchedi.com";3 const SERIES_CELL_TYPE = 26;4 const FALLBACK_SHELL_KEYS = ["__hasUrlCity", "is_gray", "has_gray", "clientIp", "sensitiveSeriesIdList"];5 6 const input = typeof params === "object" && params ? params : {};7 8 function clean(value) {9 return String(value == null ? "" : value).replace(/\s+/g, " ").trim();10 }11 12 function fail(message) {13 throw new Error(message);14 }15 16 function requireArray(value, label) {17 if (!Array.isArray(value)) {18 fail(`${label} 返回的数据结构不符合预期,应为数组。`);19 }20 return value;21 }22 23 function requireStableId(value, label) {24 const id = String(value ?? "").trim();25 if (!/^\d+$/.test(id) || id === "0") {26 fail(`${label} 未包含稳定的数字 ID。`);27 }28 return id;29 }30 31 function requireText(value, label) {32 const text = clean(value);33 if (!text) {34 fail(`${label} 未包含稳定文本。`);35 }36 return text;37 }38 39 function requireLimit(value, fallback, max) {40 const raw = value == null || value === "" ? fallback : value;41 const n = typeof raw === "number" ? raw : Number(String(raw).trim());42 if (!Number.isInteger(n) || n < 1 || n > max) {43 fail(`limit 必须是 1 到 ${max} 之间的整数。`);44 }45 return n;46 }47 48 function extractPageProps(html) {49 const match = String(html || "").match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);50 if (!match) return null;51 try {52 const data = JSON.parse(match[1]);53 return data && data.props && data.props.pageProps ? data.props.pageProps : null;54 } catch {55 return null;56 }57 }58 59 function isFallbackShell(pageProps) {60 if (!pageProps || typeof pageProps !== "object") return true;61 return Object.keys(pageProps).filter((key) => !FALLBACK_SHELL_KEYS.includes(key)).length === 0;62 }63 64 async function fetchPageProps(path, label) {65 const response = await fetch(`${DCD_BASE}${path}`, {66 credentials: "include",67 headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },68 });69 if (!response.ok) {70 fail(`${label} 请求失败:HTTP ${response.status}`);71 }72 const pageProps = extractPageProps(await response.text());73 if (!pageProps) {74 fail(`${label} 未返回页面数据,页面结构可能已变化或请求被拦截。`);75 }76 if (isFallbackShell(pageProps)) {77 fail(`${label} 返回空壳页面,关键词可能无结果或页面入口已变化。`);78 }79 return pageProps;80 }81 82 function parseSearchRows(searchData, limit) {83 const data = requireArray(searchData?.data, "搜索结果");84 const rows = [];85 for (const [index, item] of data.entries()) {86 if (item?.cell_type !== SERIES_CELL_TYPE) continue;87 const seriesId = requireStableId(item?.series_id, `第 ${index + 1} 条车系结果`);88 const display = item.display || {};89 const name = requireText(display.series_name || display.title, `第 ${index + 1} 条车系名称`);90 rows.push({91 rank: rows.length + 1,92 series_id: seriesId,93 name,94 brand: clean(display.sub_brand_name),95 official_price: clean(display.official_price),96 dealer_price: clean(display.agent_price),97 pictures: Number.isFinite(Number(display.picture_num)) ? Number(display.picture_num) : null,98 url: `${DCD_BASE}/auto/series/${seriesId}`,99 });100 if (rows.length >= limit) break;101 }102 return rows;103 }104 105 const keyword = clean(input.keyword ?? input.query ?? input.q);106 if (!keyword) {107 fail("请通过 params.keyword 传入搜索关键词。");108 }109 110 const limit = requireLimit(input.limit, 15, 30);111 const pageProps = await fetchPageProps(`/search?keyword=${encodeURIComponent(keyword)}`, `搜索「${keyword}」`);112 const rows = parseSearchRows(pageProps.searchData, limit);113 if (rows.length === 0) {114 fail(`没有匹配到车系结果:${keyword}`);115 }116 return rows;117})();