Tech

리눅스 cron으로 Tistory 블로그 자동 발행, 막히는 건 캡차 한 군데뿐

lifehardmode 2026. 5. 21. 14:28

Cron 자동 발행 셋업 일러스트이미지: lifehardmode 자체 제작

블로그 글을 며칠 단위로 큐에 쌓아두고 정해진 시각에 한 편씩 자동 발행하는 셋업은 1인 운영자에게 가장 가성비 좋은 자동화다.

리눅스 cron 한 줄과 Playwright 스크립트 하나면 끝나는데, 실제로 돌려보면 막히는 지점은 두 군데로 좁혀진다.

자동 발행의 진짜 비용은 작성도 업로드도 아니다. 캡차와 cron 환경 변수 두 가지에서 발생한다.

핵심만 보면

  • cron은 비대화형 셸이라 DISPLAY가 비어 있다. Playwright headed 모드는 X 디스플레이가 없으면 즉시 실패한다.
  • DKAPTCHA(카카오)는 한국어 지도 라벨 패턴 매칭 캡차다. 작은 OCR 모델로는 정확도가 부족하고, 결국 멀티모달 reasoning이 필요하다.
  • 이 두 가지만 정리하면 cron 한 줄로 매일 정해진 시각에 발행이 끝까지 굴러간다.

무슨 일이 있었나

리눅스 데스크톱에 OPENCLAW의 tistory-publish skill을 깔고 cron 한 줄을 추가했다. 큐 디렉토리(~/blogs/lifehardmode/queue/)에 마크다운을 쌓아두면 정해진 시각에 가장 앞 글 한 편이 발행되고, 성공한 글은 posted/로, 실패한 글은 failed/로 이동한다.

처음에는 cron 트리거가 떨어져도 발행기가 멈췄다. 로그를 보니 Playwright가 X 서버가 없다며 죽고 있었다.

Looks like you launched a headed browser without having a XServer running.

cron은 비대화형 셸이라 DISPLAYXAUTHORITY 환경 변수가 비어 있다. 사용자가 GUI에 로그인되어 있어도 cron이 그 세션을 자동으로 물고 오지 않는다.

왜 중요한가

문제는 Playwright의 동작 방식이 아니라 cron의 환경 격리다.

발행기 셸 스크립트 도입부에서 활성 Xorg 세션을 탐지해 DISPLAY=:1XAUTHORITY를 직접 주입하면 cron에서도 headed 브라우저가 그대로 뜬다.

if [ -z "${DISPLAY:-}" ]; then
  pgrep -u "$(id -u)" -f 'Xorg|Xwayland' >/dev/null && export DISPLAY=:1
fi
[ -r "/run/user/$(id -u)/gdm/Xauthority" ] && \
  export XAUTHORITY="/run/user/$(id -u)/gdm/Xauthority"

한국 독자가 볼 지점

카카오 DKAPTCHA가 두 번째 벽이다. 지도 위에 한국어 상호명 라벨이 흩뿌려져 있고, 그중 하나의 일부 글자나 전체 명칭을 빈칸에 입력하는 형태다.

  • 1.7B급 한국어 OCR 모델은 라벨 자체는 잘 읽지만, 빈칸 패턴과 매칭하는 reasoning에서 무너진다.
  • 결국 멀티모달 LLM이 이미지를 그대로 받아 한 번에 풀어야 정확도가 나온다.
  • Qwen3.6-35B 같은 멀티모달 메인 모델이 떠 있으면 별도 비전 사이드카는 오히려 메모리 낭비다.

과장하면 안 되는 부분

캡차 자동 풀이는 100% 통과가 아니다. 한 시도에서 12회까지 시도하고, 라벨 인식이 흐릿한 회차는 그냥 새 캡차를 받는다.

같은 글을 짧은 간격으로 반복 발행하면 카카오 측에서 "Bad Request"를 던지며 30초 쿨다운을 강제한다. 그래서 큐 기반 1회 1편 발행이 안전하다.

지금 체크할 것

  • cron 라인에서 환경 변수가 비어 있어 죽는 부분이 있는지 (DISPLAY, XAUTHORITY, PATH)
  • 캡차 자동 풀이에 사용하는 모델이 한국어 멀티모달인지, 단순 OCR인지
  • 발행 실패한 글이 자동으로 큐로 복귀하는지, 매번 손으로 옮겨야 하는지

참고