ev-subsidy-status:基于环境部无公害汽车综合网站的无登录韩国电动车补贴查询方案
2026/9/17 22:08:41 网站建设 项目流程

ev-subsidy-status:基于环境部无公害汽车综合网站的无登录韩国电动车补贴查询方案

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

本指南深入解析 k-skill 仓库中ev-subsidy-status包(对应根目录ev-subsidy-statusSkill)的实现与使用:它如何在不登录、不依赖用户浏览器、不使用代理与 API Key 的前提下,直接读取韩国环境部无公害汽车综合网站(ev.or.kr)公开的电动车购买补贴发放现状(公告、受理、出库、出库剩余台数),并支持按车型与细分车型查询国费、地方费补贴。读完本文,你将掌握该工具的全部 CLI 命令与参数、direct-httpbrowser双传输机制、pnp4web保护 HTML 的还原原理、返回数据字段语义、状态判定规则以及错误码体系,可直接复现官方文档示例并在 Agent 场景中正确消费其结果。

一、工具定位:面向韩国地方自治团体的电动车补贴查询

ev-subsidy-status(包目录)是一个以 Node.js 18+ 运行的命令行工具与库,其核心目标是用一行命令回答"我所在地区还能不能申请电动车补贴、还剩多少台"这类问题。它的特点与边界在官方 README.md 中明确界定:

  • 查询环境部无公害汽车综合网站公开的购买补贴发放现状,全程无需登录,也不使用用户浏览器;
  • 默认传输方式为direct-http:从公开响应中读取官方pnp4web.js字符表,在不执行远程代码的前提下还原被保护的 HTML
  • 不使用代理,也不使用 API Key
  • 官方页面结构一旦变化,可通过--transport browser显式选择诊断用浏览器路径;
  • 官方页面提供的是公告、受理、出库、出库剩余台数,而非韩元余额;仅在指定--model时,才在 direct-http 路径下额外查询模型级国费、地方费与合计,并给出剩余换算值;
  • 输入名称匹配到多个细分模型时,返回全部候选,绝不任意挑选其中一个;换算值不是精确的可支配预算余额。

在仓库中它同时以 Skill 形态存在(ev-subsidy-status/SKILL.md),供 Agent 在回答用户所在地区电动车补贴问题时调用,并在无公害汽车综合网站页面进行后续动作时使用官方表层界面。

二、快速开始:安装与三个核心命令

包声明在 package.json 中,bin入口为src/cli.jsmainsrc/index.js,运行时要求 Node.js >= 18,运行时依赖仅有k-skill-browser-runtime(browser 路径使用)。

官方 README 给出的三条基础命令可直接运行(通过npx免全局安装):

# 查询某地区某车型某年的补贴发放现状 npx ev-subsidy-status status --region "경기 성남시" --vehicle passenger --year 2026 # 查询某地区指定模型的补贴并输出 JSON npx ev-subsidy-status status --region "서울 강남구" --model "모델명" --json # 按关键词搜索地区(解决同名区划歧义) npx ev-subsidy-status regions --query "중구"

第一条命令会输出类似下方的人类可读结果(格式来自 cli.js 的 formatStatus):

경기 성남시 전기승용 보조금 현황 조회: 2026-07-18T20:00:00+09:00 민간공고대수: 1,949대 (우선 55, 법인·기관 0, 택시 20, 일반 1,043) 접수대수: ... 출고대수: ... 출고잔여대수: ... 상태: closed 접수방법: ... 비고: ...

输出末尾会强制附上免责声明:"출고잔여대수는 실제 신청 가능 대수와 다를 수 있으며 정확한 원화 예산 잔액이 아닙니다."(出库剩余台数可能与实际可申请台数不同,也不是精确韩元预算余额),以及官方来源 URL 与 KST 查询时刻——这正是源码中"保守回答"设计原则的体现。

三、命令与参数全解

CLI 参数解析实现在 cli.js 的 parseArgs 与 printHelp 中。工具提供两个子命令:

子命令说明
status查询指定地区的补贴发放现状(默认命令,可省略)
regions按查询词搜索地区候选,返回sido_name/local_code等字段

3.1 全局参数

参数别名类型说明默认值
--region <region>-rstring查询地区,建议"시도 + 시군구"格式,如경기 성남시无(status 必填)
--query <text>-qstringregions命令的地区搜索词无(regions 必填)
--vehicle <type>--vehicle-typestring车型:passenger/승용cargo/화물bus/승합passenger
--year <year>-number基准年度,默认 Asia/Seoul 当前年当前年
--category <category>-string类别过滤,默认allall
--model <model>-string指定模型后查询地方自治团体的模型级补贴与剩余换算值
--transport <transport>-stringdirect-http(默认)或browserdirect-http
--provider <provider>-stringbrowser 传输下的浏览器供应方:autoasidebrowseroschrome-cdp依运行时
--cdp-url <url>-string用户自行启动的 Chrome/BrowserOS CDP 地址
--timeout <ms>-number页面等待超时(毫秒),源码默认 30000ms30000
--json-flag输出 JSON 而非人类可读文本

