유료 검색/스크래핑 API 없이 로컬 에이전트에 웹 접근 권한 부여하기: SearXNG + Scrapling
Giving a local agent web access without paid search/scrape APIs: SearXNG + Scrapling
핵심 요약
유료 API 없이 SearXNG와 Scrapling을 활용해 로컬 에이전트가 직접 웹 검색과 페이지 추출을 수행하도록 구축하는 방법입니다.
- 웹 검색 — SearXNG를 활용해 자체 호스팅 검색 엔진 구축
- 페이지 추출 — Scrapling과 Trafilatura로 웹페이지를 마크다운으로 변환
- 비용 절감 — 유료 API 키 없이 로컬 환경에서 모든 과정 제어
- 성능 고려 — 스텔스 모드 활용 및 브라우저 기반 추출의 한계점 파악
Tavily, Serper, Firecrawl 같은 거 안 쓰고 로컬 우선 에이전트한테 웹 검색 기능 붙여보고 싶었음.
이번 에이전트 작업하면서 유료 API 키는 절대 안 쓰고, 내가 직접 관리하는 검색 서비스에, 직접 돌릴 수 있는 페이지 추출기만 쓰기로 함.
결국 web_search랑 web_extract라는 도구 두 개로 정리됨. 별거 없음. 그냥 괜찮은 오픈소스 도구들 잘 엮은 거임.
1. 검색 -> SearXNG
SearXNG는 직접 호스팅 가능한 메타 검색 엔진임. 도커로 돌리고 에이전트가 이놈의 JSON 엔드포인트 찌르게 만들었음.
검색 호출은 대충 이런 식임:
text GET {SEARXNG_URL}/search?q=<query>&format=json&pageno=1
그다음 결과값 잘라서 {title, url, description} 형태로 정규화함.
description은 그냥 SearXNG 스니펫임. 페이지 본문 아님.
설정은 대충 이거면 됨:
text SEARXNG_URL=http://localhost:8080
주의할 점:
- SearXNG
settings.yml의search.formats에json추가해야 함. - 공개된 SearXNG 인스턴스는 프로그램으로 돌리기엔 보통 별로임.
- SearXNG는 검색만 됨. 에이전트가 페이지 내용을 읽어야 하면 추출 도구 써야 함.
2. 추출 -> Scrapling + Trafilatura
검색 스니펫만으론 부족함. 에이전트가 실제 페이지를 읽어야 하니까.
web_extract는 Scrapling을 쓰는데, 두 가지 경로로 나눔:
- 빠른 경로:
Fetcher.get(url, impersonate="chrome"). 브라우저 안 씀. 일반적인 페이지는 이걸로 충분함. - 스텔스 경로: 빠른 경로에서 내용이 안 나오거나, 차단당하거나, 봇 방지 화면 뜨면 헤드리스 브라우저 돌림:
StealthyFetcher.fetch(
url,
headless=True,
solve_cloudflare=True,
block_webrtc=True,
hide_canvas=True,
)
스텔스 경로는 시도하는 거지 무조건 뚫린다는 보장은 없음. 그래도 캡차나 클라우드플레어 벽 나오면 차단됨/부분 추출됨으로 표시함.
HTML 가져오면 Trafilatura로 링크랑 표 포함된 마크다운으로 변환함. 모델 입장에선 생 HTML보다 마크다운이 훨씬 읽기 편함. Trafilatura가 제대로 못 긁어오는 페이지를 대비해서 텍스트만 추출하는 예비 기능도 넣어둠.
그 외 중요한 것들:

