cheap-gas-nearby 实战指南:基于韩国 Opinet 官方 API 的附近最便宜加油站查询包
2026/9/17 18:08:19 网站建设 项目流程

cheap-gas-nearby 实战指南:基于韩国 Opinet 官方 API 的附近最便宜加油站查询包

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

cheap-gas-nearby是 k-skill 仓库中面向韩国场景发布的 Node.js 包(v0.4.0,MIT 协议),它以 한국석유공사(韩国石油公社)Opinet 官方开放 API 为价格数据源,将「동네/역명/랜드마크(小区/地铁站名/地标)」这类自然语言位置串通过 Kakao Map 锚点搜索解析为 WGS84 坐标,再经 WGS84→KATEC 坐标变换接入 Opinet 周边加油站检索,最终按「价格优先、距离次之」返回排序结果。读完本文,你将掌握该包的安装方式、完整调用链、9 个公开 API 的用途与源码实现细节,并能在自己的 Node.js(≥18)项目或 AI Agent 技能中直接落地「查附近最便宜的加油站」这一实战能力。

一、包定位与使用原则

cheap-gas-nearby的核心职责非常聚焦:用官方数据源回答「근처 가장 싼 주유소」(附近最便宜的加油站在哪)。它不是一个通用地图 SDK,而是一条经过封装的「位置→坐标→油价→排序」流水线,这在 README.md 的开篇就明确定位为:使用韩国石油公社 Opinet 官方 API 查找附近最便宜的加油站。

该包同时以cheap-gas-nearbySkill 的形式存在于仓库根目录的 cheap-gas-nearby/instruction.md 与 cheap-gas-nearby/SKILL.md 中,服务于 AI Agent 场景,并据此沉淀了四条硬性使用原则:

  • 不自动追踪用户位置:包本身不会读取或猜测设备定位,Agent 必须先把「当前在哪」问清楚;
  • 先问位置,再搜索:拿到 동네(小区)/ 역명(站名)/ 랜드마크(地标)/ 위도·경도(经纬度)中的任意一种格式后再动手;
  • 价格数据优先走官方 Opinet Open API,保证数据权威性与合法性;
  • 位置字符串先经 Kakao Map anchor 搜索拿坐标,再接 OpinetaroundAll.do周边查询,两条链路分工明确。

其中关于「默认产品为 휘발유(汽油,B027),用户明确要 경유(柴油)才切换为 D047」的行为,也写进了 instruction.md 的应答策略中。

二、安装与运行环境

包在发布后可像普通 npm 包一样安装:

npm install cheap-gas-nearby

若要在本仓库内直接开发、调试该包(例如配合 test/index.test.js 跑测试):

npm install

运行环境要求可从 package.json 确认:

项目
版本0.4.0
Node.js 要求>=18(依赖全局fetch,见下文)
入口文件src/index.js
许可MIT
发布文件srcREADME.md
测试命令npm testnode --test
Lint 命令npm run lintnode --check语法检查)

需要特别说明的是,包的 HTTP 层在 src/index.js 中直接使用options.fetchImpl || global.fetch,因此必须运行在支持全局fetch的 Node 18+ 环境,或者通过fetchImpl注入自定义实现(测试正是利用这一注入点做 mock)。

三、整体架构与数据流

从 src/index.js 的常量与函数组织可以看出,整个查询是一条五段式流水线:

用户位置字符串(서울역 / 37.55472,126.97068) │ ▼ ① Kakao Map 锚点解析(仅字符串位置需要) m.map.kakao.com/actions/searchView?q=<query> → place-api.map.kakao.com/places/panel3/<confirmId> 取 WGS84 坐标 │ ▼ ② WGS84 → KATEC 坐标变换(parse.js 中的 wgs84ToKatec) │ ▼ ③ Opinet aroundAll.do 周边检索(sort=1 价格升序) │ ▼ ④ detailById.do 详情补全(地址/电话/自助/洗车/保养/品质认证) │ ▼ ⑤ 价格优先、距离次之排序 → 输出 limit 条结果

这一链路在 instruction.md 的 Workflow 一节有等价描述,并在 test/index.test.js 中通过 mockfetchImpl一次串起了「서울역 搜索 HTML → 地点面板 JSON → aroundAll.do → detailById.do」的全部环节进行端到端验证。

值得强调的是第 ① 步的健壮性设计:resolveAnchor(src/index.js)会先对 Kakao 搜索结果按评分排序,然后逐个尝试候选地点的面板接口——若第一个候选的 panel 返回 404,或拿到的经纬度不是有限数值,就自动回退到下一个候选,全部失败才抛出No usable Kakao Map place panel was available for <query>。测试 index.test.js#L178-L254 专门验证了「首个候选无坐标时回退到第二候选」的行为。

四、官方 API 表面与关键参数

