엔드포인트는 죽는다 — X·Threads·Google Trends 수집을 유지하는 규율 3가지
무료 수집 경로는 자산이 아니다. 규율이 자산이다
2026년 4월, 내 사이드 프로젝트 trend-scout(매일 여러 플랫폼의 트렌드를 한 번에 모으는 오픈소스)가 X에서 쓰던 Nitter mirror 3개가 같은 날 HTTP 000과 403으로 떨어졌다. Q2 들어 Nitter 생태계는 사실상 죽었다.
그때 내가 배운 건 “Nitter 대체 경로 3개” 자체가 아니었다. 엔드포인트는 언제든 죽는다는 전제 위에서 어떻게 다음 경로를 찾느냐는 규율이었다. 경로 자체는 플랫폼 정책에 따라 언제든 막히지만, 규율은 플랫폼이 바뀌어도 남는다.
이 글에선 내가 2026년 4월에 다시 세운 수집 경로 3개(X Syndication, Jina Reader, Google Trends RSS)를 각각 어떤 규율로 찾았는지 기록한다. 경로가 막힌 뒤에 새 경로를 찾으려는 독자가 이 세 규율을 패턴으로 복제할 수 있게 썼다.
YES 3번 공감
- YES, Nitter mirror 리스트를 주기적으로 갱신하다 어느 날 3개가 전부 HTTP 000으로 떨어지는 걸 본 적이 있다.
- YES, 개인 사이드 프로젝트에 X API Basic $100/월이 어색한 시점이 있다.
- YES, “playwright 쓰면 된다”는 조언이 실제론 로그인 벽과 TLS 지문 체크에 막힌 적이 있다.
실측 먼저 — 7 플랫폼 566건
| 플랫폼 | 수집량 | 경로 |
|---|---|---|
| Naver | 226 | 공식 검색 API |
| 200 | 인증 RSS | |
| Threads | 71 | Jina Reader 경유 |
| HackerNews | 30 | 공식 API |
| GitHub Trending | 24 | HTML 파싱 |
| Google Trends | 15 | RSS 피드 |
| X Syndication | 0 | 테스트 반복으로 rate-limit 소진 |
X가 0건으로 찍힌 이유는 경로가 막혀서가 아니라 개발 중 동일 IP로 반복 호출해 per-IP 차단에 걸렸기 때문이다. 정상 범위는 20~30건.
규율 1. 공식 embed 경로를 찾아라
적용 사례: X (Twitter)
Nitter는 비공식 미러였다. 비공식이 막히면 다음은 공식이다. Twitter가 자사 위젯용으로 운영하는 embed 경로는 아직 무인증으로 열려있다.
https://syndication.twitter.com/srv/timeline-profile/screen-name/{handle}
응답은 HTML이지만 <script id="__NEXT_DATA__"> 안에 타임라인 JSON이 그대로 들어있다.
import re, json, httpx
NEXT_DATA_RE = re.compile(r'__NEXT_DATA__.*?>(.*?)</script>', re.DOTALL)
resp = httpx.get(
f"https://syndication.twitter.com/srv/timeline-profile/screen-name/{handle}",
headers={"User-Agent": "Mozilla/5.0 ..."},
)
data = json.loads(NEXT_DATA_RE.search(resp.text).group(1))
tweets = [
e["content"]["tweet"]
for e in data["props"]["pageProps"]["timeline"]["entries"]
if e.get("type") == "tweet"
]
규율이 감당해야 할 비용
- 계정 간 30초 간격: 1초 간격은 뒷 계정들이 전부 429.
- 첫 429 발견 시 즉시 포기: 계속 돌려도 rate-limit 회복 안 되고 실패 로그만 쌓인다.
실측 근거로 trend-scout에 ACCOUNT_SLEEP_SEC = 30과 첫 429 break를 넣었다. 이 두 줄이 없으면 경로가 열려있어도 수집량은 0이다.
Syndication이 막히면
Twitter가 embed 경로를 닫는 날이 오면 공식 oEmbed API(publish.twitter.com/oembed)로 HTML 스니펫만 받는 경로, 그다음은 유료 X API Basic의 무료 tier 샘플 권장. 어느 쪽도 타임라인 전체를 주지 않으니 수집량은 감소한다.
규율 2. 서버측 렌더 서비스를 중간에 끼워라
적용 사례: Threads
Threads는 완전 SPA다. httpx + BeautifulSoup으로는 DOM 0개다. 직접 Puppeteer를 띄우면 인프라 비용이 생기는데, 서버측 렌더링을 대행하는 무료 서비스가 있다.
Jina Reader(r.jina.ai)는 URL 앞에 prefix만 붙이면 Puppeteer가 서버측에서 렌더링한 뒤 Markdown으로 돌려준다.
https://r.jina.ai/https://www.threads.net/search?q=Claude
- 무료 tier 500 RPM
- 응답이 Markdown이라 permalink, 본문, engagement 숫자를 정규식으로 분리 가능
- 실측: 3개 키워드(“Claude”, “ChatGPT”, “AI”) 71개 포스트 수집
규율이 감당해야 할 비용
- 간헐적 빈 응답 대응: Jina가 간혹 렌더 실패. 재시도 2회 + 4초 백오프.
- engagement 숫자 정규화: “1.7K”, “1.2K”, “2만”, “1천” 접미사가 섞인다. K/M/만/천을 전부 변환하는 함수 한 개.
정규화 처리를 빼먹으면 숫자 추출은 되는데 “1.7K”가 1.7로, “2만”이 2로 집계돼 합계가 실제의 1/100~1/1000이 된다. 경로는 살아있지만 데이터는 못 쓰는 상태다.
Jina가 막히면
다음 순위는 ScrapingBee·Firecrawl 유료 tier 1일 체험 → 그다음은 내 인프라에 Puppeteer 컨테이너 직접 올리기. 체험 tier로 우선 경로 검증하고, 수집량이 늘면 내 Puppeteer로 옮긴다.
규율 3. Deprecated 라이브러리 대신 공식 RSS를 찾아라
적용 사례: Google Trends
pytrends trending_searches(pn='south_korea')는 어느 시점부터 404다. Google이 해당 엔드포인트를 조용히 deprecated했다. 라이브러리는 여전히 PyPI에 올라있는데 핵심 함수만 죽은 상태다.
이런 경우 해당 플랫폼의 RSS/Atom 피드를 먼저 찾아본다. Google Trends는 공식 RSS가 있다.
https://trends.google.com/trending/rss?geo=KR
- 인증 불필요, ISO country code 교체로 지역 변경
feedparser한 줄로 파싱interest_over_time같은 다른 함수는 pytrends에서 여전히 작동 (전체 교체 아님, 부분 교체)
규율이 감당해야 할 비용
라이브러리 의존을 한 번 끊어봤으면 된다. RSS가 JSON API보다 촌스러워 보여도 수명은 더 길다.
RSS도 막히면
Wayback Machine에서 과거 RSS URL 히스토리를 뒤져 현재 살아있는 공식 대체 피드를 찾거나, 공식 DataFeed API가 있는지 플랫폼 개발자 포털을 검색한다. 공식 경로는 “문서화되지 않은 것처럼 보여도” RSS/Atom/JSON Feed 중 하나는 남아있는 경우가 많다.
세 규율의 공통 전제 — 정책 변화는 LLM이 못 따라온다
위 세 경로는 모두 2023~2024년 이후 외부 환경이 바뀌면서 등장한 우회다. X 비로그인 스크래핑 차단, Threads SPA 전환, pytrends 엔드포인트 deprecated. LLM 훈련 데이터가 끊긴 지점 이후 벌어진 변화다.
실제로 LLM에게 “X 수집기 만들어”라고 시키면 playwright 기반 Chrome 자동화 코드를 준다. 실행하면 0건이다. 이 현상을 구체 실측한 경험은 Kimi K2.6 판정 보류 글에 따로 정리했다. 교훈 한 줄만 옮기면, 외부 플랫폼 수집 코드를 LLM에 맡길 때는 최신 우회 경로를 정리한 레퍼런스를 프롬프트에 먼저 주입해야 한다.
나는 insane-search Claude Code 플러그인의 twitter.md, jina.md, naver.md 파일에서 이번 세 경로를 찾았다. 이 레퍼런스 없이는 LLM이 위 세 경로를 조합해주지 못한다.
결론 — 이 글 수명은 독자가 직접 확인한다
- 규율 1: 공식 embed → X Syndication
- 규율 2: 서버측 렌더 대행 → Jina Reader
- 규율 3: Deprecated API → 공식 RSS
이 조합으로 trend-scout는 월 $0으로 하루 500~600건을 수집한다. 유지 비용은 월 2~4시간 수준이다 (엔드포인트 헬스체크 주 1회 + 파서 패치 월 1~2회). 유료 API로 넘어갈 때가 오긴 할 거다 — 엔드포인트 3개 모두 동시에 막히거나, 무료 유지에 드는 시간이 월 10시간을 넘어서는 날.
이 글을 며칠/몇 달 뒤에 읽는다면 경로부터 확인해라.
# X Syndication
curl -s -o /dev/null -w "%{http_code}\n" \
-H "User-Agent: Mozilla/5.0" \
"https://syndication.twitter.com/srv/timeline-profile/screen-name/OpenAI"
# Jina Reader (Threads)
curl -s -o /dev/null -w "%{http_code}\n" \
"https://r.jina.ai/https://www.threads.net/search?q=Claude"
# Google Trends RSS
curl -s -o /dev/null -w "%{http_code}\n" \
"https://trends.google.com/trending/rss?geo=KR"
셋 다 200이면 본문 경로 그대로 유효. 하나라도 다른 코드면 위 세 규율로 대체 경로를 다시 찾아야 한다. 이 글에 담은 3개가 끝이 아니다. 다음에 막힐 엔드포인트도 같은 패턴(공식 embed → 서버측 렌더 대행 → 공식 RSS·아카이브)의 변형으로 찾으면 된다. 엔드포인트는 죽는다는 전제 위에서 살아남는 수집기만이 오래 산다.
관련 글
- Kimi K2.6 써보고 판정을 보류했다 — 첫날 생긴 6가지 의문
- Claude Opus 4.7 말투, 나는 4.6으로 내렸다
- ChatGPT 쓰는 걸 창피해하는 해외 — 한국 블로그와 결의 차이 관찰
태그: #Nitter #XSyndication #JinaReader #GoogleTrends #크롤링 #trendscout #오픈소스
댓글
댓글 쓰기