| name | insane-search |
| description | Adaptive access for blocked websites โ tries every method until one works. Use when ordinary fetch returns 402/403/blocked, or when accessing X/Twitter, Reddit, YouTube, GitHub, Mastodon, Medium, Substack, Stack Overflow, Threads, Naver, Coupang, LinkedIn, or any platform with WAF/bot protection. Leverages yt-dlp (1,858 media sites), Jina Reader, public APIs (HN, Bluesky, arXiv), and a generic WAF-profile-driven fetch chain (curl_cffi TLS impersonation, mobile URL transforms, Playwright real-Chrome) with auto dependency install. Korean triggers โ ํธ์ํฐ/X ๋ชป ์ด์ด, ๋ ๋ง ์ ์ฝํ, ์ ํ๋ธ ์๋ง ๋ฝ์์ค, ๊นํ ๊ฒ์, ์ฌ์ดํธ ์ฐจ๋จ๋จ, ์ค๋ ๋ ์ ์ด๋ ค, ๋ง์คํ ๋, ๋ฏธ๋์, ์๋ธ์คํ, ์คํ์ค๋ฒํ๋ก์ฐ, ๋ค์ด๋ฒ ๋ธ๋ก๊ทธ, ๋์์ธ์ฌ์ด๋, ์ํจ์ฝ๋ฆฌ์, ์์ฆIT, ๊ธฑ๋ด์ค, ํด๋ฆฌ์, ์ฟ ํก, ๋งํฌ๋์ธ, ๋น๊ทผ๋ง์ผ. English triggers โ twitter access, reddit blocked, youtube subtitles, github search, arxiv papers, threads, mastodon, medium, substack, stackoverflow, naver blog, dcinside, fmkorea, coupang, linkedin, yozm, wishket. Do NOT trigger for simple web searches that web.search_query can handle directly.
|
Insane Search for Codex
URL ์ ๊ทผ์ด ์ฐจ๋จ๋ ๋, ์ฌ์ดํธ ๋ฌด๊ดํ ๋์ฒด ์ ๊ทผ ์ ๋ต์ ์๋ ์ ํํ๋ค.
์ด ์คํฌ์ ์ธํฐ๋ทฐ ๋์์ด ์๋๋ค โ ์ฌ์ฉ์์๊ฒ ๊ฑฐ์ ๋ฌป์ง ์๊ณ ๋ฐ๋ก ์คํํ๋ค. ์ง์ง ์ ํ์ง๊ฐ
๋ถ๊ฐํผํ ๋๋ง shared/questioning-policy.md ยงA ๋ฒํธํ ๋ธ๋ก์ ์ฐ๋, ์ค๋น๋ ์ฌ์ฉ์๋ฅผ ๊ณผ๋ํ๊ฒ
๋ถ๋ค์ง ์๋๋ค(ยง2c). ํ์์ ์ฐจ๋จ ๊ฐ์ง โ engine ์คํ โ trace ์ง๋จ์ ๊ฒฐ์ ๋ก ์ ํ๋ฆ๋ง ๋๋ค.
ํ๋ค์ค ๊ท์น (์ด์์คํดํธ์๊ฒ ๊ฐ์ ๋๋ ์ง์นจ)
์ด ๊ท์น์ ์ด์์คํดํธ๊ฐ ์ฆํฅ ํ๋จ์ผ๋ก ์๋๊ฐ์ง ๋ชปํ๊ฒ ํ๊ธฐ ์ํ ๊ณ ์๋ค. ์๋ฐ ์ "200์์ break โ
๋์ฒด ๊ฒฝ๋ก ๋ฏธ์๋ โ Playwright ๋ฏธ์ค์น๋ผ ํฌ๊ธฐ" ์์ ์คํ์ด ์ฌํ๋๋ค.
R1 โ ์ผ๋ฐ ์น URL ์ฐจ๋จ/403/402 ๊ฐ์ง ์:
- ๊ธฐ๋ณธ
web fetch, ์ฆํฅ curl, ์๋ ํค๋ ์กฐํฉ ์๋ ๊ธ์ง
- ์ฆ์ engine ๋ํผ๋ฅผ ์คํ:
bash scripts/run_engine.sh "<URL>" [--selector "<CSS>"] [--device auto|desktop|mobile] [--trace]
(๋ํผ๋ ์คํฌ ๋๋ ํ ๋ฆฌ์์ python3 -m engine์ ํธ์ถํ๋ค. ์ง์ ํธ์ถ๋ ๋์ผ:
python3 -m engine "<URL>" ...)
- ์ข
๋ฃ์ฝ๋ 0(ok) ๋๋ 1(fail) ๋ฐ์ ๋ค ํ๋จ. trace๋ฅผ ๋จผ์ ์ฝ๊ณ ์ฌ์๋ ๊ฒฐ์ .
- ์คํจ ์์๋ง
--trace --json์ผ๋ก ์ฌํธ์ถํด์ ์์ธ ์ง๋จ ํ --device ๋๋ user_hint ์กฐ์ .
R2 โ ์ฒซ 200์์ ํ์ถ ๊ธ์ง: HTTP 200์ ๊ฒ์ฌ ์์ ์กฐ๊ฑด์ด์ง ์ฑ๊ณต์ด ์๋๋ค. validate()์
4-๊ณ์ธต ๊ฒ์ฆ์ ํต๊ณผํด์ผ ์ฑ๊ณต ์ ์ธ. CLI๋ ์ด๋ฏธ ๊ฐ์ ํ๋ค.
R3 โ ํธํฅ ๊ธ์ง: engine/**, waf_profiles.yaml์ ํน์ ์ฌ์ดํธ ๋๋ฉ์ธยท์
๋ ํฐยท๋ธ๋๋๋ช
ํ๋์ฝ๋ฉ
๊ธ์ง. python3 engine/bias_check.py๊ฐ CI ๊ฒ์ดํธ. ์์ธํ ๊ท์น์ No-Site-Name Rule ์น์
.
R4 โ ํํธ๋ ๋ฐํ์์๋ง: ์ฌ์ดํธ ๊ณ ์ ์ ๋ณด(์ฑ๊ณต ์
๋ ํฐ, ์ฐ์ Referer)๋ CLI ์ธ์ ๋๋
user_hint๋ก๋ง ์ ๋ฌ, ์ ์ฅ์์ ๊ณ ์ ๊ธ์ง.
R5 โ Phase 0 ๊ณต์ API ์ฐ์ : X/Reddit/YouTube/HN/arXiv ๋ฑ ๊ณต์ ๊ณต๊ฐ ์๋ํฌ์ธํธ๊ฐ ์๋
ํ๋ซํผ์ Phase 0 ํ
์ด๋ธ์ ๋จผ์ ํ์ธํ๊ณ ํด๋น API๋ฅผ ์ด๋ค. ์ด๊ฑด ํธํฅ์ด ์๋๋ผ ํฉ์๋ ์ ๊ทผ ๊ฒฝ๋ก.
R6 โ ์คํจ ์ ์ธ์ "์ ์ ์๋" ํ์๋ง (engine์ด ๊ฐ์ ํ๋ ์คํจ ๊ฒ์ดํธ): engine์ ์คํจ ์ ok=false์ ํจ๊ป ์์ง ์ ํด๋ณธ ๊ฒฝ๋ก(untried_routes)์ must_invoke_playwright_mcp ํ๋๊ทธ๋ฅผ ๋ฐํํ๋ค. ์๋๊ฐ ๋ชจ๋ ์ถฉ์กฑ๋๊ธฐ ์ ์ "๋ซ์ ์ ์์" ๊ฒฐ๋ก ๊ธ์ง:
grid_exhausted=true โ false๋ฉด fetch(max_attempts=None)(=CLI ๊ธฐ๋ณธ, exhaustive)๋ก ๋๊น์ง ์ฌํธ์ถ.
untried_routes๊ฐ ๋น ๋ฐฐ์ด โ ๋น์ด์์ง ์์ผ๋ฉด ๊ทธ ๊ฒฝ๋ก๋ค์ ๋จผ์ ์คํ.
must_invoke_playwright_mcp=false โ true๋ฉด ์ด์์คํดํธ๊ฐ ์ง์ ์ ์ฐฐ ๋ผ์ฐํธ(scripts/playwright_recon.js)๋ฅผ ๋๋ฆฐ ๋ค์๋ง ํต๊ณผ: Local Node + channel:'chrome'๋ก ๋์ ํ์ด์ง๋ฅผ ๋ก๋ โ ๋ฐ์ํ ๋คํธ์ํฌ ์์ฒญ์์ ๋ด๋ถ /apiยท/graphqlยท.json ์๋ํฌ์ธํธ ํ์ง โ ๊ทธ URL์ bash scripts/run_engine.sh(= python3 -m engine)๋ก ์ฌํธ์ถ(API๋ WAF๊ฐ ์์); ๋๋ ๋ ๋๋ HTML ํ์. (engine์ ๋ก์ปฌ Node Chrome๋ง ๋์ธ ์ ์์ผ๋ฏ๋ก, ์ ์ฐฐ์ ๊ตฌ์กฐ์ ์ผ๋ก ์ด์์คํดํธ์ ๋ชซ์ด๋ค โ Codex์๋ MCP Playwright๊ฐ ์์ผ๋ must_invoke_playwright_mcp๋ ๊ณง ๋ก์ปฌ ์ ์ฐฐ ์คํฌ๋ฆฝํธ ํธ์ถ ์ ํธ๋ก ์ฝ๋๋ค.)
stop_reason์ด auth_required/404/paywall ๋ฑ terminal์ผ ๋๋ง ์ ์งํ๊ฒ ์คํจ ์ธ์ โ engine์ด untried_routes๋ฅผ ๋น ์ฑ๋ก ๋๋ ค์ค๋ค. 429(rate-limit)๋ terminal ์๋ โ ๋ฐฑ์คํ ํ ์ฌ์๋/๋ค๋ฅธ TLS/์ ์ฐฐ๋ก ์ฌ์ ๊ทผ.
์์ง: engine์ give-up์ "๊ทธ๋งํด๋ ๋๋ค"๋ ํ๊ฐ๊ฐ ์๋๋ค. CLI๋ ์คํจ ์ โ NOT EXHAUSTED ๋ธ๋ก์ stderr๋ก ์ถ๋ ฅํ๋ค โ ๊ทธ๊ฒ ๋ณด์ด๋ฉด ์ 4๊ฐ๋ฅผ ๋๋ผ ๋๊น์ง ๋ฉ์ถ์ง ์๋๋ค.
๋จ, R7 ์กฐ๊ฑด(WAF ์กฐ๊ธฐ ๊ฐ์ง)์ด ์ฑ๋ฆฝํ๋ฉด engine ๊ฒฉ์๋ ๊ณ์ ๋๋, ์ด์์คํดํธ๊ฐ ๋ณ๋ ฌ๋ก ์ ์ฐฐ ๋ฃจํธ๋ฅผ ์๋ํ ์ ์๋ค. ๋น ๋ฅธ ์ชฝ์ด ์ด๊ธด๋ค.
R7 โ WAF ์กฐ๊ธฐ ๊ฐ์ง ์ API-first ๋ณํ ๋ถ๊ธฐ (๋ถ๊ธฐ ๊ฒฐ์ ์ ์๋์ด์ง๋ง ์ฌ์ฉ์๊ฐ ๊ฒฐ๊ณผ์์ ํ์ธ ๊ฐ๋ฅ
โ ์ด๋ค ์ ๊ทผ ๊ฒฝ๋ก๋ก ์ฑ๊ณต/์คํจํ๋์ง ๊ฒฐ๊ณผ metadata์ ๋ช
์):
๋ฐ๋ ์กฐ๊ฑด (AND):
- engine ์คํ ์ด๊ธฐ์ ์ฒซ 2~3ํ attempt๊ฐ ๋ชจ๋
verdict=challenge
profile_used๊ฐ akamai_bot_manager, cloudflare_turnstile, datadome_probable,
perimeterx_human, f5_big_ip, aws_waf ์ค ํ๋๋ก ํ์
- ์ฌ์ฉ์ ์์ฒญ์ด ๋ฆฌ์คํธ/์์ง/๋ฐ๋ณต ์๋ (์ฌ๋ฌ ํ์ด์ง, N๊ฐ ์ด์, "์ ๋ถ", "ํฌ๋กค๋ง", ํ์ด์ง๋ค์ด์
๋ฑ). ๋จ๊ฑด ๋ณธ๋ฌธ ์กฐํ๋ ํด๋น ์์.
์ธ ์กฐ๊ฑด ๋ชจ๋ ์ฐธ์ผ ๋ ์ด์์คํดํธ๋ ๋ณ๋ ฌ ๊ฒฝ๋ก๋ฅผ ์์ํ๋ค:
"๋ณ๋ ฌ"์ ์คํ ์๋ฏธ (Codex ๋๊ตฌ ํธ์ถ์ด ์์ฐจ์ด๋ฏ๋ก ๋ช
ํํ):
- engine์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๋์๋๋ค โ ๊ฒฉ์๋ ๊ทธ๋๋ก ๋๋ ๋ธ๋กํนํ์ง ์์
- ๊ทธ ์ฌ์ด foreground์์ Playwright ์ ์ฐฐ ๋ฃจํธ๋ฅผ ์งํ:
node scripts/playwright_recon.js <<'EOF' / {"url":"https://example.com","timeout":20000} / EOF
- engine์ด ๋จผ์ ์ฑ๊ณตํด๋ ์ข๊ณ , ์ ์ฐฐ๋ก ์ป์ API๊ฐ ๋จผ์ ์ฑ๊ณตํด๋ ์ข์. ๋น ๋ฅธ ์ชฝ ๊ฒฐ๊ณผ ์ฑํ
์ ์ฐฐ ๋ฃจํธ (scripts/playwright_recon.js):
- ๋์ ํ์ด์ง๋ฅผ ๋ก๋ (Local Node +
channel:'chrome')
- ํ์ด์ง๊ฐ ๋ฐ์์ํจ XHR/fetch ๋คํธ์ํฌ ์์ฒญ ๋ชฉ๋ก์ ์์ง,
/api/ยทgraphqlยท\.json
ํจํด์ผ๋ก ๋ด๋ถ ์๋ํฌ์ธํธ๋ฅผ ์๋ ํํฐํด stdout JSON์ผ๋ก ๋ฐํ
- ์๋ณ๋ JSON API URL์
bash scripts/run_engine.sh <API_URL>(๋๋ python3 -m engine <API_URL>)๋ก
์ฌํธ์ถ. ๋๋ถ๋ถ API ๋ ์ด์ด๋ ํ์ด์ง HTML๋ณด๋ค WAF ๋ณดํธ๊ฐ ์์ curl_cffi๋ก ๋ฐ๋ก ์์ง๋จ
- ์๋ต ์คํค๋ง ํ์
ํ pagination / query parameter ์กฐํฉํด ๋ฐ๋ณต ์์ง
์: SPA + WAF ์ฌ์ดํธ(์ผํ๋ชฐยท์ปค๋จธ์ค ๋ค์)๋ ๋ง์ผํ
ํ์ด์ง(HTML)๋ง WAF๋ก ์คํฌ์ํ๊ณ ๋ด๋ถ API๋
gateway ๋ ๋ฒจ ๊ธฐ๋ณธ ๋ฐฉ์ด๋ง ์ฐ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๋ค. HTML ๊ฒฉ์ ์ ์ ๋ญ๋น(50ํ ร 0.5s + Playwright fallback
40s โ 65์ด)๋ณด๋ค **์ ์ฐฐ 1ํ(5~10์ด) + API ์ฌํธ์ถ(0.5์ด)**๊ฐ ํจ์ฌ ๊ฒฝ์ ์ ์ด๊ณ ์ฑ๊ณต๋ฅ ๋์.
R7์ ์ฐ์ง ๋ง์์ผ ํ ๋: ๋จ์ผ ํ์ด์ง ๋ณธ๋ฌธ ์ฝ๊ธฐ๋ง ํ์ํ ๋จ๊ฑด ์กฐํ(๋ฌธ์ ํ๋, ๋ธ๋ก๊ทธ ํฌ์คํธ
ํ๋)๋ engine๋ง์ผ๋ก ์ถฉ๋ถํ๋ค โ ๋ฐ๋ ์กฐ๊ฑด #3์ด ์ด๋ฅผ ๋ฐฐ์ ํ๋ค.
R7 ํธํฅ ๋ฐฉ์ง: ๋ด๋ถ API URLยทํ๋ผ๋ฏธํฐ๋ engine/**์ ํ๋์ฝ๋ฉ ๊ธ์ง. ํ์ง๋ URL์ ๋ฐํ์
ํธ์ถ์๋ง ์ฐ๊ณ ์ ์ฅ์์ ๊ณ ์ ํ์ง ์๋๋ค.
์ด ์คํฌ์ ํต์ฌ ๋ถ๋ณ์:
- ๋จ์ผ ์ง์
์ : ์ผ๋ฐ ์น ํ์ด์ง๋ ํญ์
bash scripts/run_engine.sh <URL> (= python3 -m engine <URL>)
๋๋ from engine import fetch; fetch(...).
- ํธํฅ ๊ธ์ง:
engine/**, waf_profiles.yaml์ ํน์ ์ฌ์ดํธ ํ๋์ฝ๋ฉ ๊ธ์ง.
- ํํธ๋ ๋ฐํ์์๋ง: ์ฌ์ดํธ ๊ณ ์ ์ ๋ณด๋ CLI/
user_hint ๊ฒฝ์ .
์๋ ๋ถ๋ฅ (Phase 0 ์ง์
์ )
| ์ฌ์ฉ์ ์
๋ ฅ | ๊ฒฝ๋ก |
|---|
URL ์ ๊ณต (https://...) | โ Phase 0 ๊ฒ์ฌ ํ ์์ผ๋ฉด Phase 1 (generic fetch chain) |
ํธ๋ค ์ ๊ณต (@username) | โ Phase 0 syndication/API |
| ํค์๋๋ง ("X์์ AI ๊ฒ์") | โ web.search_query(site:{domain} {keyword}) ๋จผ์ โ URL ํ๋ณด ํ ์ฌ์ง์
|
ํ๊ตญ์ด ์ ๊ท ์ฝํ
์ธ ํ๊ณ: ๋ค์ด๋ฒ/๋ค์/ํ๊ตญ ์ปค๋ฎค๋ํฐ์ ํค์๋ ๊ฒ์์ web.search_query ๊ฒฝ์ ๊ฐ
์ ์ผํ๋ฉฐ, ์ ๊ท ์ฝํ
์ธ ์ธ๋ฑ์ฑ์ด ์ง์ฐ๋ ์ ์๋ค.
Phase 0 โ ํ๋ซํผ ๊ณต์ API ์ธ๋ฑ์ค
ํ๋ซํผ์ด ๊ณต์ ๊ณต๊ฐํ ์ ์ฉ API/CLI๋ง ์ฌ๊ธฐ์ ๋๋ค. ์ด๊ฑด ํธํฅ์ด ์๋๋ผ ํฉ์๋ ์๋ํฌ์ธํธ ์ฌ์ฉ์ด๋ค.
์์
/์ปค๋ฎค๋ํฐ ์ ์ฉ API
| ํ๋ซํผ | ๋ฐฉ๋ฒ | ์์ธ |
|---|
| X/Twitter | syndication (ํ์๋ผ์ธ) + tweet-result/oEmbed (๊ฐ๋ณ ํธ์) + ํค์๋ ๊ฒ์: web.search_query โ tweet-result | twitter.md |
| Reddit | Atom/RSS ํผ๋(.rss) โ ๋น์ธ์ฆ .json์ WAF ์ฐจ๋จ(403), scoreยท๋๊ธ์๋ OAuth | json-api.md |
| Bluesky | AT Protocol (public.api.bsky.app/xrpc/...) | public-api.md |
| Mastodon | ์ธ์คํด์ค๋ณ ๊ณต๊ฐ API | public-api.md |
| Hacker News | Firebase API + Algolia Search | json-api.md |
| Stack Overflow | SE API v2.3 | public-api.md |
| Lobste.rs / V2EX / dev.to | ๊ณต๊ฐ JSON API | json-api.md |
๋ฏธ๋์ด (CLI ๋๊ตฌ ํ์)
| ํ๋ซํผ | ๋ฐฉ๋ฒ | ์์ธ |
|---|
| YouTube/Vimeo/Twitch/TikTok/SoundCloud ๋ฑ 1,858๊ฐ | yt-dlp --dump-json | media.md |
ํ์ /๋ ์ง์คํธ๋ฆฌ
ํ๊ตญ ์ ์ฉ ๊ณต์ API
| ํ๋ซํผ | ๋ฐฉ๋ฒ | ์์ธ |
|---|
| ๋ค์ด๋ฒ ๊ฒ์ | search.naver.com (ํตํฉ/๋ธ๋ก๊ทธ/๋ด์คํญ) | naver.md |
| ๋ค์ด๋ฒ ๊ธ์ต ์์ธ | api.finance.naver.com/siseJson.naver (๋น๊ณต์ JSON) | naver.md |
๊ทธ ์ธ ๋ชจ๋ ์ฌ์ดํธ๋ Phase 1(generic fetch chain)์ด ์๋ ์ฒ๋ฆฌํ๋ค.
Phase 1 โ Generic Fetch Chain
๋จ์ผ ์ง์
์
CLI(๊ถ์ฅ):
bash scripts/run_engine.sh "https://example.com/path" --selector "article" --device auto --trace
Python API:
from engine import fetch
result = fetch(
"https://example.com/path",
success_selectors=["article", "[class*='product-card']"],
device_class="auto",
user_hint=None,
timeout=25,
)
if result.ok:
print(result.verdict)
html = result.content
else:
pass
๋ด๋ถ ๋จ๊ณ (๋๋ฒ๊น
์ฉ ๋
ธ์ถ)
fetch()๋ ๋จ์ผ API์ด์ง๋ง ๋ด๋ถ๋ phase๋ก ๋๋์ด ์๋ค. result.trace(๋๋ --trace)์์ ๊ฐ ์๋๋ฅผ
ํ์ธํ ์ ์๋ค.
probe โ curl_cffi + safari + self-referer๋ก ์ฒซ ์๋
validate โ 4-๊ณ์ธต ๊ฒ์ฆ (marker / size / cookie / success_selectors)
detect โ WAF ์ ํ ๊ฐ์ง ([(profile_id, confidence)] ๋ญํน)
plan โ ํ๋กํ์ผ์ tls_candidates ร url_transforms ร referer ๊ฒฉ์ ๊ตฌ์ฑ
execute โ ๊ฒฉ์ ์ ์ ์๋ (์ฒซ 200์์ ํ์ถํ์ง ์์)
fallback โ capability ํ๊ทธ ๊ธฐ๋ฐ Playwright ๋ผ์ฐํ
(์ ์ฐฐ ์คํฌ๋ฆฝํธ or local+chrome)
report โ FetchResult(ok, verdict, profile_used, trace, summary)
๊ฒ์ฆ ์์น
- HTTP 200์ ๊ฒ์ฌ ์์ ์กฐ๊ฑด์ด์ง ์ฑ๊ณต์ด ์๋๋ค.
- ์ฑ๊ณต ํ์ ์ 4-๊ณ์ธต AND:
- ์ฑ๋ฆฐ์ง ๋ง์ปค ์์ (
sec-if-cpt-container, Access Denied, Just a moment..., DataDome)
- ๋น์ ์ ํฌ๊ธฐ ์๋ (< 3KB ๋๋ WAF fingerprint ํฌ๊ธฐ)
- ์ฟ ํค ์ผ์ ์ํ ์ ์ (
_abck=~-1~ ์๋)
success_selectors ์ค ํ๋ ์ด์ ๋งค์นญ (caller ์ ๊ณต ์ โ strong_ok, ๋ฏธ์ ๊ณต ์ โ weak_ok)
๊ฒฉ์ ์ถ (profile์ด ์ฐ์ ์์ ์ถ์ฒ, ๊ฒฉ์๋ ์ ์ ์๋)
| ์ถ | ๊ฐ | ๋น๊ณ |
|---|
url_transforms | original, mobile_subdomain (www.โm.), am_prefix, drop_www | ์ฌ์ดํธ๋ช
์์, ๊ท์น๋ง |
tls_impersonate | safari, safari_ios, chrome99, chrome119, chrome131, chrome_android, firefox... | ํ๋กํ์ผ๋ณ avoid ๋ฆฌ์คํธ ์กด์ฌ |
referer_strategy | self_root, google_search, none | |
device_class:
"auto" (๊ธฐ๋ณธ) โ ํ๋กํ์ผ ์ ๋ต ๋ฐ๋ฆ
"desktop" โ TLS ๋ฐ์คํฌํฑ๋ง + mobile_subdomain ๋นํ์ฑ
"mobile" โ TLS ๋ชจ๋ฐ์ผ๋ง + mobile_subdomain ํ์ฑ
Playwright ํด๋ฐฑ (capability-matched)
engine/executor.py๊ฐ ํ๋กํ์ผ์ capabilities_needed๋ฅผ ์ฝ๊ณ ์คํ๊ธฐ๋ฅผ ์๋ ์ ํ:
| ํ๊ทธ | ์คํ๊ธฐ | ์ธ์ |
|---|
needs_real_tls_stack + needs_js_exec | engine/templates/playwright_real_chrome.js (๋ก์ปฌ Node) | Akamai Bot Manager ๋ฑ โ Chromium ๋ฒ๋ค TLS๋ ํ์ง๋จ |
needs_js_exec only | Playwright MCP / ํ์ฌ ์ด์์คํดํธ ์ธ์
์ ๋ธ๋ผ์ฐ์ ๋๊ตฌ | Cloudflare ๊ธฐ๋ณธ ๋ฐฉ์ด ๋ฑ |
needs_mobile_context (+ real_tls) | engine/templates/playwright_mobile_chrome.js | ๋ชจ๋ฐ์ผ ๋๋ฐ์ด์ค ์๋ฎฌ๋ ์ด์
ํ์ |
์์ธํ ์ ํ ๊ธฐ์ค: playwright.md.
Playwright (JS ์คํ) ํธ์ถ ๊ท์น
fetch_chain์ needs_js_exec only ์ผ์ด์ค๋ ํ์ฌ ์ด์์คํดํธ ์ธ์
์์ ๋ธ๋ผ์ฐ์ ๋๊ตฌ๋ฅผ ์ง์ ๊ตฌ๋
ํด์ผ ํ๋ค. subprocess ๊ฒฝ๋ก ์์. ์ฆ:
result.summary์ "Playwright MCP must be invoked from the active assistant session"์ด ํฌํจ๋๋ฉด
- ์ฌ์ฉ ๊ฐ๋ฅํ ๋ธ๋ผ์ฐ์ ๋๊ตฌ(navigate โ wait โ snapshot)๋ก ์ธ์
์ด ์ง์ ์ฒ๋ฆฌํ๋ค.
๋ธ๋ผ์ฐ์ ๋๊ตฌ๊ฐ ์๋ ํ๊ฒฝ์ด๋ฉด ํ๊ณ๋ฅผ ๋ณด๊ณ ํ๊ณ Local Node ํ
ํ๋ฆฟ
(
engine/templates/playwright_real_chrome.js) ๋๋ Jina/archive/ํ๋ซํผ API ๊ฒฝ๋ก๋ก ๋์ฒดํ๋ค.
Phase 2 โ ์๋ ๊ฐ์
(์ต์
)
Phase 1์ด ok=False๋ฅผ ๋ฐํํ๋ฉด ์ฌ์ฉ์ ํํธ๋ฅผ ๋ฐ์ ์ฌ์๋:
result = fetch(
url,
success_selectors=[...],
user_hint={"impersonate_first": "safari_ios", "referer_strategy": "none"},
)
ํํธ๋ ํ์ฌ ํธ์ถ 1ํ์๋ง ์ ์ฉ๋๋ฉฐ ์ ์ฅ๋์ง ์๋๋ค.
์์กด์ฑ ์๋ ์ค์น
์ต์ด ํธ์ถ ์ ํ์ ํจํค์ง๋ฅผ ํ์ธ/์ค์นํ๋ค. ์ ๊ฒ๋ง ํ๋ ค๋ฉด ์ธ์ ์์ด, ์ค์น๊น์ง ํ๋ ค๋ฉด --install.
curl_cffi๋ 0.15.0 ์ด์์ ์๊ตฌํ๋ค โ 0.15๋ถํฐ impersonate="chrome"์ด ์ต์ Chrome(146+) ์ง๋ฌธ์ผ๋ก
๊ฐฑ์ ๋๊ณ (0.14๋ chrome142์ ๊ณ ์ ), HTTP/3 ์ง๋ฌธ๊ณผ SSRF-safe redirect ๊ธฐ๋ณธ๊ฐ์ด ์ถ๊ฐ๋๋ค. ์๋ ์๋ ๊ฐ๋๋
๋ฏธ์ค์น๋ฟ ์๋๋ผ 0.15 ๋ฏธ๋ง์ด๋ฉด ์
๊ทธ๋ ์ด๋ํ๋ค:
bash scripts/bootstrap.sh
bash scripts/bootstrap.sh --install
python3 -c "import curl_cffi,bs4,yaml; v=curl_cffi.__version__.split('.'); assert (int(v[0]),int(v[1]))>=(0,15)" 2>/dev/null \
|| pip install -U "curl_cffi>=0.15.0" beautifulsoup4 pyyaml -q
Playwright ๋ก์ปฌ ๊ฒฝ๋ก ์ฌ์ฉ ์ Node๊ฐ ํ์:
npm i -g playwright playwright-extra puppeteer-extra-plugin-stealth
npx playwright install chrome
engine ์ฝ๋๋ฅผ ๊ฑด๋๋ ธ๋ค๋ฉด (CI ๊ฒ์ดํธ)
python3 engine/bias_check.py
bash scripts/smoke_test.sh
๋น ๋ฅธ ์ฐธ์กฐ โ Phase 0 ๋ช
๋ น์ด
๋จผ์ ์ด๊ฑธ ๊ธฐ์ตํ๋ผ: Reddit/X/YouTube๋ ์ด์ engine์ด ์๋ ์ฒ๋ฆฌํ๋ค.
bash scripts/run_engine.sh "<URL>" (= python3 -m engine "<URL>") ํ๋๋ฉด Phase 0 ๋ผ์ฐํฐ(engine/phase0.py)๊ฐ
๊ฒฉ์๋ณด๋ค ๋จผ์ ๊ณต์ ๊ฒฝ๋ก๋ฅผ ์๋ํ๋ค โ Redditโ.rss, X ํธ์โtweet-result/oEmbed, X ํ๋กํโsyndication, YouTubeโyt-dlp.
์๋ ์๋ ์ค๋ํซ์ ๋๋ฒ๊ทธ/์ฐธ์กฐ์ฉ์ด๋ฉฐ trace์ phase=phase0๋ก ๊ธฐ๋ก๋๋ค.
(์ค์ธก ์ฃผ์: Reddit .json+๋ชจ๋ฐ์ผUAยทsyndication-timeline์ ํํ 403/429๋ผ plain curl์ ์ ๋ขฐ ๋ถ๊ฐ โ engine์ด curl_cffi ์ง๋ฌธ์ผ๋ก ์ ๊ทผํ๋ค.)
bash scripts/run_engine.sh "<URL>"
curl -s "https://r.jina.ai/{URL}"
yt-dlp --dump-json "URL"
yt-dlp --write-sub --write-auto-sub --sub-lang "en,ko" --skip-download -o "/tmp/%(id)s" "URL"
python3 -c "from curl_cffi import requests as r; print(r.get('https://www.reddit.com/r/{sub}/.rss', impersonate='safari').text[:2000])"
python3 -c "from curl_cffi import requests as r; print(r.get('https://cdn.syndication.twimg.com/tweet-result?id={TWEET_ID}&token=a', impersonate='safari').text)"
curl -sL "https://syndication.twitter.com/srv/timeline-profile/screen-name/{handle}"
curl -sL "https://hacker-news.firebaseio.com/v0/topstories.json?limitToFirst=10&orderBy=%22%24key%22"
์ปค๋ฒ๋ฆฌ์ง ํ๊ท ์ ๊ฒ: python3 tests/coverage_battery.py โ ํ๋ซํผ๋ณ ์ ์ ๊ฒฝ๋ก pass/fail + ์ฉ์ ์์ ์๋ ์ ๋ฐ.
No-Site-Name Rule
engine/**, waf_profiles.yaml, engine/templates/** ํ์ผ์๋ ํน์ ์ฌ์ดํธ์
๋๋ฉ์ธ/URL/์
๋ ํฐ/๋ธ๋๋๋ช
์ ํ๋์ฝ๋ฉํ์ง ์๋๋ค.
๊ธ์ง
"coupang.com": {...} ๊ฐ์ ์ฌ์ดํธ๋ณ ๋ ์ง์คํธ๋ฆฌ ์ํธ๋ฆฌ
if "coupang" in url: ... ๊ฐ์ ๋๋ฉ์ธ ๋ถ๊ธฐ
- WAF ํ๋กํ์ผ
notes์ ํน์ ์ฌ์ดํธ ์ด๋ฆ์ด๋ ๊ฒฝํ์ byte ํฌ๊ธฐ ๋ฐ์
ํ์ฉ
SKILL.md / references/*.md์ ์ค๋ช
ํ
์คํธ์ ์ฌ์ดํธ ์ด๋ฆ ์์ (๋
์ ์ดํด์ฉ)
Phase 0 ๊ณต์ API ์ธ๋ฑ์ค (ํ๋ซํผ์ด ๊ณต์ ๊ณต๊ฐํ ์๋ํฌ์ธํธ)
observations/*.jsonl ๋ก๊ทธ (append-only ๊ด์ธก ๋ฐ์ดํฐ โ ์ฝ๋ ๊ฒฝ๋ก์ ์ํฅ ์์)
- ํธ์ถ์๊ฐ ์ ๊ณตํ๋
success_selectors, user_hint (ํ์ฌ ํธ์ถ์๋ง ์ ํจ)
๊ฒฝ๊ณ ์ฌ๋ก ํ๋จ ๊ธฐ์ค
"์ด ์ํธ๋ฆฌ๊ฐ ๋ค๋ฅธ ์ฌ์ดํธ์์๋ ๊ฐ์ WAF๋ฅผ ์ฐ๋ฉด ์ผ๋ฐ์ ์ผ๋ก ์ ํจํ๊ฐ?" โ YES๋ฉด waf_profiles.yaml,
NO๋ฉด runtime hint.
์ ์ฌ์ดํธ๊ฐ ์ ๋ซ๋ฆด ๋
- ๋จผ์
result.trace์์ ์ด๋ phase๊ฐ ์คํจํ๋์ง ํ์ธ
- ์ฌ์ฉ์์
user_hint๋ก 1ํ ์ฌ์๋
- ๋ฐ๋ณต ์ฑ๊ณต ํจํด์ด ๊ด์ธก๋๋ฉด
observations/์ ๋ก๊ทธ (์์ง ์๋ ๊ธฐ๋ก ์์ โ ์๋)
- 3ํ+ ๋ฐ๋ณต ํ์ธ๋๊ณ ๋์ผ WAF๋ฅผ ์ฐ๋ ๋ค๋ฅธ ์ฌ์ดํธ์๋ ์ ํจํ๋ฉด
waf_profiles.yaml ํด๋น
ํ๋กํ์ผ์ tls_impersonate_candidates / url_transform_order๋ฅผ ํ๋ (์ฌ์ดํธ๋ช
์ ๋ ๋ฃ์ง ์์)
- ์ฌ์ ํ ์ ๋๋ฉด ์ WAF ํ๋กํ์ผ ํ๋ณด ๊ฒํ (์: DataDome ์ธ๋ถํ, Kasada ๋ฑ)
๊ด๋ จ ๋ฌธ์ (references/) โ ์ธ์ ๋ฌด์์ ์ฝ์์ง
์ด ์น์
์ ์ฐธ์กฐ ํ์ผ ์ ํ ๊ฐ์ด๋๋ค. ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ์ด๋ค references/*.md๋ฅผ ์ด์ด์ผ ํ ์ง
๊ฒฐ์ ํ๋ ๊ธฐ์ค์ผ๋ก ์ด๋ค. ํ์ํ ๋๋ง ํด๋น ํ์ผ์ ์ฝ๊ณ , ์ ์ ์ ์ผ๋ก ์ ๋ถ ์ฝ์ง ์๋๋ค.
๋น ๋ฅธ ์์์ด ํ์ํ๋ฉด references/fallback.md๋ถํฐ ๋ณธ๋ค.
A. Engine ํ์ฅยท์ง๋จ (ํ๋ค์ค ๋ด๋ถ)
| ํ์ผ | ์ธ์ ์ฝ๋๊ฐ | ๋ฌด์์ ๋ค๋ฃจ๋๊ฐ |
|---|
tls-impersonate.md | curl_cffi ๊ฒฉ์๊ฐ ์ ๋ถ challenge/blocked๋ก ๋๋ ๋, ์ impersonate ํ๊ฒ์ waf_profiles.yaml์ ์ถ๊ฐํ ๋ | curl_cffi๋ก Safari/Chrome/Firefox TLS(JA3/JA4) ์ง๋ฌธ ๋ณต์ ํ๋ ๋ฐฉ๋ฒ, WAF(Akamai/Cloudflare/F5 ๋ฑ)๋ณ ์ต์ ํ๊ฒ ์กฐํฉ, ์ํผ์๋ค์ด์
ํ๊ฒ ๋ฒ์ ๋ชฉ๋ก, tls_impersonate_avoid์ ์ค์ฆ ๊ทผ๊ฑฐ |
playwright.md | engine์ด Playwright fallback์ผ๋ก ๋์ด๊ฐ๋๋ฐ MCP/Local Chrome ์ค ์ด๋๋ก ๊ฐ์ง ํ์ธ ํ์ํ ๋ | Approach 1 (๋ธ๋ผ์ฐ์ ๋๊ตฌ โ Cloudflare๊ธ ์ฑ๋ฆฐ์ง), Approach 2 (Local Node + channel:'chrome' + stealth โ Akamai Bot Manager๊ธ), ํ
ํ๋ฆฟ ํ๋ผ๋ฏธํฐ ๊ท๊ฒฉ |
fallback.md | verdict๊ฐ ์ ๋งคํ๊ฑฐ๋ Phase ์ ํ ํ์ด๋ฐ ๊ฒฐ์ ํ์ํ ๋ | engine์ Phase 0โ1โ2โ3 ์์ค์ปฌ๋ ์ด์
์์น, ์๋ต ์ฑ๊ณต/์คํจ ํ์ ๊ธฐ์ค ์ธ๋ถ, ๊ฐ Phase ์ข
๋ฃ ์กฐ๊ฑด |
metadata.md | ๋ณธ๋ฌธ ์ ์ฒด๋ฅผ ๋ชป ๊ฐ์ ธ์์ง๋ง ์ ๋ชฉยท์์ฝยท๊ฐ๊ฒฉยท์ ์ ๊ฐ์ ํต์ฌ๋ง์ด๋ผ๋ ํ์ํ ๋ | OGP ๋ฉํ ํ๊ทธ, JSON-LD (Schema.org), Twitter Card ํ์ฑ, ๊ตฌ์กฐํ ๋ฐ์ดํฐ ์ถ์ถ ํจํด |
B. ๊ฒฝ๋ ๋์ (engine ๋ง๊ณ ๋ค๋ฅธ ๋๊ตฌ๊ฐ ๋์ ์ํฉ)
| ํ์ผ | ์ธ์ ์ฝ๋๊ฐ | ๋ฌด์์ ๋ค๋ฃจ๋๊ฐ |
|---|
jina.md | WAF ์๋ ์ผ๋ฐ ์น(๋ธ๋ก๊ทธยท๋ด์คยทWiki)์ ๊นจ๋ํ ๋งํฌ๋ค์ด ์ถ์ถ ํ์ํ ๋ | r.jina.ai/URL ํ ์ค๋ก Puppeteer ๊ธฐ๋ฐ JS SPA ๋ ๋๋ง, ๋งํฌ๋ค์ด ๋ณํ, ๋ฌด๋ฃ 500 RPM, API ํค ๋ถํ์ |
cache-archive.md | ์๋ณธ ์ฌ์ดํธ๊ฐ ์ฐจ๋จ๋์ง๋ง ๊ณผ๊ฑฐ ์ค๋
์ท์ผ๋ก๋ผ๋ ์ ๊ทผ ํ์ํ ๋ | Wayback Machine CDX API, archive.today, AMP Cache (Google Cache๋ 2024-07 ์ข
๋ฃ๋จ) |
rss.md | ๋ด์คยท๋ธ๋ก๊ทธยท์ปค๋ฎค๋ํฐ์ ์๊ณ์ด ์
๋ฐ์ดํธ๋ฅผ ๊ตฌ์กฐํํด ๋ฐ๊ณ ์ถ์ ๋ | RSS/Atom ์๋ ๋ฐ๊ฒฌ, ํผ๋ ํ์ฑ, ์ธ์ฆ ๋ถํ์ โ ๊ฐ์ฅ ๊น๋ํ ์๊ณ์ด ๋ฐ์ดํฐ ์์ค |
C. ํ๋ซํผ๋ณ ๊ณต์/๊ณต๊ฐ API (Phase 0 ์ธ๋ฑ์ค์ ์ฐ๊ฒฐ)
| ํ์ผ | ์ธ์ ์ฝ๋๊ฐ | ๋ฌด์์ ๋ค๋ฃจ๋๊ฐ |
|---|
json-api.md | Reddit/Wikipedia/HN/npm/PyPI ๋ฑ URL ๋ณํ๋ง์ผ๋ก JSON/ํผ๋๋ฅผ ์ฃผ๋ ์ฌ์ดํธ | Reddit Atom/RSS(.rss) ๋์ฒด ๊ฒฝ๋ก + scoreยท๋๊ธ์ฉ OAuth(.json์ WAF ์ฐจ๋จ), HN Firebase, Algolia Search, Wikipedia REST, npm/PyPI Registry API |
public-api.md | Bluesky/Mastodon/arXiv/Stack Overflow/CrossRef/GitHub/OpenLibrary/Wayback ๊ณต์ API ์ฌ์ฉ ์ | ์ธ์ฆ ์์ด ์ฐ๋ ๊ณต์ ๊ณต๊ฐ REST/AT/Atom API ์๋ํฌ์ธํธ, ์์ฒญ ํ์, ๊ณตํต ํ๋ผ๋ฏธํฐ |
twitter.md | X/Twitter ์ ๊ทผ โ ํ๋กํ ํ์๋ผ์ธ, ํน์ ํธ์, ํค์๋ ๊ฒ์ | syndication.twitter.com ํ์๋ผ์ธ, tweet-result/oEmbed ๊ฐ๋ณ ํธ์, ๊ฒ์์ web.search_query๋ก URL ํ๋ณด ํ tweet-result |
naver.md | ๋ค์ด๋ฒ ๋ธ๋ก๊ทธยท๋ด์คยท์ฆ๊ถยท๊ฒ์ ์ ๊ทผ | ์๋น์ค๋ณ ๋์ฒด ์ ๊ทผ(๋ธ๋ก๊ทธ๋ m.blog.naver.com ๋ณํ, ์ฆ๊ถ์ ๋น๊ณต์ JSON, ๊ฒ์์ search.naver.com), ํ๊ธ ๊ฒ์ ์ฟผ๋ฆฌ ํจํด |
media.md | YouTube/Vimeo/Twitch/TikTok/SoundCloud ๋ฑ ๋ฏธ๋์ด ๋ฉํยท์๋งยท์ค๋์ค ํ์ ์ | yt-dlp --dump-json ๊ธฐ๋ฐ 1,858๊ฐ ์ฌ์ดํธ ์ปค๋ฒ, ์๋ง ๋ค์ด๋ก๋(--write-sub), ํฌ๋งท ์ ํ, ๋ผ์ด๋ธ/ํ์บ์คํธ |
D. Engine ์ฝ๋ ์ง์ ์ฝ์ ๋
| ํ์ผ | ์ธ์ ์ฝ๋๊ฐ |
|---|
engine/phase0.py | Phase 0 ๊ณต์-API ๋ผ์ฐํฐ (Reddit/X/YouTube ์๋ ๊ฒฝ๋ก). ํ๋ซํผยท๊ฒฝ๋ก ์ถ๊ฐ ์. bias_check ๋ฉด์ ํ์ผ(R5 sanctioned) |
engine/fetch_chain.py | ์ฒด์ธ ๋จ๊ณ ๋ก์งยทAttempt/FetchResult schemaยทuntried_routes/must_invoke_playwright_mcp ์คํจ๊ฒ์ดํธ |
engine/validators.py | 4-๊ณ์ธต ๊ฒ์ฆ ์ธ๋ถ (Verdict ๋ถ๋ฅ, ์ฑ๋ฆฐ์ง ๋ง์ปค ๋ชฉ๋ก) |
engine/waf_detector.py | WAF ๋ญํน ๊ฐ์ง ์๊ณ ๋ฆฌ์ฆ, _LAST_LOAD_ERROR ์ฒ๋ฆฌ |
engine/waf_profiles.yaml | ํ๋กํ์ผ๋ณ detectorsยทtls_candidatesยทcapabilities_needed |
engine/url_transforms.py | URL ๋ณํ ๊ท์น ์ถ๊ฐํ ๋ |
engine/executor.py | Playwright MCP vs local capability ๋งค์นญ ๋ก์ง |
engine/templates/*.js | Playwright ํ
ํ๋ฆฟ ํ๋ (warmup, reload, devices) |
engine/bias_check.py | ํธํฅ ๋ฆฐํฐ ๊ท์น โ brand denylist, URL_PATTERN, excluded dirs |
Port Notes (Codex)
- ๋จ์ผ ์ง์
์ ์
bash scripts/run_engine.sh <URL> (= python3 -m engine <URL>). engine์ WAF
๋ก์ง์ ์ฆํฅ์ผ๋ก ์ฌ๊ตฌํํ์ง ์๋๋ค.
- ์์กด์ฑ ์ ๊ฒ/์ค์น๋
scripts/bootstrap.sh [--install].
- R7 ์ ์ฐฐ์
scripts/playwright_recon.js (stdin JSON โ ๋ด๋ถ /api/ยทgraphqlยท.json ์์ฒญ ์ถ์ถ).
- Local Node ๋ธ๋ผ์ฐ์ ํด๋ฐฑ์ด ์์ผ๋ฉด ํ๊ณ๋ฅผ ๋ณด๊ณ ํ๊ณ generic engine / Jina / archive / ํ๋ซํผ API
๊ฒฝ๋ก๋ก ๊ณ์ ์งํํ๋ค.
- ์ฐ๋ฌธ๋ณด๋ค ๊ฒฐ์ ๋ก ์ ์ฆ๊ฑฐ(trace ์์ฝ)๋ฅผ ์ฐ์ ํ๋ค. engine ์คํจ ์ trace ์์ฝ + ์ฐจ์ ๊ฒฝ๋ก๋ฅผ ๋จผ์ ๋ณด์ธ๋ค.