k-skill 之 SH Notice Search:基于公开 HTML 公告板实现的首尔住宅公社公告检索 Skill
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
导读
本文围绕 k-skill 仓库中的sh-notice-searchSkill(关联文档为 packages/k-skill-cli/skills/sh-notice-search/instruction.md)展开,讲解如何让 Agent 直接读取 서울주택도시개발공사(SH,首尔住宅城市开发公社,官网www.i-sh.co.kr)的公开公告/通知 HTML 公告板,将公告列表、详情正文与附件元数据整理为 JSON。读完本文,你将掌握该 Skill 的能力边界、公开访问路径的发现过程、分类别名映射、srchTp/page等关键参数的正确用法,以及底层 npm 包sh-notice-search的源码实现与测试验证细节,可直接用 Node.js 或 CLI 复现同样的检索流程。
一、Skill 定位:只读查询,不做业务自动化
sh-notice-search的核心职责是读取 SH 官方公开 HTML 公告板,把结构化信息交给 Agent 使用。根据 instruction.md 的定义,它支持:
- 使用关键词检索 SH 公告/通知列表;
- 选择官方公告板分类(주택임대、주택분양、주택매입/주거복지、토지、상가/공장 等);
- 在详情页中提取正文、담당부서(负责部门)、등록일(登记日期)、조회수(浏览量)以及真实附件文件名;
- 附件提取以实际带
existFile('N')onclick 的附件锚点与downList元数据为准,而不是解析扩展名图标模板。
同时它的边界非常明确:不执行 청약 신청(认购申请)、서류 제출(文件提交)、需要登录的 마이페이지 查询、결제(支付)或 알림 발송(通知发送)。这正是 skill.json 中description所指的"公开公告板直接查询"能力——即使它声明了profiles: ["proxy", "action:submission"],其查询本体仍是公开只读的 HTML 面。
典型使用场景
instruction.md 给出了几个可直接触发该 Skill 的 Agent 输入示例:
- "SH 행복주택 공고 찾아줘"(帮我找 SH 幸福住宅公告)
- "서울주택도시개발공사 매입임대 공고 보여줘"(给我看 SH 购租公告)
- "SH 공고 seq 304371 상세와 첨부파일 알려줘"(告诉我 seq 304371 的详情与附件)
- "SH 분양 공고 최신 목록 조회"(查询 SH 最新预售公告)
运行前置条件
| 条件 | 说明 |
|---|---|
| 网络连接 | 需要直接访问www.i-sh.co.kr |
| Node.js 18+ | 运行时要求,见 packages/sh-notice-search/package.json 的engines字段 |
| 依赖来源 | 本仓库的sh-notice-searchnpm 包,或逻辑等价的自有实现 |
二、公开访问路径的发现与规范化
这是整个 Skill 的基石:不经过 k-skill-proxy、不需要 API key。instruction.md 记录了 2026-05-15 的一次真实 smoke 探测结果——直接无认证抓取www.i-sh.co.kr即可返回列表/详情 HTML。
官方公开 HTML 公告板地址
- 默认租赁(주택임대)列表:
https://www.i-sh.co.kr/app/lay2/program/S1T294C297/www/brd/m_247/list.do?multi_itm_seq=2 - 默认租赁详情:
https://www.i-sh.co.kr/app/lay2/program/S1T294C297/www/brd/m_247/view.do?multi_itm_seq=2&seq=<seq> - 标题关键词搜索:追加
srchWord=<keyword>&srchTp=0 - 内容关键词搜索:追加
srchWord=<keyword>&srchTp=1 - 公告板固定每页 10 行,分页使用
page参数
探测得出的两个关键结论
- 不带
srchTp的srchWord=행복주택会返回完整的租赁公告板总数,而加上srchTp=0才能收窄结果集。因此只要存在关键词,客户端就强制携带srchTp,否则srchWord会被 SH 公告板忽略。 - 上游为公开源且无需 API key,所以不引入
k-skill-proxy路由(对比本仓库其他依赖代理的 Skill,这是一个重要差异点)。
源码侧,packages/sh-notice-search/src/index.js 的buildSearchUrl/buildDetailUrl完整体现了上述 URL 构造逻辑:按分类配置取path,设置multi_itm_seq/multi_itm_seqs,再追加page、srchWord、srchTp等参数。normalizeSearchType(index.js)实现了srchTp的规范化:无关键词时为null,有关键词时默认"0"(标题),同时接受title/제목/0与content/본문/내용/1两组别名。
三、分类别名与官方 Tab 映射
SH 公告板按官方 Tab 划分,而用户习惯用自然语言别名。instruction.md 给出了完整的别名表,这也是 Agent 应答时必须遵循的映射:
| 输入别名 | 官方 Tab |
|---|---|
rent、임대、주택임대 | 주택임대(multi_itm_seq=2) |
sale、분양、주택분양 | 주택분양(multi_itm_seq=1) |
purchase、매입、매입임대、welfare、주거복지 | 주택매입(multi_itm_seq=512) |
land、토지 | 토지 |
commercial、상가、공장 | 상가/공장 |
compensation、보상、이주 | 보상/이주 |
design、현상설계 | 현상설계 |
etc、기타 | 기타 |
all、전체 | 전체 |
需要特别说明:주거복지并不是 SH 公告板的公开 Tab 名,而是为提升用户体验增加的别名,当前映射到 SH 公开的주택매입Tab。Agent 在回答时必须向用户披露这一映射关系,避免产生"SH 存在同名 Tab"的误解。
源码中的完整分类配置
npm 包在 src/index.js 的CATEGORY_CONFIGS中定义了比文档别名表更完整的配置,包括每个分类的官方 board path与multiItmSeq:
| key | 官方名称 | board path(相对www.i-sh.co.kr) | multiItmSeq |
|---|---|---|---|
all | 전체 | /app/lay2/program/S1T294C295/www/brd/m_241 | 1,2,4,8,16,32,64,128,256,512(multi_itm_seqs) |
sale | 주택분양 | /app/lay2/program/S1T294C296/www/brd/m_244 | 1 |
rent | 주택임대 | /app/lay2/program/S1T294C297/www/brd/m_247 | 2 |
purchase | 주택매입 | /app/lay2/program/S1T294C3379/www/brd/m_247 | 512 |
movein | 입주안내 | /app/lay2/program/S1T294C298/www/brd/m_248 | 4 |
land | 토지 | /app/lay2/program/S1T294C299/www/brd/m_255 | 8 |
commercial | 상가/공장 | /app/lay2/program/S1T294C300/www/brd/m_256 | 16 |
compensation | 보상/이주 | /app/lay2/program/S1T294C301/www/brd/m_257 | 32 |
design | 현상설계 | /app/lay2/program/S1T294C302/www/brd/m_258 | 64 |
etc | 기타 | /app/lay2/program/S1T294C304/www/brd/m_260 | 256 |
别名通过CATEGORY_ALIAS(index.js)反查得到内部 key,normalizeCategory遇到未知分类会抛出Unsupported SH category错误。注意all分类使用的是复数参数multi_itm_seqs,其他分类使用单数multi_itm_seq——这是 URL 构造(buildSearchUrl)中需要区分的关键细节,测试用例 index.test.js 也专门验证了분양→sale 与welfare→purchase 的路径和参数映射。
四、工作流:三步完成公告检索
步骤 1:检索公告列表
以 npm 包方式调用:
const { searchNotices } = require("sh-notice-search") const result = await searchNotices({ keyword: "행복주택", category: "임대", page: 1, limit: 5 }) console.log(result.items)CLI 方式:
node packages/sh-notice-search/src/cli.js 행복주택 --category 임대 --limit 5 node packages/sh-notice-search/src/cli.js 매입임대 --category 주거복지 --status 진행列表项返回字段:
seqtitledepartmentregistered_dateviewsis_newcategory、category_namestatus、status_basisdetail_url
参数规范化细节(来自 normalizeSearchOptions):
keyword兼容q/query/srchWord写法,长度上限 100 字符(超出抛错);category兼容kind/noticeType,内部归一到分类 key;page默认 1,合法范围 1–1000;pageSize/limit默认 10,最大值被钳制为 10(MAX_PAGE_SIZE = 10),因为 SH 公告板固定每页 10 行,传更大的值不会让 SH 返回更多行;timeoutMs默认 20000ms,上限 120000ms;srchTp兼容searchType/type写法;- 支持注入
fetcher与signal(便于测试与取消)。
返回结构上,parseListHtml(index.js)会给出query(回显查询条件)、summary(page、page_size、returned_count、total_count)、source(name: "sh-public-html"、官方 URL、proxy: false)、warnings与items。total_count通过正则匹配页面上的총 <strong>N</strong> 건文本得到(extractTotalCount)。测试 index.test.js 验证了 95 条总数、2 行返回、seq/title/views/is_new/category_name与detail_url的解析结果。
步骤 2:获取详情与附件
const { getNoticeDetail } = require("sh-notice-search") const detail = await getNoticeDetail({ seq: "304371", category: "임대" }) console.log(detail.notice.content_text) console.log(detail.notice.attachments)CLI:
node packages/sh-notice-search/src/cli.js --seq 304371 --category 임대附件字段:
filenamefile_seqfile_sizefile_typepreview_url(SH 官方 preview/converter 地址)
刻意不返回直接下载链接:SH 的文件下载行为可能依赖会话与站点策略,因此应把detail_url或preview_url交给用户浏览器处理。
详情解析的源码实现(parseDetailHtml):
- 标题:匹配
detailTable/firgs0401Table容器内的<caption>或colspan=2的<th>; - 登记日期:匹配
<strong>등록일 : </strong>后的YYYY[-.]MM[-.]DD,并归一为-分隔; - 浏览量:匹配
<strong>조회수 : </strong>后的数字; - 负责部门:匹配
personInfo列表中的담당부서 : ...(extractDepartment); - 正文:匹配
class="cont"的<td>,去除 script/style 与标签后得到content_text(stripTags); - 状态:同样基于标题文本分类(见下一节)。
附件提取的严谨性是本文档强调的重点。实现位于 parseAttachments 与 parseAttachmentDownList:
- 先从页面内嵌脚本中解析
downList(JS 对象/数组字面量)元数据,按fileSeq建立索引; - 定位
첨부(파일)表头行,遍历该单元格内的<a>锚点; - 只认可同时满足以下条件的锚点为真实附件:
class含btnAttach、onclick形如existFile('<数字>')、且锚点文本不是纯扩展名标签(如.pdf、.hwp这类图标模板,由isAttachmentIconLabel过滤); preview_url只接受 SH 本域且路径为/app/com/util/htmlConverter.do的链接(normalizeAttachmentPreviewUrl),防止外部伪造域混入。
测试用例 index.test.js 专门验证了:HTML 注释中的图标模板.pdf/.hwp不会被当作真实附件,而两个带existFile('0')/existFile('1')的锚点会与downList中的fileSize/oriFileNm/fileTp合并,产出filename、file_seq、file_size、file_type与preview_url,且不包含download_url字段;同文件 L194-L206 还验证了外部域evil.example/htmlConverter.do会被丢弃。
步骤 3:保守地解读"状态"
SH 公开公告板列表没有一等公民的状态字段(如접수중/마감)。包虽然支持status过滤,但它是一个标题文本分类器:
open/진행:标题含 모집공고、입주자 모집、신청、접수、공고closed/마감:标题含 마감、계약결과、결과、완료、종료announced/당첨자:标题含 당첨、발표
源码实现为 classifyNoticeStatus:按"당첨/발표 → 마감/계약결과/결과/완료/종료 → 모집공고/입주자 모집/신청/접수/공고"的优先级正则判定,否则返回unknown;过滤逻辑 statusMatches 会对closed、announced做精确匹配。状态别名表 STATUS_ALIASES 支持韩英两组写法,如진행/모집중/공고중 → open、마감/종료/결과 → closed、당첨/발표 → announced。
instruction.md 明确要求:应答时必须披露"状态是从标题推断的",除非详情正文给出了确切的日期。测试 index.test.js 也验证了status: "closed"只返回含계약결과的行、status: "open"在该样本下返回 0 行,印证分类器的保守性。
五、CLI 与参数总览
CLI 入口为 packages/sh-notice-search/src/cli.js,同时是 npm bin(见 package.json 的bin字段,安装后可直接使用sh-notice-search命令)。其帮助信息给出完整选项:
Usage: sh-notice-search [keyword] [options] Search public SH notices: sh-notice-search 행복주택 --category 임대 --limit 5 sh-notice-search 매입임대 --category 주거복지 --status 진행 Fetch one detail: sh-notice-search --seq 304371 --category 임대 Options: -q, --query <text> Keyword. Defaults to title search when present. --search-type <type> title/제목 or content/내용. --category <category> all, rent/임대, sale/분양, welfare/주거복지, land/토지, etc. --status <status> open/진행, closed/마감, announced/당첨자 (title classifier). --page <number> Page number (default: 1). --limit <number> Returned rows; capped at SH fixed page size 10. --seq <number> Fetch detail by SH notice seq. --include-html Include raw HTML in output for diagnostics.参数解析器(parseArgs)支持--query/-q/--keyword、--category/--kind、--status、--page、--limit/--page-size、--srch-tp/--search-type、--seq/--id、--include-html与--help/-h;首个裸参数会被当作 keyword,detail/--detail后跟数字可指定 seq。CLI 输出为标准 JSON(JSON.stringify(result, null, 2)),--include-html可在诊断时保留原始 HTML。测试 index.test.js 验证了参数解析与--help输出。
六、完成标准(Done when)
根据 instruction.md,一次合格的 SH 公告查询应满足:
- 直接从用户机器查询 SH 官方列表/详情 URL;
- 关键词搜索包含
srchTp,避免srchWord被忽略; - 分页使用
page,并识别公告板固定每页 10 行的限制; - 附件取自真实的
existFile()锚点与downList元数据,而非扩展名图标模板; - 展示公开来源 URL,避免登录/申请类自动化。
七、失败模式与处理原则
instruction.md 明确列出的失败场景,也是 Agent 运行时需要向用户说明的情况:
- 上游结构变更:SH 可能调整公告板路径、表格标记、JavaScript 函数或
downList结构,导致解析部分失败或整体失败; - 访问受限:IP 限速、NetFunnel 节流、维护页面或临时 4xx/5xx 会阻断实时抓取;不得绕过CAPTCHA/登录/排队保护;
- 搜索参数陷阱:
srchWord不带srchTp会被 SH 公告板忽略,必须带srchTp=0(标题)或srchTp=1(内容); - 分页陷阱:
pageSize大于 10 不会让 SH 返回更多行,追加结果必须用page; - 附件策略:附件预览 URL 可能需要浏览器接力,并受 SH 当前直链/下载策略约束;
- 状态推断局限:公开列表无显式状态列,
status只能从标题文本推断。
源码中的防御性设计值得借鉴:fetchText(index.js)设置user-agent: Mozilla/5.0 (compatible; k-skill/sh-notice-search)与accept头,使用AbortSignal.timeout实现超时,非 2xx 响应会抛出含状态码与前 200 字符的异常;buildUnexpectedHtmlWarnings(index.js)通过findUpstreamBlockMarkers检测页面是否含NetFunnel、captcha/보안문자、로그인/login、점검/maintenance、대기열/queue、차단/block等特征词,并在warnings中提示"可能被拦截或处于维护"。测试 index.test.js 与 L185-L192 分别用模拟的维护页验证了列表/详情场景下会输出NetFunnel、로그인、점검警告。
八、边界与安全准则
- 只读查询:本 Skill 仅做查找,不做任何写操作;
- 无代理、无 API key、无密钥:所有请求直接发往 SH 公开面,这也是它在设计上与依赖代理的 Skill 的最大区别;
- 禁止自动化敏感流程:不得自动化 청약 신청、로그인、서류 제출、결제 或 마이페이지 流程;
- 保持来源透明:应答时展示 SH 官方 URL 与
source.name = "sh-public-html",让用户可自行核实。
九、快速上手指南
- 在仓库根目录安装/链接
sh-notice-search包(Node.js 18+,参考 packages/sh-notice-search/package.json); - 先做一次冒烟查询验证网络可达:
node packages/sh-notice-search/src/cli.js 행복주택 --category 임대 --limit 5; - 观察返回 JSON 的
summary.total_count、source.url与warnings,确认命中正常公告板标记而非维护页; - 用
--seq <编号> --category <分类>拉取详情,核对content_text、department、registered_date、views与attachments; - 需要内容关键词检索时显式加
--search-type 내용(等价srchTp=1); - 需要排查解析问题时加
--include-html输出原始 HTML 定位结构变化。
更多实现细节可继续阅读 packages/sh-notice-search/README.md、src/index.js、src/cli.js 与测试 test/index.test.js,以及 Skill 元数据 packages/k-skill-cli/skills/sh-notice-search/skill.json。
【免费下载链接】k-skill한국인을 위한 스킬 모음집 - 에이전트를 한국인으로项目地址: https://gitcode.com/GitHub_Trending/ks/k-skill
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考