로컬 검색 스킬
자동 엔진 폴백이 적용된 로컬 웹 검색. DuckDuckGo / Bing / Google 직접 HTML 스크래핑 · 요금 제한에 강함 · 표준 결과 스키마.
이 스킬은 사용자 기기에서 직접 공개 검색 엔진을 호출합니다. API 키, SDK, 네트워크 홉이 필요 없습니다. 한 엔진에서 요금 제한이 발생하면, 오케스트레이터가 자동으로 다음 엔진으로 전환합니다. 따라서 Google이 차단된 데이터센터 IP에서도, 첫 번째 시도에 성공하는 경우가 많습니다.
각 결과는 7개의 표준 필드(url, name, snippet, host_name, rank, date, favicon)와 3개의 선택적 확장 필드(source_engine, raw_html, score)를 가진 SearchFunctionResultItem입니다. 유사한 형태의 웹 검색 스킬을 사용하는 코드는 수정 없이 그대로 사용할 수 있습니다.
✨ 주요 특징
- 제로 클라우드 SDK 종속성 —
cheerio(HTML 파서)와 Node 내장fetch만 사용. - 세 엔진, 자동 폴백 —
engine: "auto"(기본값)일 때 DuckDuckGo → Bing → Google 순서. - 로케일 인식 —
--locale en-US/zh-CN/ja-JP등 모든 BCP-47 태그 지원. Bing이 영어 쿼리에도 지역화된 결과를 제공하는 비-US IP에서 중요함. - 최신 필터 —
--recency-days 7로 지난주 결과로 제한. DDG는 정확한 N일을 지원하고, Bing/Google은 일/주/월 버킷을 사용. - 표준 결과 스키마 —
url/name/snippet/host_name/rank/date/favicon필드로, ClawHub 레지스트리의 다른 웹 검색 스킬과 동일한 형태. - CLI + SDK — 일회성 검색에는
tsx bin/web-search.ts <query>, 코드 내에서는import { search } from "local-search"사용. - 순수 TypeScript ESM — Node 18+에서
tsx로 실행하거나,bun으로 제로 설정 실행 가능.
🚀 빠른 시작
# 1. 설치 (런타임 종속성: cheerio)
cd skills/local-search && npm install
# 2. 검색 (DDG → Bing → Google 자동 폴백)
tsx bin/web-search.ts "인공지능"
# 3. 특정 엔진 및 로케일 지정
tsx bin/web-search.ts "머신러닝" --engine bing --locale en-US --num 5
# 4. 최신 뉴스를 JSON으로 저장
tsx bin/web-search.ts "AI 혁신" --recency-days 7 --json -o ai_news.json
프로그래매틱 사용:
import { search } from "local-search";
const outcome = await search("프랑스의 수도는?", { num: 5 });
if (outcome.success) {
console.log(`${outcome.engine}에서 ${outcome.elapsedMs}ms만에 응답`);
outcome.results.forEach(r => console.log(`- ${r.name}\n ${r.url}`));
}
🎯 이 스킬을 사용해야 할 때
local-search는 사용자가 모델의 훈련 데이터에 없는 공개 웹 정보가 필요할 때 사용합니다:
- 실시간 정보: 최신 뉴스, 주식 가격, 날씨, 스포츠 점수
- 최신 문서: 프레임워크 릴리스 노트, API 변경 사항, 최근 발표된 논문
- 사실 확인: 실시간 웹 소스로 주장 검증
- 연구: 주제에 대한 여러 소스 수집, 관점 비교
- 콘텐츠 발견: 튜토리얼, 블로그 포스트, 최근 발표 검색
- 경쟁/시장 분석: 경쟁사 발표, 업계 트렌드
- 학술 검색: 논문, 인용, 저자 페이지 검색
🔄 엔진 비교
| 엔진 | 엔드포인트 | API 키 | 거주용 IP | 데이터센터 IP | 최신성 |
|---|---|---|---|---|---|
| DuckDuckGo | html.duckduckgo.com/html/ (GET) | 없음 | ✅ 높음 | ⚠️ 부하 시 요금 제한 | df=d<N> (정확한 일) |
| Bing | www.bing.com/search | 없음 | ✅ 높음 | ✅ 높음 | freshness=d1|w1|m1 (버킷) |
www.google.com/search | 없음 | ⚠️ 중간 | ❌ 낮음 (enablejs 장벽) | tbs=qdr:d|w|m|y (버킷) |
engine: "auto"는 DuckDuckGo → Bing → Google 순서로 시도하며, 첫 번째 비어 있지 않은 결과 세트를 반환합니다. 데이터센터 IP에서 유효 체인은 DDG → Bing입니다(Google은 보통 차단됨). 거주용 IP에서는 세 엔진 모두 사용 가능합니다. src/engines/index.ts의 AUTO_ENGINE_ORDER를 편집하여 우선순위를 변경할 수 있습니다.
📦 설치 경로
권장 위치: {project_path}/skills/local-search
이 스킬 패키지를 프로젝트의 위 경로에 압축 해제하세요.
사전 요구 사항
- Node.js >= 18 (내장 전역
fetch사용). - 하나의 npm 종속성:
cheerio(HTML 파서). - 작동하는 인터넷 연결 — 스킬이 공개 검색 엔진을 직접 호출합니다.
스킬 디렉토리에서 한 번 설치:
cd {project_path}/skills/local-search
npm install # 또는: bun install / pnpm install
이 스킬은 순수 TypeScript ESM입니다.
tsx(권장, 개발자 친화적) 또는bun(제로 설정)으로 실행할 수 있습니다. 런타임에 구애받지 않는node --experimental-strip-types bin/web-search.ts도 Node 22+에서 작동합니다.
로컬인 이유는?
| 항목 | 클라우드 검색 스킬 | 이 스킬 (local-search) |
|---|---|---|
| 검색 백엔드 | 클라우드 함수 / API | DuckDuckGo / Bing / Google 직접 HTTP |
| API 키 / SDK | 필수 | 없음 |
| 네트워크 경로 | 클라이언트 → 클라우드 → 검색 엔진 | 클라이언트 → 검색 엔진 |
| 실패 처리 | SDK 종속 | 3개 엔진 자동 폴백 |
| 결과 스키마 | SearchFunctionResultItem (7개 필드) | 동일 7개 필드 + 3개 선택적 확장 필드 |
| CLI | SDK 특화 | tsx bin/web-search.ts |
아키텍처
local-search/
├── SKILL.md ← 현재 위치
├── LICENSE.txt
├── package.json ← `cheerio` 종속성 선언
├── tsconfig.json
├── bin/
│ └── web-search.ts ← CLI 진입점
├── src/
│ ├── index.ts ← 공개 SDK 내보내기
│ ├── search.ts ← 자동 폴백 오케스트레이터
│ ├── types.ts ← SearchFunctionResultItem + 옵션
│ └── engines/
│ ├── _shared.ts ← fetch/파싱 헬퍼
│ ├── duckduckgo.ts ← POST https://html.duckduckgo.com/html/
│ ├── bing.ts ← GET https://www.bing.com/search
│ ├── google.ts ← GET https://www.google.com/search
│ └── index.ts ← 엔진 레지스트리 + AUTO_ENGINE_ORDER
└── scripts/
└── web_search.ts ← 빠른 시작 예제
유일한 런타임 종속성은 cheerio(HTML 파서)이며, package.json에 선언되어 있습니다. Node 18+의 내장 fetch가 네트워크 계층을 처리하므로, 이 패키지에는 다른 SDK가 전혀 임포트되지 않습니다.
CLI 사용법
CLI는 bin/web-search.ts에 있습니다. tsx, bun 또는 TypeScript를 지원하는 모든 실행기로 실행하세요.
기본 검색
# 기본: 자동 엔진, 10개 결과, 사람이 읽기 쉬운 출력
tsx bin/web-search.ts "인공지능"
# 결과 개수 제한
tsx bin/web-search.ts "머신러닝" --num 5
# (짧은 옵션도 작동)
tsx bin/web-search.ts "머신러닝" -n 5
특정 엔진 지정
# DuckDuckGo만 사용
tsx bin/web-search.ts "최신 기술 뉴스" --engine duckduckgo
# Bing만 사용
tsx bin/web-search.ts "최신 기술 뉴스" --engine bing
# Google만 사용 (가장 높은 품질, 요금 제한 가능성 높음)
tsx bin/web-search.ts "최신 기술 뉴스" --engine google
# 기본 — DDG → Bing → Google 시도, 첫 번째 비어 있지 않은 결과 반환
tsx bin/web-search.ts "최신 기술 뉴스" --engine auto
최신 필터
# 지난 7일간의 결과
tsx bin/web-search.ts "암호화폐 뉴스" --num 10 --recency-days 7
# (짧은 옵션)
tsx bin/web-search.ts "암호화폐 뉴스" -r 7
결과를 JSON으로 저장
# JSON을 파일로 쓰기 (사람이 읽기 쉬운 배너는 출력되지 않음)
tsx bin/web-search.ts "기후 변화 연구" --num 5 --json -o search_results.json
# 또는 JSON을 stdout으로 파이프
tsx bin/web-search.ts "AI 혁신" --num 3 --recency-days 1 --json > ai_news.json
조용한 모드 (스크립트/파이핑)
# "엔진/시간" 배너를 출력하지 않고 결과만 출력
tsx bin/web-search.ts "react hooks" --quiet
CLI 매개변수
| 플래그 | 짧은 옵션 | 설명 |
|---|---|---|
--num <N> | -n | 결과 개수 (기본값: 10, 최대: 50) |
--engine <id> | -e | duckduckgo | bing | google | auto (기본값: auto) |
--recency-days <N> | -r | 지난 N일 이내 결과로 제한 (기본값: 0 = 필터 없음) |
--timeout <ms> | — | 엔진별 타임아웃(ms) (기본값: 8000) |
--json | — | 사람이 읽기 쉬운 대신 JSON 출력 |
--output <경로> | -o | stdout 대신 JSON을 파일로 쓰기 |
--pretty | — | JSON 예쁘게 출력 (기본값: 켜짐; --no-pretty로 끄기 — 아직 미지원, 대신 --json 생략) |
--quiet | -q | 엔진 정보 배너 숨기기 |
--help | -h | 도움말 표시 |
검색 결과 구조
각 결과 항목은 SearchFunctionResultItem입니다:
interface SearchFunctionResultItem {
// ----- 표준 필드 (ClawHub 웹 검색 스킬 간 공유) -----
url: string; // 결과의 전체 URL
name: string; // 페이지 제목
snippet: string; // 미리보기 텍스트/설명
host_name: string; // 도메인 이름 (예: "ko.wikipedia.org")
rank: number; // 엔진 결과 세트 내 1부터 시작하는 순위
date: string; // 게시/업데이트 날짜; 알 수 없는 경우 "N/A"
favicon: string; // 파비콘 URL (Google S2)
// ----- 확장 필드 (local-search 신규) -----
source_engine: "duckduckgo" | "bing" | "google";
raw_html?: string; // 스니펫 요소의 원본 HTML (선택적)
score?: number; // 휴리스틱 관련성 점수 [0, 100] (선택적)
}
SDK 사용법
TypeScript/JavaScript 코드를 작성 중이라면(CLI를 실행하는 것이 아니라면), src/index.ts에서 임포트하세요:
간단한 검색
import { search } from "local-search";
const outcome = await search("프랑스의 수도는?", { num: 5 });
if (outcome.success) {
console.log(`사용 엔진: ${outcome.engine}`);
console.log(`시도 순서: ${outcome.enginesTried.join(" → ")}`);
console.log(`경과 시간: ${outcome.elapsedMs}ms`);
for (const item of outcome.results) {
console.log(`- ${item.name} [${item.source_engine}]`);
console.log(` ${item.url}`);
console.log(` ${item.snippet}`);
}
} else {
console.error("검색 실패:", outcome.error);
if (outcome.errors) {
// 자동 모드: 어떤 엔진이 시도되었고 무엇을 보고했는지 확인
for (const e of outcome.errors) {
console.error(` ${e.engine}:`, e.error);
}
}
}
실패 시 예외 발생 변형
import { searchOrThrow, AllEnginesFailedError } from "local-search";
try {
const results = await searchOrThrow("JavaScript 프레임워크", { num: 10 });
console.log(results);
} catch (err) {
if (err instanceof AllEnginesFailedError) {
console.error("모든 엔진 실패:", err.errors);
} else {
console.error("단일 엔진 오류:", err);
}
}
특정 엔진 지정
import { search } from "local-search";
// 폴백 체인을 건너뛰고 DuckDuckGo만 사용.
const outcome = await search("양자 컴퓨팅 응용", {
num: 8,
engine: "duckduckgo",
});
최신 필터
const outcome = await search("유전체학 연구", {
num: 5,
recency_days: 30, // 지난 30일
});
사용자 정의 fetch (테스트/에지 런타임)
const outcome = await search("hello world", {
fetchImpl: myMockFetch, // WhatWG-fetch 호환 함수
userAgent: "my-bot/1.0",
timeoutMs: 5000,
});
엔진 백엔드
DuckDuckGo (duckduckgo)
- 엔드포인트:
https://html.duckduckgo.com/html/(POST 폼q=...) - API 키 없음. 대략적인 요금 제한이 있음 — 지속적인 과도한 요청은 빈 결과 세트를 반환함.
- 최신성:
df=d<N>폼 필드. - 최적 용도: 기본 첫 번째 시도; 거의 완전히 차단되지 않음; 개인정보 보호.
Bing (bing)
- 엔드포인트:
https://www.bing.com/search?q=...&count=... - API 키 없음. HTML 레이아웃이 안정적이고 파싱하기 쉬움.
- 최신성:
freshness=d1/w1/m1버킷 (Bing에는 임의 N일 URL 필터가 없음). - 최적 용도: 두 번째 폴백; 좋은 영어 결과; 가끔 로케일 문제 발생 (
ensearch=1로 완화).
Google (google)
- 엔드포인트:
https://www.google.com/search?q=...&num=... - API 키 없음, 하지만 Google이 스크래퍼를 가장 적극적으로 차단함. 데이터센터/클라우드 IP에서 Google은 일반적으로 "enable JS" 리디렉션 페이지(/httpservice/retry/enablejs)를 반환하며 파싱 가능한 결과가 없음 — 엔진은 이를 소프트 실패로 처리하고 자동 체인이 넘어감. 거주용 IP에서는 Google HTML 스크래핑이 잘 작동하는 경우가 많음.
- 최신성:
tbs=qdr:d|w|m|y버킷. - 최적 용도: 거주용 IP에서 최후의 폴백; 클라우드 IP에서는 보통 실패함(DDG+Bing이 이미 요구를 충족).
자동 폴백 전략
engine: "auto"(기본값)일 때, 오케스트레이터는 다음 순서로 엔진을 시도합니다:
- DuckDuckGo — 가장 덜 적극적인 차단, 가장 세밀한 날짜 필터
- Bing — 모든 IP(데이터센터 포함)에서 일반적으로 안정적
- Google — 도달 가능할 때 최고 결과 품질; 데이터센터 IP에서 자주 차단됨
결과가 ≥ 1개인 첫 번째 엔진을 반환합니다. 세 엔진 모두 실패하면 { success: false, errors: [...] } 결과가 각 엔진의 오류와 함께 반환됩니다. searchOrThrow 모드에서는 AllEnginesFailedError가 발생합니다.
실제 신뢰성 표(테스트 중 관찰):
| 호출자 IP 유형 | DDG | Bing | 유효 자동 체인 | |
|---|---|---|---|---|
| 거주용 | 높음 | 높음 | 중간 | DDG → Bing → Google (3개 사용 가능) |
| 데이터센터/클라우드 | 중간 (부하 시 요금 제한) | 높음 | 낮음 (enablejs 장벽) | 실질적으로 DDG → Bing |
순서는 src/engines/index.ts (AUTO_ENGINE_ORDER)에 정의되어 있으며, 다른 우선순위를 원하면 해당 배열을 편집하세요.
일반적인 사용 사례
- 실시간 정보 수집 — 최신 뉴스, 주식 가격, 날씨 확인
- 최신 문서 참조 — 프레임워크 업데이트, API 변경 사항 확인
- 사실 확인 — 클레임을 라이브 웹 소스와 대조
- 연구 — 여러 소스에서 정보 수집 및 비교
- 콘텐츠 발견 — 튜토리얼, 블로그 게시물, 최근 발표 검색
- 경쟁 분석 — 경쟁사 발표, 업계 트렌드 파악
- 학술 검색 — 논문, 인용, 저자 페이지 찾기