regions命令还支持位置参数:npx ev-subsidy-status regions "중구",与--query "중구"等价(见 cli.js parseArgs)。

3.2 车型别名的归一化

车型解析位于 constants.js resolveVehicleType,支持韩英双语别名,不支持的车型会抛出VEHICLE_TYPE_NOT_AVAILABLE错误:

key页面标签carTypeCode支持别名
passenger전기승용(电动乘用)11passengercar승용전기승용전기차
cargo전기화물(电动货车)12cargotruck화물전기화물
bus전기승합(电动客车)13busvan승합전기승합

3.3 地区名的归一化与歧义处理

地区输入会经过 parse.js normalizeRegionKey 做归一化:去除空白、将경기도→경기충청북도→충북제주특별자치도→제주等全称折叠为标准名,并剥离특별시/광역시/특별자치시/도等后缀。constants.jsSIDO_ALIASES(constants.js)内置 17 个市道(首尔、釜山、大邱、仁川、光州、大田、蔚山、世宗、京畿、江原、忠北、忠南、全北、全南、庆北、庆南、济州)的全部常用写法。

对于중구강서구这类多个市道共有的区名,系统会抛出REGION_AMBIGUOUS,此时应先用regions找到候选并让用户指定市道:

npx ev-subsidy-status regions --query "중구"

四、双传输架构:direct-http 与 browser

传输选择逻辑位于 index.js browserRequested:当显式指定transport: "browser",或传入pageruntimeprovidercdpUrl任一选项时走浏览器路径,否则走直连 HTTP 路径。

4.1 direct-http:解析 pnp4web 字符表,不执行远程代码

这是默认且推荐的路径,instruction.md 对其行为做了明确描述,源码在 http.js 与 pnp.js:

  1. https://ev.or.kr/nportal/buySupprt/initSubsidyPaymentCheckAction.do发送POST,表单字段为car_type(车型码)、year1(年度)以及全国地区条件localDo_cd=alllocal_cd1=all(http.js fetchDecodedStatusHtml);
  2. 若响应含<meta name="penc">标记,说明正文被pnp4web保护,则从 HTML 中提取官方pnp4web.js的 URL(pnp.js extractPnpScriptUrl);
  3. 只解析脚本中声明的var In=[...]片段字符表与o0~o6七个字母表(parsePnpAlphabets),绝不通过eval/vm执行远程代码
  4. 从页面onload中提取受保护 payload,按其首字符选择字符表、次字符做轮转位移,再进行类 Base64 解码还原出 UTF-8 HTML(decodePnpPayload);
  5. 在还原后的表格中定位含"출고잔여"的表(extractStatusRows),解析十列官方行,并从公告下载函数goDownloadFile(...)的参数中读取地区代码;
  6. 若响应不含penc标记(页面未被保护),则直接作为明文处理。

请求带默认请求头与 30 秒超时(http.js DEFAULT_TIMEOUT_MS),超时会转为UPSTREAM_TIMEOUT。该路径全程无浏览器、无登录、无代理、无 API Key,是 README 所述"即使没有用户浏览器也应正常工作"的默认路径。注意:官方公开的是 keyless 页面,因此本工具不使用k-skill-proxy

4.2 browser:诊断用浏览器路径

当官方页面结构变更、需要诊断渲染结果或核对模型级换算时,可显式选择浏览器路径:

npx ev-subsidy-status status --region "경기 성남시" --transport browser --provider auto npx ev-subsidy-status status --region "경기 성남시" --transport browser --provider aside npx ev-subsidy-status status --region "경기 성남시" --transport browser --provider browseros npx ev-subsidy-status status --region "경기 성남시" --transport browser --provider chrome-cdp

该路径通过k-skill-browser-runtime(package.json 依赖^0.4.0)连接用户自己启动的 Aside Browser、BrowserOS 或 Chrome CDP 会话,相关实现见 browser.js withAutomationPage。其设计原则在 instruction.md 的 "Optional browser behavior" 中有明确约束:

  • 页面加载使用domcontentloaded刻意不用networkidle,因为pnp4web存在后台请求会导致永不静默;
  • 通过轮询显式等待目标 selector 与地区行出现(waitFor 默认 300ms 轮询间隔);
  • 用渲染后<option>的 label/value 动态解析地区代码(resolveRegion);
  • 不关闭用户已有的标签页与个人资料,只清理本 Skill 创建的页面/上下文,并仅对支持的 client 执行disconnect
  • 绝不绕过登录、CAPTCHA、支付、电子签名与最终提交等边界——检测到 CAPTCHA 立即抛CAPTCHA_DETECTED并停止(assertPublicStatusPage)。

