Metadata-Version: 2.5
Name: dongchedi-cli
Version: 0.4.0
Summary: 懂车帝（dongchedi.com）数据查询 CLI：销量榜 / 品牌 / 车系 / 车型库筛选 / 车型 / 参数配置 / 车主评论 / 搜索
Author: weisanju
Keywords: automobile,car,cli,dongchedi,vehicle-data
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: Chinese (Simplified)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# dongchedi-cli

懂车帝（www.dongchedi.com）数据查询 CLI。免登录命令走 PC 站公开 JSON 接口；登录命令读取浏览器登录态，走 HTML 页面的 SSR 数据。无第三方依赖（纯标准库）。

## 安装

```bash
uv tool install dongchedi-cli    # 从 PyPI 安装；也可 pipx install dongchedi-cli 或 pip install dongchedi-cli

# 从源码安装（开发）
uv tool install --editable scripts/dongchedi-cli
# 免安装临时运行
PYTHONPATH=scripts/dongchedi-cli/src python3 -m dongchedi_cli.cli rank
```

## 用法

```bash
# ---- 免登录 ----
dcd rank                          # 销量总榜（默认前 20，全国零售量）
dcd rank -c 杭州 -n 50            # 指定城市与条数（注意：榜单仍是全国榜，仅指导价按城市变化）
dcd rank --local -c 杭州          # 杭州城市榜：本地新车上牌量（共 317 款车系）
dcd brands -f J                   # 品牌列表，按名称/首字母过滤
dcd series 858                    # 吉利银河下的全部车系（含指导价/经销商价/评分）
dcd filter --price 10,20 --fuel 纯电 --body 轿车   # 车型库筛选：10-20万纯电轿车
dcd filter --body 小型SUV         # 全部小型SUV（级别细分，如 紧凑型SUV/中大型车/中型MPV）
dcd filter --price 10, --fuel 新能源 --sort 销量高 -n 50   # 10万以上新能源，按销量排序
dcd cars 20154                    # 星愿的车型年款与价格（含停售/未上市）
dcd cars 26051 -k 700 --fuel 纯电 # 车型筛选：名称含 700 的纯电车型（大小写不敏感）
dcd cars 26051 --price 15,17      # 按指导价筛选（万，一侧留空表示不限）
dcd specs 20154 259371            # 车型参数配置表（默认按 ◆ 章节/◇ 子章节分类）
dcd specs 20154 259371 259369 -s 电池   # 多车对比，只看"电池"章节
dcd specs 26051 261982 257691 -d  # 差异对比：只显示配置不同的项（至少 2 款车型）
dcd specs 26051:261982 26012:261406        # 跨车系对比：车系ID:车型ID 配对（启源Q06 vs MG 07 EV）
dcd specs 26051 261982 26012:261406 -d     # 混用：首个参数为默认车系，其余可写 车系:车型
dcd specs 26051 --all             # 全系可查配置车型横向对比
dcd specs 26051 --all --price 15,17 --fuel 纯电 -s 电池   # 全系筛选后对比
dcd reviews 20154 -p 1 -n 20      # 车主评论（含成交价/评分）
dcd search 秦plus                 # 搜车系
dcd dealer 259371                 # 某车型的经销商报价与优惠（默认杭州）
dcd dealer 259371 259369 -c 上海  # 多车型对比、指定城市

# ---- 需登录态（见下方「登录态」）----
dcd series-info 20154             # 车系主页 SSR：概览/评分/价格卡/经销商
dcd series-info 20154 -s dealers  # 只看经销商（overview/scores/price/dealers）
dcd series-info 20154 -c 北京     # 指定城市（覆盖登录态里的城市 Cookie）
dcd me                            # 当前登录账号信息

# 任意命令加 --json 输出原始数据
```

典型链条：`dcd brands -f 吉利` → `dcd series 858` → `dcd cars 20154` → `dcd specs 20154 <车型ID>`。

表格结果输出到 stdout，进度/提示输出到 stderr，可安全重定向。

