Skill: downwind7clawd-ctrl/naver-search (version 1.0.4)

# Naver Search Skill 명세서 이 문서는 OpenClaw 에이전트가 네이버 검색 스킬을 가장 효율적으로 호출하고 결과를 분석할 수 있도록 돕는 지침서입니다. ## 🎯 스킬 목적 네이버의 실시간 뉴스, 쇼핑 데이터, 영상 및 장소 정보를 정교한 파라미터 제어를 통해 확보합니다. ## 🛠️ 호출 명세 (Command Reference) ### 1. 통합 어그리게이터 (추천) 에이전트가 여러 카테고리의 정보를 한꺼번에 파악해야 할 때 사용합니다. ```bash python3 scripts/naver_search.py "<검색어>" ``` - **반환 데이터**: 최신 뉴스(3건), 쇼핑 트렌드(3건), 이미지 썸네일, 비디오 요약, 상위 웹 결과. ### 2. 고성능 뉴스 엔진 ```bash python3 scripts/naver_news.py "<검색어>" --sort <sort> --time <time> ``` - `--sort`: `0`(관련도순), `1`(최신순-추천), `2`(오래된순) - `--time`: `all`, `1h`(1시간), `1d`(1일), `1w`(1주), `1m`(1개월) - **팁**: 실시간 이슈 분석 시 `--sort 1 --time 1d` 조합이 가장 유용합니다. ### 3. 쇼핑/이미지/비디오 전문 엔진 | 기능 | 스크립트 | 특수 옵션 | | :--- | :--- | :--- | | **쇼핑** | `naver_shopping.py` | 가격, 판매처 정보 포함 | | **이미지** | `naver_images.py` | `--num [n]` (결과 개수 제어) | | **비디오** | `naver_video.py` | 출처 및 재생 시간 정보 포함 | ### 4. 예약 및 장소 엔진 장소의 물리적 위치나 예약 링크가 필요할 때 호출합니다. ```bash python3 scripts/naver_booking.py "<맛집/장소명>" ``` - **반환 데이터**: 업체명, 주소, 카테고리, 네이버 예약 상품 정보 및 링크. ## 📊 출력 포맷 제어 모든 명령어 뒤에 `--format` 옵션을 추가하여 데이터 구조를 선택할 수 있습니다. - `compact` (기본): 에이전트가 읽기 좋은 핵심 요약 텍스트. - `json`: 코드 레벨에서 정밀 분석이 필요한 경우 유용한 JSON raw 데이터. ## 🔐 보안 및 환경 변수 - 본 스킬은 **`SERPAPI_API_KEY`** 환경 변수가 필수입니다. - 환경 변수가 설정되어 있지 않다면 스킬 루트 폴더의 `.env` 파일에 키를 기입하세요. --- **유의사항**: 네이버 엔진 특성상 검색어 뒤에 '뉴스', '예약' 등의 단어를 직접 포함하면 더 정확한 결과를 얻을 수 있습니다. (`naver_news.py`, `naver_booking.py` 내부에 이미 일부 자동 보강 로직이 포함되어 있습니다.)

path: README.md
content: # Naver Search Skill for OpenClaw

OpenClaw 에이전트가 네이버의 방대한 데이터를 카테고리별(웹, 뉴스, 쇼핑, 이미지, 비디오, 예약)로 정밀하게 활용할 수 있도록 설계된 스킬입니다. 모든 엔진은 SerpAPI를 기반으로 하며, 독립적인 스크립트 구조를 가져 안정적입니다.

## 🏗️ 전체 구조 (Architecture)

```text
naver-search/
├── .env                  # API 키 (SERPAPI_API_KEY)
├── SKILL.md              # [Agent 전용] 사용 가이드 및 명령어 명세
├── README.md             # [User 전용] 설치 및 정보 요약
├── requirements.txt      # 의존성 패키지 (serpapi)
├── lib/
│   └── naver_base.py     # 공용 통신 및 데이터 정제 엔진
└── scripts/
    ├── naver_search.py   # [Main] 모든 결과를 요약 취합하는 통합 어그리게이터
    ├── naver_web.py      # 웹/지식iN/블로그 검색
    ├── naver_news.py     # 뉴스 (최신순/기간 필터 완벽 지원)
    ├── naver_shopping.py # 쇼핑 (최저가/판매처 상세 정보)
    ├── naver_images.py   # 이미지 (썸네일 및 출처 정보)
    ├── naver_video.py    # 비디오 (YouTube/TikTok 플랫폼 연동)
    └── naver_booking.py  # 예약/장소 (지도 위치 및 예약 상품 정보)
```

