# Daangn Search

> 당근마켓을 구·시·전국 단위로 훑는다. 당근 웹 검색은 동 하나로만 되기 때문에, 지역코드를 순회해야 넓게 찾을 수 있다.

- Skill: `bam-bam-2/daangn-search` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bam-bam-2/daangn-search`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bam-bam-2/daangn-search/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bam-bam-2 (https://skillmd.com/u/bam-bam-2)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/bam-bam-2/daangn-search

---


# 당근마켓 광역 검색

당근 웹(`www.daangn.com`)의 중고거래 검색은 **선택된 행정동 하나**의 매물만 보여준다.
"전국 검색" 기능이 없다. 넓게 찾으려면 지역코드를 바꿔가며 순회해야 한다.

## URL 구조

```
https://www.daangn.com/kr/buy-sell/?in=x-<지역ID>&search=<검색어>&only_on_sale=true
```

- `in` 파라미터의 **이름 부분은 무시되고 숫자 ID만 유효하다.** `x-448`처럼 아무 이름이나 써도 된다
- `only_on_sale=true` 를 붙이면 거래완료 매물이 빠진다. 안 붙이면 결과 대부분이 이미 팔린 것
- 응답은 서버사이드 렌더링이라 fetch만으로 매물이 다 들어온다 (브라우저 불필요)

## 지역 ID 지도 (2026-08 실측)

시도 순서로 연속 배치돼 있다. 서울은 구 단위로 아래와 같다.

| 구 | ID | 구 | ID |
|---|---|---|---|
| 종로 | 3~19 | 양천 | 239~260 |
| 중구 | 20~35 | 강서 | 261~280 |
| 용산 | 36~52 | 구로 | 281~296 |
| 성동 | 53~70 | 금천 | 297~306 |
| 광진 | 71~86 | 영등포 | 307~325 |
| 동대문 | 87~110 | 동작 | 326~340 |
| 중랑 | 111~127 | 관악 | 341~361 |
| 성북 | 128~144 | 서초 | 362~380 |
| 강북 | 145~157 | 강남 | 381~403 |
| 도봉 | 158~172 | 송파 | 404~431 |
| 노원 | 173~185 | 강동 | 432~450 |
| 은평 | 186~206 | | |
| 서대문 | 207~220 | 마포 | 221~238 |

서울 밖: 부산 451~, 대구 ~680~830, 인천 ~831~960, 광주 ~961~, 대전 ~1051~,
울산 ~1151~, 세종·경기 ~1251~1800, 강원 ~1900, 충북 ~2100, 충남 ~2400, 전북 ~2700, 전남 ~3000.

**경계는 정확하지 않다.** 확인이 필요하면 아래로 지역명을 읽어 검증한다.

```js
const m = html.match(/data-gtm="search_filter">([^<]+)</);  // "서울시 강동구 둔촌동"
```

## 매물 파싱

```js
function parseL(html){
  const re=/<a data-gtm="search_article"[^>]*href="(\/kr\/buy-sell\/[^"]+)"[\s\S]*?<\/a>/g;
  const res=[]; let m;
  while((m=re.exec(html))){
    const seg=m[0];
    const txt=seg.replace(/<img[^>]*>/g,'').replace(/<svg[\s\S]*?<\/svg>/g,'')
                 .replace(/<[^>]+>/g,'\u0001').split('\u0001').map(s=>s.trim()).filter(Boolean);
    res.push({href:'https://www.daangn.com'+m[1], t:txt.join(' | ')});  // "제목 | 가격 | 동네 | 시간"
  }
  return res;
}
```

상세 페이지는 fetch 후 `매너온도` 다음 20줄 정도를 읽으면 본문·채팅수·관심수가 나온다.

## 속도와 차단

CloudFront가 앞단에 있어 **너무 빠르면 403**이 뜬다. 한 번 막히면 1~2분 쉬어야 풀린다.

- 안전 구간: **2~3개 병렬 + 250~450ms 간격** (초당 약 2.5~5건). 이 속도로 900개 동을 돌았고 차단 0회
- 위험 구간: 10병렬 연속. 약 120건에서 막혔다
- 403이 연속 8건 넘게 나오면 15~20초 쉬고 재개하는 가드를 넣는다
- `page.goto()`(브라우저)는 동당 2.4초로 fetch보다 3~4배 느리다. **fetch를 쓴다**
- REPL 툴콜은 120초에서 끊긴다. 한 콜에 250~300개 동이 한계이니 나눠 돌린다
- 에이전트 탭이 중간에 닫혀 `page`가 null이 되는 일이 있다. fetch 방식이면 영향 없다

## 검색어 전략

당근 검색은 토큰 기반이라 **한 단어로는 다 못 잡는다.** 2~3개를 돌린다.

- 예: 세탁건조기 → `콤보`(워시콤보·AI콤보 잡힘) + `세탁건조기` + `워시타워`
- `콤보` 같은 일반어는 영화 팝콘 쿠폰 같은 노이즈가 압도적이다. 수집 후 `/세탁|건조/` 등으로 거른다
- 결과는 `Map`(href → 제목)에 모아 자동 중복 제거한다. 같은 판매자가 여러 동네에 중복 게시하는 일이 잦다

## 결과 정리

제목 문자열에서 가격·세대를 뽑아 정렬하면 시세 분포가 바로 보인다.

```js
const price = t => { const m=t.match(/([\d,]+)원/); return m?+m[1].replace(/,/g,''):null; };
```

## 주의

- **대형가전은 지방 매물을 넣지 말 것.** 용달비 40~50만원이면 가격 이점이 사라진다. 수도권까지가 실용 범위
- 중고 시세가 신품가에 근접하면 신품·구독과 나란히 비교해서 보고한다. 중고만 훑고 "이게 최저가"라고 하면 반쪽짜리다

