项目文件夹

文件
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

109 行
4.0 KiB
JavaScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/**
* autohome brand — list a brand's car series with guide prices.
*
* Fetches the brand catalog page `grade/carhtml/<INITIAL>.html` (UTF-8, fully
* server-rendered), isolates the `<dl>` block whose `<dt>` names the brand,
* and reads each `<li id="s<seriesId>">` series + its 指导价. Pure HTML→rows
* so it is unit-tested against a frozen catalog slice.
*
* This is Autohome's login-free "search": you search by brand (the catalog is
* brand-organized). Free-text model search is signature-gated and not offered;
* for that, use `dongchedi search`.
*/
import { cli, Strategy } from '@jackwener/opencli/registry';
import {
AH_BASE,
BRAND_COLUMNS,
CommandExecutionError,
EmptyResultError,
ahFetch,
clean,
requireLimit,
requireStableId,
requireText,
resolveBrandInitial,
} from './utils.js';
/**
* Pure parser: catalog HTML + brand name → series rows. Exported for tests.
*/
export function parseBrandSeries(html, brandName, limit) {
const source = String(html || '');
const blocks = source.match(/<dl[^>]*>[\s\S]*?<\/dl>/g);
if (!blocks) {
throw new CommandExecutionError('autohome brand catalog returned an unexpected HTML shape; expected brand <dl> blocks.');
}
const want = String(brandName || '').replace(/[·\s]/g, '');
// No brand name (single-letter catalog mode): scan the whole page.
// Otherwise isolate the <dl> block whose <dt> names the brand.
let block = html;
if (want) {
block = null;
for (const b of blocks) {
const nameM = b.match(/<dt>[\s\S]*?<div>\s*<a[^>]*>([^<]+)<\/a>/);
const name = nameM ? clean(nameM[1]).replace(/[·\s]/g, '') : '';
if (name && (name === want || name.startsWith(want) || want.startsWith(name))) {
block = b;
break;
}
}
if (!block) return [];
}
const rows = [];
const liRe = /<li id="s(\d+)">([\s\S]*?)<\/li>/g;
let m;
while ((m = liRe.exec(block)) !== null) {
const seriesId = requireStableId(m[1], `autohome brand row ${rows.length + 1}`);
const li = m[2];
const nameM = li.match(/<h4>\s*<a[^>]*>([^<]+)<\/a>/) || li.match(/<a[^>]*>([^<]+)<\/a>/);
const name = requireText(nameM && nameM[1], `autohome brand row ${rows.length + 1} name`);
const priceM = li.match(/指导价[:]\s*<[^>]*>([^<]+)</) || li.match(/指导价[:]\s*([^<]+)</);
let price = clean(priceM && priceM[1]);
if (/暂无|未上市|停售/.test(price)) price = '';
rows.push({
series_id: seriesId,
name,
price,
url: `${AH_BASE}/${seriesId}/`,
});
if (rows.length >= limit) break;
}
return rows;
}
cli({
site: 'autohome',
name: 'brand',
access: 'read',
aliases: ['series'],
description: '汽车之家按品牌列出全部车系 + 厂商指导价(免登录)',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'brand', required: true, positional: true, help: '品牌名(宝马 / 比亚迪 / 理想 / 丰田 …)或车系目录首字母 A-Z' },
{ name: 'limit', type: 'int', default: 60, help: '返回的车系数量(最多 120' },
],
columns: BRAND_COLUMNS,
func: async (args) => {
const brand = String(args.brand || '').trim();
const initial = resolveBrandInitial(brand);
const limit = requireLimit(args.limit, 60, 120);
const html = await ahFetch(
`${AH_BASE}/grade/carhtml/${initial}.html`,
`brand ${brand}`,
);
const rows = parseBrandSeries(html, /^[A-Za-z]$/.test(brand) ? '' : brand, limit);
if (rows.length === 0) {
throw new EmptyResultError(
`autohome brand ${brand}`,
`No series found for '${brand}'. Check the brand name spelling (simplified Chinese), or try a single A-Z catalog letter.`,
);
}
return rows;
},
});