## 🚀 빠른 시작 (Quick Start)

가장 권장되는 사용법은 **통합 검색**을 통해 전체적인 흐름을 파악하는 것입니다.

```bash
# 한 번의 실행으로 뉴스, 쇼핑, 이미지, 비디오 요약 결과를 통합 조회
python3 scripts/naver_search.py "검색어"
```

## 🛠️ 기능별 상세 명령어 (Detailed Usage)

에이전트는 필요에 따라 아래의 전문 엔진을 직접 호출하여 더 깊은 데이터를 확보할 수 있습니다.

| 엔진명 (-e) | 전문 스크립트 | 주요 특징 및 옵션 |
| :--- | :--- | :--- |
| **통합** | `naver_search.py` | 모든 카테고리 톱 결과를 한 뷰에 요약 출력 |
| **뉴스** | `naver_news.py` | `--sort 1`(최신순), `--time 1d`(1일 이내) 옵션으로 정확한 속보 확인 |
| **쇼핑** | `naver_shopping.py` | 가격(`price`), 평점, 판매처 링크 등 이커머스 최적화 데이터 |
| **이미지** | `naver_images.py` | `--num [개수]` 옵션으로 다량의 썸네일 및 출처 확보 |
| **비디오** | `naver_video.py` | 영상 플랫폼(유튜브 등) 링크 및 재생 시간 정보 확인 |
| **예약** | `naver_booking.py` | 업체 위치, 예약 링크, 예약 상품 정보(가격 등) 추출 |

## ⚙️ 설정 (Setup)

1. **의존성 설치**: `pip install -r requirements.txt`
2. **API 키 설정**: 루트의 `.env` 파일에 `SERPAPI_API_KEY=발급받은키` 형식으로 저장하세요.

---

### 💡 에이전트 팁 (Tips for Agents)
- 결과가 너무 많을 때는 `--format full` 대신 기본값인 `compact` 모드를 사용하세요.
- 프로그램 방식으로 데이터를 처리하려면 `--format json` 옵션을 사용하면 분석하기 쉬운 JSON 객체가 반환됩니다.
- 네이버 통합 검색 화면과 가장 유사한 데이터 흐름을 원하신다면 `naver_search.py`를 호출하세요.


path: _meta.json
content: ownerId: kn7bpcrsr9pscqrvmn8h14gbxn80t7md
slug: naver-search
version: 1.0.3
publishedAt: 1772278942899

path: lib/naver_base.py
content: import os
import sys
import json
import site

# 유저 사이트 패키지 경로 추가 (하드코딩 제거)
user_site = site.getusersitepackages()
if user_site not in sys.path:
    sys.path.insert(0, user_site)

try:
    import serpapi
except ImportError:
    print("Error: serpapi package not installed. Run: pip install serpapi", file=sys.stderr)
    sys.exit(1)

def get_api_key():
    """환경 변수 또는 .env 파일에서 API 키 로드"""
    [REDACTED_SECRET]"SERPAPI_API_KEY")

    if not [REDACTED_SECRET] = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".env")
        if os.path.exists(env_path):
            # .env 파일 보안 강화 안내 (chmod 600 권장)
            # 실제 운영 환경이라면 여기서 파일 권한 체크 로직을 넣을 수 있음
            with open(env_path, "r") as f:
                for line in f:
                    if line.startswith("SERPAPI_API_KEY="):
                        return line.split("=", 1)[1].strip().strip('"').strip("'")
    return api_key

def clean_dict(obj):
    """표준 JSON 자료형만 남기도록 재귀적으로 정제"""
    if hasattr(obj, 'to_dict'):
        return clean_dict(obj.to_dict())
    elif isinstance(obj, dict):
        return {str(k): clean_dict(v) for k, v in obj.items() if not str(k).startswith('_')}
    elif isinstance(obj, list):
        return [clean_dict(x) for x in obj]
    elif isinstance(obj, (str, int, float, bool, type(None))):
        return obj
    else:
        # 그 외 객체는 문자열로 변환 (RLock 등 방지)
        return str(obj)

