@nomadamas/k-skill 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/bin/k-skill.js +60 -0
  2. package/package.json +37 -0
  3. package/skills/assembly-bill-vote-search/instruction.md +111 -0
  4. package/skills/assembly-bill-vote-search/skill.json +10 -0
  5. package/skills/biz-health-check/instruction.md +69 -0
  6. package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
  7. package/skills/biz-health-check/skill.json +10 -0
  8. package/skills/bok-ecos-stats/instruction.md +95 -0
  9. package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
  10. package/skills/bok-ecos-stats/skill.json +10 -0
  11. package/skills/building-register-search/instruction.md +69 -0
  12. package/skills/building-register-search/scripts/building_register.py +309 -0
  13. package/skills/building-register-search/scripts/building_register_xml.py +32 -0
  14. package/skills/building-register-search/skill.json +9 -0
  15. package/skills/bunjang-search/instruction.md +154 -0
  16. package/skills/bunjang-search/skill.json +10 -0
  17. package/skills/catchtable-sniper/instruction.md +270 -0
  18. package/skills/catchtable-sniper/skill.json +9 -0
  19. package/skills/cheap-gas-nearby/instruction.md +107 -0
  20. package/skills/cheap-gas-nearby/skill.json +10 -0
  21. package/skills/corporate-registration-consulting/instruction.md +117 -0
  22. package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
  23. package/skills/corporate-registration-consulting/skill.json +8 -0
  24. package/skills/coupang-product-search/instruction.md +219 -0
  25. package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
  26. package/skills/coupang-product-search/skill.json +10 -0
  27. package/skills/court-auction-notice-search/instruction.md +201 -0
  28. package/skills/court-auction-notice-search/skill.json +9 -0
  29. package/skills/court-payment-order-assistant/instruction.md +120 -0
  30. package/skills/court-payment-order-assistant/skill.json +10 -0
  31. package/skills/d2b-notice-search/instruction.md +117 -0
  32. package/skills/d2b-notice-search/skill.json +10 -0
  33. package/skills/daangn-cars-search/instruction.md +91 -0
  34. package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
  35. package/skills/daangn-cars-search/skill.json +9 -0
  36. package/skills/daangn-jobs-search/instruction.md +90 -0
  37. package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
  38. package/skills/daangn-jobs-search/skill.json +9 -0
  39. package/skills/daangn-realty-search/instruction.md +114 -0
  40. package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
  41. package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
  42. package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
  43. package/skills/daangn-realty-search/skill.json +8 -0
  44. package/skills/daangn-used-goods-search/instruction.md +90 -0
  45. package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
  46. package/skills/daangn-used-goods-search/skill.json +9 -0
  47. package/skills/daishin-report-search/instruction.md +138 -0
  48. package/skills/daishin-report-search/skill.json +10 -0
  49. package/skills/daiso-product-search/instruction.md +172 -0
  50. package/skills/daiso-product-search/skill.json +8 -0
  51. package/skills/danawa-price-search/instruction.md +183 -0
  52. package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
  53. package/skills/danawa-price-search/skill.json +8 -0
  54. package/skills/delivery-tracking/instruction.md +358 -0
  55. package/skills/delivery-tracking/skill.json +8 -0
  56. package/skills/donation-place-search/instruction.md +129 -0
  57. package/skills/donation-place-search/skill.json +9 -0
  58. package/skills/emergency-room-beds/instruction.md +82 -0
  59. package/skills/emergency-room-beds/skill.json +8 -0
  60. package/skills/ev-charger-nearby/instruction.md +80 -0
  61. package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
  62. package/skills/ev-charger-nearby/skill.json +10 -0
  63. package/skills/ev-subsidy-status/instruction.md +165 -0
  64. package/skills/ev-subsidy-status/skill.json +10 -0
  65. package/skills/express-bus-booking/instruction.md +207 -0
  66. package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
  67. package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
  68. package/skills/express-bus-booking/skill.json +8 -0
  69. package/skills/fine-dust-location/instruction.md +89 -0
  70. package/skills/fine-dust-location/skill.json +9 -0
  71. package/skills/flight-ticket-search/instruction.md +237 -0
  72. package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
  73. package/skills/flight-ticket-search/skill.json +9 -0
  74. package/skills/foresttrip-vacancy/instruction.md +167 -0
  75. package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
  76. package/skills/foresttrip-vacancy/skill.json +10 -0
  77. package/skills/fsc-corporate-info/instruction.md +57 -0
  78. package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
  79. package/skills/fsc-corporate-info/skill.json +10 -0
  80. package/skills/g2b-order-plan-search/instruction.md +131 -0
  81. package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
  82. package/skills/g2b-order-plan-search/skill.json +10 -0
  83. package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
  84. package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
  85. package/skills/g2b-sanctioned-supplier/skill.json +10 -0
  86. package/skills/gangnamunni-clinic-search/instruction.md +113 -0
  87. package/skills/gangnamunni-clinic-search/skill.json +9 -0
  88. package/skills/geeknews-search/instruction.md +69 -0
  89. package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
  90. package/skills/geeknews-search/skill.json +8 -0
  91. package/skills/gongsijiga-search/instruction.md +128 -0
  92. package/skills/gongsijiga-search/skill.json +9 -0
  93. package/skills/gov-overseas-trip-report/instruction.md +488 -0
  94. package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
  95. package/skills/gov-overseas-trip-report/skill.json +9 -0
  96. package/skills/han-river-water-level/instruction.md +83 -0
  97. package/skills/han-river-water-level/skill.json +10 -0
  98. package/skills/highway-traffic-status/instruction.md +88 -0
  99. package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
  100. package/skills/highway-traffic-status/skill.json +11 -0
  101. package/skills/hipass-receipt/instruction.md +97 -0
  102. package/skills/hipass-receipt/skill.json +10 -0
  103. package/skills/hola-poke-yeoksam/instruction.md +247 -0
  104. package/skills/hola-poke-yeoksam/skill.json +8 -0
  105. package/skills/household-waste-info/instruction.md +117 -0
  106. package/skills/household-waste-info/skill.json +10 -0
  107. package/skills/housing-official-price/instruction.md +177 -0
  108. package/skills/housing-official-price/skill.json +10 -0
  109. package/skills/hwp/instruction.md +206 -0
  110. package/skills/hwp/skill.json +8 -0
  111. package/skills/intercity-bus-booking/instruction.md +189 -0
  112. package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
  113. package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
  114. package/skills/intercity-bus-booking/skill.json +8 -0
  115. package/skills/iros-registry-automation/instruction.md +229 -0
  116. package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
  117. package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
  118. package/skills/iros-registry-automation/skill.json +9 -0
  119. package/skills/job-posting-match/instruction.md +130 -0
  120. package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
  121. package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
  122. package/skills/job-posting-match/skill.json +8 -0
  123. package/skills/jobkorea-talent-search/instruction.md +118 -0
  124. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
  125. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
  126. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
  127. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
  128. package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
  129. package/skills/jobkorea-talent-search/skill.json +9 -0
  130. package/skills/joseon-sillok-search/instruction.md +76 -0
  131. package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
  132. package/skills/joseon-sillok-search/skill.json +8 -0
  133. package/skills/k-dart/instruction.md +406 -0
  134. package/skills/k-dart/skill.json +8 -0
  135. package/skills/k-schoollunch-menu/instruction.md +109 -0
  136. package/skills/k-schoollunch-menu/skill.json +9 -0
  137. package/skills/k-skill-cleaner/instruction.md +80 -0
  138. package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
  139. package/skills/k-skill-cleaner/skill.json +8 -0
  140. package/skills/k-skill-setup/instruction.md +253 -0
  141. package/skills/k-skill-setup/skill.json +11 -0
  142. package/skills/kakao-bar-nearby/instruction.md +76 -0
  143. package/skills/kakao-bar-nearby/skill.json +8 -0
  144. package/skills/kakao-map/instruction.md +176 -0
  145. package/skills/kakao-map/skill.json +10 -0
  146. package/skills/kakaotalk-mac/instruction.md +189 -0
  147. package/skills/kakaotalk-mac/skill.json +8 -0
  148. package/skills/kbl-results/instruction.md +89 -0
  149. package/skills/kbl-results/skill.json +9 -0
  150. package/skills/kbo-results/instruction.md +82 -0
  151. package/skills/kbo-results/skill.json +8 -0
  152. package/skills/keris-academic-search/instruction.md +81 -0
  153. package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
  154. package/skills/keris-academic-search/skill.json +10 -0
  155. package/skills/kleague-results/instruction.md +92 -0
  156. package/skills/kleague-results/skill.json +8 -0
  157. package/skills/kopis-performance-search/instruction.md +109 -0
  158. package/skills/kopis-performance-search/skill.json +10 -0
  159. package/skills/korea-weather/instruction.md +93 -0
  160. package/skills/korea-weather/skill.json +10 -0
  161. package/skills/korean-character-count/instruction.md +87 -0
  162. package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
  163. package/skills/korean-character-count/skill.json +8 -0
  164. package/skills/korean-cinema-search/instruction.md +177 -0
  165. package/skills/korean-cinema-search/skill.json +8 -0
  166. package/skills/korean-heritage-search/instruction.md +106 -0
  167. package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
  168. package/skills/korean-heritage-search/skill.json +8 -0
  169. package/skills/korean-holiday-calendar/instruction.md +97 -0
  170. package/skills/korean-holiday-calendar/skill.json +9 -0
  171. package/skills/korean-humanizer/instruction.md +389 -0
  172. package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
  173. package/skills/korean-humanizer/skill.json +8 -0
  174. package/skills/korean-jangbu-for/instruction.md +133 -0
  175. package/skills/korean-jangbu-for/scripts/install.sh +237 -0
  176. package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
  177. package/skills/korean-jangbu-for/skill.json +8 -0
  178. package/skills/korean-law-search/instruction.md +126 -0
  179. package/skills/korean-law-search/skill.json +10 -0
  180. package/skills/korean-marathon-schedule/instruction.md +111 -0
  181. package/skills/korean-marathon-schedule/skill.json +9 -0
  182. package/skills/korean-middle-korean/instruction.md +79 -0
  183. package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
  184. package/skills/korean-middle-korean/skill.json +8 -0
  185. package/skills/korean-patent-search/instruction.md +79 -0
  186. package/skills/korean-patent-search/scripts/patent_search.py +409 -0
  187. package/skills/korean-patent-search/skill.json +9 -0
  188. package/skills/korean-privacy-terms/instruction.md +128 -0
  189. package/skills/korean-privacy-terms/scripts/install.sh +108 -0
  190. package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
  191. package/skills/korean-privacy-terms/skill.json +8 -0
  192. package/skills/korean-scholarship-search/instruction.md +317 -0
  193. package/skills/korean-scholarship-search/references/report-format.md +40 -0
  194. package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
  195. package/skills/korean-scholarship-search/references/search-clues.md +58 -0
  196. package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
  197. package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
  198. package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
  199. package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
  200. package/skills/korean-scholarship-search/skill.json +8 -0
  201. package/skills/korean-slang-writing/instruction.md +181 -0
  202. package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
  203. package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
  204. package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
  205. package/skills/korean-slang-writing/skill.json +8 -0
  206. package/skills/korean-spell-check/instruction.md +105 -0
  207. package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
  208. package/skills/korean-spell-check/skill.json +9 -0
  209. package/skills/korean-stock-search/instruction.md +194 -0
  210. package/skills/korean-stock-search/skill.json +10 -0
  211. package/skills/korean-transit-route/instruction.md +113 -0
  212. package/skills/korean-transit-route/skill.json +10 -0
  213. package/skills/kosis-stats/instruction.md +232 -0
  214. package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
  215. package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
  216. package/skills/kosis-stats/skill.json +9 -0
  217. package/skills/kr-whois-lookup/instruction.md +107 -0
  218. package/skills/kr-whois-lookup/skill.json +10 -0
  219. package/skills/kstartup-search/instruction.md +186 -0
  220. package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
  221. package/skills/kstartup-search/skill.json +11 -0
  222. package/skills/ktx-booking/instruction.md +244 -0
  223. package/skills/ktx-booking/skill.json +10 -0
  224. package/skills/lck-analytics/instruction.md +192 -0
  225. package/skills/lck-analytics/scripts/_lib.js +103 -0
  226. package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
  227. package/skills/lck-analytics/scripts/build-match-report.js +44 -0
  228. package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
  229. package/skills/lck-analytics/skill.json +9 -0
  230. package/skills/lh-notice-search/instruction.md +206 -0
  231. package/skills/lh-notice-search/skill.json +10 -0
  232. package/skills/library-book-search/instruction.md +139 -0
  233. package/skills/library-book-search/skill.json +9 -0
  234. package/skills/local-election-candidate-search/instruction.md +77 -0
  235. package/skills/local-election-candidate-search/skill.json +8 -0
  236. package/skills/localdata-business-status/instruction.md +64 -0
  237. package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
  238. package/skills/localdata-business-status/skill.json +8 -0
  239. package/skills/lotto-results/instruction.md +80 -0
  240. package/skills/lotto-results/skill.json +8 -0
  241. package/skills/lovebug-report/instruction.md +185 -0
  242. package/skills/lovebug-report/skill.json +9 -0
  243. package/skills/market-kurly-search/instruction.md +125 -0
  244. package/skills/market-kurly-search/skill.json +8 -0
  245. package/skills/mfds-drug-safety/instruction.md +86 -0
  246. package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
  247. package/skills/mfds-drug-safety/skill.json +10 -0
  248. package/skills/mfds-food-safety/instruction.md +126 -0
  249. package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
  250. package/skills/mfds-food-safety/skill.json +10 -0
  251. package/skills/myrealtrip-search/instruction.md +239 -0
  252. package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
  253. package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
  254. package/skills/myrealtrip-search/skill.json +9 -0
  255. package/skills/naming-house/instruction.md +146 -0
  256. package/skills/naming-house/skill.json +8 -0
  257. package/skills/national-pension-workplace/instruction.md +64 -0
  258. package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
  259. package/skills/national-pension-workplace/skill.json +10 -0
  260. package/skills/naver-ad-performance/instruction.md +108 -0
  261. package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
  262. package/skills/naver-ad-performance/skill.json +9 -0
  263. package/skills/naver-blog-research/instruction.md +128 -0
  264. package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
  265. package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
  266. package/skills/naver-blog-research/scripts/naver_read.py +256 -0
  267. package/skills/naver-blog-research/scripts/naver_search.py +192 -0
  268. package/skills/naver-blog-research/skill.json +8 -0
  269. package/skills/naver-news-search/instruction.md +103 -0
  270. package/skills/naver-news-search/skill.json +10 -0
  271. package/skills/naver-shopping-search/instruction.md +94 -0
  272. package/skills/naver-shopping-search/skill.json +11 -0
  273. package/skills/nhis-care-checkup-search/instruction.md +116 -0
  274. package/skills/nhis-care-checkup-search/skill.json +10 -0
  275. package/skills/nts-business-registration/instruction.md +115 -0
  276. package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
  277. package/skills/nts-business-registration/skill.json +10 -0
  278. package/skills/nts-tax-delinquency/instruction.md +54 -0
  279. package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
  280. package/skills/nts-tax-delinquency/skill.json +8 -0
  281. package/skills/ohou-today-deal/instruction.md +182 -0
  282. package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
  283. package/skills/ohou-today-deal/skill.json +9 -0
  284. package/skills/olive-young-search/instruction.md +154 -0
  285. package/skills/olive-young-search/skill.json +8 -0
  286. package/skills/parking-lot-search/instruction.md +96 -0
  287. package/skills/parking-lot-search/skill.json +9 -0
  288. package/skills/popbill/instruction.md +146 -0
  289. package/skills/popbill/scripts/popbill_cli.py +284 -0
  290. package/skills/popbill/scripts/popbill_registry.py +54 -0
  291. package/skills/popbill/scripts/popbill_safety.py +37 -0
  292. package/skills/popbill/scripts/popbill_templates.py +71 -0
  293. package/skills/popbill/skill.json +9 -0
  294. package/skills/public-restroom-nearby/instruction.md +89 -0
  295. package/skills/public-restroom-nearby/skill.json +9 -0
  296. package/skills/real-estate-search/instruction.md +172 -0
  297. package/skills/real-estate-search/skill.json +10 -0
  298. package/skills/rhwp-advanced/instruction.md +145 -0
  299. package/skills/rhwp-advanced/skill.json +8 -0
  300. package/skills/rhwp-edit/instruction.md +153 -0
  301. package/skills/rhwp-edit/skill.json +8 -0
  302. package/skills/s2b-notice-search/instruction.md +66 -0
  303. package/skills/s2b-notice-search/skill.json +9 -0
  304. package/skills/saju-fortune/instruction.md +177 -0
  305. package/skills/saju-fortune/skill.json +8 -0
  306. package/skills/saramin-talent-search/instruction.md +119 -0
  307. package/skills/saramin-talent-search/skill.json +9 -0
  308. package/skills/seoul-bike/instruction.md +83 -0
  309. package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
  310. package/skills/seoul-bike/skill.json +10 -0
  311. package/skills/seoul-density/instruction.md +109 -0
  312. package/skills/seoul-density/scripts/seoul_density.py +271 -0
  313. package/skills/seoul-density/skill.json +10 -0
  314. package/skills/seoul-subway-arrival/instruction.md +85 -0
  315. package/skills/seoul-subway-arrival/skill.json +9 -0
  316. package/skills/sh-notice-search/instruction.md +150 -0
  317. package/skills/sh-notice-search/skill.json +9 -0
  318. package/skills/srt-booking/instruction.md +181 -0
  319. package/skills/srt-booking/scripts/srt_booking.py +272 -0
  320. package/skills/srt-booking/scripts/srt_seats.py +156 -0
  321. package/skills/srt-booking/skill.json +20 -0
  322. package/skills/subway-lost-property/instruction.md +93 -0
  323. package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
  324. package/skills/subway-lost-property/skill.json +8 -0
  325. package/skills/ticket-availability/instruction.md +175 -0
  326. package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
  327. package/skills/ticket-availability/skill.json +9 -0
  328. package/skills/toss-securities/instruction.md +116 -0
  329. package/skills/toss-securities/skill.json +9 -0
  330. package/skills/used-car-price-search/instruction.md +109 -0
  331. package/skills/used-car-price-search/skill.json +8 -0
  332. package/skills/yebigun-training/instruction.md +177 -0
  333. package/skills/yebigun-training/skill.json +9 -0
  334. package/skills/zipcode-search/instruction.md +159 -0
  335. package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
  336. package/skills/zipcode-search/skill.json +8 -0
  337. package/src/assemble.js +134 -0
  338. package/src/detect.js +18 -0
  339. package/templates/action-account.md +4 -0
  340. package/templates/action-booking.md +5 -0
  341. package/templates/action-commerce.md +5 -0
  342. package/templates/action-communication.md +5 -0
  343. package/templates/action-submission.md +5 -0
  344. package/templates/browser.md +4 -0
  345. package/templates/core.md +5 -0
  346. package/templates/hard-boundary.md +3 -0
  347. package/templates/local.md +2 -0
  348. package/templates/lookup.md +2 -0
  349. package/templates/proxy.md +2 -0
  350. package/templates/vault.md +4 -0
