본문 바로가기

Python 업무 자동화

티스토리 React 에디터에서 Selenium 자동 입력이 46자만 들어간 이유와 해결책

파이썬 기반 티스토리 자동 포스팅 봇을 개발하며 수많은 난관에 부딪혔지만, 가장 황당했던 에러를 꼽으라면 단연 "46자 컷오프(Cut-off) 현상"일 것입니다. 수천 자의 정성스러운 원고를 셀레늄(Selenium)에게 쥐여주고 자동 발행을 지시했는데, 정작 발행된 글에는 딱 46자만 적혀 있었습니다.

오늘은 이 기괴한 현상의 원인인 'React 에디터의 이벤트 처리 병목'과 이를 우회하기 위해 TinyMCE 코어에 직접 접근하여 문제를 해결한 깊이 있는 트러블슈팅 과정, 그리고 시스템 레벨의 재발 방지 체계 구축 경험을 공유합니다.

[왜 이 문제를 다루는가?] React 렌더링 지연으로 인해 텍스트가 유실되는 컷오프 현상을 마주하고, 이를 구조적으로 해결하려는 의지를 표현하기 위해 본 포스팅의 썸네일을 준비했습니다.

[이미지 해설] 모든 텍스트가 날아간 빈 화면 앞에서 망연자실한 파이선생의 모습입니다. 시스템 오류를 대하는 개발자의 당혹스러움을 상징적으로 보여줍니다.

악몽의 시작: 2,000자의 원고가 허공으로 증발하다

평소처럼 자동화 스크립트를 가동하고 커피를 내리고 왔을 때였습니다. 파이썬 터미널 로그에는 아무런 에러 없이 [성공] 메시지가 찍혀 있었지만, 티스토리 블로그에 접속해 보니 2,000자가 넘어야 할 본문이 첫 줄만 덜렁 적힌 채 끝이 나 있었습니다.

정확히 글자를 세어보니 46자였습니다. 처음에는 네트워크 지연이나 브라우저 멈춤 증상인 줄 알았으나, 여러 번 테스트를 반복해도 귀신같이 40~50자 내외에서 입력이 멈춰버렸습니다. 더 심각한 것은 셀레늄 봇이 에러를 뱉어내지 않고 남은 텍스트 입력을 "완료한 척" 넘어간다는 점이었습니다.

[컷오프를 뚫고 주입된 터미널 로그 증빙] 46자에서 입력이 끊기는 현상을 우회하여, 자바스크립트 백도어 인젝션(setContent)을 통해 5,900여 자의 본문을 단숨에 주입하고 길이를 검증한 실제 파이썬 터미널 로그입니다.

[이미지 해설] "TinyMCE setContent HTML 정상 주입 (길이: 5975자)" 메시지와 함께, 기존 46자 컷오프의 늪을 빠져나와 527번 포스트 업데이트를 성공적으로 마친 실제 safe_auto_post_master.py 실행 로그 화면입니다.

실제 에러 로그의 흔적과 치명적 결함

당시 백그라운드 모니터링 로그에는 다음과 같은 에러가 찍혀 있었습니다. 자동화 파이프라인의 마지막 단계인 사후 검증(Post-monitoring) 로직이 아니었다면 텅 빈 글이 그대로 대중에게 노출될 뻔했습니다.

[2026-06-19 06:28:32] [ERROR] [publish 실패] 티스토리 발행 후 본문 글자 수가 2000자 미만(46자)이 감지되었습니다!
[2026-06-19 06:28:32] [WARNING] ActionChains.send_keys 실행 중 브라우저 응답 지연 발생 추정.

이 문제는 단순히 글자가 잘리는 것을 넘어, 블로그 품질 지수를 하락시키고 봇의 신뢰도를 바닥으로 떨어뜨리는 치명적인 결함이었습니다.

범인은 누구인가? (Deep Dive 원인 분석)

보통 셀레늄으로 텍스트를 입력할 때는 send_keys() 함수나 ActionChains를 사용해 사람의 키보드 타이핑을 흉내 냅니다. 기존의 구형 에디터나 단순한 HTML <textarea> 환경에서는 이 방식이 완벽하게 통했습니다. 하지만 최근 업데이트된 티스토리의 신형 에디터는 React 기반의 복잡한 상태 관리 시스템(State Management)과 TinyMCE 에디터 엔진이 결합된 형태입니다.

1. React Synthetic Event와 Virtual DOM의 병목 현상