def perform_search(params):
    """실제 SerpAPI 호출 수행 (재시도 로직 포함)"""
    [REDACTED_SECRET])
    if not [REDACTED_SECRET]"Error: SERPAPI_API_KEY not found.", file=sys.stderr)
        sys.exit(1)
    
    params["api_key"] = api_key
    
    max_retries = 2
    for attempt in range(max_retries + 1):
        try:
            results = serpapi.search(**params)
            # SerpResults 객체의 핵심 데이터만 추출 (직렬화 오류 원인 차단)
            if hasattr(results, 'data'):
                return clean_dict(results.data)
            return clean_dict(results)
        except Exception as e:
            if attempt < max_retries:
                continue
            return {"error": f"Search failed after {max_retries} retries: {str(e)}"}

def format_output(results, format_type, compact_formatter):
    """포맷에 따른 최종 출력 제어"""
    if "error" in results:
        return f"❌ 오류 발생: {results['error']}"

    if format_type == "json" or format_type == "full":
        return json.dumps(results, indent=2, ensure_ascii=False)
    
    return compact_formatter(results)


path: requirements.txt
content: serpapi


path: scripts/naver_booking.py
content: #!/usr/bin/env python3
"""
Naver Booking/Local Search Script
네이버 예약 및 장소 정보를 조회합니다.
"""

import sys
import os
import argparse
# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    
    # 1. Booking Results (Specialized)
    bookings = results.get("booking_results", [])
    if bookings:
        output.append(f"🎫 네이버 예약 상품 ({len(bookings)}개)")
        output.append("=" * 30)
        for i, res in enumerate(bookings[:5], 1):
            price = f" | 💰 {res.get('price')}" if res.get('price') else ""
            output.append(f"{i}. {res.get('title')}{price}")
            if res.get('link'):
                output.append(f"   🔗 {res.get('link')}")
            output.append("")

    # 2. Local Results (Places)
    local = results.get("local_results", [])
    if local:
        if output: output.append("-" * 30 + "\n")
        output.append(f"📍 네이버 장소 결과 ({len(local)}개)")
        output.append("=" * 30)
        for i, res in enumerate(local[:10], 1):
            title = res.get('title')
            category = res.get('type')
            address = res.get('address')
            
            output.append(f"{i}. {title} ({category})")
            if address:
                output.append(f"   🏠 {address}")
            if res.get('link'):
                output.append(f"   🔗 {res.get('link')}")
            output.append("")

    if not output:
        return "예약 또는 장소 관련 정보를 찾을 수 없습니다. (일반 검색어에 정보를 포함하여 다시 시도해 보세요.)"
    
    return "\n".join(output)

def main():
    parser = argparse.ArgumentParser(description="Naver Booking/Local Search")
    parser.add_argument("query", help="검색어 (예: 강남역 맛집, 서울 호텔 예약)")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    query = args.query
    # '예약' 키워드가 없으면 보강하여 결과 유도 (단, nexearch 특성을 고려)
    if "예약" not in query and "맛집" not in query and "카페" not in query:
        query = f"{args.query} 예약"

    params = {
        "engine": "naver",
        "query": query,
        "where": "nexearch" # 네이버 로컬/예약 정보는 통합검색(nexearch) 결과에 포함됨
    }
    
    results = perform_search(params)
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


path: scripts/naver_images.py
content: #!/usr/bin/env python3
import sys
import os
import argparse

# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    images = results.get("images_results")
    if images:
        output.append(f"🖼️ 네이버 이미지 검색 결과 ({len(images)}개)")
        output.append("=" * 30)
        for i, res in enumerate(images[:20], 1): # Images can show more
            output.append(f"{i}. {res.get('title', '이미지')}")
            output.append(f"   📷 출처: {res.get('source', '알 수 없음')}")
            output.append(f"   🔗 {res.get('link')}")
            if res.get('thumbnail'):
                output.append(f"   🖼️ {res.get('thumbnail')}")
            output.append("")
    
    return "\n".join(output) if output else "이미지 검색 결과가 없습니다."

def main():
    parser = argparse.ArgumentParser(description="Naver Image Search")
    parser.add_argument("query", help="Search query")
    parser.add_argument("-n", "--num", type=int, default=10, help="Number of results (max 100)")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    params = {
        "engine": "naver",
        "query": args.query,
        "where": "image",
        "num": min(args.num, 100)
    }
    
    results = perform_search(params)
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


path: scripts/naver_news.py
content: #!/usr/bin/env python3
import sys
import os
import argparse

# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    news = results.get("news_results")
    if news:
        output.append(f"📰 네이버 뉴스 검색 결과 ({len(news)}개)")
        output.append("=" * 30)
        for i, res in enumerate(news[:10], 1):
            news_info = res.get('news_info', {})
            source = news_info.get('press_name', res.get('source', '알 수 없음'))
            date = news_info.get('news_date', res.get('date', ''))
            
            date_info = f" | {date}" if date else ""
            output.append(f"{i}. {res.get('title')}")
            output.append(f"   📰 {source}{date_info}")
            output.append(f"   🔗 {res.get('link')}")
            output.append("")
    
    return "\n".join(output) if output else "뉴스 검색 결과가 없습니다."

def convert_time_to_tbs(time_str):
    """시간 옵션을 SerpAPI tbs 파라미터로 변환"""
    # 네이버 뉴스 엔진은 tbs 파라미터를 지원하지 않음
    # 기본 검색으로 폴백하고 검색어에 "최신" 키워드 추가
    # 또는 Google 엔진의 tbs를 사용할 수 있음
    time_map = {
        "all": "all",
        "1h": "qdr:h",
        "1d": "qdr:d",  # 최근 24시간
        "1w": "qdr:w",
        "1m": "qdr:m"
    }
    return time_map.get(time_str, "all")

def main():
    parser = argparse.ArgumentParser(description="Naver News Search")
    parser.add_argument("query", help="Search query")
    parser.add_argument("-p", "--page", type=int, default=1, help="Page number")
    parser.add_argument("-s", "--sort", default="0", choices=["0", "1", "2"], 
                        help="Sort: 0-Relevance, 1-Latest, 2-Oldest")
    parser.add_argument("-t", "--time", default="all", help="Period (all, 1h, 1d, 1w... or date range)")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    # 시간 파라미터 변환 (tbs 파라미터 형식으로)
    time_param = convert_time_to_tbs(args.time)
    
    # 네이버 뉴스는 period 파라미터를 지원하지 않을 수 있음
    # tbs 파라미터를 사용하여 시간 필터 적용
    params = {
        "engine": "naver",
        "query": args.query,
        "where": "news",
        "page": args.page,
        "sort": args.sort,
        "period": args.time,  # period는 유지 (네이버 호환성)
        "tbs": time_param  # Google 엔진 스타일의 tbs 파라미터
    }
    
    # SerpAPI Naver News 특이사항: sort=1(최신)이 가끔 공백을 반환하면 dd로 대체 시도 고려
    # 최적화: 검색어가 단어 하나이고 --time 옵션 사용 시 "뉴스"를 미리 추가
    if len(args.query.split()) == 1 and args.time != "all":
        params["query"] = f"{args.query} 뉴스"
    
    results = perform_search(params)
    
    # 네이버 뉴스에서 결과가 없을 때: 검색어 보강 로직 (단어만 검색 시)
    if "error" in results and ("Naver hasn't returned any results" in results["error"] or 
                                     "Search failed" in results.get("error", "")):
        # 검색어가 단어 하나인 경우 "뉴스" 추가하여 재시도
        if len(args.query.split()) == 1 and args.query.endswith("뉴스") == False:
            params["query"] = f"{args.query} 뉴스"
            results = perform_search(params)
    
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


path: scripts/naver_search.py
content: #!/usr/bin/env python3
"""
Naver Search Aggregator (Integrated Wrapper)
모든 카테고리(웹, 뉴스, 쇼핑, 이미지, 비디오)의 결과를 취합하여 한 번에 보여줍니다.
"""

import sys
import os
import argparse
import subprocess
import json

def run_script(script_name, query, extra_args=None):
    """개별 스크립트를 실행하여 JSON 결과를 반환합니다."""
    script_path = os.path.join(os.path.dirname(__file__), script_name)
    cmd = ["python3", script_path, query, "--format", "json"]
    if extra_args:
        cmd.extend(extra_args)
    
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return json.loads(result.stdout)
    except Exception:
        return None