包所对接的官方接口在 README 与 instruction.md 中都有完整罗列,整理如下(均为包内部实际请求的真实端点):

用途端点
Opinet 开放 API 说明https://www.opinet.co.kr/user/custapi/openApiInfo.do
半径内加油站列表https://www.opinet.co.kr/api/aroundAll.do
加油站详情(按 ID)https://www.opinet.co.kr/api/detailById.do
地区代码https://www.opinet.co.kr/api/areaCode.do
Kakao Map 移动端锚点搜索https://m.map.kakao.com/actions/searchView?q=<query>
Kakao Map 地点面板 JSONhttps://place-api.map.kakao.com/places/panel3/<confirmId>

其中aroundAll.do的核心请求参数与取值范围,在buildAroundSearchParams(src/parse.js)中有强制校验,是调用该 API 的「契约」:

参数含义取值 / 约束
out返回格式固定json
x,y基准位置KATEC坐标必须是有限数值,输出时保留 4 位小数
radius搜索半径(米)正数,最大 5000,默认 1000,超出即抛错
prodcd油品代码B027汽油(默认)、D047柴油、B034高标号汽油、C004煤油、K015LPG
sort排序方式1表示按价格排序
certkey官方 API 密钥直连模式必填

油品代码到语义键的映射在 src/parse.js 中定义:B027→gasolineB034→premiumGasolineC004→keroseneD047→dieselK015→lpg,详情接口返回的多油品价格会按此映射到统一的prices对象中。

五、快速上手:按位置字符串查询

README 中的核心示例就是searchCheapGasStationsByLocationQuery——传一个「서울역」这样的自然语言位置,其余坐标解析全部自动完成:

const { searchCheapGasStationsByLocationQuery } = require("cheap-gas-nearby"); async function main() { const result = await searchCheapGasStationsByLocationQuery("서울역", { apiKey: process.env.OPINET_API_KEY, // 官方 API key,或走代理模式时可省略 radius: 1000, // 半径(米),默认 1000,最大 5000 productCode: "B027", // 油品代码,默认 B027(汽油) limit: 3 // 最多返回条数 }); console.log(result.anchor); // 锚点:name/sourceUrl/latitude/longitude 等 console.log(result.items); // 排序后的加油站列表 } main().catch((error) => { console.error(error); process.exitCode = 1; });

该函数的实现(src/index.js)做了两件事:

  1. 先用parseCoordinateQuery判断入参是否本身就是坐标——形如"37.55472,126.97068"(支持逗号、斜杠或空格分隔)的字符串会被直接识别为经纬度,从而跳过 Kakao 锚点搜索,直接走坐标查询;
  2. 否则走resolveAnchor完成位置→坐标解析,再调用坐标查询函数,并在返回结果中额外附带anchoranchorCandidates以及meta.resolvedQuery(回填用户原始查询串)。

返回的result结构如下:

  • anchor:解析后的锚点对象,包含idnamecategoryaddressphonelatitudelongitudesourceUrl(形如https://place.map.kakao.com/1001);
  • items:按价格升序、距离次之排序的加油站数组,每个元素在detailLimit开启时还会合并详情字段(见第七节);
  • metaproductCoderadiustotal(候选总数),以及位置字符串模式下额外的resolvedQuery

六、坐标直查:searchCheapGasStationsByCoordinates

如果调用方(如地图应用)已经持有用户经纬度,可以跳过锚点解析直接调用searchCheapGasStationsByCoordinates(src/index.js)。其可用选项与默认值如下:

选项默认值说明
latitude,longitude必填WGS84 经纬度,必须是有限数值
radius1000搜索半径(米,≤5000)
productCode"B027"油品代码
sort1排序方式(1 = 价格优先)
limit5返回条数,最小 1
detailLimit等于limit需要拉取详情的条数,最小 0(0 表示不拉详情)
apiKey/certKey环境变量OPINET_API_KEY官方密钥(直连模式必填)

limitdetailLimit都会经过normalizeCountOption(src/index.js)的严格校验:传入非有限数值会直接抛错(如limit must be a finite number)而不是静默返回空列表,这一点有专门的测试用例覆盖(index.test.js#L345-L367)。

函数内部流程为:WGS84→KATEC 变换 → 调aroundAll.dosortStationsByPriceAndDistance排序 → 对前detailLimit条并发拉取详情(单个详情失败不会中断整体,而是以{ error }占位)→ 返回anchor(内含katecX/katecY)、itemsmeta三部分。

七、9 个公开 API 逐一解析

README「공개 API」一节列出了包的完整对外函数面,结合源码与测试,逐个说明其职责:

API位置职责与关键行为
parseSearchResultsHtml(html)src/parse.js#L89-L116解析 Kakao 移动搜索 HTML,抽取search_item base卡片中的idnamecategoryaddressphone
selectAnchorCandidate(query, items)src/parse.js#L180-L188对候选按评分排序后取第一名,无候选则抛错
normalizeAnchorPanel(panel, searchItem)src/parse.js#L190-L203把 Kakao 地点面板 JSON 规整为统一锚点对象;point缺失时经纬度保持null而非强制 0(有测试专门守护该行为)
wgs84ToKatec(latitude, longitude)src/parse.js#L254-L294WGS84→Bessel→KATEC 坐标变换,返回{ x, y }
buildAroundSearchParams(options)src/parse.js#L306-L324生成 OpinetaroundAll.do请求参数并做参数校验(半径、坐标合法性)
parseAroundResponse(payload)src/parse.js#L355-L359解析RESULT.OIL列表(兼容数组/单对象),过滤掉无 ID 或价格非数值的脏数据
normalizeDetailItem(payload)src/parse.js#L388-L418规整详情:地址、电话、自助/洗车/保养/便利店/品质认证布尔位、按油品归类的pricesrawPrices
searchCheapGasStationsByCoordinates(options)src/index.js#L220-L272坐标直查主入口(见第六节)
searchCheapGasStationsByLocationQuery(locationQuery, options)src/index.js#L274-L305位置字符串查询主入口(见第五节)

除此之外,src/index.jsmodule.exports还额外导出了fetchSearchResultsfetchPlacePanelfetchDetailByIdrankAnchorCandidatessortStationsByPriceAndDistancebuildAroundSearchParams以及四个 URL 常量(AROUND_ALL_URLDETAIL_BY_ID_URLSEARCH_VIEW_URLPLACE_PANEL_URL_BASEDEFAULT_PROXY_BASE_URL),方便上层做更细粒度的组合与二次开发。

八、锚点候选的评分排序机制

位置字符串能自动选中最合适的锚点,依赖的是 src/parse.js#L118-L178 中的scoreAnchorCandidate评分体系。它对查询串与每个候选的名称/地址/分类做规范化(NFKC 归一化 + 去标点小写)后累加权重:

匹配条件加分
名称与查询串完全一致+1000
名称恰为「查询串+역」或去掉「역」后一致+950
名称以查询串开头+800
名称包含查询串+600
地址包含查询串+120
名称/分类命中 역·기차역·광장·공원·랜드마크 等锚点模式+250
名称/分类是 주유소(加油站)本身-200(避免把结果站当锚点)
候选 ID 非纯数字-500
分类含 기차역 / 전철역+80

同分时按韩文名称localeCompare(..., "ko")稳定排序。测试 index.test.js#L37-L42 验证了「서울역」能正确选中 id=1001 的 기차역 而非同名其他结果,index.test.js#L256-L343 则验证了「강남역」场景下首选候选 404 后,能按评分次序回退到「강남역 11번출구」的候选。

九、WGS84→KATEC 坐标变换原理

OpinetaroundAll.do要求 KATEC 坐标系下的x/y,而 Kakao Map 锚点面板返回的是 WGS84 经纬度,因此wgs84ToKatec(src/parse.js#L254-L294)是链路中不可省略的一环,其实现分两步:

  1. WGS84→Bessel 基准面转换:采用三参数平移(src/parse.js#L18 中的WGS84_TO_BESSEL = [146.43, -507.89, -681.46]),配合 WGS84 与 Bessel 椭球长半轴/扁率,先用空间直角坐标平移得到 Bessel 经纬度(纬度用迭代法求解,收敛阈值1e-14,最多 8 次迭代);
  2. Bessel→KATEC 投影:KATEC(Korea Transverse Mercator)以 38°N、128°E 为中央纬线/经线(KATEC_LAT0/KATEC_LON0),假东 400000、假北 600000,比例因子 0.9999,经子午弧长展开式计算平面坐标(src/parse.js#L9-L18)。

测试 index.test.js#L72-L77 给出了可复现的基准值:(37.55472, 126.97068)转换结果为(309252.2237, 550779.9944)(容差 ±1),任何对该函数的改动都可以此回归校验。

十、两种数据获取模式:直连与代理

cheap-gas-nearby支持两条访问 Opinet 的路径,由 API key 的有无自动切换:

  • 直连模式:当options.apiKey/options.certKey或环境变量OPINET_API_KEY存在时(useDirectApi判定,见 src/index.js#L86-L88),包直接请求https://www.opinet.co.kr/api/aroundAll.do(拼接certkey)与detailById.do。此时若缺少密钥,会抛出OPINET_API_KEY or options.apiKey is required for official Opinet lookups.
  • 代理模式:没有密钥时,请求改走默认代理https://k-skill-proxy.nomadamas.org(常量DEFAULT_PROXY_BASE_URL,可用options.proxyBaseUrl或环境变量KSKILL_PROXY_BASE_URL覆盖)下的/v1/opinet/around/v1/opinet/detail。这一默认路径同样写入了 instruction.md:使用代理时用户侧无需自备OPINET_API_KEY,Agent 直接可用。

两种模式的切换点在fetchAroundStationsfetchDetailById(src/index.js#L164-L202)中,代理模式下参数名对齐为x/y/radius/prodcd/sort(/around)与id(/detail),返回结构由parseAroundResponse/normalizeDetailItem统一规整,对上层透明。

请求层还内置了贴近真实浏览器的请求头:普通文本请求使用带accept-language: ko的浏览器 UA 头;Kakao 地点面板请求额外携带originrefererappVersion: 6.6.0sec-ch-ua等头(DEFAULT_PANEL_HEADERS);Opinet JSON 请求则使用轻量的 JSON 头(src/index.js#L19-L43)。所有请求都支持options.headers合并与options.signal超时控制。

十一、结果规范化:品牌映射与多油品价格

parseAroundResponse输出的每个周边条目含idbrandCodebrandNamenamepricedistanceMeterskatecXkatecY。品牌代码到中文/韩文名称的映射表定义在 src/parse.js#L20-L31:

品牌代码名称
SKESK에너지(SK 能源)
GSCGS칼텍스
HDO현대오일뱅크(现代 Oilbank)
SOLS-OIL
E1GE1
SKGSK가스
NHO농협알뜰(农协实惠)
RTE자영알뜰(个体实惠)
RTX고속도로알뜰(高速实惠)
ETC자가상표(自有品牌)

详情条目(normalizeDetailItem)在周边信息之上追加:lotAddress/roadAddress(地籍/道路名地址)、phonesigunCodelpgYn、布尔型的isSelf(自助)、hasMaintenance(保养)、hasCarWash(洗车)、hasConvenienceStore(便利店)、kpetroCertified(品质认证),以及按PRODUCT_CODE_TO_KEY归类好的prices.gasoline/diesel/…和保留原始代码的rawPrices(含tradeDate/tradeTime成交时间)。mergeStationDetail(src/index.js#L204-L218)负责把两者合并,并保证合并后pricedistanceMeters仍以周边数据为准。

排序函数sortStationsByPriceAndDistance(src/parse.js#L420-L432)的三级比较逻辑是:先比价格(升序),再比距离(升序),最后按韩文名排序。测试 index.test.js#L96-L108 验证了「A1000001(1635 韩元/112.4m)→ A1000003(1649 韩元/220m)→ A1000002(1649 韩元/315m)」这一价格优先、距离次之的期望顺序,其中后两者同价时 220m 排在 315m 前面。

十二、测试与可验证性

包的测试用 Node 内置的node:test编写(npm test即可运行),测试文件 test/index.test.js 与 test/fixtures 目录下的固定样本(anchor-search.htmlanchor-panel.jsonaround-response.jsondetail-a1000001.jsondetail-a1000003.json)共同覆盖了:

  • HTML 搜索卡片解析与锚点选优(parseSearchResultsHtml/selectAnchorCandidate);
  • 锚点面板规整与坐标缺失时的null保持;
  • WGS84→KATEC 数值基准(309252.2237, 550779.9944);
  • aroundAll.do参数编码契约;
  • 周边结果价格排序与详情字段补全(自助、洗车、保养、品质认证、多油品价格);
  • 带 mock fetch 的端到端调用链、锚点回退、评分序回退、非法limit拒绝。

这些测试既是行为规格,也是读者验证「包工作正常」的最直接手段。

十三、失败模式与使用注意事项

结合 instruction.md 的 Failure modes 一节与源码中的防御逻辑,实际使用中需要关注以下风险点:

  • 密钥缺失:直连模式必须有OPINET_API_KEY(或apiKey/certKey),否则抛错;代理模式下则要求代理服务可用且服务端已配置密钥;
  • Kakao 锚点歧义:位置串模糊时可能选中错误坐标,包会用评分与候选回退尽量缓解,但仍建议 Agent 在结果可疑时向用户二次确认位置;
  • Opinet 响应波动:官方 API 可能出现临时空结果或数据更新中,parseAroundResponse会过滤无价格记录,此时应如实告知「没找到」及下一步追问,而不是编造结果;
  • 参数边界radius超过 5000 米、坐标为 NaN、limit/detailLimit非有限数值都会立即抛错,属于设计上的「快速失败」策略。

对于 Agent 场景,searchCheapGasStationsByLocationQuery的使用纪律是:先问清当前位置与所需油品,再调用;默认按汽油(B027)、半径 1000 米、返回 3~5 条简洁整理即可满足绝大多数「附近最便宜的加油站」类需求。更完整的 Skill 级应答规范(必问问题、字段整理模板、Done when 检查清单)可继续参阅 cheap-gas-nearby/instruction.md 与 cheap-gas-nearby/SKILL.md。

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

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

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

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

立即咨询