티스토리 뷰

카테고리 없음

티스토리 자동 발행, 확인창 하나에..전체가 멈췄다?(브라우저 자동화·세션 판정)

ststst1255 2026. 9. 3. 04:15

목차


    티스토리는 몇 해 전 오픈 API를 닫았습니다. 그래서 이 블로그를 무인으로 발행하는 파이프라인은 API 호출 대신, 사람이 브라우저로 로그인하고 글을 쓰고 발행 버튼을 누르는 순서를 그대로 흉내 내는 방식을 택했습니다.
    결론부터 적으면 이렇습니다. 이 방식에서 진짜 위험한 지점은 자동화 자체가 아니라 "판정 기준을 뭘로 잡느냐"였습니다.

    티스토리 브라우저 자동화_1

    쿠키 날짜를 안 믿기로 한 이유

    세션이 살아 있는지 확인하는 도구(check_session.py)를 처음 만들 때 가장 먼저 버린 방법이 있습니다. 쿠키에 적힌 만료일자를 읽는 방법입니다.
    날짜만 보면 간단하지만, 실제로 서버가 그 쿠키를 여전히 받아주는지는 또 다른 문제였습니다.

    그래서 이 도구는 저장해둔 쿠키로 관리자 페이지(/manage/posts/)를 직접 GET 요청하고, 응답이 최종적으로 어느 주소에 떨어지는지만 봅니다. 로그인 페이지(auth/login)로 튕기면 죽은 것이고, 아니면 산 것입니다.

    도구 판정 방식과 결과
    check_session.py 쿠키로 관리 페이지 GET → 최종 URL에 로그인 페이지가 섞여 있는지로만 판정. ALIVE(exit 0)·EXPIRED(exit 1)·UNKNOWN(exit 2)
    pending_upload.py 10분 주기로 세션 상태 확인 → 살아 있으면 대기 원고 저장·발행까지 자동 진행, 죽어 있으면 6시간마다 재로그인 요청. 같은 원고 재시도는 최대 2회
    publish_draft.py 발행 클릭 뒤 RSS에서 공개 주소를 찾고, 쿠키 없는 GET으로 HTTP 200이 뜨는지 재검증. 실패하면 공개 전환을 한 번 더 시도

    출처 : tools/check_session.py

    # 판정은 최종 URL이 로그인 페이지인지로만 한다. 302 자체는 만료 신호가 아니다
    # — 관리 페이지끼리의 정상 리다이렉트(/manage/posts/ → 같은 주소)를 만료로
    # 오판해 살아 있는 세션을 EXPIRED로 보고한 전례가 있다(2026-08-12).
    if "auth/login" in r.url:
        print(f"EXPIRED — 로그인 페이지로 리다이렉트: {r.url[:120]}")
        return 1

    이 주석 자체가 사고의 흔적입니다. 302 자체를 만료 신호로 잘못 취급했던 적이 실제로 있었고, 그 이후로 판정 기준을 "최종 URL"로 한 번 더 좁혔습니다.

    세션이 죽으면 사람이, 살아나면 다시 자동으로

    세션 판정이 EXPIRED로 나오는 순간, 이 파이프라인은 스스로 로그인하지 않습니다. 카카오 로그인은 사람만 할 수 있기 때문입니다.
    대신 데스크탑에서 기존 크롬 로그인 세션을 다시 가져와 전달하면, pending_upload.py가 10분마다 도는 크론으로 자동 저장을 재시도합니다.

    이 도구가 10분마다 관리 페이지를 건드리는 것 자체가 하나의 설계입니다. 계속 쓰는 브라우저와 똑같은 활동 신호를 서버에 남겨, 사람이 매번 다시 로그인하지 않고도 세션을 오래 끌고 가는 것이 목적입니다.

    출처 : tools/pending_upload.py

    이 워커가 10분마다 관리 페이지를 건드리는 것 자체가 '계속 쓰는 브라우저'와
    같은 활동 신호가 되고, 서버가 회전시킨 쿠키 값은 여기서 파일에 반영해야
    다음 요청도 산다. 대표 크롬이 몇 달씩 로그인이 유지되는 것과 같은 원리 —
    한 번 세션을 받으면 사람 재로그인 없이 오래 끌고 가는 게 목적이다(2026-07-27 대표 지시).

    같은 원고를 무한정 재시도하지도 않습니다. 저장이 계속 실패하면 2회에서 멈추고, 그때부터는 자동 재시도 대신 사람에게 알림이 갑니다. 무한 재시도가 오히려 사고를 숨긴다는 걸 이미 겪었기 때문입니다.

    확인창 하나가 파이프라인 전체를 멈춘다

    티스토리 브라우저 자동화_2

    사람이 브라우저로 티스토리 에디터를 쓸 때는 아무렇지 않게 넘기는 순간들이 있습니다. 모드를 바꾸면 뜨는 확인창, 이어서 쓸지 묻는 팝업 같은 것들입니다.
    브라우저 자동화는 이런 순간을 사람처럼 눈으로 보고 클릭하는 게 아니라, 코드로 미리 정해둔 규칙대로 처리해야 합니다. 규칙을 안 정해두면 그 자리에서 그대로 멈춥니다.

    2026년 7월, 에디터를 기본모드에서 HTML 모드로 바꾸는 순간 브라우저 기본 confirm 창이 뜨면서 페이지 전체가 멈추는 함정을 실제로 겪었습니다. page.on("dialog") 핸들러를 달지 않으면 자동화는 그 화면 앞에서 영원히 대기합니다.

    그런데 핸들러를 달았다고 끝난 게 아니었습니다. 다음 날, 뜨는 확인창을 전부 무조건 수락하도록 짜둔 게 다른 사고를 냈습니다. 새 글을 쓰려고 들어갔을 때 뜨는 "이어서 작성" 확인창까지 수락해버려서, 직전에 쓰던 초안이 그대로 로드된 채 새 원고가 그 위에 저장됐습니다.
    그날 3편을 연속으로 올렸는데 1편만 살아남았습니다.

    지금 코드에는 이 사고 이후 고친 조건문이 그대로 남아 있습니다.

    출처 : tools/publish_draft.py

    page.on("dialog", lambda d: d.dismiss() if "이어서" in d.message else d.accept())

    "이어서"라는 글자가 있는 창만 취소하고, 나머지는 수락합니다. 확인창을 하나로 뭉뚱그려 다루면 안 된다는 걸 원고 하나를 잃고 나서 배운 셈입니다.

    같은 계열의 '조용한 실패'는 이번이 처음이 아니었습니다. 검수를 백그라운드로 띄웠다가 상위 세션이 먼저 끝나버려 검수·발행이 통째로 누락됐던 검수 서브에이전트 백그라운드 사고도, 크론이 겹친 포트를 문 채 신호 없이 멈췄던 포트 충돌 자동화 사고도 같은 성격이었습니다. 에러 로그가 안 남는다고 해서 아무 일도 없었던 게 아니라는 점이 세 사고에서 반복됩니다.

    발행됐다는 말도 곧이곧대로 믿지 않는다

    발행 버튼을 눌렀다는 사실과, 그 글이 실제로 남들 눈에 보인다는 사실은 다른 문제입니다.
    티스토리는 마지막에 쓴 공개 설정을 기억하는데, 무인 저장으로 만든 초안은 비공개로 잡혀 있는 경우가 있었습니다. 2026년 7월 27일, 실제로 한 편이 비공개 상태로 발행된 적이 있습니다.

    그래서 발행 직전에 공개 라디오 버튼을 코드로 강제 클릭하고, 발행 직후에는 로그인하지 않은 상태로 그 주소를 다시 GET해서 HTTP 200이 뜨는지 확인합니다.

    출처 : tools/publish_draft.py

    def verify_public(url: str) -> tuple[bool, str]:
        """쿠키 없이 열리는지로 공개 여부를 판정한다 — 로그인한 화면은 증거가 못 된다."""

    로그인한 화면에서는 비공개 글도 멀쩡하게 보입니다. 그래서 이 검증은 일부러 쿠키를 빼고 요청합니다. 그날 이후 이 검증이 실패하면 자동으로 공개 전환을 한 번 더 시도하도록 고쳤습니다.

    주소를 찾는 과정에서도 함정이 하나 더 있었습니다. RSS 피드의 제목이 특수문자를 이중으로 이스케이프해 내보내는 경우가 있어서, 실제로는 발행에 성공했는데도 "주소 확인 실패"로 잘못 보고한 적이 있습니다. 지금은 특수문자를 걷어내고 글자만 남겨 비교하는 정규화 과정을 거칩니다.

    같은 구조를 확인하고 싶다면

    1. 공식 API가 없는 서비스를 자동화할 때는, 판정 기준을 간접 신호(만료일·상태 코드)가 아니라 최종 결과(리다이렉트 주소·응답 본문)로 잡습니다. 간접 신호는 한 번은 맞아도 언젠가 어긋납니다.

    2. 브라우저가 띄우는 확인창은 종류별로 다르게 처리합니다. 뜨는 창을 전부 수락하거나 전부 취소하는 방식은 편해 보이지만, 문구가 다른 창을 구분 못 해 다른 사고로 이어집니다.

    3. "성공했다"는 로그와 "실제로 남들에게 보인다"는 사실을 따로 검증합니다. 로그인한 화면은 증거가 되지 못합니다.

    4. 재시도 상한을 둡니다. 같은 작업을 무한정 다시 시도하게 두면, 실패가 반복되고 있다는 신호 자체가 묻힙니다.

    FAQ

    Q1. 공식 API 없이 브라우저 자동화만으로 글을 발행해도 안전한가요?
    사람이 하는 절차를 그대로 재현하는 방식이라 서비스 약관상 문제 되는 조작(글쓰기·발행)만 하는 한 특별한 우회는 아닙니다. 다만 판정 기준을 잘못 잡으면 세션 오판이나 비공개 발행 같은 사고로 이어질 수 있어, 그 지점을 코드로 최대한 좁혀둔 상태입니다.

    Q2. 세션이 만료되면 완전히 사람 손이 필요한가요?
    로그인 자체는 사람만 할 수 있습니다(카카오 로그인). 다만 데스크탑에 있는 기존 크롬 로그인 세션을 다시 가져오는 것으로 충분해서, 처음부터 새로 로그인할 필요는 없습니다. 그 뒤 자동 저장·발행은 다시 무인으로 돌아갑니다.

    Q3. 확인창(confirm) 처리는 왜 이렇게 까다롭나요?
    브라우저가 띄우는 확인창은 종류가 여러 개인데, 문구를 구분하지 않고 전부 같은 방식(수락 또는 취소)으로 처리하면 의도와 다른 동작이 실행됩니다. 실제로 문구를 구분하지 않아서 직전 초안이 덮어써진 사고가 있었습니다.