lifecarelog
개발일지

트위터 API가 막혔을 때 Bluesky로 우회한 기록 — 1인 개발자의 n8n 마케팅 자동화

트위터 syndication API가 429로 막혀서 Bluesky AT Protocol 공개 API로 우회해, 인디 빌더 5명 158개 글의 engagement를 실측 수집한 기록이에요. n8n 마케팅 자동화와 Claude·Codex 분담 워크플로우도 함께 정리했어요.

9분 읽기AI 보조 작성

혼자 개발하고 혼자 마케팅까지 하다 보면, 콘텐츠를 매일 만드는 일이 가장 먼저 지칩니다. 그래서 저는 게시 자체는 n8n에 맡겨 두고, 정작 더 중요한 질문에 시간을 쓰기로 했어요. "잘하는 1인 개발자들은 어떤 글에 사람들이 반응할까?" 오늘은 그 답을 데이터로 보려다 트위터 API에 막히고, Bluesky로 우회한 하루를 그대로 기록해 둡니다.

왜 벤치마크를 자동으로 모으려 했을까

지금 마케팅 게시는 n8n 마케팅 자동화 파이프라인이 맡고 있어요. X·Threads·Instagram에 올리는 흐름은 돌아가는데, 정작 "무엇을 쓸지"의 감은 여전히 제 손에서 나옵니다. 감이 아니라 근거가 필요했어요.

그래서 인디 빌더 몇 명(Arvid Kahl, Tony Dinh 같은 차분한 빌더들)의 글과 반응을 자동으로 수집해서, 어떤 형태의 글이 실제로 대화를 만드는지 보기로 했습니다. 1인 개발자 SNS 자동화의 핵심은 게시 자동화가 아니라, 사실 이런 "입력 데이터 수집"이라는 걸 이번에 다시 느꼈어요.

트위터 syndication API가 429로 막혔어요

처음엔 트위터의 공개 syndication 타임라인을 긁으려 했습니다. 로그인 없이 프로필 타임라인 HTML을 주는 엔드포인트예요.

EP = "https://syndication.twitter.com/srv/timeline-profile/screen-name/{}"
# 응답 HTML 안의 __NEXT_DATA__ JSON에서 글 + engagement를 파싱

문제는 곧바로 나타났어요. HTTP 429 (Too Many Requests). 공용 IP에서 여러 계정을 연달아 호출하니 IP 단위 레이트리밋에 걸린 겁니다. 트위터 API 막혔을 때 흔히 쓰는 방법이 TLS 지문 위장이라, insane-search 엔진(curl_cffi로 Safari의 TLS 지문을 흉내 냄)에 지수 백오프를 붙여 재시도하게 만들었어요.

def collect(handle, max_attempts=4, base_backoff=30):
    for attempt in range(max_attempts):
        r = fetch(EP.format(handle), timeout=25)  # curl_cffi TLS 지문
        if r and r.ok and "__NEXT_DATA__" in (r.content or ""):
            tweets = parse_timeline(r.content)
            if tweets:
                return {"handle": handle, "count": len(tweets), "tweets": tweets}
        time.sleep(base_backoff * (attempt + 1))  # 30s, 60s, 90s...

여기서 한 가지 배웠어요. TLS 지문 위장은 "지문 기반 봇 차단"은 통과시켜도, 순수한 IP 레이트리밋은 TLS와 무관하다는 것. 백오프로 일부 윈도우는 통과했지만, 안정적인 1차 소스로 삼기엔 불안했습니다. 막힌 길을 억지로 뚫는 것보다, 더 열린 길을 찾기로 했어요.

Bluesky AT Protocol로 우회했어요

대안은 Bluesky API였습니다. Bluesky는 AT Protocol 기반이라 공개 엔드포인트(public.api.bsky.app)가 인증 없이 열려 있고, 레이트리밋도 상대적으로 관대해요. 무엇보다 좋아요·리포스트·답글·인용 같은 engagement가 전부 공개됩니다. 벤치마크엔 이게 딱이었어요.

API = "https://public.api.bsky.app/xrpc/"
 
def call(path, **p):
    url = API + path + "?" + urllib.parse.urlencode(p)
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    for attempt in range(3):
        try:
            with urllib.request.urlopen(req, timeout=20) as r:
                return json.load(r)
        except urllib.error.HTTPError as e:
            if e.code in (429, 502, 503) and attempt < 2:
                time.sleep(3 * (attempt + 1)); continue   # 짧은 백오프
            return {"_err": f"HTTP {e.code}"}

핸들이 바뀔 수 있어서 app.bsky.actor.searchActors로 이름을 검색해 핸들을 자동으로 찾고, app.bsky.feed.getAuthorFeedfilter="posts_no_replies"를 줘서 답글·리포스트를 뺀 원글만 모았어요. 하드코딩을 줄이려고요. 그리고 이 스크립트는 외부 라이브러리 없이 파이썬 표준 라이브러리(urllib)만으로 돌아갑니다. 1인 개발에선 의존성 하나가 곧 유지보수 부담이라, 표준 라이브러리로 끝낼 수 있으면 그게 제일 편하더라고요.

158개 글에서 본 것

결과적으로 Bluesky에서 5개 계정, 158개 글과 각 글의 engagement(좋아요·리포스트·답글)를 실측으로 수집했어요. 숫자를 자랑하려는 게 아니라, 패턴을 보려고 모은 데이터입니다. 거칠게 본 경향은 이랬어요.

  • 열린 질문형 글이 댓글(대화)을 가장 많이 만들었어요. 답하기 쉬운 한 줄 질문이 "여러분은 어떻게 하세요?"처럼 끝날 때 반응이 컸습니다.
  • 수익 자랑보다 차분한 빌더의 톤이 반응이 높았어요. "월 얼마 벌었다"보다, 막힌 지점과 그걸 어떻게 넘었는지를 담담히 적은 글이 더 오래 읽혔습니다.
  • 검증된 수치는 크게 외치기보다 작게, 맥락으로 끼워 넣을 때 신뢰가 갔어요.

세 가지 다 제가 평소 쓰고 싶던 톤과 같아서, 오히려 안심이 됐습니다. 과장해서 크게 떠드는 글은 짧게 튀고, 솔직하게 기록하는 글이 길게 붙잡는다는 거요.

Claude와 Codex를 나눠 쓰는 1인 개발 워크플로우

이 작업도 AI를 한 명처럼 쓰지 않았어요. 역할을 나눴습니다.

  • Claude = 손(빠른 구현). 스크립트 초안, 파싱 로직, UI 같은 걸 빠르게 만들어요.
  • Codex = 눈(리뷰). 보안·예외처리·검증 관점에서 적대적으로 되짚어 줍니다. HTTP 200이 곧 "데이터 있음"은 아니니까, 산출 JSON을 직접 열어 수치를 확인하는 게이트도 여기서 챙겼어요.

중요한 건, 무엇을 만들지는 제가 먼저 정한다는 점이에요. AT Protocol을 1차 소스로 바꾸는 판단, 백오프 간격, "원글만 모은다" 같은 결정은 사람이 합니다. AI는 그 결정을 빠르게 구현하고 되짚어 줄 뿐이에요. 이 경계를 지키니 자동화가 폭주하지 않고, 제 손에 남았습니다.

마무리

오늘은 "막힌 길(429)을 억지로 뚫기보다, 열린 길(Bluesky)로 돌아가는 게 빠르다"를 다시 배운 하루였어요. 그리고 그 과정 자체가 그대로 콘텐츠가 됐고요.

저는 생활 속 작은 문제를 작은 도구로 푸는 1인 AI 회사 라이프케어로그를 혼자 만들고 있어요. 이렇게 막히고 우회한 기록을 계속 남기려 합니다.

여러분은 외부 API가 레이트리밋으로 막혔을 때, 끝까지 뚫는 편인가요, 아니면 다른 소스로 갈아타는 편인가요? 어떻게 푸시는지 궁금해요.

#n8n#마케팅자동화#bluesky#api#1인개발#devlog

라이프케어로그 서비스가 궁금하신가요?

AI 기반 건강·일정·재활 관리 앱을 직접 써보세요.

서비스 살펴보기

관련 글

댓글

아직 댓글이 없어요. 첫 댓글을 남겨주세요.