@@ -0,0 +1,114 @@
1
+ # Daangn Realty Search
2
+
3
+ ## What this skill does
4
+
5
+ 당근부동산 지도 페이지(`realty.daangn.com/map/{name1}/{name2}/{name3}`)의 SSR `window.RELAY_STORE`(Relay 정규화 스토어)를 파싱해 매물 후보를 정리한다. 제목·주소·층수는 상세 페이지의 JSON-LD에서 보강한다. 외부 패키지 없이 Python 표준 라이브러리만 사용한다.
6
+
7
+ ## When to use
8
+
9
+ - "당근부동산 매교동 월세 찾아봐"
10
+ - "수원 팔달구 상가 매물 봐줘" (`--expand`로 인접 동까지)
11
+ - "이 당근부동산 URL 상세 요약해줘"
12
+
13
+ ## When not to use
14
+
15
+ - 당근 계정 로그인이 필요한 작업
16
+ - 채팅, 찜, 거래 제안, 문의, 예약, 계약, 구매처럼 상대방/계정에 영향을 주는 작업
17
+ - CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
18
+
19
+ ## Prerequisites
20
+
21
+ - 인터넷 연결, Python 3.9+
22
+
23
+ ## Data surfaces (2026-06 도메인 이전 대응)
24
+
25
+ - Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>` → `{"locations":[{id,name1,name2,name3,name*Id,depth}]}`
26
+ - 매물 목록: `https://realty.daangn.com/map/{name1}/{name2}/{name3}` 의 `window.RELAY_STORE`
27
+ - 상세: `https://realty.daangn.com/articles/<id>` 의 `application/ld+json`
28
+
29
+ > ⚠️ **구버전(`www.daangn.com/kr/realty/?_data=routes/kr.realty._index`)은 2026-06부터 HTTP 204(빈 응답)로 폐기됨.** 절대 사용 금지.
30
+
31
+ ## RELAY_STORE 파싱 경로 (검증됨)
32
+
33
+ ```
34
+ ArticleFeedConnection.edges → ArticleFeedEdge.node → ArticleFeedCard.article → Article
35
+ ```
36
+ - 스토어에 `ArticleFeedCard`가 직접 다 들어있어, **Card를 순회 → article ref 디레퍼런스**가 가장 견고하다.
37
+ - `Article`: `originalId`, `area`(㎡, **문자열일 수 있어 float 변환 필수**), `salesTypeV3`(→`*SalesTypeV2.type`), `trades`(→ Month/Buy/BorrowTrade)
38
+ - 가격 단위 = **만원**: `deposit`(보증금), `monthlyPay`(월세), `price`(매매가). 예: deposit 2000 = 2천만원, price 28700 = 2억8,700만.
39
+ - `window.RELAY_STORE`는 **JS 문자열로 이스케이프**돼 있다 → `json.loads(json.loads('"'+raw+'"'))` 2단 디코드.
40
+
41
+ ## Trade 유형 & 평당 단가
42
+
43
+ | 거래유형 | typename | 가격 필드 | 평당 단가 |
44
+ |---|---|---|---|
45
+ | 월세(MONTH) | MonthTrade | deposit + monthlyPay | monthlyPay / 평 |
46
+ | 매매(BUY) | BuyTrade | price | price / 평 |
47
+ | 전세(BORROW) | BorrowTrade | deposit | deposit / 평 |
48
+
49
+ 평 = ㎡ / 3.305785.
50
+
51
+ ## salesType enum
52
+
53
+ `APART`, `OFFICETEL`, `STORE`, `OPEN_ONE_ROOM`, `SPLIT_ONE_ROOM`, `TWO_ROOM`, `HOUSE` 등.
54
+
55
+ ## 층수 (상세 JSON-LD)
56
+
57
+ 목록엔 층수가 없다. 상세 페이지 `Product.additionalProperty` 배열에서:
58
+ - `floor`(예 "8.0") / `topFloor`(예 "10") → `floor_label` = "8층/10층"
59
+ - `nearbySubwayStation` 도 함께 추출.
60
+ 제목은 `Product.name`, 주소는 `Place.name`.
61
+
62
+ ## Commands
63
+
64
+ ```bash
65
+ # 기본 검색 (상위 5개 제목·층수 보강)
66
+ python3 daangn-realty-search/scripts/daangn_realty.py search --region "매교동" --limit 20
67
+
68
+ # 거래/용도 필터
69
+ python3 ... search --region "매교동" --trade-type BUY # 매매만
70
+ python3 ... search --region "매교동" --sales-type STORE,OFFICETEL # 용도 콤마구분
71
+
72
+ # 인접 동까지 확장 (같은 구/시)
73
+ python3 ... search --region "매교동" --expand --expand-max 6
74
+
75
+ # 상세
76
+ python3 ... search --region "매교동" --titles 0 # 제목 보강 끄기(빠름)
77
+ python3 ... detail "https://realty.daangn.com/articles/2947028"
78
+ ```
79
+
80
+ 옵션: `--limit`(기본 20), `--titles N`(상세로 제목·층수 보강할 상위 N, 기본 5, 0=끔), `--expand`/`--expand-max`(기본 6).
81
+ 기존 CLI 호환 옵션인 `--keyword`는 반환 매물의 공개 필드 텍스트를 필터링한다. `--only-verified`는 공개 피드에서 별도 인증 필드를 제공하지 않아 호환 목적으로만 허용한다.
82
+
83
+ ## Output fields
84
+
85
+ 매물: `article_id, salesType, area_sqm, area_pyeong, trades[{type,label,deposit_manwon,monthly_manwon,price_manwon,per_pyeong_manwon}], url, region, title, address, floor_label, nearby_subway, release_date`
86
+
87
+ `release_date`는 상세 페이지 JSON-LD의 `Product.releaseDate`(ISO 8601, UTC)이며 `--titles` 보강 대상 매물에만 채워진다. 최초 등록일인지 최근 끌어올리기/수정일인지는 당근 쪽 명세에 없어 확정할 수 없다.
88
+
89
+ ## Region handling
90
+
91
+ 지역명 → region API로 내부 id 해석. 동명이 여럿이면 정확일치 → 서울 depth=3 → 첫 후보 순. 응답에 `effective_region` 포함. `--expand`는 같은 `name2Id`(구/시) 인접 동을 모은다.
92
+
93
+ ## Safety and scope
94
+
95
+ - 읽기 전용 검색/상세만. 로그인·채팅·거래·예약·계약 자동화 없음.
96
+ - 공개 표면이 바뀌거나 빈 응답/봇 차단이면 실패 모드로 보고하고 우회하지 않는다.
97
+ - 결과는 실시간 호가라 실거래와 다를 수 있으므로 source URL을 함께 제시.
98
+
99
+ ## Failure modes
100
+
101
+ - `RELAY_STORE 없음` (sources note) → 페이지 구조 재변경 또는 봇 차단. HTML 구조 재확인 필요.
102
+ - 시군구(name2) 랜딩 URL은 매물이 없다 — **반드시 동(name3)까지** 있어야 articleFeed가 SSR로 채워진다.
103
+ - 동명이 넓거나 중복되면 다른 행정동 선택 가능.
104
+ - 상세는 삭제/비공개 글에서 실패할 수 있다.
105
+
106
+ ## Done when
107
+
108
+ - 지역 id 해석 → map URL의 RELAY_STORE에서 매물 추출 → source URL·effective_region 포함.
109
+ - 인증/거래성 액션은 수행하지 않았다.
110
+
111
+ ## Notes
112
+
113
+ - Windows stdout 한글 깨짐 방지: 지원 런타임에서는 `sys.stdout.reconfigure(encoding='utf-8')`를 적용한다.
114
+ - `area`·가격 필드가 문자열로 오는 케이스가 있어 모든 수치 연산 전 float 변환 가드.
@@ -0,0 +1,76 @@
1
+ import json
2
+ import re
3
+
4
+
5
+ def parse_detail(url, fetch_text):
6
+ html = fetch_text(url)
7
+ out = {
8
+ "source": url,
9
+ "title": None,
10
+ "address": None,
11
+ "floor": None,
12
+ "top_floor": None,
13
+ "floor_label": None,
14
+ "nearby_subway": None,
15
+ "release_date": None,
16
+ "json_ld": [],
17
+ }
18
+ scripts = re.findall(
19
+ r"<script[^>]*application/ld\+json[^>]*>(.*?)</script>", html, re.S
20
+ )
21
+ for script in scripts:
22
+ try:
23
+ document = json.loads(script)
24
+ except json.JSONDecodeError:
25
+ continue
26
+ out["json_ld"].append(document)
27
+ for node in _json_ld_nodes(document):
28
+ _apply_node(out, node)
29
+ if out["floor"] is not None:
30
+ floor = str(out["floor"]).replace(".0", "")
31
+ top = str(out["top_floor"]).replace(".0", "") if out["top_floor"] else "?"
32
+ out["floor_label"] = f"{floor}층/{top}층"
33
+ out["json_ld"] = out["json_ld"][:3]
34
+ return out
35
+
36
+
37
+ def _json_ld_nodes(document):
38
+ if isinstance(document, list):
39
+ return document
40
+ if not isinstance(document, dict):
41
+ return []
42
+ graph = document.get("@graph")
43
+ if isinstance(graph, list):
44
+ return graph
45
+ if isinstance(graph, dict):
46
+ return [graph]
47
+ return [document]
48
+
49
+
50
+ def _apply_node(out, node):
51
+ if not isinstance(node, dict):
52
+ return
53
+ if node.get("@type") == "Product":
54
+ if not out["title"]:
55
+ out["title"] = node.get("name")
56
+ if not out["release_date"]:
57
+ out["release_date"] = node.get("releaseDate")
58
+ if node.get("@type") == "Place" and not out["address"]:
59
+ out["address"] = node.get("name")
60
+ for prop in _properties(node.get("additionalProperty")):
61
+ name = prop.get("name")
62
+ value = prop.get("value")
63
+ if name == "floor":
64
+ out["floor"] = value
65
+ elif name == "topFloor":
66
+ out["top_floor"] = value
67
+ elif name == "nearbySubwayStation":
68
+ out["nearby_subway"] = value
69
+
70
+
71
+ def _properties(value):
72
+ if isinstance(value, list):
73
+ return [item for item in value if isinstance(item, dict)]
74
+ if isinstance(value, dict):
75
+ return [value]
76
+ return []
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """당근부동산(realty.daangn.com) 읽기 전용 매물 검색/상세.
4
+
5
+ 2026-06 당근부동산 도메인 이전 대응:
6
+ - 기존 www.daangn.com/kr/realty/?_data=routes/kr.realty._index → HTTP 204 (폐기)
7
+ - 신규 realty.daangn.com/map/{name1}/{name2}/{name3} 페이지의
8
+ window.RELAY_STORE (Relay 정규화 스토어)를 파싱한다.
9
+
10
+ RELAY_STORE 경로:
11
+ ArticleFeedConnection.edges → ArticleFeedEdge.node → ArticleFeedCard.article → Article
12
+ Article: originalId, area(㎡), salesTypeV3(→*SalesTypeV2.type), trades(→Month/Buy/BorrowTrade)
13
+ 가격 단위: 만원 (deposit 2000 = 2천만원, monthlyPay 100 = 100만원, price 28700 = 2억8700만).
14
+ 층수: 상세 페이지 JSON-LD additionalProperty 의 floor/topFloor.
15
+ """
16
+ import argparse
17
+ import json
18
+ import pathlib
19
+ import sys
20
+ import urllib.error
21
+ import urllib.parse
22
+ import urllib.request
23
+
24
+ SCRIPT_DIR = pathlib.Path(__file__).resolve().parent
25
+ if str(SCRIPT_DIR) not in sys.path:
26
+ sys.path.insert(0, str(SCRIPT_DIR))
27
+
28
+ from daangn_detail_ld import parse_detail as _parse_detail
29
+ from daangn_relay_store import extract_articles, extract_relay_store
30
+
31
+ reconfigure_stdout = getattr(sys.stdout, "reconfigure", None)
32
+ if callable(reconfigure_stdout):
33
+ try:
34
+ reconfigure_stdout(encoding="utf-8")
35
+ except (OSError, ValueError):
36
+ pass
37
+
38
+ HEADERS = {"User-Agent": "Mozilla/5.0", "Accept": "text/html,application/json;q=0.9,*/*;q=0.8"}
39
+ REGION_API = "https://www.daangn.com/kr/api/v1/regions/keyword?keyword="
40
+ MAP_BASE = "https://realty.daangn.com/map/"
41
+
42
+
43
+ def fetch_json(url):
44
+ req = urllib.request.Request(url, headers=HEADERS)
45
+ with urllib.request.urlopen(req, timeout=25) as r:
46
+ return json.load(r)
47
+
48
+
49
+ def fetch_text(url):
50
+ req = urllib.request.Request(url, headers=HEADERS)
51
+ with urllib.request.urlopen(req, timeout=25) as r:
52
+ return r.read().decode("utf-8", "ignore")
53
+
54
+
55
+ def print_json(obj):
56
+ print(json.dumps(obj, ensure_ascii=False, indent=2))
57
+
58
+
59
+ # ------------------------- region 해석 -------------------------
60
+
61
+ def resolve_region(region):
62
+ """지역명 → 당근 내부 region 객체 (id, name1/2/3, name*Id)."""
63
+ if not region:
64
+ return None
65
+ data = fetch_json(REGION_API + urllib.parse.quote(region))
66
+ locs = data.get("locations") or []
67
+ if not locs:
68
+ raise SystemExit(f"지역 후보 없음: {region}")
69
+ exact = [x for x in locs if region in (x.get("name"), x.get("name1"), x.get("name2"), x.get("name3"))]
70
+ seoul = [x for x in locs if x.get("name1") == "서울특별시" and x.get("depth") == 3]
71
+ return (exact or seoul or locs)[0]
72
+
73
+
74
+ def find_sibling_regions(sel, max_siblings=6):
75
+ """같은 name2(구/시) 내 인접 동들을 조회 (--expand 용).
76
+
77
+ name2 키워드로 다시 region API를 때려 같은 name2Id 를 가진 depth=3 동들을 모은다.
78
+ """
79
+ name2 = sel.get("name2") or ""
80
+ if not name2:
81
+ return []
82
+ try:
83
+ data = fetch_json(REGION_API + urllib.parse.quote(name2.split()[-1]))
84
+ except (json.JSONDecodeError, OSError, TimeoutError, urllib.error.URLError):
85
+ return []
86
+ sibs = []
87
+ seen = {sel.get("name3Id")}
88
+ for x in (data.get("locations") or []):
89
+ if x.get("depth") != 3:
90
+ continue
91
+ if x.get("name2Id") != sel.get("name2Id"):
92
+ continue
93
+ if x.get("name3Id") in seen:
94
+ continue
95
+ seen.add(x.get("name3Id"))
96
+ sibs.append(x)
97
+ if len(sibs) >= max_siblings:
98
+ break
99
+ return sibs
100
+
101
+
102
+ def map_url(sel):
103
+ parts = [sel.get("name1") or "", sel.get("name2") or "", sel.get("name3") or ""]
104
+ path = "/".join(urllib.parse.quote(p) for p in parts)
105
+ return MAP_BASE + path
106
+
107
+
108
+ # ------------------------- 상세(JSON-LD) -------------------------
109
+
110
+ def parse_detail(url):
111
+ return _parse_detail(url, fetch_text)
112
+
113
+
114
+ # ------------------------- 커맨드 -------------------------
115
+
116
+ def collect_for_region(sel, sales_type_filter, trade_type_filter, limit, keyword=None):
117
+ url = map_url(sel)
118
+ html = fetch_text(url)
119
+ store = extract_relay_store(html)
120
+ if not store:
121
+ return url, [], "RELAY_STORE 없음 (페이지 구조 변경 또는 차단)"
122
+ items = extract_articles(store, max_items=10_000)
123
+ # 필터
124
+ if sales_type_filter:
125
+ sset = {s.strip().upper() for s in sales_type_filter.split(",")}
126
+ items = [it for it in items if (it.get("salesType") or "").upper() in sset]
127
+ if trade_type_filter:
128
+ tset = {t.strip().upper() for t in trade_type_filter.split(",")}
129
+ items = [it for it in items if any((tr["type"] or "").upper() in tset for tr in it["trades"])]
130
+ if keyword:
131
+ needle = keyword.casefold()
132
+ items = [it for it in items if needle in json.dumps(it, ensure_ascii=False).casefold()]
133
+ return url, items, None
134
+
135
+
136
+ def cmd_search(args):
137
+ sel = resolve_region(args.region) if args.region else None
138
+ if not sel:
139
+ raise SystemExit("--region 이 필요합니다")
140
+ regions = [sel]
141
+ if args.expand:
142
+ regions += find_sibling_regions(sel, max_siblings=args.expand_max)
143
+
144
+ all_items, sources, errors = [], [], []
145
+ seen = set()
146
+ for rg in regions:
147
+ try:
148
+ url, items, err = collect_for_region(rg, args.sales_type, args.trade_type, args.limit, args.keyword)
149
+ except (json.JSONDecodeError, OSError, TimeoutError, urllib.error.URLError) as e:
150
+ errors.append({"region": rg.get("name3"), "error": str(e)})
151
+ continue
152
+ sources.append({"region": f"{rg.get('name1')} {rg.get('name2')} {rg.get('name3')}", "url": url,
153
+ "count": len(items), "note": err})
154
+ for it in items:
155
+ if it["article_id"] in seen:
156
+ continue
157
+ seen.add(it["article_id"])
158
+ it["region"] = f"{rg.get('name2')} {rg.get('name3')}"
159
+ all_items.append(it)
160
+
161
+ all_items = all_items[:args.limit]
162
+ # 제목 보강(상세 JSON-LD) — 상위 N개만 (네트워크 비용 절약)
163
+ if args.titles > 0:
164
+ for it in all_items[:args.titles]:
165
+ if not it.get("url"):
166
+ continue
167
+ try:
168
+ d = parse_detail(it["url"])
169
+ it["title"] = d.get("title")
170
+ it["address"] = d.get("address")
171
+ it["floor_label"] = d.get("floor_label")
172
+ it["nearby_subway"] = d.get("nearby_subway")
173
+ it["release_date"] = d.get("release_date")
174
+ except (json.JSONDecodeError, OSError, TimeoutError, urllib.error.URLError):
175
+ pass
176
+
177
+ print_json({
178
+ "effective_region": f"{sel.get('name1')} {sel.get('name2')} {sel.get('name3')}",
179
+ "expand": bool(args.expand),
180
+ "regions_searched": len(regions),
181
+ "sources": sources,
182
+ "count": len(all_items),
183
+ "errors": errors,
184
+ "items": all_items,
185
+ })
186
+
187
+
188
+ def cmd_detail(args):
189
+ print_json(parse_detail(args.url))
190
+
191
+
192
+ def build_parser():
193
+ p = argparse.ArgumentParser(description="당근부동산 읽기전용 검색/상세 (realty.daangn.com)")
194
+ sub = p.add_subparsers(dest="cmd", required=True)
195
+
196
+ s = sub.add_parser("search", help="지역 매물 검색")
197
+ s.add_argument("--region", required=True, help="동 이름 (예: 매교동, 합정동)")
198
+ s.add_argument("--sales-type", help="용도 필터(콤마구분): APART,OFFICETEL,STORE,OPEN_ONE_ROOM,SPLIT_ONE_ROOM,TWO_ROOM,HOUSE")
199
+ s.add_argument("--trade-type", help="거래 필터(콤마구분): MONTH(월세),BUY(매매),BORROW(전세)")
200
+ s.add_argument("--keyword", help="기존 CLI 호환용 키워드 필터(현재 공개 피드에서는 미사용)")
201
+ s.add_argument("--only-verified", action="store_true", help="기존 CLI 호환용 인증 매물 플래그")
202
+ s.add_argument("--expand", action="store_true", help="같은 구/시 인접 동까지 확장 검색")
203
+ s.add_argument("--expand-max", type=int, default=6, help="확장 시 인접 동 최대 개수 (기본 6)")
204
+ s.add_argument("--titles", type=int, default=5, help="상세 JSON-LD로 제목·층수 보강할 상위 N개 (기본 5, 0=비활성)")
205
+ s.add_argument("--limit", type=int, default=20, help="최대 매물 수 (기본 20)")
206
+ s.set_defaults(func=cmd_search)
207
+
208
+ d = sub.add_parser("detail", help="매물 상세 (제목·주소·층수)")
209
+ d.add_argument("url", help="https://realty.daangn.com/articles/<id>")
210
+ d.set_defaults(func=cmd_detail)
211
+ return p
212
+
213
+
214
+ if __name__ == "__main__":
215
+ args = build_parser().parse_args()
216
+ args.func(args)
@@ -0,0 +1,174 @@
1
+ import json
2
+ import re
3
+
4
+ DETAIL_BASE = "https://realty.daangn.com/articles/"
5
+ PY_PER_SQM = 3.305785
6
+ TRADE_LABEL = {"MONTH": "월세", "BUY": "매매", "BORROW": "전세"}
7
+
8
+
9
+ def extract_relay_store(html):
10
+ match = re.search(r'window\.RELAY_STORE\s*=\s*"((?:[^"\\]|\\.)*)"', html)
11
+ if match:
12
+ try:
13
+ return json.loads(json.loads('"' + match.group(1) + '"'))
14
+ except json.JSONDecodeError:
15
+ return None
16
+
17
+ start = html.find("window.RELAY_STORE")
18
+ if start < 0:
19
+ return None
20
+ eq = html.find("=", start)
21
+ if eq < 0:
22
+ return None
23
+
24
+ literal = _scan_object_literal(html[eq + 1 :])
25
+ if literal is None:
26
+ return None
27
+ try:
28
+ return json.loads(literal)
29
+ except json.JSONDecodeError:
30
+ return None
31
+
32
+
33
+ def _scan_object_literal(source):
34
+ depth = 0
35
+ in_string = False
36
+ escaped = False
37
+ quote = ""
38
+ end = 0
39
+ for idx, ch in enumerate(source):
40
+ if in_string:
41
+ if escaped:
42
+ escaped = False
43
+ elif ch == "\\":
44
+ escaped = True
45
+ elif ch == quote:
46
+ in_string = False
47
+ continue
48
+ if ch in "\"'":
49
+ in_string = True
50
+ quote = ch
51
+ elif ch == "{":
52
+ depth += 1
53
+ elif ch == "}":
54
+ depth -= 1
55
+ if depth == 0:
56
+ end = idx + 1
57
+ break
58
+ if end == 0:
59
+ return None
60
+ return source[:end]
61
+
62
+
63
+ def _deref(store, ref):
64
+ if isinstance(ref, dict) and "__ref" in ref:
65
+ return store.get(ref["__ref"])
66
+ return ref
67
+
68
+
69
+ def _refs(store, node, key):
70
+ value = node.get(key)
71
+ out = []
72
+ if isinstance(value, dict):
73
+ if "__refs" in value:
74
+ out = [store.get(ref) for ref in value["__refs"]]
75
+ elif "__ref" in value:
76
+ out = [store.get(value["__ref"])]
77
+ elif isinstance(value, list):
78
+ for item in value:
79
+ if isinstance(item, dict) and "__ref" in item:
80
+ out.append(store.get(item["__ref"]))
81
+ return [item for item in out if item]
82
+
83
+
84
+ def sales_type(store, article):
85
+ sales = _deref(store, article.get("salesTypeV3"))
86
+ if isinstance(sales, dict):
87
+ return sales.get("type") or sales.get("name")
88
+ return None
89
+
90
+
91
+ def parse_trade(trade):
92
+ typename = trade.get("__typename")
93
+ if typename == "MonthTrade":
94
+ return ("MONTH", trade.get("deposit"), trade.get("monthlyPay"), None)
95
+ if typename == "BuyTrade":
96
+ return ("BUY", None, None, trade.get("price"))
97
+ if typename == "BorrowTrade":
98
+ return ("BORROW", trade.get("deposit"), None, None)
99
+ return (
100
+ trade.get("type"),
101
+ trade.get("deposit"),
102
+ trade.get("monthlyPay"),
103
+ trade.get("price"),
104
+ )
105
+
106
+
107
+ def per_pyeong(kind, deposit, monthly, price, pyeong):
108
+ if not pyeong or pyeong <= 0:
109
+ return None
110
+ base = None
111
+ if kind == "MONTH":
112
+ base = monthly
113
+ elif kind == "BUY":
114
+ base = price
115
+ elif kind == "BORROW":
116
+ base = deposit
117
+ if base is None:
118
+ return None
119
+ try:
120
+ numeric_base = float(base)
121
+ except (TypeError, ValueError):
122
+ return None
123
+ return round(numeric_base / pyeong, 2)
124
+
125
+
126
+ def extract_articles(store, max_items):
127
+ items = []
128
+ cards = [
129
+ value
130
+ for value in store.values()
131
+ if isinstance(value, dict) and value.get("__typename") == "ArticleFeedCard"
132
+ ]
133
+ for card in cards:
134
+ article = _deref(store, card.get("article"))
135
+ if not article or article.get("__typename") != "Article":
136
+ continue
137
+ area = _parse_area(article.get("area"))
138
+ pyeong = round(area / PY_PER_SQM, 2) if area else None
139
+ trades = []
140
+ for trade in _refs(store, article, "trades"):
141
+ kind, deposit, monthly, price = parse_trade(trade)
142
+ trades.append(
143
+ {
144
+ "type": kind,
145
+ "label": TRADE_LABEL.get(kind, kind),
146
+ "deposit_manwon": deposit,
147
+ "monthly_manwon": monthly,
148
+ "price_manwon": price,
149
+ "per_pyeong_manwon": per_pyeong(
150
+ kind, deposit, monthly, price, pyeong
151
+ ),
152
+ }
153
+ )
154
+ article_id = article.get("originalId")
155
+ items.append(
156
+ {
157
+ "article_id": article_id,
158
+ "salesType": sales_type(store, article),
159
+ "area_sqm": area,
160
+ "area_pyeong": pyeong,
161
+ "trades": trades,
162
+ "url": DETAIL_BASE + str(article_id) if article_id else None,
163
+ }
164
+ )
165
+ if len(items) >= max_items:
166
+ break
167
+ return items
168
+
169
+
170
+ def _parse_area(value):
171
+ try:
172
+ return float(value) if value is not None and value != "" else None
173
+ except (TypeError, ValueError):
174
+ return None
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "daangn-realty-search",
3
+ "description": "당근부동산 공개 웹 데이터로 매물을 검색하고, 돌쇠에서는 공식 계정 표면에서 문의·방문예약·계약 준비까지 진행한다.",
4
+ "profiles": [
5
+ "action:commerce"
6
+ ],
7
+ "frontmatter": "name: daangn-realty-search\ndescription: 당근부동산 공개 웹 데이터로 매물을 검색하고, 돌쇠에서는 공식 계정 표면에서 문의·방문예약·계약 준비까지 진행한다.\nlicense: MIT\nmetadata:\n category: real-estate\n locale: ko-KR\n phase: v1.5"
8
+ }
@@ -0,0 +1,90 @@
1
+ # Daangn Used-Goods Search
2
+
3
+ ## What this skill does
4
+
5
+ 당근 중고거래 공개 Remix `_data` JSON route를 사용해 매물 목록과 상세 정보를 읽기 전용으로 조회한다.
6
+
7
+ 최종 사용자는 자연어로 요청해도 되고, 필요하면 아래의 Python helper를 직접 실행한다. 외부 패키지나 k-skill-proxy 없이 Python 표준 라이브러리만 사용한다.
8
+
9
+ ## When to use
10
+
11
+ - "당근에서 맥북 찾아봐"
12
+ - "합정동 아이폰 매물 검색"
13
+ - "이 당근 중고거래 URL 상세 봐줘"
14
+
15
+ ## When not to use
16
+
17
+ - 당근 계정 로그인이 필요한 작업
18
+ - 채팅, 찜, 거래 제안, 문의, 지원, 예약, 계약, 구매처럼 상대방 또는 계정에 영향을 주는 작업
19
+ - CAPTCHA/봇 차단/로그인벽 우회가 필요한 작업
20
+
21
+ ## Prerequisites
22
+
23
+ - 인터넷 연결
24
+ - Python 3.9+
25
+ - 이 저장소 루트에서 실행하거나, 스크립트 경로를 절대경로로 지정
26
+
27
+ ## Data surfaces
28
+
29
+ - Region resolver: `https://www.daangn.com/kr/api/v1/regions/keyword?keyword=<지역명>`
30
+ - Search `_data`: `/kr/buy-sell/all/?in=<지역명>-<id>&search=<keyword>&only_on_sale=true&_data=routes/kr.buy-sell._index`
31
+ - Detail `_data`: `<listing-url>?_data=routes%2Fkr.buy-sell.%24buy_sell_id`
32
+
33
+ ## Workflow
34
+
35
+ 1. 사용자 요청에서 키워드, 지역명, 가격/거래 유형 같은 필터를 추출한다.
36
+ 2. 지역명이 있으면 region resolver로 내부 region id를 찾는다.
37
+ 3. 목록 검색은 category별 `_data` route를 호출한다.
38
+ 4. 상세 URL이 주어지면 category별 detail route 또는 공개 HTML 메타를 조회한다.
39
+ 5. 결과를 짧게 정리하되 source URL과 적용 지역을 보존한다.
40
+
41
+ ## Commands
42
+
43
+ ```bash
44
+ python3 daangn-used-goods-search/scripts/daangn_used_goods.py search "맥북" --region "합정동" --limit 5
45
+ python3 daangn-used-goods-search/scripts/daangn_used_goods.py detail "https://www.daangn.com/kr/buy-sell/.../"
46
+ ```
47
+
48
+ ## Output fields
49
+
50
+ - title, price, price_text, status, region, url
51
+ - detail: product 원문, view/chat/count류 필드가 있으면 함께 확인
52
+
53
+ ## Region handling
54
+
55
+ 지역 필터가 있으면 먼저 당근 지역 검색 API로 내부 지역 id를 해석한다.
56
+
57
+ ```text
58
+ https://www.daangn.com/kr/api/v1/regions/keyword?keyword=합정동
59
+ → 서울특별시 마포구 합정동, id=231
60
+ → in=합정동-231
61
+ ```
62
+
63
+ 동일한 지명이 여러 지역에 있으면 다음 우선순위로 선택한다.
64
+
65
+ 1. 사용자가 입력한 문자열이 `name`, `name1`, `name2`, `name3` 중 하나와 정확히 맞는 후보
66
+ 2. 서울 `depth=3` 동 단위 후보
67
+ 3. 첫 번째 후보
68
+
69
+ 응답에는 항상 `effective_region` 또는 실제 적용된 지역명을 포함한다. 사용자의 의도와 다른 지역으로 보이면 결과를 단정하지 말고 후보 확인을 요청한다. IP/쿠키 기본 위치에 의존하지 않는다.
70
+
71
+ ## Safety and scope
72
+
73
+ - 읽기 전용 검색/상세 조회만 수행한다.
74
+ - 로그인, 채팅, 찜, 거래 제안, 지원, 문의, 예약, 계약, 구매 자동화는 하지 않는다.
75
+ - 공개 웹 표면이 바뀌거나 빈 응답/봇 차단/로그인벽이 나오면 실패 모드로 보고하고 우회하지 않는다.
76
+ - 결과는 실시간 재고/공고 상태와 달라질 수 있으므로 source URL을 함께 제시한다.
77
+
78
+ ## Failure modes
79
+
80
+ - 당근의 Remix route 이름이나 JSON shape가 변경되면 `_data` 조회가 실패할 수 있다.
81
+ - 지역명이 넓거나 중복되면 다른 행정동이 선택될 수 있다.
82
+ - 검색 결과가 0건이어도 사이트 정책/지역 기본값/필터 조합 때문일 수 있으므로 source URL을 보존한다.
83
+ - 상세 조회는 삭제/종료/비공개 전환된 글에서 실패할 수 있다.
84
+
85
+ ## Done when
86
+
87
+ - 지역명이 있으면 지역 id를 해석하고 적용했다.
88
+ - 목록 조회 또는 상세 조회를 최소 1회 수행했다.
89
+ - 결과에 source URL과 effective region을 포함했다.
90
+ - 인증/거래성 액션은 수행하지 않았다.