项目文件夹

文件
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

104 行
3.5 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/**
* autohome score — 口碑 (owner-rating) summary for a car series.
*
* Reads `__NEXT_DATA__.props.pageProps.baseData` (+ `qualityData`) from the
* koubei page `k.autohome.com.cn/<seriesId>`: overall rating, per-dimension
* scores, level, guide price, the reliability PPH (每百辆车故障数), and the
* competitor comparison. All unsigned, login-free. Returns a key/value sheet.
*
* Note: Autohome's per-review TEXT list loads from a separate signed XHR and
* is intentionally not scraped — this command surfaces the aggregate only.
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import {
AH_KOUBEI_BASE,
SCORE_COLUMNS,
CommandExecutionError,
EmptyResultError,
assertPlainObject,
ahFetch,
clean,
extractPageProps,
normalizeSeriesId,
} from './utils.js';
/** Number or null. */
function num(v) {
const n = Number(v);
return Number.isFinite(n) ? n : null;
}
/**
* Pure parser: koubei pageProps → field/value rows. Exported for unit tests.
*/
export function parseScore(pp, seriesId) {
const bd = assertPlainObject(pp?.baseData, 'autohome baseData');
const qd = (pp && pp.qualityData) || {};
const competitors = (Array.isArray(bd.cmpSeriesScore) ? bd.cmpSeriesScore : [])
.map((c) => {
const name = clean(c.seriesname || c.seriesName);
const s = c.average || c.score;
return name ? `${name}(${s})` : '';
})
.filter(Boolean)
.slice(0, 4)
.join(', ');
const fields = [
['series_id', String(seriesId)],
['name', clean(bd.seriesname)],
['brand', clean(bd.brandName)],
['level', clean(bd.levelname)],
['guide_price', bd.pricerange ? `${clean(bd.pricerange)}万` : ''],
['overall', num(bd.average ?? bd.seriesAverage)],
];
for (const axis of (Array.isArray(bd.seriesScoreList) ? bd.seriesScoreList : [])) {
const label = clean(axis.typeName);
if (label) fields.push([label, num(axis.score)]);
}
fields.push(['pph_每百车故障', num(qd.pph)]);
fields.push(['review_users', num(qd.userCount)]);
fields.push(['competitors', competitors]);
fields.push(['url', `${AH_KOUBEI_BASE}/${seriesId}`]);
return fields.map(([field, value]) => ({ field, value }));
}
cli({
site: 'autohome',
name: 'score',
access: 'read',
aliases: ['koubei', 'rating'],
description: '汽车之家车系口碑评分(总分 + 各维度 + 故障率PPH + 竞品对比,免登录)',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'series_id', required: true, positional: true, help: '车系 ID(来自 brand 的 series_id,或 k.autohome.com.cn/<id> URL' },
],
columns: SCORE_COLUMNS,
func: async (args) => {
const seriesId = normalizeSeriesId(args.series_id);
const html = await ahFetch(`${AH_KOUBEI_BASE}/${seriesId}`, `score ${seriesId}`);
const pp = extractPageProps(html);
if (!pp) {
throw new CommandExecutionError(
`autohome score ${seriesId}`,
'No koubei data found — the series id may be wrong, or Autohome changed its page.',
);
}
const rows = parseScore(pp, seriesId);
const map = Object.fromEntries(rows.map((r) => [r.field, r.value]));
if (!map.name && map.overall == null) {
throw new EmptyResultError(
`autohome score ${seriesId}`,
'This series has no koubei rating yet.',
);
}
return rows;
},
});