AI 글 품질 파이프라인과 색인핑 자동화를 함께 다듬은 저녁
목차
저녁 6시쯤 자리에 앉았을 때 머릿속에 있던 건 두 가지였다. 블로그 자동 발행 파이프라인에서 여전히 새어나오는 AI 냄새를 어떻게 체계적으로 막을 것인지, 그리고 GSC 색인핑이 하루 200건 쿼터를 낭비 없이 쓰도록 만들 수 있는 방법이 무엇인지. 두 문제는 표면적으로 전혀 달라 보이지만, 돌이켜보면 같은 근본 질문에서 나온 것이었다. 파이프라인이 "그냥 돌아가는 것"과 "제대로 돌아가는 것" 사이의 차이를 어떻게 코드로 명시하느냐의 문제였다.
ExcelStocks를 포트폴리오에 추가하는 작업도 있었는데, 그건 20분 안에 끝난 루틴이었고 이 날 세션의 본체는 아니었다.
humanizer 파이프라인: 왜 지금 또 손댔나
humanizer 모듈은 한동안 돌고 있었다. AI가 뽑아낸 블로그 초고를 발행 가능한 자연스러운 문체로 정제하는 역할인데, 문제는 결과물이 여전히 AI가 쓴 티가 너무 났다는 점이다. 독자가 명확하게 "이거 AI 글이네"라고 인식하지 못하더라도, 텍스트 전체에 특정 패턴이 깔려 있으면 읽는 느낌이 납작해진다. 검색엔진 입장에서도 AI 생성 콘텐츠 판별이 고도화되고 있어서, 이 패턴들을 방치하는 게 점점 리스크가 됐다.
가장 자주 걸린 패턴들을 정리하면 이렇다:
- 인사/도입 멘트: "안녕하세요", "이 글에서는", "오늘은 ~에 대해 알아보겠습니다"
- 마무리 인사: "도움이 되셨길 바랍니다", "끝까지 읽어주셔서 감사합니다", "앞으로도 ~할 예정입니다"
- 제목/소제목/불릿 앞 이모지 남발
- 낚시성 형용사: "필수", "꼭 알아야 할", "완벽 정리"
- em-dash(—) 남용: 영문 글쓰기 습관이 한국어 문장 안에 그대로 새어 들어오는 경우
- 상투어 반복: "결론적으로", "~하는 것이 중요합니다"
이 중에서 em-dash는 좀 특이한 케이스다. 영어로 글을 쓸 때 em-dash는 자연스럽지만, 한국어 문장에서 em-dash가 등장하면 AI가 번역하거나 한국어로 생성할 때 영문 습관이 그대로 묻어난 것처럼 읽힌다. 독자가 의식적으로 파악하기보다 무의식적으로 "뭔가 어색하다"는 느낌을 받는 부류의 패턴이다.
작업은 두 레이어에서 동시에 진행했다. 첫 번째 레이어는 CLAUDE.md와 bulk_seed.py의 프롬프트 자체였다. 애초에 저 패턴이 나오지 않도록 시스템 프롬프트와 발행 지시 프롬프트에 명시적 금지 조항을 강화했다. 두 번째 레이어는 humanizer.mjs와 humanizer.py였다. humanize() 함수가 단순한 문체 변환이 아니라 발행 게이트 역할도 겸하도록 구조를 바꿨다. 금지 패턴이 하나라도 감지되면 발행 큐에 들어가지 않고 리젝되는 방식이다.
두 레이어를 모두 두는 게 중복처럼 보일 수 있다. 하지만 실제 운영에서는 프롬프트 레이어만으로 완전히 막기가 어렵다. 모델 버전이 올라가거나 내려가거나, 특정 주제나 키워드에서 프롬프트 지시가 희석될 때 이모지가 슬그머니 들어오거나 인사말이 붙어 나오는 경우가 있었다. 프롬프트는 확률적으로 동작하지만 게이트는 결정론적으로 동작한다. 그 차이가 장기 운영에서 생각보다 크다.
게이트 로직 자체는 복잡하지 않다. 금지 패턴 목록을 정규식으로 스캔하고, 하나라도 걸리면 발행 큐에 들어가지 않고 리젝 마킹이 된다. 리젝된 글은 재처리 대상으로 표시되어 다음 사이클에서 다시 humanize()를 통과시킨다. 이렇게 하면 운영자가 직접 개입하지 않아도 자동으로 한 번 더 정제 사이클이 돌아가고, 여전히 걸리면 로그에 남아서 수동 확인 큐로 넘어간다.
GSC 색인핑: idx_ping_all에서 idx_ping_smart로
색인핑 시스템 고도화는 이번 세션에서 커밋이 세 번 이어졌다. 커밋 순서 자체가 설계가 진화한 과정을 그대로 보여준다.
첫 번째 커밋 - idx_ping_all 통합, 쿼터 200/day 확정 반영
처음에는 idx_ping_all이라는 이름 그대로 색인 요청을 전체 URL에 보내는 방식이었다. GSC Indexing API의 하루 쿼터가 200건으로 확정됐다는 걸 문서에 반영하면서, 이 제약이 실제로 문제가 된다는 게 명확해졌다. 사이트 규모에 따라 다르지만, 이미 색인된 URL들에 매일 핑을 보내는 건 쿼터를 그냥 날리는 짓이다. 색인이 필요한 URL이 200건보다 많은 날에는 정작 필요한 것들이 밀려날 수 있다는 문제도 있었다.
두 번째 커밋 - idx_ping_smart, 검사 API 기반으로 미색인만 선별
idx_ping_smart로의 전환이 이 세션의 핵심 변경이었다. GSC URL Inspection API를 먼저 조회해서 색인 상태를 확인하고, 미색인 판정을 받은 URL만 핑 대상으로 올리는 구조다.
for url in candidate_urls:
status = gsc_inspect(url) # URL Inspection API 조회
if status not in INDEXED_STATES:
ping_queue.append(url) # 미색인 - 핑 대상
else:
log(f"SKIP(indexed): {url}") # 이미 색인됨 - 스킵
send_indexing_requests(ping_queue) # 선별된 URL만 Indexing API 전송
이렇게 하면 쿼터 200건을 실제로 필요한 URL에만 집중할 수 있다. 미색인 URL이 200건 이하인 날에는 쿼터를 아낄 수 있고, 200건을 넘는 날에는 우선순위 로직을 별도로 넣어서 선별하는 게 가능해진다.
세 번째 커밋 - 404/5xx 자동감지, 핑 제외, 로그, 자동수정 하지 않는 이유 명시
스마트 선별까지 됐는데, HTTP 상태 코드 체크가 빠져 있었다. 404나 5xx인 URL을 색인 요청하면 GSC에 크롤 에러가 쌓이고 역효과가 난다. Inspection API 조회 전에 HTTP 상태를 먼저 확인하는 레이어를 추가했다.
| HTTP 상태 | 처리 방식 |
|---|---|
| 200 OK | GSC Inspection API 조회 후 미색인이면 핑 대상 포함 |
| 404 Not Found | 핑 제외, SKIP(404) 로그 기록 |
| 5xx Server Error | 핑 제외, SKIP(5xx) 로그 기록 |
| 301/302 Redirect | 최종 목적지 URL 추적 후 재판단 |
커밋 메시지에 "자동수정X 이유 명시"라고 따로 적은 건 의도적인 결정이었다. 404가 감지됐을 때 해당 URL을 자동으로 삭제하거나 리다이렉트를 생성하는 것도 기술적으로는 구현할 수 있다. 하지만 그렇게 하지 않았다. 이유는 404가 의도적인 경우가 있기 때문이다. 임시로 내린 페이지, 마이그레이션 중인 URL, 아직 배포 안 된 신규 콘텐츠 경로 같은 경우들. 자동수정이 들어가면 운영자 모르게 콘텐츠가 지워지거나 URL 구조가 바뀔 수 있다. 감지는 하되 로그에만 남기고 판단은 사람이 하는 구조로 명시해둔 이유가 여기 있다. 자동화의 범위를 의도적으로 제한하는 것이 때로는 올바른 설계다.
포트폴리오에 ExcelStocks 추가
stocks.opsvoro.com에 배포된 ExcelStocks 툴을 포트폴리오 index.html의 tools 카테고리에 썸네일과 함께 추가했다. 새 서비스가 올라가면 포트폴리오에도 바로 반영하는 게 루틴으로 굳어져 있어서, 별다른 판단이 필요한 작업은 아니었다. 썸네일 이미지 준비하고 HTML 카드 하나 추가하는 수준이었다.
이 세션을 관통한 것
저녁 내내 두 축의 작업을 병행하면서 계속 같은 생각이 들었다. 자동화 파이프라인이 일정 규모를 넘으면, "일단 돌아간다"는 것만으로는 부족하다는 것.
humanizer 쪽에서는 "프롬프트만 잘 짜면 AI가 자연스러운 글을 써주겠지"라는 전제를 포기했다. 프롬프트는 확률적 지시고, 실제 출력은 그 확률이 빗나갈 때도 있다. 게이트가 그 빗나감을 붙잡아주지 않으면 쌓이고 나서야 발견하게 된다. 문제가 쌓인 뒤에 정리하는 것보다 파이프라인 끝에 결정론적인 필터 하나를 두는 게 훨씬 낫다.
GSC 핑 쪽에서는 "핑 보내면 언젠가 색인되겠지"라는 느슨한 전제를 버렸다. 쿼터가 한정된 리소스라는 걸 인식하는 순간, 그 리소스를 어디에 쓸지 파이프라인 자체가 판단해야 한다는 결론이 나왔다. 그리고 자동 판단의 범위가 어디까지여야 하는지도 코드가 아니라 문서에 명시해둬야 다음에 손대는 사람이 왜 이렇게 짰는지 이해할 수 있다.
상태를 실측하고, 예외를 명시적으로 처리하고, 자동 수정의 범위를 의도적으로 제한하는 것. 이게 실제 운영에서 안전하게 돌아가는 자동화와 그냥 돌아가는 자동화의 차이라고 본다. 이번 세션에서 그 차이를 코드로 옮기는 데 저녁 시간 대부분을 썼다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.