
Danawa Price Search
- 1.7k installs
- 6.5k repo stars
- Updated July 27, 2026
- nomadamas/k-skill
danawa-price-search is an agent skill for 다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
About
The danawa-price-search skill is designed for 다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다. Danawa Price Search What this skill does 다나와의 로그인 없는 공개 검색/가격비교 표면을 읽기 전용으로 호출해 한국 쇼핑몰 가격을 비교한다. 상품가만이 아니라 배송비 포함 실구매가, 무료배송 여부, 카드 할인가, 무이자 할부 문구를 함께 정리한다. Invoke when the user asks about danawa price search or related SKILL.md workflows.
- 상품명/검색어로 다나와 상품 후보와 pcode를 찾는다.
- 선택한 상품의 쇼핑몰별 오퍼를 조회한다.
- 상품가만이 아니라 배송비 포함 실구매가, 무료배송 여부, 카드 할인가, 무이자 할부 문구를 함께 정리한다.
- 구매, 로그인, 장바구니, 찜, 주문 액션은 하지 않는다.
- "다나와에서 에어팟 최저가 찾아줘".
Danawa Price Search by the numbers
- 1,686 all-time installs (skills.sh)
- +129 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #204 of 1,896 Design & UI/UX skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
danawa-price-search capabilities & compatibility
- Capabilities
- 상품명/검색어로 다나와 상품 후보와 pcode를 찾는다 · 선택한 상품의 쇼핑몰별 오퍼를 조회한다 · 상품가만이 아니라 배송비 포함 실구매가, 무료배송 여부, 카드 할인가, 무이자 할부 문 · 구매, 로그인, 장바구니, 찜, 주문 액션은 하지 않는다
- Use cases
- frontend
What danawa-price-search says it does
다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
npx skills add https://github.com/nomadamas/k-skill --skill danawa-price-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 6.5k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | nomadamas/k-skill ↗ |
How do I 다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다?
다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
Who is it for?
Developers using danawa price search workflows documented in SKILL.md.
Skip if: Skip when the task falls outside danawa-price-search scope or needs a different stack.
When should I use this skill?
User asks about danawa price search or related SKILL.md workflows.
What you get
Completed danawa-price-search workflow with documented commands, files, and expected deliverables.
- JSON Danawa search results
- Seller offer lists
- Multi-retailer price comparisons
By the numbers
- Exposes 3 CLI subcommands: search, offers, and compare
- Default query limits include --limit 8 for search and --limit 10 for offers
Files
Danawa Price Search
What this skill does
다나와의 로그인 없는 공개 검색/가격비교 표면을 읽기 전용으로 호출해 한국 쇼핑몰 가격을 비교한다.
- 상품명/검색어로 다나와 상품 후보와
pcode를 찾는다. - 선택한 상품의 쇼핑몰별 오퍼를 조회한다.
- 상품가만이 아니라 배송비 포함 실구매가, 무료배송 여부, 카드 할인가, 무이자 할부 문구를 함께 정리한다.
- 구매, 로그인, 장바구니, 찜, 주문 액션은 하지 않는다.
When to use
- "다나와에서 에어팟 최저가 찾아줘"
- "다나와 가격비교로 쇼핑몰별 가격 비교해줘"
- "무료배송인지, 카드 할인까지 보면 어디가 제일 싸?"
- "무이자 할부 붙은 최저가도 같이 봐줘"
When not to use
- 실제 구매/주문/결제/로그인이 필요한 경우
- 회원 전용 쿠폰, 개인화 포인트, 앱 전용 혜택을 확정해야 하는 경우
- 대량 모니터링이나 고빈도 크롤링을 해야 하는 경우
- CAPTCHA, 접근 차단, fingerprint 우회를 해야 하는 경우
Required inputs
상품명 또는 검색어가 필요하다. 검색어가 넓으면 브랜드, 모델명, 용량, 색상, 자급제/통신사 여부 등을 추가로 물어본다.
권장 질문:
찾을 다나와 상품명이나 모델명을 알려주세요. 예: 갤럭시 S25 울트라 256GB 자급제, 에어팟 프로 2세대 USB-C
Public surfaces
현재 구현은 인증 없는 공개 표면만 사용한다.
- 검색 페이지:
https://search.danawa.com/dsearch.php?query=... - 상품 상세 페이지:
https://prod.danawa.com/info/?pcode=... - 가격비교 AJAX:
https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php
AJAX endpoint는 HTML fragment를 반환한다. helper는 .diff_item, 쇼핑몰 로고 alt, em.prc_c/em.prc_t, 배송 문구, 결제조건 배지(.ico.cash/.ico.point/.ico.coupon/.ico.discount/.ico.card/.ico.membership 등), 카드 할인 라인, 무이자 할부 레이어, 다나와 bridge link를 파싱한다.
Commands
스킬 디렉터리에서 실행한다.
python scripts/danawa_search.py search "에어팟 프로 2세대" --limit 8
python scripts/danawa_search.py offers 28208783 --limit 10
python scripts/danawa_search.py compare "에어팟 프로 2세대" --limit 5 --offers 5helper는 JSON만 출력한다. 결과를 확인한 뒤 사용자에게는 한국어 표와 짧은 결론으로 정리한다.
Output shape
search
{
"query": "...",
"source_url": "...",
"count": 0,
"items": []
}각 items[] 주요 필드:
pcodetitleprice,price_textmall_texturlimage_urlspec
offers
{
"pcode": "...",
"title": "...",
"source_url": "...",
"count": 0,
"normal_count": 0,
"conditional_count": 0,
"offers": [],
"meta": { "sort": "total_price" }
}offers[]는 배송비 포함 실구매가(`total_price`) 오름차순으로 정렬된다. count / normal_count / conditional_count는 limit 적용 후 실제 반환된 offers[] window 기준이다. 결제조건(현금/쿠폰/포인트/할인/특정카드/멤버십 한정)이 붙은 row도 같은 정렬에 그대로 참여한다 — 가장 싸면 1위로 올라온다. 결제조건은 분리 그룹이나 추가 필터링 없이 row 단위 payment_badges / payment_condition_types / payment_condition_label / cash_only / point_only / coupon_only / card_only_badge / discount_badge / membership_badge / is_conditional_price 필드로 노출한다. 호출자는 사용자의 결제 수단에 따라 직접 판단한다.
각 offers[] 주요 필드:
mallprice,price_textshippingis_free_shippingshipping_feetotal_price,total_price_textcard_price,card_price_textcard_namecard_discount,card_discount_textinstallmentinstallment_detailpayment_badges— Danawa가 가격 옆에 노출한 결제조건 배지의 표시 라벨 목록. 배지 텍스트가 비어 있고.ico.cash처럼 클래스만 있는 경우도 정규화 라벨을 합성한다 (예:["현금"],["포인트"],["쿠폰"],["카드"],["할인"],["멤버십"])payment_condition_types— 화이트리스트 배지를 정규화한 조건 타입 목록 (cash/point/coupon/card/discount/membership)payment_condition_label— 사용자 응답용 결제조건 라벨 (예:현금,할인,멤버십, 복수 조건이면현금, 할인)cash_only— 현금 결제 전용가point_only— 포인트 차감 적용가coupon_only— 쿠폰 적용가card_only_badge— 특정 카드 한정 노출가discount_badge— 할인 조건 배지 노출가membership_badge— 멤버십 조건 배지 노출가is_conditional_price—payment_condition_types가 하나 이상 있으면 True. 일반 결제가가 아니므로 카드 일반 결제 시 가격이 다르거나 불가능할 수 있음url
항상 무료배송 여부, 배송비 포함 실구매가, 카드별 할인 가격, 무이자 할부 문구, 그리고 `payment_badges`/`payment_condition_label`/`is_conditional_price`를 함께 확인한다. 조건부 가격을 일반가처럼 1위로 노출하면 비교 결과가 거짓이 된다.
compare
compare는 검색 결과를 먼저 가져온 뒤 각 후보 상품에 대해 offers[]를 best-effort로 붙인다. 검색 결과가 애매하면 상위 후보의 제목과 pcode를 먼저 보여주고 선택을 요청한다.
Response style
Discord/Telegram/chat 응답에서는 표 형식을 우선한다.
| 순위 | 판매처 | 상품가 | 결제조건 | 배송 | 실구매가 | 카드할인가 | 무이자 | 링크 |
|---:|---|---:|---|---|---:|---:|---|---|
| 1 | 킴스클럽 | 979,000원 | **현금 전용** | 유/무료 | 979,000원 | - | - | 보기 |
| 2 | 롯데ON | 1,073,890원 | 일반 | 무료배송 | 1,073,890원 | - | - | 보기 |
| 3 | G마켓 | 1,089,590원 | 일반 | 무료배송 | 1,089,590원 | - | 최대 24개월 | 보기 |
| 4 | 옥션 | 1,121,780원 | **쿠폰 적용가** | 무료배송 | 1,121,780원 | 우리카드 303,720원 | 최대 24개월 | 보기 |정렬 기준:
1. `total_price` 오름차순 단일 기준. 결제조건(현금/쿠폰/포인트/할인/특정카드/멤버십 한정)이 붙은 row도 같은 정렬에 그대로 참여한다 — 가장 싸면 1위로 올라온다. 결제조건은 분리 그룹화하지 않고 표의 "결제조건" 컬럼에 행별로 표시한다 (payment_condition_label이 있으면 그 값을 우선 표시, 없으면 "일반"; 세부 매핑은 cash → "현금 전용", coupon → "쿠폰 적용가", point → "포인트 적용가", card → 카드명/카드 조건, discount → "할인 조건", membership → "멤버십 조건"). 사용자는 자기 결제 수단에 따라 직접 판단한다. 2. card_price가 있고 카드 적용 시 승자가 바뀌면 표 아래에 "카드 기준 최저가"를 별도로 적는다. 3. 무이자 할부는 결제 조건이 달라질 수 있으므로 Danawa 노출 문구 기준이라고 밝힌다. 4. 1위가 조건부 가격이면 요약 문장에 결제수단 단서를 짧게 덧붙인다. 예: "최저 실구매가: 킴스클럽 979,000원 / 현금 결제 한정, 카드 결제 기준 최저가는 롯데ON 1,073,890원". 카드 결제 가능한 최저가도 같이 알려 사용자가 결제수단별 결과를 한 번에 비교할 수 있게 한다.
요약 예시:
최저 실구매가: G마켓 217,950원 / 무료배송
카드 기준 최저가: 옥션 우리카드 303,720원
무이자: G마켓·옥션 최대 24개월 표기카드 할인 markup이 없으면 "카드 할인가 표기 없음"이라고 쓰고, 체크아웃 할인 자체가 없다고 단정하지 않는다.
Workflow
1. 검색어를 확인한다. 2. python scripts/danawa_search.py search "<검색어>" --limit 5로 후보를 확인한다. 3. 후보가 명확하면 해당 pcode로 offers를 실행한다. 4. 후보가 애매하면 상위 3~5개 상품명/가격/pcode를 보여주고 선택을 요청한다. 5. 오퍼는 `total_price` 오름차순 단일 기준으로 정렬한다 (결제조건 분리 그룹화하지 않음). 결제조건은 표의 "결제조건" 컬럼과 row 단위 플래그로만 표기하고, 1위가 현금/쿠폰가여도 그대로 1위로 노출한다. 6. 카드 할인가가 있으면 카드 기준 최저가도 별도 요약한다. 1위가 조건부 가격이면 "카드 결제 기준 최저가"도 요약 문장에 함께 적어 결제수단별 최저가를 한 번에 알게 한다. 7. 조회 시점 기준이며 가격/배송/카드 혜택은 변동될 수 있음을 짧게 덧붙인다.
Failure modes
- 검색 결과가 0개면 검색어를 더 구체화한다.
- Danawa HTML/AJAX 구조가 바뀌면 selector가 깨져
offers가 비거나 필드가 누락될 수 있다. - 다나와가 새로운 결제조건 배지 클래스나 문구를 도입하면 결제조건 배지 화이트리스트(
cash/point/coupon/discount/card/membership클래스,현금/포인트/쿠폰/할인/카드/멤버십텍스트 키워드)와payment_condition_types/payment_condition_label매핑을 함께 갱신해야 한다. - 검색 결과 가격과 오퍼 AJAX 가격은 갱신 시점·카드가·제휴 링크 기준 차이로 다를 수 있다.
- 카드 할인과 무이자 문구는 Danawa가 노출한 경우에만 확정적으로 보여준다.
- 공개 표면 기반이므로 고빈도 요청에는 throttling/backoff를 추가해야 한다.
- 접근 차단이나 CAPTCHA가 나오면 우회를 시도하지 말고 실패 모드로 보고한다.
Done when
- 검색어 또는 모델명을 확인했다.
- 상품 후보를 최소 1개 이상 반환하거나, 반환 실패 이유를 설명했다.
- 쇼핑몰별 상품가, 배송비, 실구매가, 카드 할인가, 무이자 문구를 조회 시점 기준으로 정리했다.
- 사용자 응답은 표 형식으로 제공했다.
- 로그인/구매/차단 우회 범위를 벗어나지 않았다.
#!/usr/bin/env python3
"""Read-only Danawa search/price comparison helper for Hermes.
Usage:
python scripts/danawa_search.py search "에어팟 프로 2세대" --limit 8
python scripts/danawa_search.py offers 28208783 --limit 10
python scripts/danawa_search.py compare "에어팟 프로 2세대" --limit 5 --offers 5
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import time
import urllib.parse
import urllib.request
from html import unescape
from typing import Any, Dict, List, Optional
try:
from bs4 import BeautifulSoup
except ImportError as exc: # pragma: no cover - environment guard
raise SystemExit("beautifulsoup4 is required: python -m pip install beautifulsoup4") from exc
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/121 Safari/537.36"
def fetch(url: str, *, method: str = "GET", data: Optional[dict] = None, referer: Optional[str] = None) -> str:
headers = {
"User-Agent": UA,
"Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
}
body = None
if data is not None:
body = urllib.parse.urlencode(data).encode("utf-8")
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
headers["X-Requested-With"] = "XMLHttpRequest"
if referer:
headers["Referer"] = referer
req = urllib.request.Request(url, data=body, headers=headers, method=method)
with urllib.request.urlopen(req, timeout=25) as resp:
return resp.read().decode("utf-8", "replace")
def soup_for(html: str) -> BeautifulSoup:
return BeautifulSoup(html, "html.parser")
def clean_text(s: Optional[str]) -> Optional[str]:
if s is None:
return None
return " ".join(unescape(s).split())
def parse_int(s: Optional[str]) -> Optional[int]:
if not s:
return None
digits = re.sub(r"\D", "", s)
return int(digits) if digits else None
def abs_url(url: Optional[str]) -> Optional[str]:
if not url:
return None
if url.startswith("//"):
return "https:" + url
if url.startswith("/"):
return "https://prod.danawa.com" + url
return url
def search(query: str, limit: int = 10) -> Dict[str, Any]:
url = "https://search.danawa.com/dsearch.php?query=" + urllib.parse.quote(query)
html = fetch(url)
soup = soup_for(html)
items: List[Dict[str, Any]] = []
for li in soup.select("li.prod_item"):
pid = (li.get("id") or "").replace("productItem", "") or None
name_el = li.select_one(".prod_name a") or li.select_one("p.prod_name a") or li.select_one('a[name="productName"]')
if not name_el:
continue
name = clean_text(name_el.get_text(" ", strip=True))
link = abs_url(name_el.get("href"))
min_input = li.select_one(f"#min_price_{pid}") if pid else None
price = parse_int(min_input.get("value") if min_input else None)
if price is None:
price_el = li.select_one(".price_sect strong") or li.select_one(".prod_pricelist strong")
price = parse_int(price_el.get_text() if price_el else None)
img = li.select_one(".thumb_image img")
image = abs_url((img.get("data-original") or img.get("src")) if img else None)
mall_el = li.select_one(".prod_pricelist .memory_sect") or li.select_one(".meta_item")
spec = " / ".join(clean_text(e.get_text(" ", strip=True)) or "" for e in li.select(".spec_list a, .spec_list span")[:10])
items.append(
{
"pcode": pid,
"title": name,
"price": price,
"price_text": f"{price:,}원" if price else None,
"mall_text": clean_text(mall_el.get_text(" ", strip=True)) if mall_el else None,
"url": link,
"image_url": image,
"spec": spec[:300] if spec else None,
}
)
if len(items) >= limit:
break
return {"query": query, "source_url": url, "count": len(items), "items": items, "meta": {"extraction": "danawa-search-html", "ts": int(time.time())}}
def js_value(html: str, key: str) -> str:
patterns = [
rf"{re.escape(key)}\s*:\s*\"([^\"]*)\"",
rf"{re.escape(key)}\s*:\s*'([^']*)'",
rf"{re.escape(key)}\s*:\s*([0-9]+)",
]
for pat in patterns:
m = re.search(pat, html)
if m:
raw = m.group(1)
if "\\u" in raw or "\\/" in raw:
try:
return json.loads('"' + raw.replace('"', '\\"') + '"')
except Exception:
return raw.replace("\\/", "/")
return raw
return ""
def product_meta(pcode: str) -> Dict[str, str]:
url = f"https://prod.danawa.com/info/?pcode={urllib.parse.quote(str(pcode))}"
html = fetch(url)
meta = {
"pcode": str(pcode),
"source_url": url,
"cate1": js_value(html, "nCategoryCode1"),
"cate2": js_value(html, "nCategoryCode2"),
"cate3": js_value(html, "nCategoryCode3"),
"cate4": js_value(html, "nCategoryCode4") or "0",
"UICategoryCode": js_value(html, "nCategoryCode"),
"powerLinkKeyword": js_value(html, "powerLinkKeyword"),
"minPrice": js_value(html, "nMinPrice"),
"keyword": js_value(html, "sKeyword"),
"NaPm": js_value(html, "sNaPm"),
"sProductFullName": js_value(html, "sProductName"),
"makerCode": js_value(html, "makerCode"),
"makerName": js_value(html, "makerName"),
}
title = soup_for(html).select_one(".prod_tit .title")
if title:
meta["sProductFullName"] = clean_text(title.get_text(" ", strip=True)) or meta["sProductFullName"]
return meta
def offers(pcode: str, limit: int = 20, include_shipping: bool = False) -> Dict[str, Any]:
meta = product_meta(pcode)
post_price = "Y" if include_shipping else "N"
data = {
"pcode": meta["pcode"],
"cate1": meta.get("cate1", ""),
"cate2": meta.get("cate2", ""),
"cate3": meta.get("cate3", ""),
"cate4": meta.get("cate4", "0"),
"UICategoryCode": meta.get("UICategoryCode", "0"),
"powerLinkKeyword": meta.get("powerLinkKeyword", ""),
"minPrice": meta.get("minPrice", ""),
"keyword": meta.get("keyword", ""),
"NaPm": meta.get("NaPm", ""),
"bDeliveryLeftRightYN": "N",
"bQuickPostSortYN": "N",
"sSortType": "minPrice",
"sProductFullName": meta.get("sProductFullName", ""),
"bPostPriceYN": post_price,
"bBadgeDefaultYN": "N",
"bWarrantyDefaultYN": "N",
"nOpenMarketMoreCount": "30",
"nAffiliateMoreCount": "30",
"nOverseasShoppingMoreCount": "30",
"nGeneralAffiliateMoreCount": "3",
"sRelationMenuType": "",
"sRelationType": "",
"bCoupangSortYN": "N",
"makerCode": meta.get("makerCode", ""),
"makerName": meta.get("makerName", ""),
}
html = fetch("https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php", method="POST", data=data, referer=meta["source_url"])
soup = soup_for(html)
rows: List[Dict[str, Any]] = []
for div in soup.select(".diff_item"):
mall_img = div.select_one(".d_mall img")
mall = mall_img.get("alt") if mall_img else None
price_el = div.select_one("em.prc_c") or div.select_one("em.prc_t")
price = parse_int(price_el.get_text() if price_el else None)
if not mall or price is None:
continue
ship_el = div.select_one(".ship") or div.select_one(".stxt")
shipping = clean_text(ship_el.get_text(" ", strip=True)) if ship_el else None
shipping_fee = 0 if shipping and "무료" in shipping else parse_int(shipping)
card_line = div.select_one(".card_line")
card_price_el = card_line.select_one(".card_prc") if card_line else None
card_name_el = card_line.select_one(".txt") if card_line else None
card_price = parse_int(card_price_el.get_text() if card_price_el else None)
installment_el = div.select_one(".btn_foi .txt")
installment_detail_el = div.select_one(".foi_layer .ly_cont")
link = div.select_one("a.priceCompareBuyLink")
# 결제조건 ico만 캡처. 다른 ico(빠른배송, 안내, 상품리뷰 등)는 노이즈라 제외.
# 클래스만 있고 텍스트가 비어 있는 아이콘도 row 라벨이 누락되지 않도록
# 같은 정규화 테이블에서 표시 라벨/타입/boolean 필드를 모두 파생한다.
payment_condition_labels = {
"cash": "현금",
"point": "포인트",
"coupon": "쿠폰",
"card": "카드",
"discount": "할인",
"membership": "멤버십",
}
payment_condition_types: List[str] = []
payment_badges: List[str] = []
for el in div.select(".prc_line .ico, .d_dsc .ico"):
classes = set(el.get("class") or [])
text = clean_text(el.get_text(" ", strip=True)) or ""
matched_types = [
kind
for kind, label in payment_condition_labels.items()
if kind in classes or label in text
]
if not matched_types:
continue
for kind in matched_types:
if kind not in payment_condition_types:
payment_condition_types.append(kind)
label = payment_condition_labels[kind]
if label not in payment_badges:
payment_badges.append(label)
cash_only = "cash" in payment_condition_types
point_only = "point" in payment_condition_types
coupon_only = "coupon" in payment_condition_types
card_only_badge = "card" in payment_condition_types
discount_badge = "discount" in payment_condition_types
membership_badge = "membership" in payment_condition_types
payment_condition_label = ", ".join(payment_badges) or None
is_conditional_price = bool(payment_condition_types)
rows.append(
{
"mall": clean_text(mall),
"price": price,
"price_text": f"{price:,}원",
"shipping": shipping,
"is_free_shipping": bool(shipping and "무료" in shipping),
"shipping_fee": shipping_fee,
"total_price": price + (shipping_fee or 0),
"total_price_text": f"{price + (shipping_fee or 0):,}원",
"card_price": card_price,
"card_price_text": f"{card_price:,}원" if card_price else None,
"card_name": clean_text(card_name_el.get_text(" ", strip=True)) if card_name_el else None,
"card_discount": (price - card_price) if card_price else None,
"card_discount_text": f"{price - card_price:,}원" if card_price else None,
"installment": clean_text(installment_el.get_text(" ", strip=True)) if installment_el else None,
"installment_detail": clean_text(installment_detail_el.get_text(" ", strip=True)) if installment_detail_el else None,
"payment_badges": payment_badges,
"cash_only": cash_only,
"point_only": point_only,
"coupon_only": coupon_only,
"card_only_badge": card_only_badge,
"discount_badge": discount_badge,
"membership_badge": membership_badge,
"payment_condition_types": payment_condition_types,
"payment_condition_label": payment_condition_label,
"is_conditional_price": is_conditional_price,
"url": abs_url(link.get("href") if link else None),
}
)
# 정렬은 단순히 배송비 포함 실구매가 오름차순. 결제조건(현금/쿠폰/포인트/특정카드)은
# 분리 그룹으로 묶지 않고 row 단위로 payment_badges / payment_condition_types /
# payment_condition_label 및 세부 boolean 플래그로 노출한다. 호출자(또는 사용자)는 자기 결제수단에 맞춰 판단한다.
rows.sort(key=lambda row: (
row["total_price"] is None,
row["total_price"] or row["price"],
row["price"],
row["mall"] or "",
))
rows = rows[:limit]
return {
"pcode": str(pcode),
"title": meta.get("sProductFullName"),
"source_url": meta["source_url"],
"count": len(rows),
"normal_count": sum(1 for r in rows if not r.get("is_conditional_price")),
"conditional_count": sum(1 for r in rows if r.get("is_conditional_price")),
"offers": rows,
"meta": {
"extraction": "danawa-price-ajax",
"include_shipping": include_shipping,
"sort": "total_price",
"ts": int(time.time()),
},
}
def compare(query: str, limit: int, offer_limit: int) -> Dict[str, Any]:
result = search(query, limit=limit)
enriched = []
for item in result["items"]:
row = dict(item)
if item.get("pcode"):
try:
off = offers(item["pcode"], limit=offer_limit)
row["offers"] = off.get("offers", [])
except Exception as exc: # keep search result usable if a detail call fails
row["offers_error"] = f"{type(exc).__name__}: {exc}"
enriched.append(row)
result["items"] = enriched
result["meta"]["detail_extraction"] = "best-effort"
return result
def positive_int(raw: str) -> int:
value = int(raw)
if value < 1:
raise argparse.ArgumentTypeError("must be >= 1")
return value
def main() -> int:
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
s = sub.add_parser("search")
s.add_argument("query")
s.add_argument("--limit", type=positive_int, default=10)
o = sub.add_parser("offers")
o.add_argument("pcode")
o.add_argument("--limit", type=positive_int, default=20)
o.add_argument("--include-shipping", action="store_true")
c = sub.add_parser("compare")
c.add_argument("query")
c.add_argument("--limit", type=positive_int, default=5)
c.add_argument("--offers", type=positive_int, default=5)
args = ap.parse_args()
try:
if args.cmd == "search":
out = search(args.query, args.limit)
elif args.cmd == "offers":
out = offers(args.pcode, args.limit, args.include_shipping)
else:
out = compare(args.query, args.limit, args.offers)
print(json.dumps(out, ensure_ascii=False, indent=2))
return 0
except Exception as exc:
print(json.dumps({"error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
Related skills
FAQ
What does danawa-price-search do?
다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다.
When should I use danawa-price-search?
User asks about danawa price search or related SKILL.md workflows.
Is danawa-price-search safe to install?
Review the Security Audits panel on this page before installing in production.