匹配范围
*://www.dongchedi.com/*dongchedi-series-score用途 读取懂车帝车系公开页面的懂车分评分,返回综合、空间、动力、操控、舒适性、外观、内饰、配置 8 个维度,并附带同级车均值。 执行前提 需要在懂车帝网页中执行,建议打开目标车系页面,例如 https://www.dongchedi.com/auto/series/<id>。脚本读取公开页面数据,不需要登录。若未传入 seriesid,会尝试从当前页面 U…
*://www.dongchedi.com/*dom.read1return await (async () => {2 const DCD_BASE = "https://www.dongchedi.com";3 const FALLBACK_SHELL_KEYS = ["__hasUrlCity", "is_gray", "has_gray", "clientIp", "sensitiveSeriesIdList"];4 const AXES = [5 ["score", "综合"],6 ["space_score", "空间"],7 ["power_score", "动力"],8 ["control_score", "操控"],9 ["comfort_score", "舒适性"],10 ["appearance_score", "外观"],11 ["interiors_score", "内饰"],12 ["configuration_score", "配置"],13 ];14 15 const input = typeof params === "object" && params ? params : {};16 17 function clean(value) {18 return String(value == null ? "" : value).replace(/\s+/g, " ").trim();19 }20 21 function fail(message) {22 throw new Error(message);23 }24 25 function requireArray(value, label) {26 if (!Array.isArray(value)) {27 fail(`${label} 返回的数据结构不符合预期,应为数组。`);28 }29 return value;30 }31 32 function parseScore(raw) {33 const n = Number(raw);34 if (!Number.isFinite(n) || n <= 0) return null;35 return Number((n / 100).toFixed(2));36 }37 38 function normalizeSeriesId(rawInput) {39 const raw = clean(rawInput);40 const match = raw.match(/series\/(\d+)/) || raw.match(/^(\d+)$/);41 if (!match) {42 fail("series_id 必须是车系数字 ID,或包含 /auto/series/<id> 的链接。");43 }44 return match[1];45 }46 47 function currentSeriesId() {48 const match = String(location.href).match(/series\/(\d+)/);49 return match ? match[1] : "";50 }51 52 function extractPageProps(html) {53 const match = String(html || "").match(/<script id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);54 if (!match) return null;55 try {56 const data = JSON.parse(match[1]);57 return data && data.props && data.props.pageProps ? data.props.pageProps : null;58 } catch {59 return null;60 }61 }62 63 function isFallbackShell(pageProps) {64 if (!pageProps || typeof pageProps !== "object") return true;65 return Object.keys(pageProps).filter((key) => !FALLBACK_SHELL_KEYS.includes(key)).length === 0;66 }67 68 async function fetchPageProps(path, label) {69 const response = await fetch(`${DCD_BASE}${path}`, {70 credentials: "include",71 headers: { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" },72 });73 if (!response.ok) {74 fail(`${label} 请求失败:HTTP ${response.status}`);75 }76 const pageProps = extractPageProps(await response.text());77 if (!pageProps) {78 fail(`${label} 未返回页面数据,页面结构可能已变化或请求被拦截。`);79 }80 if (isFallbackShell(pageProps)) {81 fail(`${label} 返回空壳页面,车系 ID 可能不存在或页面入口已变化。`);82 }83 return pageProps;84 }85 86 function sameLevelAverage(reviewData) {87 const list = reviewData?.same_level_review;88 if (list == null) return reviewData?.average_series_review || {};89 requireArray(list, "同级车均值");90 return list.find((item) => String(item?.series_id) === "0") || list[0] || {};91 }92 93 function parseScoreBreakdown(scoreSimpleInfo, sameLevelAvg) {94 if (!scoreSimpleInfo || typeof scoreSimpleInfo !== "object" || Array.isArray(scoreSimpleInfo)) {95 fail("该车系暂无懂车分,可能是车主评价数量不足。");96 }97 const scoreInfo = scoreSimpleInfo || {};98 const avg = sameLevelAvg || {};99 return AXES.map(([key, label]) => ({100 dimension: label,101 score: parseScore(scoreInfo[key]),102 same_level_avg: parseScore(avg[key]),103 }));104 }105 106 const seriesId = normalizeSeriesId(input.series_id ?? input.seriesId ?? input.url ?? currentSeriesId());107 const pageProps = await fetchPageProps(`/auto/series/${seriesId}`, `车系评分 ${seriesId}`);108 const rows = parseScoreBreakdown(pageProps.scoreSimpleInfo, sameLevelAverage(pageProps.reviewData));109 if (rows.every((row) => row.score == null)) {110 fail("该车系暂无懂车分,可能是车主评价数量不足。");111 }112 return rows;113})();