셀레늄이 초당 수천 자의 속도로 텍스트를 때려 넣으면, 브라우저의 이벤트 큐에 keydown, keypress, keyup 이벤트가 말 그대로 폭포수처럼 쏟아집니다. React는 브라우저의 기본 DOM 이벤트를 직접 사용하지 않고 자체적인 Synthetic Event(합성 이벤트) 래퍼를 씌워 처리합니다.

이 과정에서 텍스트가 입력될 때마다 React는 다음을 수행합니다: 1. 이벤트를 감지하고 내부 컴포넌트의 State(상태)를 업데이트 2. Virtual DOM(가상 돔)을 재생성 3. 실제 DOM과 비교(Reconciliation) 4. 변경된 부분만 실제 화면에 반영(Re-render)

하지만 셀레늄의 무자비한 기계적 입력 속도를 React의 렌더링 사이클이 물리적으로 쫓아가지 못한 것입니다. 결국 이벤트 큐가 꽉 차서 병목(Bottleneck)이 발생하고, React 상태 업데이트 로직이 뻗어버리면서 약 46자 지점에서 에디터가 입력을 거부하며 멈춰버리는 컷오프 현상이 발생했습니다.

2. 실패한 우회 접근법들

이 문제를 해결하기 위해 다양한 방법을 시도했지만 모두 실패했습니다.

  • 입력 속도 지연 (Throttling): time.sleep()을 사용하여 글자 하나를 칠 때마다 0.05초씩 강제로 쉬도록 만들었습니다. 하지만 2,000자 포스팅 하나를 발행하는 데 무려 100초가 넘게 걸렸습니다. 초고속 비동기 처리가 생명인 AI 자동화 랩의 취지가 퇴색되는 끔찍한 방법이었습니다.
  • ClipboardEvent (복사 & 붙여넣기): 클립보드에 텍스트를 통째로 넣고 Ctrl + V (또는 Cmd + V) 이벤트를 강제로 발생시켰습니다. 그러나 운영체제(OS) 환경이나 브라우저 보안 정책에 따라 클립보드 접근이 차단되기도 했고, 백그라운드(Headless) 모드에서는 성공과 실패를 오가는 극도로 불안정한 모습을 보였습니다.

완벽한 해결책: 프론트엔드 UI를 건너뛰고 Core API에 직접 주입하라

타이핑을 흉내 내는 UI 계층(View Layer)의 자동화는 한계가 명확했습니다. 해답은 React 이벤트 큐를 통하지 않고, 에디터의 심장부인 TinyMCE 코어 인스턴스에 직접 자바스크립트(JS)로 텍스트를 꽂아 넣는 방식에 있었습니다.

티스토리 신형 에디터는 내부적으로 텍스트 포매팅을 위해 전역 객체인 tinymce를 사용하고 있습니다. 브라우저 콘솔에서 tinymce.activeEditor를 호출하면 현재 활성화된 에디터 객체 API에 다이렉트로 접근할 수 있습니다.

[JS 주입 방식의 실제 구현 화면] UI 계층(ActionChains)을 우회하고, 브라우저의 전역 객체에 접근해 HTML 데이터를 직접 꽂아 넣는 실제 파이썬 코드를 시각화했습니다.

[이미지 해설] execute_script 함수를 통해 tinymce.activeEditor.setContent()를 호출하는 핵심 트러블슈팅 로직의 실제 코드 캡처본입니다. React 병목을 우회하는 백도어(Backdoor) 접근 방식의 명확한 구조를 보여줍니다.

실제 해결 코드 비교 (ActionChains vs JS Injection)

[기존 실패한 코드 - UI 계층 의존 방식]

# 기존 방식: React의 한계 속도에 부딪혀 46자에서 멈춤
editor_element = driver.find_element(By.CSS_SELECTOR, ".ProseMirror")
ActionChains(driver).click(editor_element).send_keys(final_html).perform()

[최종 성공한 코드 - API 직접 제어 방식] 파이썬 셀레늄 스크립트에서 execute_script()를 활용해 아래와 같이 HTML 본문을 통째로 주입하도록 로직을 전면 수정했습니다.

# 변경된 방식: 파이썬 셀레늄(Selenium) 코드 발췌
set_html_script = """
// 전역 객체인 tinymce가 존재하는지, 활성화된 에디터가 있는지 검사
if (typeof tinymce !== 'undefined' && tinymce.activeEditor) {
    // 1. TinyMCE 에디터 코어 API를 호출하여 HTML을 직접 세팅
    // 이 과정은 브라우저 키보드 이벤트를 발생시키지 않아 React 병목이 없음
    tinymce.activeEditor.setContent(arguments[0]);

    // 2. React 상태와의 동기화를 위해 변경 이벤트 강제 트리거
    // 에디터 내용이 바뀌었음을 React에 알려주어 임시저장 등이 정상 작동하게 함
    tinymce.triggerSave();
    return true;
} else {
    return false;
}
"""