浏览器上下文固定使用ko-KR语言与Asia/Seoul时区、1280×900 视口(browser.js withAutomationPage),保证页面渲染与解析结果一致。

五、返回数据结构与字段语义

status命令的 JSON 结果由 parse.js buildStatusResult 组装,顶层结构如下:

{ "query": { "region": "경기 성남시", "year": 2026, "vehicle_type": "passenger", "category": "all", "model": null }, "region": { "sido_name": "경기", "sido_code": "41000", "local_name": "성남시", "local_code": "41130" }, "status": { "sido_name": "경기", "local_name": "성남시", "vehicle_label": "전기승용", "notice_files": [{ "label": "본공고 1", "title": "", "onclick": "goDownloadFile('...')" }], "application_method": "출고등록순", "notice_count": { "total": 1949, "priority": 55, "corporate": 0, "reserved": 20, "general": 1043 }, "application_count": { "total": 1745, "priority": 550, "corporate": 49, "reserved": 60, "general": 1086 }, "delivered_count": { "total": 1740, "priority": 549, "corporate": 49, "reserved": 60, "general": 1082 }, "delivery_remaining_count": { "total": 209, "priority": 0, "corporate": 0, "reserved": 0, "general": 867 }, "note": "★ 공고 마감 ★ 추경예산 확보 후 재공고 예정", "warnings": [] }, "availability": { "label": "closed", "basis": ["note:closed"], "warnings": [], "official_remaining_count": 209, "pending_application_count": 5, "actual_application_count_known": false }, "remaining_budget": { "exact_available": false, "exact_amount_krw": null, "reason": "...", "model_equivalent_estimate_krw": null, "estimate_assumptions": [] }, "source": { "name": "환경부 무공해차 통합누리집", "url": "https://ev.or.kr/...", "fetched_at": "2026-07-18T20:00:00+09:00" }, "transport": "direct-http", "warnings": [] }

关键语义说明:

  • 四个台数对象均保留"전체(total)/ 우선(priority)/ 법인·기관(corporate)/ 예약 대상군(reserved)/ 일반(general)"五个槽位;乘用车的reserved同时以taxi别名返回,货车的以small_business别名返回(parse.js categoryCountsForVehicle);
  • source.fetched_at是 KST 格式的查询时刻(formatKst,UTC+9);
  • pending_application_count= 受理台数 - 出库台数,表示在办申请量;actual_application_count_known: false表明公开数据无法给出精确的"实际可申请台数";
  • remaining_budget.exact_available恒为false,因为公开发放现状无法精确计算韩元预算余额;
  • 当目标群数值出现负数、或分项之和与总数不一致时,不做修正,原样返回并追加 warning(countWarnings)。

六、状态判定:非注明优先于剩余台数

availability.label的判定逻辑在 availability.js classifyAvailability,优先级如下(instruction.md 亦有说明):

优先级判定依据(비고 原文正则)label
1마감/소진/접수 종료/신청 종료closed
2접수 예정/추경 예정/추가 공고 예정/재공고 예정scheduled
3접수 중/신청 기간/접수 기간/신청 가능open
4无明确措辞但剩余台数为正unknown_with_remaining_count
5无任何依据unknown

两条铁律:

  1. 备注原文优先于数字:即使剩余台数为正,只要备注写明"마감/소진",就判定为closed并附 warning"出库剩余台数为正但备注为截止/耗尽"(availability.js)。测试 index.test.js "availability gives explicit closure text precedence" 验证了"일반 접수 마감, 추경 공고 예정"在剩余 20 台时仍判为closed
  2. 不编造可申请性:无明确措辞时绝不擅自说"可申请",只报unknown_with_remaining_count

七、模型级补贴与剩余换算值(model 查询)

指定--model后,即便在 direct-http 路径下也会以POST请求官方模型补贴弹窗面https://ev.or.kr/nportal/buySupprt/psPopupLocalCarModelPrice.do,表单为year=<年度>&local_cd=<地区代码>&car_type=<11|12|13>(见 constants.js MODEL_SUBSIDY_PATH 与 http.js fetchDecodedModelHtml)。

模型表解析(parse.js parseModelSubsidyRows)会读取"제조사/모델/국비/지방비/합계"等表头并按输入名做模糊匹配,金额解析(parseMoneyKrw)自动识别억원만원单位——例如1,200만원解析为 12,000,000 韩元(测试见 index.test.js)。

