1827 단어
9 분
레딧을 읽다가 블로그 글이 되기까지 — 수집·번역·다이제스트 파이프라인 개발기

이 블로그의 레딧 다이제스트 글들은 손으로 쓴 게 아닙니다. 레딧에서 글을 수집하고, 번역하고, 고른 글들을 동향글로 엮어 발행하는 개인용 파이프라인이 만든 결과물입니다. 이 글은 그 시스템을 만든 기록입니다.

왜 만들었나#

레딧에는 국내 커뮤니티보다 빠른 개발 동향 논의가 많습니다. 문제는 영어 장문 글과 수백 개의 댓글을 매일 읽는 게 부담이라는 것. 처음엔 “번역해서 보여주는 대시보드”면 충분하다고 생각했는데, 만들다 보니 욕심이 생겼습니다 — 읽는 것에서 끝나지 않고, 읽은 글이 내 블로그 콘텐츠로 이어지는 완결된 흐름을 만들고 싶었습니다.

그래서 최종 형태는 이렇게 됐습니다:

수집(레딧) → 번역(DeepSeek) → 카드로 읽기 → AI 요약 → 주제별로 담기
→ 글 고르고 내 생각 메모 → 다이제스트(동향글) 생성 → 편집 → 블로그 발행

발행 버튼을 누르면 Astro 블로그 레포에 .md가 커밋·푸시되고, GitHub Actions가 배포합니다. 사람 손이 필요한 지점은 “글 고르기”와 “내 생각 쓰기” 딱 둘인데, 이건 의도적으로 남겼습니다.

포스트 카드 목록다이제스트 편집·미리보기
포스트 카드 목록다이제스트 편집 화면

설계에서 재미있었던 결정들#

Spring UI와 Python 엔진의 분리#

웹 서빙·CRUD는 Spring(JPA, 단일 JAR)이 편하고, LLM 호출·스크래핑은 Python 생태계가 강합니다. 하나의 런타임에 욱여넣는 대신 stateless HTTP 계약으로 분리했습니다. 엔진의 /summarize, /digest는 DB를 전혀 모릅니다 — 텍스트를 받아 결과만 반환하고 캐싱·영속화는 전부 Spring이 소유합니다. 덕분에 엔진은 언제든 재시작·교체가 가능했습니다. 실제로 개발 내내 모델과 프롬프트를 갈아끼우면서 UI는 한 번도 흔들리지 않았습니다.

LLM 비용은 설계로 줄인다#

  • 댓글 배치 번역: 댓글을 1건씩 번역하면 글 하나에 수십 콜이 나갑니다. 15개씩 JSON으로 묶어 한 콜에 처리하고, LLM이 형식을 깨거나 일부를 빼먹으면 그 청크만 건별 번역으로 폴백합니다.
  • 요약 캐시: (글, provider) 키로 저장해서 같은 글을 다시 열면 호출 없이 반환. 재생성은 명시적으로만.
  • 다이제스트는 원문으로: 처음엔 번역본을 재료로 동향글을 만들었는데, “영→한 번역 → 그걸로 작문”은 뉘앙스가 한 번 씻겨나간 재료로 글을 쓰는 셈이었습니다. 영어 원문을 주고 한국어 보고서를 쓰게 하는 편이 결과가 좋았습니다.

”AI가 쓴 내 의견”은 가짜다#

동향글에 1인칭 관점을 넣으라고 프롬프트를 짜봤는데, 나오는 건 “주목할 필요가 있다” 류의 무난한 소리뿐이었습니다. 페르소나를 정교하게 넣어도 결국 하지 않은 경험을 지어내는 문제가 남았습니다. 그래서 방향을 바꿨습니다 — AI는 의견을 만들지 않습니다. 대신 다이제스트 생성 직전에 내가 막메모를 남기면, AI는 그 메모의 어투만 다듬어 “내 생각” 섹션으로 배치합니다. 의견의 출처는 100% 나이고, AI는 문장 정리만 합니다. 글을 골라 담은 직후가 생각이 가장 생생한 시점이라 타이밍도 잘 맞았습니다.

부딪힌 문제들#

텍스트 전용 모델에 이미지를 보내면. 요약에 게시글 이미지를 base64로 첨부했는데, Ollama의 텍스트 전용 모델이 400을 뱉고 그게 502로 전파됐습니다. 더 근본적으로는 DeepSeek API가 비전을 아예 지원하지 않는다는 것도 실제 호출로 확인했습니다(image_url 타입 자체를 거부). 결론은 역할 분담 — 비전 모델(Ollama)이 이미지를 1~2문장 설명으로 변환하고, 그 텍스트를 DeepSeek 입력에 주입합니다. 이미지를 못 보는 모델에게 이미지 내용을 전달하는 가장 값싼 방법이었습니다.

“좋은 글”은 정렬 기준이 아니라 추천 엔진에 있었다. 처음엔 서브레딧별 top/day를 수집했는데, 인기 글 ≠ 유익한 글이었습니다(밈과 드라마가 섞입니다). 그런데 레딧이 보내주는 추천 메일의 글들은 이상하게 유익했습니다. 알고 보니 그건 랜덤이 아니라 개인화 추천 엔진의 출력이었고, 같은 것을 /best.json(로그인된 홈 피드)으로 직접 가져올 수 있었습니다. 추천 점수 2점짜리 글에 보석이 섞여 있는 이유가 설명되는 순간이었습니다.

삭제한 글이 되살아난다. 수집 → 마음에 안 들어 삭제 → 다음 수집에서 같은 글이 다시 들어옵니다. 중복 체크가 “현재 저장된 글” 기준이었기 때문입니다. 해결은 append-only 수집 이력 테이블 — 글을 지워도 이력은 남아 자동 수집이 건너뜁니다. 단, URL 직접 입력은 이력을 무시하게 해서 의도적 재수집의 탈출구를 남겼습니다.

두 런타임이 한 DB를 만질 때. Python init_db()와 Spring ddl-auto: update가 같은 PostgreSQL을 만집니다. 테이블별 생성 주체를 문서로 못박고(수집 테이블 = Python, 요약·다이제스트 = Spring), 서로의 테이블은 읽기만 하도록 정리했습니다.

결과#

지금은 이 흐름이 매일 돌아갑니다: 추천 피드에서 수집 → 출근길에 카드로 훑고 → 괜찮은 글을 주제 버킷에 담고 → 생각이 정리되면 메모와 함께 다이제스트 생성 → 편집 후 발행. 이 블로그의 다이제스트 글들이 전부 이 파이프라인의 산출물입니다.

발행된 다이제스트 글들

만들면서 가장 크게 남은 건, LLM을 서비스 흐름에 넣을 때의 감각입니다 — 모델은 계속 갈아끼우게 되므로 호출부는 얇고 교체 가능하게, 비용은 프롬프트가 아니라 구조(캐시·배치·2단계 분리)로 줄이고, 사람이 잘하는 것(고르기, 의견)은 자동화하지 않기.

다음으로는 서브레딧을 가로지르는 주제 클러스터링(임베딩 기반)을 붙여볼 생각입니다. 코드는 reddit-translator · reddit-translator-ui에 있습니다.

레딧을 읽다가 블로그 글이 되기까지 — 수집·번역·다이제스트 파이프라인 개발기
https://malgcheong.github.io/posts/reddit-translator-build-log/
저자
malgcheong
게시일
2026-07-08
라이선스
CC BY-NC-SA 4.0