# HTML로 변환된 최종 원고(final_html)를 자바스크립트의 arguments[0]으로 넘겨 실행
success = driver.execute_script(set_html_script, final_html)

if success:
    print("TinyMCE setContent 방식을 통한 본문 100% 주입 완료!")
else:
    print("에디터 코어를 찾을 수 없습니다. 페이지 렌더링 지연 확인 필요.")

이 코드를 적용한 결과는 놀라웠습니다. 글자 수에 상관없이 단 0.01초 만에 3,000자가 넘는 글, 복잡한 테이블(Table), 수많은 이미지 태그들이 완벽하고 깔끔하게 에디터에 안착했습니다. 46자에서 멈추던 악몽이 10줄 남짓한 자바스크립트로 완벽히 철거된 순간이었습니다.

재발 방지: 시스템 철학의 재정립과 6단계 검증 파이프라인

이번 사태는 단순한 코딩 에러가 아닙니다. 봇이 자신이 46자만 적었다는 사실을 인지하지 못하고 대표(CEO)에게 성공했다고 허위 보고한 "시스템 검증의 부재"가 근본 원인입니다.

이를 교훈 삼아 파이선생 AI 자동화랩은 Fail Fast(빠른 실패) 철학을 도입하고, 아래와 같은 10대 재발 방지 규정6단계 필수 검증 파이프라인을 시스템에 하드코딩했습니다.

10대 재발 방지 절대 수칙 (Failure Prevention Protocol)

  1. 수동 복붙 전환을 임의로 제안 금지 (자동화 돌파가 원칙)
  2. 신규 글 생성과 기존 글 수정(Update) 혼동 금지
  3. 발행 전 반드시 draft/preview 검증 스텝 강제
  4. 이미지 규칙 위반 절대 금지 (비인증 가짜 이미지 사용 금지)
  5. 공식 캐릭터 reference(Base image) 사용 엄수
  6. AI 가짜 코드 이미지 사용 금지 (IDE나 실제 실행 화면 캡처만 허용)
  7. 본문에 raw URL 문자열 노출 절대 금지
  8. 내부 디렉토리명(예: 052_...)이나 로컬 경로 노출 금지
  9. QA 검수자가 대표(CEO)보다 늦게 문제를 발견하는 것 엄격히 금지
  10. 직원(서브 에이전트) 체계가 상호 견제 없이 형식적으로만 작동하는 것 금지

6단계 필수 통과 시스템 (State Workflow)

앞으로 모든 블로그 자동 포스팅은 01_planner_pass 부터 06_cto_pass 까지 6개의 상태 파일(State)에 True가 찍히지 않으면 절대로 발행 버튼을 누를 수 없도록 파이썬 레벨에서 락(Lock)을 걸어 두었습니다. 특히, QA 단계에서는 image_content_match_check, actual_code_check 등 5대 필수 항목이 모두 PASS를 받아야만 다음 단계로 넘어갈 수 있습니다.

결론: UI 자동화의 함정을 피하는 방법

이번 46자 컷오프 에러 트러블슈팅을 통해 배운 가장 큰 교훈은 다음과 같습니다. 웹 자동화를 설계할 때 무작정 '사람의 행동(마우스 클릭, 키보드 타이핑)'을 흉내 내는 RPA(Robotic Process Automation) 방식만이 정답은 아닙니다.

특히 최신 프론트엔드 프레임워크(React, Vue, Svelte 등)와 복잡한 Virtual DOM으로 무장한 웹 애플리케이션을 상대로는 셀레늄의 고전적인 send_keys()가 한계에 부딪힐 확률이 매우 높습니다.

이럴 때는 무작정 딜레이를 주며 기다리기보다는, DOM 요소에 직접 접근하거나 웹 페이지가 노출하고 있는 전역 자바스크립트 객체(API)의 백도어를 찾아 직접 제어하는 것이 훨씬 빠르고 안정적인 자동화의 지름길입니다.

저와 같이 티스토리 자동 포스팅을 구축하다가 이유 없이 글이 잘리거나 브라우저가 뻗는 현상을 겪으셨다면, 키보드 타이핑 에뮬레이션을 과감히 버리고 setContent() API를 직접 호출해 보시기 바랍니다!