sh-notice-search 实战指南:用 Node.js 直接查询首尔 SH 公社公开公告
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
本篇技术指南围绕sh-notice-search——k-skill 仓库中面向首尔住宅都市开发公社(SH,서울주택도시개발공사)的公开公告查询客户端展开,讲解如何通过其 JS API 与 CLI 直接抓取 SH 公开 HTML 公告板,完成公告列表检索、详情正文提取与附件元数据解析。读完本文,你将掌握该客户端的全部参数语义、分类别名映射、状态分类器原理、底层 HTML 解析机制及其明确的合规边界,可直接落地到 Agent 或自动化脚本中。
一、模块定位与能力边界
sh-notice-search是一个无需认证的公开 HTML 公告查询客户端,对应 k-skill 中的sh-notice-searchskill(技能清单见 sh-notice-search/skill.json,完整使用说明见 sh-notice-search/instruction.md)。它的设计目标非常克制:只读查询,不做任何事务性动作。
模块明确承诺的能力包括:
- 按关键词检索 SH 公开公告/通知列表;
- 按官方公告板分类(주택임대、주택분양、주택매입/주거복지、토지、상가/공장 等)筛选;
- 从详情页提取正文、담당부서(负责部门)、등록일(登记日期)、조회수(浏览量)与真实附件文件名;
- 解析附件的官方预览地址(
preview_url)。
同时模块明确不做以下事情:청약 신청(认购申请)、登录、文书提交、支付、My Page 查询、通知发送等流程的自动化。数据源是 SH 的公开 HTML 页面,不需要代理、不需要 API Key、不需要任何密钥。这一点在 packages/sh-notice-search/README.md 的 Source 一节与 skill 的 instruction 中均有反复强调,也是后续所有设计(如不暴露直链下载地址)的出发点。
二、数据来源与关键发现
2.1 公开公告板的 URL 结构
模块直接抓取的列表/详情页地址格式为:
https://www.i-sh.co.kr/app/lay2/program/.../www/brd/.../{list,view}.do以默认分类주택임대为例,src/index.js 中的CATEGORY_CONFIGS给出了精确路径:
- 列表页:
/app/lay2/program/S1T294C297/www/brd/m_247/list.do?multi_itm_seq=2 - 详情页:
/app/lay2/program/S1T294C297/www/brd/m_247/view.do?multi_itm_seq=2&seq=<seq>
其中multi_itm_seq=2是 주택임대 分类的官方标识;每个分类对应独立的板面路径与multi_itm_seq(详见第五节分类映射)。
2.2 srchWord 与 srchTp 的配套约束
这是 SH 公告板最重要的一个使用细节:SH 要求关键词搜索必须同时提供srchWord与srchTp两个参数,否则srchWord会被忽略。instruction.md 记录了一次真实冒烟测试(2026-05-15):仅带srchWord=행복주택而不带srchTp时返回的是整个 주택임대 板的全部公告数,加上srchTp=0后才收窄结果集。
因此客户端在存在关键词时总是携带srchTp,且默认按标题搜索:
| 搜索范围 | srchTp值 | JS 侧写法 |
|---|---|---|
| 标题搜索(默认) | 0 | searchType: "title"/srchTp: "0" |
| 正文搜索 | 1 | searchType: "content"/srchTp: "1" |
URL 构建逻辑见 buildSearchUrl:仅当keyword非空时才写入srchWord与srchTp。对应测试 test/index.test.js 验证了srchWord、srchTp=0、multi_itm_seq=2三个参数同时出现。
三、环境要求与快速上手
sh-notice-search是一个 npm 包(package.json),发布版本 0.4.0,要求Node.js >= 18(engines.node字段),标准入口为src/index.js,并暴露sh-notice-search的 bin 命令。
安装:
npm install sh-notice-search依赖 Node 18+ 的原生fetch(代码通过global.fetch获取,也允许调用方注入自定义 fetcher,见第九节)。
最简用法——先搜列表,再取第一条的详情:
const { searchNotices, getNoticeDetail } = require("sh-notice-search") const list = await searchNotices({ keyword: "행복주택", category: "임대", page: 1 }) const detail = await getNoticeDetail({ seq: list.items[0].seq, category: "임대" })注意list.items[0].seq是字符串类型的公告序号,详情查询直接复用即可。
四、JS API 参数详解
两个核心异步函数:searchNotices(options)与getNoticeDetail(options)。参数经 normalizeSearchOptions / normalizeDetailOptions 统一归一化,支持多组别名。
4.1 searchNotices 参数
| 参数 | 别名 | 默认值 | 说明 |
|---|---|---|---|
keyword | q/query/srchWord | null | 搜索关键词,上限 100 字符,超长直接抛错 |
category | kind/noticeType | rent(주택임대) | 分类键或别名,见第五节 |
searchType | srchTp/type | 有关键词时"0" | 标题(0)或正文(1)搜索 |
page | pageNo | 1 | 页码,范围 1–1000,非数字抛错 |
pageSize | limit | 10 | 返回行数,上限 10(SH 板每页固定 10 行) |
status | — | null | 状态筛选:open/진행、closed/마감、announced/당첨자 |
timeoutMs | — | 20000 | 请求超时(毫秒),上限 120000 |
fetcher | — | global.fetch | 自定义 fetch 实现(测试注入用) |
signal | — | — | 外部 AbortSignal |
includeHtml | — | false | 为true时在结果中附带原始 HTML,便于诊断 |
非法输入均有明确报错:例如page: "abc"报Provide valid page.,未知分类报Unsupported SH category: ...,未知状态报Unsupported SH status: ...(测试见 test/index.test.js)。
4.2 getNoticeDetail 参数
| 参数 | 别名 | 说明 |
|---|---|---|
seq | noticeSeq/id | 公告序号,必填,仅允许 1–20 位数字 |
category | kind/noticeType | 分类,缺省rent |
此外同样支持timeoutMs、fetcher、signal、includeHtml。
五、CLI 使用详解
CLI 入口为 src/cli.js,安装包后可直接调用sh-notice-search命令,输出为格式化 JSON。三种典型用法:
# 按关键词搜索列表 sh-notice-search 행복주택 --category 임대 --limit 5 # 分类 + 状态筛选 sh-notice-search 매입임대 --category 주거복지 --status 진행 # 按 seq 取详情 sh-notice-search --seq 304371 --category 임대完整选项如下(--help可随时查看):
| 选项 | 别名 | 说明 |
|---|---|---|
--query <text> | -q/--keyword | 关键词,存在时默认标题搜索 |
--search-type <type> | --srch-tp | title/제목或content/내용 |
--category <category> | --kind | all、rent/임대、sale/분양、welfare/주거복지、land/토지等 |
--status <status> | — | open/진행、closed/마감、announced/당첨자(标题分类器) |
--page <number> | — | 页码,默认 1 |
--limit <number> | --page-size | 返回行数,被 SH 固定页大小 10 截断 |
--seq <number> | --id | 传该值则走详情查询 |
--include-html | — | 输出中附带原始 HTML |
CLI 还支持detail <seq>的写法(parseArgs中检测detail/--detail标记后的数字参数)。出错时输出堆栈并设置进程退出码 1(见 run)。
六、返回字段详解
6.1 列表项字段
列表解析见 parseListRows,每行包含:
| 字段 | 类型 | 说明 |
|---|---|---|
seq | string | 公告序号(来自getDetailView('...')调用) |
number | string | 公告栏编号 |
title | string | 标题(去掉 NEW 徽标文本) |
department | string | 담당부서 负责部门 |
registered_date | string | 登记日期(如2026-05-14) |
views | number | 浏览量 |
is_new | boolean | 是否带 NEW 标记 |
category/category_name | string | 分类键 / 官方分类名 |
status/status_basis | string | 推断状态 / 恒为"title_text_classifier" |
detail_url | string | 官方详情页 URL |
顶层返回结构还包含query(回显本次查询参数)、summary(page、page_size、returned_count、total_count)、source(name: "sh-public-html"、proxy: false、原始url)以及warnings数组,便于调用方自检。
6.2 详情字段
详情解析见 parseDetailHtml,getNoticeDetail返回{ notice, query, source },其中notice包含:
seq、title、registered_date、views、department、category、category_name;content_text:剥离脚本、样式与标签后的纯文本正文;attachments:附件元数据数组;detail_url、warnings(includeHtml开启时还有html)。
6.3 附件元数据
每个附件包含:
| 字段 | 说明 |
|---|---|
filename | 真实文件名(如2025년 2차 행복주택 예비3차 계약결과.pdf) |
file_seq | SH 侧文件序号 |
file_size | 字节数 |
file_type | 类型标识(如A) |
preview_url | 官方SH 预览/转换地址(/app/com/util/htmlConverter.do?...) |
刻意不暴露直接下载 URL(download_url):因为 SH 的文件下载行为可能与会话/策略相关,直链并不稳定,正确做法是把官方detail_url/preview_url交给用户浏览器处理。测试 test/index.test.js 专门断言download_url键不存在。
七、分类别名映射表
分类归一化由 normalizeCategory 完成,别名经CATEGORY_ALIAS索引映射到官方板面。完整配置见 CATEGORY_CONFIGS:
| 分类键 | 官方板面 | multi_itm_seq | 支持别名 |
|---|---|---|---|
all | 전체 | multi_itm_seqs=1,2,4,8,16,32,64,128,256,512 | all、전체 |
sale | 주택분양 | 1 | sale、분양、주택분양、분양주택 |
rent | 주택임대 | 2 | rent、임대、주택임대、임대주택 |
purchase | 주택매입 | 512 | purchase、매입、주택매입、매입임대、welfare、주거복지 |
movein | 입주안내 | 4 | movein、입주、입주안내 |
land | 토지 | 8 | land、토지 |
commercial | 상가/공장 | 16 | commercial、상가、공장 |
compensation | 보상/이주 | 32 | compensation、보상、이주 |
design | 현상설계 | 64 | design、현상설계、설계 |
etc | 기타 | 256 | etc、기타 |
重要提示:주거복지并不是 SH 公告板的公开标签,而是面向用户的友好别名,当前映射到 SH 公开的주택매입板(multi_itm_seq=512)。使用该别名时,应在答复中向用户说明这一映射关系(instruction.md 明确要求如此)。
别名匹配前会先做归一化(normalizeToken:去掉所有空白、trim、转小写),因此" 임대 "、"임대"等价。
八、状态分类器原理
SH 公开列表没有一等公民的状态字段(没有접수중/마감之类的列),因此模块采用保守的标题文本分类器(classifyNoticeStatus):
| 推断状态 | 触发关键词(标题内出现) |
|---|---|
announced | 당첨、발표 |
closed | 마감、계약결과、결과、완료、종료 |
open | 모집공고、입주자 모집、신청、접수、공고 |
unknown | 以上均未命中 |
状态筛选在解析后进行(statusMatches),并把status_basis标记为"title_text_classifier"以便下游感知其推断性质;一旦传入了status参数,返回的warnings中也会追加说明。响应时务必向用户披露“状态系从标题推断,除非公告正文写明确切日期”。测试 test/index.test.js 验证了계약결과标题被归为closed而open筛选返回空的行为。
九、源码级实现原理
9.1 请求构建与容错
- URL 构建:
buildSearchUrl用标准URL对象拼接base + config.path + "/list.do",再按需写入multi_itm_seq(s)、page、srchWord、srchTp;buildDetailUrl类似地拼view.do与seq。 - fetch 注入:
fetchText优先使用调用方注入的fetcher,否则回退global.fetch;请求头固定携带user-agent: Mozilla/5.0 (compatible; k-skill/sh-notice-search)与accept: text/html,...。测试验证了该 UA 头(test/index.test.js)。 - 超时:
createTimeoutSignal在支持AbortSignal.timeout的运行时用其实现超时;默认 20 秒。 - HTTP 错误:非 2xx 响应抛出带状态码与前 200 字节正文的错误信息。
9.2 HTML 解析的关键策略
- 列表解析:优先定位
<div id="listTb">内的<tbody>,逐<tr>提取。判定有效行的关键是找到getDetailView('seq')JS 调用;单元格不足 5 列的行跳过;标题会去掉NEW徽标。总数通过총 <strong>N</strong> 건正则提取。 - 文本清洗:
stripTags先移除<script>/<style>再剥标签;decodeHtml完整处理数字实体(十进制/十六进制)与&、<、>、"、 等常见实体。 - 附件解析(parseAttachments)是本模块最有辨识度的部分,有两条严格规则:
- 只认带
existFile('N')onclick 的真实附件锚点,并显式过滤.pdf/.hwp等图标模板文本——测试 fixture 中专门把图标模板注释掉以验证其不会被误解析(test/index.test.js); - 附件文件名、大小、类型优先来自页面内嵌的
downListJSON 元数据,并与htmlConverter.do预览链接按file_seq配对。
- 只认带
- 预览链接白名单:
normalizeAttachmentPreviewUrl只接受www.i-sh.co.kr同源且路径为/app/com/util/htmlConverter.do的链接,防止被注入外部地址——测试用evil.example替换后断言preview_url为空(test/index.test.js)。
9.3 异常面检测与 warnings
当响应缺少预期的列表/详情标记时,buildUnexpectedHtmlWarnings 会扫描页面中的NetFunnel、captcha/보안문자、로그인、점검、대기열、차단等关键词,给出形如unexpected SH list HTML; possible block/maintenance markers: NetFunnel, 로그인, 점검的警告。测试用一份含“서비스 점검 안내 / NetFunnel 대기열 또는 로그인”的拦截页验证了该行为(test/index.test.js),它让调用方在遇到限流/维护页时能快速识别,而不是得到静默的空结果。
十、测试与质量保障
测试文件 test/index.test.js 使用 Node 内置node:test+node:assert/strict,npm test即运行(package.json 中scripts.test为node --test),覆盖:
- 参数归一化:关键词默认标题搜索、韩英分类别名、状态别名、非法输入抛错;
- URL 构建:hostname、路径、
srchTp、multi_itm_seq与seq参数; - 列表/详情 HTML 解析:总数、行字段、详情字段、附件配对;
- 拦截页检测:列表与详情的 warnings;
- 端到端:通过注入的假
fetcher走通searchNotices→getNoticeDetail; - CLI:
parseArgs解析与--help输出。
十一、失败模式与使用注意
综合 README 的 Boundaries、instruction 的 Failure modes 与源码实现,接入时需注意:
- SH 可能变更板面路径、表格标记、JS 函数或
downList结构,导致解析部分失败或完全失败——务必把warnings纳入处理逻辑; - IP 限流、NetFunnel 节流、维护页或临时 4xx/5xx会阻塞实时抓取;模块只做被动检测(warnings),不得绕过CAPTCHA、登录或排队保护;
pageSize/limit超过 10 无意义,SH 板每页固定返回 10 行,追加结果请翻页(page);- 关键词必须与
srchTp成对出现,客户端已默认处理; - 附件预览/下载受 SH 当前直链与下载策略约束,应把官方 URL 交还用户浏览器;
- 状态为标题推断结果,非官方字段,答复时需披露。
结语
sh-notice-search的价值在于把“SH 公开公告查询”这件容易被各种反爬策略与 HTML 结构变化困扰的事情,收敛为一个参数规范、边界清晰、可测试的 Node 客户端:官方 URL 直连、无需代理与密钥、srchWord+srchTp配套、真实附件锚点识别、拦截页警告,配合完整的单元测试与 skill 说明文档(sh-notice-search/instruction.md),无论是嵌入 Agent 技能还是独立脚本使用,都能在合规前提下稳定读取 SH 的 청약·주택 공고 信息。
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考