## 登录态

`series-info` / `me` 需要已登录懂车帝的 Cookie。获取流程（配合 playwright-cli）：

```bash
playwright-cli open https://www.dongchedi.com --persistent   # 登录（拼图滑块 + 短信验证码）
playwright-cli state-save scripts/dongchedi-cli/.auth/state.json
dcd auth-import scripts/dongchedi-cli/.auth/state.json       # 提取 Cookie 并当场校验
# → 写入 ~/.config/dcd/cookies.txt（chmod 600），文件含 sessionid，勿提交
```

Cookie 文件解析优先级：`--cookie-file` 参数 → `DCD_COOKIE_FILE` 环境变量 → `~/.config/dcd/cookies.txt`。
登录态过期后（报错提示会话过期）重新登录并重跑 `state-save` + `auth-import` 即可。

## 已验证接口（2026-09 实测）

| 命令 | 接口 | 备注 |
| --- | --- | --- |
| rank | `GET /motor/pc/car/rank_data` | `rank_data_type=11` 零售量总榜；`=64` 为城市榜（`--local`，需 city_name，销量=本地新车上牌量）；`=2` 批发量 |
| brands | `GET /motor/brand/v4/brand/` | 品牌在 `data[].info`（其中 4 个热门品牌在 `info.brand`） |
| series | `POST /motor/pc/car/brand/select_series_v2` | 表单参数是 `brand`（不是 `brand_id`，传错会静默返回全站热门车系） |
| filter | `POST /motor/pc/car/brand/select_series_v2` | 与 series 同接口，隐藏筛选参数：`price="10,20"`（万元，区间重叠即命中，`-1` 表示不限）、`fuel_form`（1汽油/2柴油/3油电混合/4纯电/5增程/6插混/13轻混）、`series_type`（0轿车/1SUV/2MPV…）、`outter_detail_type`（细分级别：10小型SUV/11紧凑型SUV…）；`page` 分页有效，总数在 `data.series_count`；枚举出自车型库页前端 chunk（2026-09 逐项实测） |
| cars | `GET /motor/pc/car/series/car_list` | `data.tab_list[]`，type 1115=车型、1137=年款分组，1068 为占位需跳过；`-k/--price/--fuel` 为本地筛选，能源取自配置接口 `car_info[].info.fuel_form.value`（纯电动/增程式/插电式混合动力/汽油/油电混合/48V轻混系统，子串匹配），筛选时按 car_id 去重（同一车型会同时出现在「在售」与年款 tab） |
| specs | `GET /motor/pc/car/get_full_config_info` | 需 `series_id` + `car_ids`；实测忽略 car_ids，总是返回全车系，CLI 已按请求过滤（返回车型与 car_list 不完全一致，约只含可查配置/在售车型）；`--all` 一次取全系在本地筛选；默认按 `◆` 章节/`◇` 子章节分层渲染，`-d/--diff` 只保留各车型取值不全相同的行；跨车系用 `车系ID:车型ID` 配对，每个车系各请求一次后按行名合并，`-j` 多车系输出 `{车系ID: 原始数据}` |
| reviews | `GET /motor/pc/car/series/get_review_list` | 需 `Referer: /auto/series/score/{series_id}` |
| search | `GET /motor/searchapi/search_content` | `cur_tab=2`，车系卡片为 `cell_type=5002`（不一定出现，无则回退内容标题） |
| dealer | `GET /motor/pc/car/series/car_dealer_price` | 匿名可用；依赖 `city_name`（全国 → 暂无报价），未覆盖城市会回落邻近城市 |
| series-info | HTML `GET /auto/series/{id}` | 需登录态；数据在 `__NEXT_DATA__.props.pageProps`（经销商/价格卡/评分） |
| me | `GET /passport/account/info/v2/` | 需登录态；匿名返回 `error_code=13` 会话过期 |

公共参数 `aid=1839&app_name=auto_web_pc`；请求头用桌面 Chrome UA。

## 注意事项