行为细节:

  • 输入名命中多个细分模型时,返回model_subsidy_candidates全部候选及各自换算值,不任意挑选,并追加警告提示用户区分 trim 级补贴(http.js getSubsidyStatusHttp);
  • 恰好命中唯一细分模型时,额外返回model_subsidyremaining_budget.model_equivalent_estimate_krw
  • 模型查询失败不影响地区级台数结果,只附加model_lookup_error与警告。

model_equivalent_estimate_krw的计算在 estimate.js estimateModelEquivalent:

공식 출고잔여대수(官方出库剩余台数)× 선택 모델의 국비+지방비(所选模型国费+地方费)

其附带的三条假设(estimate_assumptions):剩余台数全部划拨给所选模型、按已确认的单台国费+地方费计算、不反映购买者特性追加支持与目标群间数量转换。官方 README 与源码均强调:这不是地方自治团体的精确预算余额,切勿向用户表述为"精确可用预算"。测试 index.test.js "model equivalent is clearly separate from exact budget" 专门断言exact_available=falseexact_amount_krw=null与换算值并存的边界。

八、错误码体系与失败模式

错误构造与包装位于 errors.js,CLI 失败时以 JSON 输出{ error: { code, message, details } }(cli.js formatError)。完整错误码清单(instruction.md "Failure modes"):

错误码触发场景
REGION_REQUIRED未输入查询地区
REGION_AMBIGUOUS同名区划存在于多个市道,需连同市道一起输入
REGION_NOT_FOUND官方地区列表中找不到该地区
YEAR_NOT_AVAILABLE请求年度不在页面可选列表
VEHICLE_TYPE_NOT_AVAILABLE车型不受支持或页面无此车型项
BROWSER_UNAVAILABLE没有可连接的用户浏览器
UPSTREAM_BLOCKED空壳响应、被拦截或异常页面
CAPTCHA_DETECTED出现 CAPTCHA,工具不绕过
AUTH_REQUIRED公开页面变为登录流程,工具不绕过
RESULT_EMPTY目标地区无结果行
DOM_CHANGED官方页面选择器或表格结构变化
UPSTREAM_DECODE_FAILEDpnp4web字符表或保护 payload 格式变化
MODEL_LOOKUP_FAILED模型补贴表读取失败
UPSTREAM_TIMEOUT官方站点响应超时

其中UPSTREAM_DECODE_FAILEDDOM_CHANGED是官方脚本或表格结构变化时的"哨兵错误",指示需要切换到 browser 路径诊断(详见 http.js、pnp.js)。

九、源码结构地图

若需深入阅读,仓库中与本文相关的核心文件路径如下:

  • 包根:packages/ev-subsidy-status/package.jsonpackages/ev-subsidy-status/README.md
  • CLI 层:cli.js(参数解析、人类可读/JSON 输出、错误格式化)
  • 入口分发:index.js(根据选项选择传输路径)
  • 直连 HTTP 路径:http.js(表单提交、地区解析、模型表抓取)
  • pnp4web 解码器:pnp.js(字符表解析与 payload 还原)
  • 浏览器路径:browser.js(DOM 求值操作、地区/车型联动选择、与 k-skill-browser-runtime 的连接)
  • 常量与别名:constants.js(URL、车型定义、市道别名)
  • 解析与装配:parse.js、availability.js、estimate.js
  • 测试:test/index.test.js(数值解析、状态判定、金额单位、CLI 参数、输出披露共 11 个用例)
  • Skill 侧:ev-subsidy-status/SKILL.md(Skill 元数据与硬性规则)、ev-subsidy-status/instruction.md(Agent 操作指引,与本文各节一一对应)

十、在 Agent 场景中的使用建议

综合官方 README、instruction.md 与源码行为,将ev-subsidy-status用于 Agent 或自动化流程时应注意:

  1. 默认走 direct-http:优先使用--transport direct-http(默认),确认结果中transportdirect-http,确保在无浏览器环境可用;
  2. 地区输入规范:尽量传"市道 + 市郡区",重名区划先用regions消歧;
  3. 解读优先级:先看availability.labelstatus.note原文,再看台数;备注为closed时即使剩余为正也不要说"可申请";
  4. 保守披露:始终附带"出库剩余台数≠实际可申请台数≠精确韩元余额"的警示,并给出官方来源 URL 与 KST 查询时刻;
  5. 模型换算有假设:引用remaining_budget.model_equivalent_estimate_krw时说明计算假设,不表述为精确预算余额;多候选时把model_subsidy_candidates全部列出让用户选择;
  6. 不绕过边界:遇到CAPTCHA_DETECTEDAUTH_REQUIRED时停止并如实上报,遵守 Skill 中"不绕过登录、验证码、支付、电子签名边界"的硬性规则;
  7. 结构变化时切换诊断:出现DOM_CHANGEDUPSTREAM_DECODE_FAILED时,用--transport browser --provider aside|browseros|chrome-cdp连接用户浏览器核验官方页面现状。

【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询