← 포트폴리오 목록으로

blog-analyzer

5개 AI 엔진을 통합한 네이버 블로그 SEO 최적화 원고 자동 생성 플랫폼

2025.09 ~ 현재1인 개발 (백엔드, AI 통합)📖 5분 소요

Blog Analyzer

블로그 대행사에서 일하면서 매일 비슷한 구조의 글을 반복해서 쓰는 게 너무 비효율적이라고 느꼈다. 키워드만 넣으면 네이버 상위 노출에 최적화된 원고가 자동으로 나오면 좋겠다는 생각에서 시작했다.

처음에는 GPT API 하나만 연결해서 간단하게 만들었는데, 고객마다 원하는 톤이 다르고, 카테고리별로 글 스타일이 완전히 달라서 점점 복잡해졌다. 지금은 5개 AI 엔진을 상황에 맞게 선택할 수 있고, 46개 카테고리별 맞춤 프롬프트를 적용하는 시스템이 됐다.


주요 기능

  • GPT, Claude, Gemini, Grok, SOLAR 5개 AI 엔진 통합
  • 46개 카테고리별 전문 프롬프트 (위고비, 맛집, 웨딩홀 등)
  • 참조 원고 스타일 학습 및 모방
  • MongoDB 기반 분석 데이터 축적 및 재활용
  • 10단계 단계별 원고 생성 (Step-by-Step)
  • 청크 분할 생성으로 긴 원고도 안정적 처리
  • 3단계 텍스트 후처리 파이프라인

기술 스택

분류기술
BackendFastAPI, Uvicorn, Python 3.11
AI/LLMOpenAI, Anthropic, Google AI, Upstage, xAI
DatabaseMongoDB Atlas
텍스트 처리정규식, KoNLPy, KSS

기술적 도전과제

1. AI 엔진마다 응답 형식이 다 다르다

처음에 GPT만 쓸 때는 문제없었는데, Claude랑 Gemini를 추가하니까 응답 형식이 제각각이었다. 어떤 건 JSON으로 오고, 어떤 건 마크다운 코드 블록 안에 JSON이 들어있고, 어떤 건 그냥 텍스트로 온다.

해결: 4단계 폴백 JSON 파싱 전략을 만들었다.

# llm/step_by/phase_functions.py
def parse_json_response(response_text: str) -> dict:
    # 1. 전체가 JSON인 경우
    try:
        return json.loads(response_text)
    except:
        pass

    # 2. 코드 블록(```json) 안의 JSON 추출
    json_match = re.search(r'```json\s*([\s\S]*?)\s*```', response_text)
    if json_match:
        return json.loads(json_match.group(1))

    # 3. 중괄호로 시작하는 가장 큰 블록 찾기
    brace_match = re.search(r'\{[\s\S]*\}', response_text)
    if brace_match:
        return json.loads(brace_match.group())

    raise ValueError("JSON 파싱 실패")

이렇게 하니까 어떤 AI 엔진을 써도 JSON 응답을 안정적으로 받을 수 있게 됐다.


2. "AI가 쓴 글 같다"는 피드백

가장 많이 받은 피드백이 이거였다. 글이 너무 깔끔하고 정돈되어 있어서 오히려 AI 티가 난다는 것.

문제 분석:

  • "첫째, 둘째, 셋째" 같은 나열식 표현
  • "~해야 합니다", "~하는 것이 좋습니다" 같은 교과서 어투
  • 문장 길이가 일정함
  • 감정 표현이 없음

해결: _prompts/rules/human_writing_style.py에 인간화 규칙을 정의했다.

# 핵심 규칙 발췌
human_writing_rules = """
## 금지 패턴
- "첫째/둘째/셋째" 대신 자연스러운 흐름으로
- "~해야 합니다" 대신 "~해보세요", "~하면 좋아요"
- 요약이나 정리 문단 금지
- 번역투 한자어 회피

## 필수 패턴
- 개인 경험 삽입 ("저는 처음에... ㅜㅜ")
- 대화 요소 ("여러분도 그렇지 않나요?")
- 감탄사 사용 ("와... 이건 진짜 대박!")
- 문장 길이 불규칙하게 (짧은 것, 긴 것 섞기)
"""

프롬프트에 이 규칙을 넣으니까 확실히 자연스러워졌다. 근데 아직도 가끔 AI 티가 나서 계속 개선 중이다.


3. 2000자 이상 원고 생성 시 불안정

긴 원고를 한 번에 생성하려고 하면 API가 타임아웃 나거나, 중간에 내용이 끊기는 경우가 있었다.

해결: 10단계 단계별 생성 방식을 도입했다.

PHASE 1: 화자 설정
PHASE 2: 소제목 5개 생성
PHASE 3: 연관 키워드 40개 추출
PHASE 4: 제목 생성
PHASE 5: 도입부 (200자)
PHASE 6: 본문 (350자 x 5개 소제목)
PHASE 7: 마무리
PHASE 8: 키워드 반복율 검증

각 단계별로 API를 호출하고, 이전 단계 결과를 다음 단계에 전달하는 방식이다. 한 번에 생성하는 것보다 훨씬 안정적이고, 중간에 문제가 생겨도 어느 단계에서 실패했는지 바로 알 수 있다.

단점은 API 호출 횟수가 많아져서 비용이 늘어난다는 점. 근데 안정성이 더 중요해서 이 방식을 유지하고 있다.


