
왜 만들었나: 초안 만드는 반복 작업을 코드로 줄이고 싶었다
블로그 운영에서 가장 지치는 건 글쓰기 자체보다 그 앞뒤의 반복입니다. 키워드 고르고, 카테고리 정하고, 초안 틀 잡고, 중복인지 확인하고, 품질 훑고, 워드프레스에 올리고. 저는 이 반복을 코드로 묶어 보기로 했고, 그 결과물이 AutoWP입니다. 이 글은 완성된 제품 소개가 아니라, 실제로 만들면서 무엇을 선택했고 어디서 막혔는지 적는 빌더 일지입니다.
결론부터 적으면 이렇습니다. AutoWP는 순수 Python으로 짠 파이프라인이고, 생성 엔진은 OpenAI, 발행 대상은 WordPress REST API, 상태 저장은 로컬 SQLite입니다. 그리고 핵심 원칙이 하나 있습니다 — 완전 자동이 아니라 반자동. 마지막 발행 버튼은 사람이 누릅니다.
전체 구조: 거창한 SaaS 대신 작은 Python 프로젝트
처음엔 n8n이나 Zapier 같은 노코드 오케스트레이터를 얹을까 고민했습니다. 결국 안 썼습니다. 이유는 단순합니다. ① 흐름을 내가 코드로 완전히 통제하고 싶었고, ② 추가 SaaS 비용·계정·외부 의존을 늘리고 싶지 않았고, ③ 로컬에서 Python 한 폴더로 끝나는 게 디버깅이 빨랐습니다. 노코드가 틀렸다는 게 아니라, 제 상황(혼자, 코드에 익숙, 통제 우선)에는 직접 코딩이 맞았습니다.
실제 구성 요소는 이렇습니다.
- 생성 엔진: OpenAI API. (참고로 제 작업 환경은 사내망에서 Anthropic이 막혀 있어 생성 엔진을 OpenAI로 고정했습니다 — 제약이 설계를 정한 사례)
- 발행 대상: WordPress REST API. 글을 직접 만들지 않고 REST로 초안(draft)을 올립니다.
- 상태 저장: SQLite + SQLAlchemy. 키워드·초안·검수 상태·임베딩을 로컬 DB에 둡니다.
- 실행: CLI 스크립트 + 스케줄러 틱(데몬 없이 OS 작업 스케줄러가 주기 호출).
파이프라인: 키워드 한 개가 검수큐에 들어가기까지
키워드 하나가 글 초안이 되어 검수큐에 쌓이는 흐름은 이렇게 단계가 나뉩니다. 각 단계는 다음 단계로 넘어가기 전 게이트 역할을 합니다.
- 분류 — 키워드를 사이트 카테고리 체계에 매핑(규칙 기반 classifier).
- 생성 — OpenAI 단일 호출로 본문 HTML·제목·메타·썸네일 문구를 만듭니다.
- 구조 검사 — 기계적 리뷰어가 분량·H2 개수·표·CTA 같은 구조 요건을 확인. 통과 못 하면 보강 지침을 붙여 한 번 재생성하고, 그래도 안 되면 skip.
- 중복 검사 — 임베딩(코사인 유사도)으로 같은 사이트 기존 글과 비교. 임계값 이상이면 중복으로 보고 skip.
- 품질 판정 — 경량 LLM 판정기가 구체성·유용성·가독성 등을 0~100으로 채점. 하한 미만은 게이트, 그 외엔 점수·요약을 검수 노트에 붙여 사람이 빨리 분류하도록 돕습니다.
- 검수큐 적재 — 통과분만
pending_review상태로 DB에 쌓입니다. 여기까지가 자동.
그다음은 사람입니다. 검수 CLI가 판정 점수 높은 순으로 대기 글을 보여주고, 승인한 글만 발행 단계로 갑니다.
왜 끝까지 반자동으로 두는가
가장 자주 받는 질문이 “왜 자동 발행을 안 켜냐”입니다. 의도적입니다. AI 초안은 그럴듯하지만 사실관계·맥락에서 틈이 생기고, 특히 돈·건강처럼 민감한 주제(YMYL)는 틀리면 손해가 큽니다. 그래서 발행 플래그는 기본적으로 꺼짐(draft) 상태이고, REST로 올라가는 것도 초안까지입니다. 공개 전환은 사람이 검수 후 결정합니다. 자동화의 목표를 “사람을 없애기”가 아니라 “사람의 검수 처리량을 안전하게 늘리기”로 잡은 셈입니다.
실제로 막혔던 지점들 (여기가 진짜 일지)
매끄럽게 된 건 하나도 없었습니다. 기억에 남는 벽들:
- Windows 콘솔 인코딩 크래시 — 이모지·한글 로그를 출력하다 cp949에서 깨지고 죽었습니다. 모든 진입점에서 콘솔을 UTF-8로 강제하는 헬퍼(
force_utf8_console())를 호출하도록 통일해 해결. - 사내망 HTTPS 신뢰 문제 — 외부 API 호출이 인증서 단계에서 막혔습니다. 시스템 신뢰 저장소를 쓰도록 켜는 헬퍼(
enable_system_trust())를 모든 외부 호출 앞에 두어 해결. - 모지바케(한글 깨짐) — 코드 곳곳에 깨진 한글 상수가 박혀 출력이 오염됐습니다. 죽은 함수를 걷어내고 모델이 깨끗한 한글을 직접 생성하게 바꿨습니다.
- 모델마다 다른 파라미터 규칙 — 일부 reasoning 계열 모델은 커스텀 temperature를 안 받거나 특정 reasoning 강도 값을 거부했습니다. 모델별로 허용 파라미터만 추려 넘기는 가드를 두었습니다.
- 티 나는 반복 패턴 — 초기엔 제목·구성이 몇 가지 틀로 반복됐습니다(“…방법과 현실적인 후기” 류). 고정 템플릿을 걷어내고 글마다 구성·제목이 달라지도록 프롬프트를 느슨한 의도 중심으로 바꿨습니다.
공통 교훈: 막힌 곳 대부분이 “AI 품질”이 아니라 환경·인코딩·인프라였습니다. 모델은 생각보다 잘 썼고, 그걸 안정적으로 굴리는 배관이 일의 대부분이었습니다.
직접 코딩 vs 노코드 오케스트레이터 — 내 선택과 트레이드오프
| 관점 | 직접 Python (내 선택) | n8n / Zapier / Make |
|---|---|---|
| 제어력 | 높음 — 모든 단계·게이트를 코드로 통제 | 중간 — 노드 한계 안에서 구성 |
| 시작 속도 | 느림 — 배관을 직접 짜야 함 | 빠름 — 드래그로 연결 |
| 비용 | API 사용료만 | 플랜 비용이 작업량 따라 상승 |
| 데이터 통제 | 높음 — 로컬 SQLite | 외부 서비스 경유 |
| 적합한 사람 | 코드에 익숙하고 통제·디버깅 우선 | 빠른 실험·비개발 협업 우선 |
구성 요소별로 쓴 모델·도구
| 역할 | 쓴 것 | 메모 |
|---|---|---|
| 본문 생성 | OpenAI 플래그십 텍스트 모델 | 품질 우선. 분량·구조가 안정적 |
| 썸네일 문구 | OpenAI 경량 모델 | 짧은 작업이라 경량으로 충분 |
| 품질 판정 | OpenAI 경량 판정 모델 | 0~100 점수 + 요약을 검수 노트로 |
| 중복 검사 | OpenAI 임베딩 모델 | 코사인 유사도로 의미 중복 차단 |
| 발행 | WordPress REST API | 초안(draft)으로만 업로드 |
| 알림 | Discord 웹훅 | 생성·발행 결과를 채널로 통지 |
※ 정확한 모델명·요금은 자주 바뀌어 일부러 일반화해 적었습니다. 구성은 .env 한 줄로 모델을 갈아끼울 수 있게 분리했습니다.
운영은 어떻게 도는가
OS 작업 스케줄러가 매일 정해진 시각에 스케줄러 틱을 호출합니다. 틱은 하루 생성 한도를 DB로 확인해 한도를 넘기지 않고(현재 보수적으로 소량/일로 설정), 결과를 Discord로 요약합니다. 데몬을 띄우지 않고 “외부 스케줄러가 틱을 주기 호출”하는 구조라 단순하고 죽어도 다음 틱에 복구됩니다. 다시 강조하면, 이 자동 흐름은 검수큐 적재까지만이고 공개 발행은 사람이 합니다.
자주 묻는 질문(FAQ)
1) 왜 Claude가 아니라 OpenAI인가요?
품질 선호라기보다 환경 제약입니다. 작업 환경 네트워크에서 Anthropic 접근이 막혀 있어 생성 엔진을 OpenAI로 고정했습니다. 모델 선택이 늘 품질만으로 정해지는 게 아니라는 좋은 예입니다.
2) 자동으로 글이 게시되나요?
아니요. 기본값은 draft입니다. 자동 단계는 검수큐까지이고, 사람이 검수·승인한 글만 발행 단계로 넘어갑니다.
3) 같은 주제 글이 중복으로 쌓이지 않나요?
적재 직전 임베딩 유사도로 기존 글과 비교해 임계값 이상이면 자동으로 걸러냅니다.
마무리 — 작게 만들고, 사람을 남겨두기
AutoWP를 만들며 배운 핵심은 두 가지입니다. 첫째, 콘텐츠 자동화의 난이도는 모델이 아니라 그 주변 배관(인코딩·신뢰·상태관리·운영)에 있습니다. 둘째, 끝까지 사람을 한 명 남겨두는 반자동이 — 적어도 품질과 신뢰가 중요한 글에서는 — 완전 자동보다 안전하고 오래 갑니다. 다음 단계로는 검수 처리량을 더 끌어올리는 도구와, 신뢰 데이터가 쌓이면 비민감 주제에 한해 자동 발행을 조심스럽게 다시 검토할 생각입니다.