- HTML 路由（`/auto/series/*`、`/auto/params-*`）无登录一律 302 到 `/login-required`；只有带完整登录 Cookie 才能拿到 SSR 数据（仅有 ttwid/tt_webid 不够）。
- HTML 路由有 CDN 缓存且缓存键不含 Cookie，会串城市数据；CLI 已自动附加 `?_r=<毫秒时间戳>` 破缓存。
- 城市由 `city_name` Cookie 控制且值为 **percent-encoded**（如 `%E6%9D%AD%E5%B7%9E`），URL 上的 `city_name` 参数会被页面逻辑忽略；CLI 的 `-c` 已按此处理。
- 登录态 Cookie 含中文值，urllib 请求头默认 latin-1 编码，发送前已转 UTF-8 裸字节（与浏览器线上行为一致）。
- 参数配置表字段：`data.properties[]` 为行定义（type 0/2 为章节标题，1 普通行，3 带子项，6 跳过），值在 `data.car_info[].info[<key>].value`；type 3 的值挂在 `sub_list[].key` 上。注意 `type` 字段时而为 int 时而为字符串，代码里已统一归一化。
- 车型库筛选中 `series_type` 与 `outter_detail_type` **互斥**：同传时服务端会忽略后者（按大类返回）；CLI 的 `--body` 已自动只传其一（轿车/SUV 传 series_type，小型SUV 等细分传 outter_detail_type）。
- 年款分组标题的「/ 后缀」不一定是能源：奥迪A6L 是世代代号（/ C9、/ C8），部分车系无后缀（钛7 PHEV）。`cars --fuel` 不解析标题，统一以配置接口的「能源类型」为准；因此只覆盖可查配置的车型，停售车型若无配置数据会被筛掉。
- `cars` / `specs --all` 的筛选均在本地完成；`--json` 始终输出接口原始数据、不受筛选影响。筛选无匹配时：`cars` 输出空表（退出码 0，提示在 stderr），`specs --all` 报错（退出码 1）。
- `specs -d/--diff` 行保留条件为各车型取值去重后 >1（`-` 视为相同值）；无差异的章节/子章节整段省略，汇总写 stderr（`差异 N 项 / 共 M 项`），无差异时 stdout 为空、退出码 0；最终参与对比车型不足 2 款报错（退出码 1）。
- `specs` 跨车系对比：位置参数可写 `车系ID:车型ID`；首个参数无冒号时作为其余裸车型ID的默认车系（如 `specs 26051 261982 26012:261406`），与 `--all` 不可同时使用。合并按行名 `(key, text)` 对齐，行序以先出现的车系为准；某车系没有的行，该列回退取同行其他车系的定义渲染（通常显示 `-`）。列标题在多车系时带上车系名（如 `MG 07 EV 845 旗舰版`）；每个车系各发一次请求，跨 N 个车系即 N 次网络请求。
- 接口可能随版本变化（版本参数当前为 version_code=476），失效时优先用 `--json` 排查原始返回。

## 开发与发布

开发与发布由 `Makefile` 管理（`make help` 查看全部目标）：

```bash
make lint            # ruff 检查
make smoke           # 走工作区源码对真实接口冒烟（覆盖全部免登录命令，需外网）
make bump part=minor # 升版本号（同步 pyproject.toml 与 __init__.py）
make check           # 离线校验：lint + 构建 + 版本一致性 + twine 元数据检查
make publish-test    # 发布到 TestPyPI 预演（token 放 ~/.config/dcd/testpypi-token）
make publish         # 发布到 PyPI（token 放 ~/.config/dcd/pypi-token）
make release         # 一键发布：check → smoke → bump → publish → tag
```

发布前在 pypi.org 生成 API token（`Account settings → API tokens`；首次发布 scope 只能选 "Entire account"，项目建好后可换成项目级），
存到 `~/.config/dcd/pypi-token`（`chmod 600`）即可，或临时 `export UV_PUBLISH_TOKEN=pypi-xxx` 覆盖。