4. 5개 AI 엔진 동시 호출 시 레이트 리미팅

여러 요청이 동시에 들어오면 API 레이트 리미트에 걸려서 에러가 터졌다.

해결: Asyncio Semaphore로 동시 요청 수를 제한했다.

# api.py
LLM_CONCURRENCY = int(os.getenv("LLM_CONCURRENCY", 3))
llm_semaphore = asyncio.Semaphore(LLM_CONCURRENCY)

# 라우터에서 사용
async with llm_semaphore:
    result = await run_in_threadpool(ai_generate, ...)

동시에 3개까지만 AI API를 호출하도록 제한했다. 이것만으로도 레이트 리미트 에러가 거의 사라졌다.


5. 줄바꿈 규칙이 생각보다 복잡했다

네이버 블로그는 모바일에서 많이 읽히는데, 한 줄이 너무 길면 읽기 힘들다. 그래서 적절한 위치에서 줄바꿈을 넣어야 하는데, 이게 생각보다 까다로웠다.

문제:

  • 소제목 중간에서 줄바꿈하면 안 됨
  • "3,000원" 같은 숫자+단위는 분리하면 안 됨
  • 마침표가 있어도 소수점이면 줄바꿈하면 안 됨

해결: ai_lib/line_break_service.py에서 AI 기반 줄바꿈 처리를 구현했다.

line_break_prompt = """
한 줄 30자 내외로 자연스럽게 줄바꿈해.

단, 다음은 절대 건드리지 마:
- 소제목 (## 으로 시작하는 줄)
- 숫자+단위 (3,000원, 10km 등)
- 소수점이 포함된 숫자

마침표, 쉼표 뒤에서 끊는 게 자연스러워.
"""

정규식으로 처리하려다가 예외 케이스가 너무 많아서 결국 AI한테 맡겼다. 비용은 좀 들지만 정확도가 훨씬 높다.


트러블슈팅 사례

제목이 반복되는 버그

Git 커밋: 0cf6550 fix: 제목반복제거

같은 키워드로 여러 번 생성하면 제목이 거의 똑같이 나오는 문제가 있었다. AI가 학습 데이터에서 본 패턴을 그대로 반복하는 것 같았다.

원인: temperature 값이 너무 낮았음 (0.3)

해결: 제목 생성 시에만 temperature를 0.8로 올렸다. 본문은 일관성이 중요해서 0.5 유지.


외국어가 섞여 나오는 문제

Git 커밋: e8588b1 fix(prompts): 외국어 표현 금지 규칙 강력 적용

맛집 카테고리에서 일본어나 영어가 섞여 나오는 경우가 있었다. "美味しい", "delicious" 같은 표현이 갑자기 튀어나옴.

해결: 프롬프트에 외국어 금지 규칙을 강하게 추가했다.

# _prompts/category/맛집.py
foreign_language_rule = """
## 절대 금지
- 일본어, 중국어, 영어 등 외국어 사용 금지
- "맛있다"를 "美味しい"나 "delicious"로 쓰지 마
- 외래어도 최소화 (OK: 메뉴, NO: 퀄리티)
"""

MongoDB 연결이 안 끊어지는 문제

초반에 MongoDB 연결을 제대로 종료하지 않아서 연결 풀이 고갈되는 문제가 있었다.

해결: finally 블록에서 반드시 연결을 종료하도록 패턴을 정립했다.

db_service = MongoDBService()
try:
    result = db_service.find_documents(...)
    return result
finally:
    db_service.close_connection()  # 무조건 실행

성능 개선

MongoDB 쿼리 최적화

처음에는 모든 문서를 가져와서 Python에서 필터링했는데, 데이터가 쌓이니까 느려졌다.

Before:

# 모든 문서 가져와서 Python에서 처리
all_docs = collection.find({})
unique_words = set(doc["word"] for doc in all_docs)

After:

# MongoDB에서 바로 중복 제거
unique_words = collection.distinct("word", {"category": category})

쿼리 시간이 3초 -> 0.3초로 줄었다.

집계 파이프라인 활용

카테고리별 표현 패턴을 추출할 때 aggregation을 사용했다.

pipeline = [
    {"$match": {"category": category}},
    {"$group": {
        "_id": "$category",
        "expressions": {"$addToSet": "$expression"}
    }}
]
result = collection.aggregate(pipeline)

서버 사이드에서 처리하니까 네트워크 트래픽도 줄고 속도도 빨라졌다.


배운 점

  1. 프롬프트 엔지니어링이 생각보다 어렵다

    • 한 글자 바꿔도 결과가 완전히 달라짐
    • 카테고리별로 미세 조정이 필요함
    • 규칙을 너무 많이 넣으면 오히려 이상해짐
  2. 멀티 AI 엔진 운영은 비용 관리가 핵심

    • 각 엔진별 토큰 단가가 다름
    • 간단한 작업은 저렴한 엔진, 복잡한 작업은 고성능 엔진
    • 로깅해서 비용 추적하는 게 중요함
  3. "완벽한 자동화"는 없다

    • 결국 사람이 최종 검수해야 함
    • 자동화는 시간을 줄여주는 거지, 품질을 보장하지는 않음

향후 계획

  • 생성된 원고 품질 점수 자동 평가 시스템
  • A/B 테스트로 어떤 스타일이 상위 노출에 효과적인지 분석
  • 이미지 자동 삽입 위치 추천
  • 실시간 네이버 검색 트렌드 반영