def main():
    parser = argparse.ArgumentParser(description="Naver Search Aggregator")
    parser.add_argument("query", help="검색어")
    parser.add_argument("-e", "--engine", default="naver",
                        choices=["naver", "naver_news", "naver_shopping", "naver_images", "naver_videos", "naver_booking"],
                        help="검색 엔진 (default: naver)")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"],
                        help="출력 포맷")
    
    # 하위 스크립트용 인자들 (라우팅 시 사용)
    parser.add_argument("-p", "--page", type=int, default=1)
    parser.add_argument("-n", "--num", type=int)
    parser.add_argument("-s", "--sort")
    parser.add_argument("-t", "--time")

    args, unknown = parser.parse_known_args()

    # 1. 개별 엔진 검색 (Routing)
    if args.engine != "naver":
        script_map = {
            "naver_news": ("naver_news.py", ["-p", "-s", "-t"]),
            "naver_shopping": ("naver_shopping.py", ["-p", "-s"]),
            "naver_images": ("naver_images.py", ["-n"]),
            "naver_videos": ("naver_video.py", ["-p"]),
            "naver_booking": ("naver_booking.py", []),
        }
        target_script, supported = script_map.get(args.engine, ("naver_web.py", []))
        cmd = ["python3", os.path.join(os.path.dirname(__file__), target_script), args.query, "--format", args.format]
        if "-p" in supported: cmd.extend(["--page", str(args.page)])
        if "-n" in supported and args.num: cmd.extend(["--num", str(args.num)])
        if "-s" in supported and args.sort: cmd.extend(["--sort", args.sort])
        if "-t" in supported and args.time: cmd.extend(["--time", args.time])
        subprocess.run(cmd)
        return

    # 2. 통합 검색 (Aggregating)
    if args.format == "json":
        # 모든 결과를 취합하여 하나의 JSON으로 반환
        combined = {
            "web": run_script("naver_web.py", args.query),
            "news": run_script("naver_news.py", args.query),
            "shopping": run_script("naver_shopping.py", args.query),
            "images": run_script("naver_images.py", args.query),
            "videos": run_script("naver_video.py", args.query)
        }
        print(json.dumps(combined, indent=2, ensure_ascii=False))
        return

    # Compact/Full 포맷 - 요약 출력
    print(f"\n🔍 '{args.query}'에 대한 네이버 통합 검색 결과\n" + "="*50)
    
    # 뉴스 (최신순)
    news = run_script("naver_news.py", args.query, ["--sort", "1"])
    if news and news.get("news_results"):
        print("\n📰 최신 뉴스")
        for res in news["news_results"][:3]:
            news_info = res.get('news_info', {})
            source = news_info.get('press_name', res.get('source', ''))
            date = news_info.get('news_date', res.get('date', ''))
            info = f" ({source} | {date})" if source or date else ""
            print(f"  • {res.get('title')}{info}")
    
    # 쇼핑
    shopping = run_script("naver_shopping.py", args.query)
    if shopping and shopping.get("shopping_results"):
        print("\n🛒 쇼핑 트렌드")
        for res in shopping["shopping_results"][:3]:
            price = f"[{res.get('price')}]" if res.get('price') else ""
            print(f"  • {res.get('title')} {price}")

    # 이미지
    images = run_script("naver_images.py", args.query, ["--num", "3"])
    if images and images.get("images_results"):
        print("\n🖼️ 이미지")
        titles = [res.get('title', '이미지') for res in images["images_results"][:3]]
        print(f"  • {', '.join(titles)}")

    # 비디오
    videos = run_script("naver_video.py", args.query)
    if videos and videos.get("video_results"):
        print("\n🎥 비디오")
        for res in videos["video_results"][:2]:
            print(f"  • {res.get('title')} ({res.get('source', '')})")

    # 웹 결과 요약
    web = run_script("naver_web.py", args.query)
    if web and web.get("organic_results"):
        print("\n📊 웹 검색 결과 (추천)")
        for res in web["organic_results"][:3]:
            print(f"  • {res.get('title')} - {res.get('link')}")

    print("\n" + "="*50 + "\n💡 상세 정보는 -e [엔진명] 옵션으로 확인하세요. (ex: -e naver_news)")

if __name__ == "__main__":
    main()


path: scripts/naver_shopping.py
content: #!/usr/bin/env python3
import sys
import os
import argparse

# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    # naver_shopping 엔진은 shopping_results 필드 사용
    shopping = results.get("shopping_results", [])
    if shopping:
        output.append(f"🛒 네이버 쇼핑 검색 결과 ({len(shopping)}개)")
        output.append("=" * 30)
        for i, res in enumerate(shopping[:10], 1):
            price = f" | 💰 {res.get('price')}" if res.get('price') else ""
            rating = f" | ⭐ {res.get('rating')}" if res.get('rating') else ""
            reviews = f"({res.get('reviews')})" if res.get('reviews') else ""
            # naver_shopping 엔진에서는 source 필드가 판매처명인 경우가 많음
            stores = f" | 🏪 {res.get('source', res.get('stores', ''))}" if (res.get('source') or res.get('stores')) else ""
            
            output.append(f"{i}. {res.get('title')}")
            output.append(f"   💵 정보: {price}{rating}{reviews}{stores}")
            
            if "additional" in res:
                specs = ", ".join([f"{s.get('name', s.get('title', ''))}: {s.get('value', '')}" for s in res['additional'][:3]])
                if specs:
                    output.append(f"   🔹 스펙: {specs}")
            
            output.append(f"   🔗 {res.get('link')}")
            output.append("")
    
    return "\n".join(output) if output else "쇼핑 검색 결과가 없습니다."

def main():
    parser = argparse.ArgumentParser(description="Naver Shopping Search (Specialized)")
    parser.add_argument("query", help="Search query")
    parser.add_argument("-p", "--page", type=int, default=1, help="Page number")
    parser.add_argument("-s", "--sort", help="Sort options (예: price_asc, price_dsc, rel)")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    # naver 엔진의 nexearch 사용 (가장 안정적)
    params = {
        "engine": "naver",
        "query": args.query,
        "where": "nexearch",
        "page": args.page
    }
    if args.sort:
        params["sort_by"] = args.sort
        
    results = perform_search(params)
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


path: scripts/naver_video.py
content: #!/usr/bin/env python3
import sys
import os
import argparse

# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    videos = results.get("video_results")
    if videos:
        output.append(f"🎥 네이버 비디오 검색 결과 ({len(videos)}개)")
        output.append("=" * 30)
        for i, res in enumerate(videos[:10], 1):
            video_info = res.get('video_info', {})
            source = res.get('source', '알 수 없음')
            duration = res.get('duration', '')
            
            duration_info = f" | {duration}" if duration else ""
            output.append(f"{i}. {res.get('title')}")
            output.append(f"   📺 {source}{duration_info}")
            output.append(f"   🔗 {res.get('link')}")
            output.append("")
    
    return "\n".join(output) if output else "비디오 검색 결과가 없습니다."

def main():
    parser = argparse.ArgumentParser(description="Naver Video Search")
    parser.add_argument("query", help="Search query")
    parser.add_argument("-p", "--page", type=int, default=1, help="Page number")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    params = {
        "engine": "naver",
        "query": args.query,
        "where": "video",
        "page": args.page
    }
    
    results = perform_search(params)
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


path: scripts/naver_web.py
content: #!/usr/bin/env python3
import sys
import os
import argparse

# Add lib directory to path
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
from naver_base import perform_search, format_output

def compact_formatter(results):
    output = []
    
    # AI Overview
    if "ai_overview" in results:
        output.append("🤖 AI 요약")
        output.append("=" * 30)
        output.append(results['ai_overview'].get('text', '내용 없음'))
        output.append("-" * 30 + "\n")

    # Ads
    ads = results.get("ads_results", results.get("ads"))
    if ads:
        output.append(f"📢 광고 ({len(ads)}개)")
        for ad in ads[:3]:
            output.append(f"· {ad.get('title')}")
            output.append(f"  🔗 {ad.get('link')}")
        output.append("")

    # Web/Organic results
    organic = results.get("web_results", results.get("organic_results"))
    if organic:
        output.append(f"📊 웹 검색 결과 ({len(organic)}개)")
        for i, res in enumerate(organic[:10], 1):
            output.append(f"{i}. {res.get('title')}")
            output.append(f"   🔗 {res.get('link')}")
            if res.get('snippet'):
                output.append(f"   📝 {res.get('snippet')}")
        output.append("")

    # Related queries
    related_list = results.get("related_queries", results.get("related_results", []))
    if related_list:
        related = ", ".join([r.get('query', '') for r in related_list[:8]])
        if related:
            output.append(f"🔍 관련 검색어: {related}")

    return "\n".join(output) if output else "검색 결과가 없습니다."

def main():
    parser = argparse.ArgumentParser(description="Naver Web/Integrated Search")
    parser.add_argument("query", help="Search query")
    parser.add_argument("-p", "--page", type=int, default=1, help="Page number")
    parser.add_argument("-f", "--format", default="compact", choices=["compact", "full", "json"])
    args = parser.parse_args()

    params = {
        "engine": "naver",
        "query": args.query,
        "where": "nexearch",
        "page": args.page
    }
    
    results = perform_search(params)
    print(format_output(results, args.format, compact_formatter))

if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)