Recent Posts
Recent Comments
Link
08-18 04:01
«   2026/08   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Archives
Today
Total
관리 메뉴

꿈 많은 사람의 이야기

티스토리 댓글 도배 대응하기 - Python 기반 댓글 관리/삭제 서비스 개발기(코드 공유) 본문

python

티스토리 댓글 도배 대응하기 - Python 기반 댓글 관리/삭제 서비스 개발기(코드 공유)

이수진의 블로그 2026. 8. 17. 16:19
반응형

포스팅 개요

본 포스팅은 제 티스토리 블로그에 발생한 대규모 댓글 도배를 정리하기 위해 직접 만든 댓글 관리/삭제 서비스를 소개하는 포스팅입니다.

2026년 8월 8일 저녁, 갑자기 댓글 알림이 비정상적으로 많이 들어와 있는 것을 확인했습니다. 처음에는 흔한 스팸 댓글이라고 생각했는데요. 알람이 계속 울려서 실제로 확인해보니 약 4시간 동안 여러 게시글에 수천 건의 댓글이 등록되어 있었습니다.

나중에 직접 만든 도구로 전체 댓글을 다시 수집해보니 피해 게시글은 총 13개, 그중 5,571건이 도배 댓글이었습니다.

특히 댓글의 닉네임에는 SQL Injection 취약점을 확인할 때 사용하는 문자열이 포함되어 있었습니다. 특정 글에 악플을 남기는 형태라기보다 자동화된 취약점 탐색 도구가 티스토리 댓글 입력창을 대상으로 요청을 반복한 것으로 보였습니다.

 

문제는 이렇게 쌓인 댓글을 정리할 방법이 마땅하지 않았다는 점입니다.

티스토리 관리 화면에서는 댓글을 한 건씩 삭제해야 했고, 고객센터를 통해 일괄 삭제를 요청하는 것도 어려웠습니다. 

그래서 Claude Code를 활용해서 티스토리의 실제 댓글 조회/삭제 동작을 분석하고, Python 기반의 댓글 관리 서비스를 직접 만들어보기로 했습니다.

처음에는 단순히 제 블로그의 댓글을 빠르게 지우는 스크립트를 생각했지만, 삭제는 되돌릴 수 없는 작업이기 때문에 정상 댓글을 보호해야 했고, 대량 삭제 전에 실제 대상을 눈으로 확인할 수 있어야 했으며, 세션이 만료되거나 중간에 작업이 실패해도 안전하게 멈춰야 했습니다.

결국 단순한 삭제 스크립트가 아니라 댓글 수집, 스팸 분류, 댓글 선택, 백업, 삭제, 사후 검증까지 하나의 흐름으로 관리할 수 있는 웹 서비스 형태로 만들었습니다. 시간은 대략 3시간 정도 소요된 것 같네요.

위 사진이 당시 블로그 화면입니다. 같은 형태의 댓글이 계속 이어져 있어서 관리 화면에서 하나씩 정리하는 것은 사실상 불가능한 상태였습니다.

 

같은 피해를 입으신 다른 분들을 위해서 코드도 공개해두었습니다.


포스팅 본문

포스팅 개요에서도 언급하였듯, 이번에 만든 서비스는 티스토리 게시글의 댓글을 수집하고 도배 가능성이 높은 댓글을 분류한 뒤 사용자가 직접 확인해서 삭제할 수 있도록 만든 댓글 관리 서비스를 만든 후기를 정리하는 포스팅입니다.

 

[1]. 티스토리 댓글 도배 사고

[1-1]. 약 4시간 동안 쌓인 5,622개의 댓글

제 티스토리 댓글 도배가 시작된 시점은 2026년 8월 8일 저녁 8시경이었습니다. 당시 지인들과 저녁 식사 중이었는데, 핸드폰에 알람이 계속 울려서 뭔가 하고 봤더니, 티스토리 댓글 알람이었습니다. 대수롭지 않게 생각하다가 10분 넘게 알람이 울리고, 같은 이름과 내용의 글이 반복되는 것을 보고나서 이건 악성이다 라고 생각했습니다. 집에 돌아가기까지 4시간 정도 걸렸는데, 그 사이에 5천개가 넘는 댓글 폭탄이 쌓였습니다.

 

사실 회원 댓글만 허용하도록 했으면 발생하지 않았을 문제였는데(그럼에도 하려고 하면 할 수는 있지만), 일부로 비회원도 질문남길 수 있게 오픈해뒀다가 이런 일을 당하게 됐네요 ㅠ 이렇게 운영한 지가 8년이 넘었는데 참 씁쓸합니다.

 

아무튼 최종적으로 확인한 규모는 다음과 같습니다.

 

항목 확인 결과
피해 게시글 13개
수집된 전체 댓글 5,622건
도배로 분류된 댓글 5,571건
정상 댓글 51건
도배가 집중된 시각 2026-08-08 20:03 ~ 23:55
대댓글 형태의 도배 639건
본문이 완전히 비어 있는 댓글 31건

 

처음 눈으로 확인했을 때는 약 4,500건 정도라고 생각했는데, 실제로 수집해보니 5,500건이 넘었습니다. 특히 최근 글만 공격을 받은 것도 아니었습니다. 2023년에 작성한 글에도 173건이 들어와 있었고, 2025년에 작성한 논문 리뷰 글에도 12건이 들어와 있었습니다.

 

[1-2]. 신고만으로는 정리가 끝나지 않았다

처음에는 티스토리에서 제공하는 신고 기능을 이용했습니다. 고객센터에도 문의했고요. 제가 밖에 있었기 때문에 대응이 전혀 안되는 상황에서 할 수 있는 것은 일단 신고였습니다. 그리고 신고하면서, '개발자 측에 요청해서 댓글 삭제 요청'도 했었습니다.

그러나, 고객센터에서는 티스토리 내 댓글을 임의로 수정하거나 삭제하는 것은 어렵고, 문제가 되는 댓글을 직접 신고해달라는 안내를 받았습니다. 사실 신고 글 쓰면서도 '요구사항이 받아드려지지 안될 확률이 98% 이상이다'라고 생각은 했었지만, 역시나 안되었습니다. 

 

안녕하세요. 다음 고객센터입니다.
죄송스럽게도 Daum 고객센터에서 티스토리 내 존재하는 댓글을 임의로 수정/삭제로 도움드리기 어렵습니다.
고객님의 불편사항에 조금이라도 도움을 드리고자 요청하신 신고 내용은 내부 담당부서를 통해 별도로 전달하도록 하겠습니다.
다만, 담당부서에서는 접수된 건을 순차적으로 처리하고 있어 시간이 소요되는 점 양해 부탁드리며, 신고량이 많은 경우 처리가 지연될 수 있는 점 너른 양해 부탁드립니다.
아울러 문제가 되는 댓글은 댓글 내 우측 상단에 있는 점 3개를 눌러 직접 신고하셔도 동일하게 접수가 가능하오니 이용에 참고 부탁드립니다.

 

부분적으로 신고 처리는 되었습니다. 다만 제가 기대했던 결과와는 조금 달랐습니다. "신고가 처리된 댓글은 본문이 운영정책 위배로 관리자 삭제되었습니다." 라는 문구로 바뀌었지만 댓글 항목 자체는 그대로 남아 있었습니다. 그리고, '우측 상단에 있는 점 3개를 눌러 직접 신고하셔도...'라고 안내를 해주셨지만, 사실 이걸 모르는 것도 아니었습니다. 그럼에도 대응이 안되니까 신고를 했던거죠. 하지만, 제가 원하는 '댓글 삭제' 자체는 안되었습니다.

제 블로그에서는 이런 상태의 댓글이 5,380건이었는데요. 이걸 제가 하나하나 삭제하는 것은 말도 안되는 짓이라고 생각했습니다. 

 

[1-3]. 닉네임에 들어 있던 SQL Injection 문자열

댓글의 형태를 확인해보면 일반적인 광고성 스팸과는 조금 달랐습니다.

실제로 남아 있던 닉네임 일부는 아래와 같습니다.

GRLpGpAGykOXRFmb') OR 54=(SELECT 54 FROM PG_SLEEP(15))--
GRLpGpAG-1 waitfor delay '0:0:15' --
GRLpGpAG0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z
(select 198766*667891 from DUAL)
-1' OR 5*5=25 --
if(now()=sysdate(),sleep(15),0)

 

PostgreSQL의 PG_SLEEP, SQL Server의 waitfor delay, MySQL 계열의 sleep(), Oracle의 DUAL처럼 여러 데이터베이스를 대상으로 취약점을 확인할 때 볼 수 있는 패턴이 섞여 있었습니다.

따라서 특정 개인이 댓글을 하나하나 수작업으로 직접 작성했다기보다 자동화된 스캐너가 댓글 입력 필드를 대상으로 여러 형태의 문자열을 반복해서 보낸 것으로 판단했습니다. 가장 많이 등장한 닉네임은 GRLpGpAG였는데, 이 닉네임만 4,815건이었습니다.


[2]. 왜 댓글 관리 서비스를 직접 만들었나?

[2-1]. 관리 화면에서 5,000건을 직접 지우는 것은 현실적으로 어렵다

티스토리에서 댓글 한 건을 직접 삭제하려면 댓글 메뉴를 열고 수정/삭제 팝업으로 이동한 뒤 삭제를 실행해야 합니다.

 

댓글이 몇 개라면 문제가 없지만, 5,000건이 넘어가면 이야기가 달라집니다. 이걸 물리적으로 제가 하나하나 다 삭제하는 것은 말도 안되는 거니까요. 또한, 댓글 사이사이에는 이런 도배성 댓글이 아닌 실제 독자들의 댓글도 있었습니다. 이걸 제가 단순 반복하다가 삭제할 수 있다는 위험도 있었구요. 따라서 필요한 것은 단순히 빠르게 삭제하는 자동화도 필요하지만 삭제할 댓글과 남겨야 할 댓글을 먼저 구분하고, 사용자가 확인한 뒤 안전하게 삭제하는 흐름이라고 생각했습니다.

 

[2-2]. 처음 잡은 요구사항

처음부터 아래와 같은 요구사항을 정리하고 개발을 시작했습니다.

 

1. 특정 게시글뿐 아니라 블로그 전체에서 피해 글을 찾을 수 있을 것

2. 댓글을 전부 수집하고 실제 내용을 화면에서 확인할 수 있을 것

3. 공격 시간대, 닉네임, 본문 패턴 등을 이용해서 도배 후보를 자동으로 분류할 것

4. 전체 자동 삭제가 아니라 사용자가 직접 대상 댓글을 선택할 수 있을 것

5. 정상 댓글과 운영자 답글은 보호할 것

6. 삭제 전에 백업하고, 삭제 후 다시 조회해서 실제로 사라졌는지 확인할 것

7. 다른 티스토리 사용자도 Python만 있으면 실행할 수 있을 것

 

그래서 Selenium이나 Playwright로 화면 클릭을 자동화하는 방식보다는, 티스토리 페이지가 실제로 사용하는 HTTP 요청을 확인해서 그대로 재현하는 방향으로 접근했습니다.


[3]. 티스토리 댓글 조회/삭제 요청 분석

먼저 브라우저에서 댓글을 읽고 삭제할 때 어떤 요청이 발생하는지를 확인했습니다.

블로그 HTML과 댓글 영역, 댓글 수정/삭제 팝업에서 참조하는 JavaScript를 따라가면서 common.js, base.js, manage.js 등을 확인했습니다.

이 과정에서 댓글 조회와 삭제에 필요한 요청 형식을 확인할 수 있었습니다.

 

[3-1]. 댓글 목록 가져오기

댓글 목록을 가져오는 요청은 아래와 같습니다.

POST {블로그주소}/comment/view
Content-Type: application/x-www-form-urlencoded
Referer: {블로그주소}/{글번호}

id={글번호}&ts={커서}

 

응답은 아래와 같은 형태로 돌아옵니다.

{"data": {
  "count": 20,
  "firstCommentId": "23916146",
  "ts": "1786200055",
  "isMoreComments": true,
  "comment": "<li id='comment23916146' ...>...</li>"
}}

 

한 번에 댓글 20건 단위로 내려오고, 응답의 ts 값을 다음 요청에 전달하면 그보다 과거에 작성된 댓글을 계속 가져올 수 있습니다.

isMoreCommentsfalse가 될 때까지 반복하면 해당 게시글의 댓글을 전체 수집할 수 있습니다.

 

재미있었던 부분은 댓글 조회 자체에는 로그인 세션이 필요하지 않았다는 점입니다.

대신 Referer 헤더가 없으면 HTTP 412 응답이 발생했습니다. 처음에는 요청 주소와 body만 맞으면 될 것이라고 생각했는데, 이 헤더 때문에 조금 해맸었네요. 

 

[3-2]. 댓글 삭제 요청

댓글 삭제는 아래와 같은 요청으로 동작했습니다.

POST {블로그주소}/comment/delete/{댓글번호}
Content-Type: application/x-www-form-urlencoded
Referer: {블로그주소}/comment/manage/{댓글번호}
Cookie: (블로그 소유자 세션)

commentId={댓글번호}&password=&migPassword=&mode=delete&guestbookWrittenPage=-1

 

댓글 수정/삭제 팝업에서 사용하는 manage.js를 확인하면 수정과 삭제에 따라 form action이 각각 /comment/update/{id}, /comment/delete/{id}로 바뀌는 것을 확인할 수 있습니다.

그리고 블로그 소유자로 접근했을 때는 삭제 비밀번호를 별도로 요구하지 않았습니다.

결국 본인 블로그의 댓글을 삭제하기 위해 필요한 핵심 정보는 댓글 번호와 블로그 소유자의 로그인 세션이었습니다.

 

[3-3]. sitemap으로 피해 게시글 전체 찾기

처음에는 피해 글이 11개라고 생각했습니다. 하지만 공격이 최근 글에만 들어왔다는 보장이 없었기 때문에 블로그 전체를 확인할 필요가 있었는데요. 티스토리는 sitemap.xml을 제공하기 때문에 이를 이용했습니다.

GET {블로그주소}/sitemap.xml

 

sitemap에 들어 있는 URL 중 {블로그주소}/{숫자} 형태의 게시글 주소만 추출하고, 각 글의 댓글을 확인할 수 있도록 만들었습니다.

실제로 전체 628개 글을 스캔하면서 제가 모르고 있던 피해 글 2개를 추가로 발견했고, 최종 피해 게시글이 13개라는 것을 확인했습니다.


[4]. 로그인 세션은 어떻게 처리했나?

[4-1]. 아이디/비밀번호 대신 브라우저 쿠키 사용

댓글 조회는 공개 요청으로 가능하지만 삭제는 블로그 소유자의 로그인 세션이 필요합니다. 여기서 저는 카카오 계정의 아이디와 비밀번호를 프로그램에 입력받아 직접 로그인시키는 방식은 사용하지 않았습니다.

카카오 로그인과 2단계 인증 등의 흐름까지 프로그램이 처리하도록 만드는 것은 유지보수도 어렵고, 무엇보다 사용자의 계정 정보를 직접 다뤄야 하기 때문입니다. 그래서 사용자가 브라우저에서 정상적으로 로그인한 뒤, 이미 만들어진 세션 쿠키만 가져와 사용하는 방식으로 구현했습니다.

 

[4-2]. Copy as cURL을 그대로 붙여넣기

사용 방법은 최대한 간단하게 만들려고 했습니다. 개발자도구에서 특정 쿠키 값을 하나씩 찾아 복사하는 것보다, Network 탭의 요청을 Copy as cURL로 복사해서 통째로 붙여넣는 방식이 사용자 입장에서는 더 편합니다.

 

1. 본인 티스토리 블로그에 로그인합니다.

2. F12를 눌러 개발자도구를 엽니다.

3. Network 탭에서 페이지를 새로고침합니다.

4. 본인 블로그 주소로 시작하는 요청을 하나 선택합니다.

F12 개발자도구의 네트워크에서 본인 블로그 주소로 시작하는 요청을 선택

 

5. 우클릭 후 Copy as cURL을 선택합니다.

6. 서비스의 설정 화면에 그대로 붙여넣습니다.

Copy as cURL 선택 후, 만든 서비스에 붙여넣기

 

붙여넣은 문자열에서 쿠키 정보만 파싱하도록 만들었고, bash/cmd/PowerShell 형태의 cURL과 Cookie 헤더, 일반적인 name=value 형식도 처리할 수 있도록 했습니다. 쿠키를 등록하면 소유자만 접근할 수 있는 관리 URL에 요청을 보내서 실제 소유자 권한이 있는지도 진단합니다. 이 진단을 통과해야 실제 삭제 기능을 사용할 수 있습니다.

 

소유자 인증됨 상태

 

세션 쿠키는 사실상 로그인 상태를 그대로 가지고 있는 정보라서 저장과 노출에도 신경을 썼습니다. 쿠키 값은 로컬 데이터베이스에 암호화해서 저장하고, 웹 화면과 로그에는 실제 값을 표시하지 않습니다. 또한 기본 서버 주소를 127.0.0.1로 두어서 사용자 PC에서만 접근하도록 했습니다. 외부 접속을 허용하는 설정에서는 별도의 인증을 켜지 않으면 서버가 실행되지 않도록 막았습니다.


[5]. 도배 댓글을 어떻게 구분했나?

[5-1]. 머신러닝 대신 규칙 기반으로 분류

개인적으로 개발하면서 가장 고민했던 부분입니다. 왜냐하면, 저는 빠르게 삭제하고 싶었으니까요. 현생을 살아가야하니 시간을 더 쓸 여력도, 여유도 없었습니다. 그리고 5,000건을 빠르게 지우는 것보다 중요한 것은 5,000건 사이에 섞여 있는 정상 댓글을 지우지 않는 것도 고려해야 했습니다. 한 가지 긍정적인 면은 이번 경우에는 공격 패턴이 비교적 명확했습니다. 어떻게 보면 단순무식하다랄까요? 때문에 별도의 머신러닝 모델이나 LLM API를 사용하지 않고 규칙 기반으로 만들었습니다.

 

규칙 기반으로 만든 이유는 크게 세 가지입니다.

 

첫째, 그냥 현실적으로 시간적 여유가 없었다. 빠르게 룰 기반으로 처리하고 싶었다. 패턴이 다행히도 단순했기 때문이다.

둘째, 왜 특정 댓글이 스팸으로 분류되었는지를 화면에서 그대로 설명할 수 있다.

셋째, 다른 사용자가 자신의 도배 패턴에 맞게 rules.yaml만 수정하면 바로 적용할 수 있다.

 

이 규칙 기반의 동작 방식은 간단합니다. 각 규칙에는 조건과 가중치가 있고, 댓글이 규칙에 해당하면 점수를 더합니다. 최종 점수가 70점 이상이면 스팸, 40점 이상이면 의심, 그 아래는 정상으로 분류합니다.

기본 규칙은 다음과 같습니다.

 

규칙 가중치 조건
attack-window 55 지정한 도배 시간대에 작성된 댓글
injection-nickname 45 닉네임에 SQL Injection/스크립트 계열 문자열 포함
empty-content 40 댓글 본문이 비어 있음
admin-deleted-body 35 본문이 '운영정책 위배로 관리자 삭제되었습니다.'
nickname-burst 30 같은 닉네임으로 10건 이상 작성
minute-burst 25 같은 1분에 댓글이 10건 이상 작성됨
link-spam 20 본문에 외부 링크가 2개 이상 포함됨

 

여기서 한 가지 중요한 점은 어떤 규칙도 하나만 만족해서 바로 스팸이 되지 않도록 가중치를 잡았다는 것입니다.

예를 들어 특정 시간대에 댓글을 썼다는 이유만으로 삭제 대상이 되어서는 안 됩니다. 실제 독자가 공격 시간대에 우연히 댓글을 남길 수도 있기 때문이죠. 여러 조건이 함께 맞을 때 점수가 올라가도록 구성했고, 어떤 규칙이 적중했는지도 함께 저장해서 UI에서 확인할 수 있도록 했습니다.

 

[5-2]. 규칙은 YAML로 수정 가능

규칙은 config/rules.yaml에 저장됩니다.

rules:
  - id: attack-window
    description: 댓글 도배가 집중된 시간대
    weight: 55
    when:
      written_between:
        start: "2026-08-08 20:00"
        end: "2026-08-08 23:59"

  - id: empty-content
    description: 닉네임만 있고 본문이 비어 있는 댓글
    weight: 40
    when:
      content_equals: ""

 

작성 시각, 닉네임 정규식, 본문 정규식, 완전 일치, 같은 닉네임의 반복 횟수, 분당 댓글 수, 비밀댓글 여부, 대댓글 여부 등을 조건으로 사용할 수 있습니다.

설정 화면에서 YAML을 직접 편집할 수도 있고, Claude code나 ChatGPT Codex 등을 사용해서도 편하게 설정을 바꾸실 수 있으실겁니다.


[6]. 서비스 화면과 주요 기능, 삭제 테스트

이제 실제로 만든 화면을 살펴보겠습니다. 별도의 Node.js나 프론트엔드 빌드 과정 없이 Python 서버를 실행하면 브라우저에서 바로 사용할 수 있도록 만들었습니다. 화면은 대시보드, 게시글 관리, 댓글 관리, 작업 이력, 설정으로 구성되어 있습니다.

 

[6-1]. 대시보드와 게시글 관리

 

대시보드에서는 수집된 전체 댓글 수, 스팸/의심/정상 분포, 게시글별 댓글 수, 상위 닉네임 등을 한 번에 확인할 수 있습니다.

개인적으로 가장 보고 싶었던 것은 시간대별 댓글 유입 차트였습니다. 8월 8일 저녁 시간대에만 댓글 수가 급격하게 올라가는 형태가 보였고, 이 구간을 그대로 attack-window 규칙에 사용할 수 있었습니다.

 

 

게시글 관리에서는 댓글을 수집할 글을 직접 등록하거나 sitemap을 이용해 블로그 전체를 스캔할 수 있습니다.

글 URL을 그대로 입력해도 되고 게시글 번호만 입력해도 됩니다. 여기에 등록된 게시글을 모니터링 할 수 있습니다.

 

[6-2]. 댓글 관리

이 서비스에서 가장 핵심적인 화면이죠.

 

게시글, 작성 기간, 닉네임, 본문, 스팸 등급, 삭제 상태 등을 조건으로 댓글을 필터링할 수 있습니다. 댓글마다 닉네임, 본문, 작성 시각, 게시글, 스팸 등 정보를 확인할 수 있습니다. 저의 댓글 관리 리스트를 보시면 아시겠지만 굉장히 많은 매크로성, 스팸성 댓글이 많이 달린 것을 확인할 수 있습니다.

 

[6-3]. 삭제 작업과 진행 상황

삭제를 누른다고 바로 요청이 시작되지는 않습니다. 먼저 대상 건수와 스팸/의심/정상 등급별 건수를 보여주고, 사용자가 삭제라는 문구를 직접 입력해야 실제 실행 버튼이 활성화됩니다. 일종의 안전장치를 둔 셈입니다.

 

실행 중에는 Server-Sent Events(SSE)를 이용해 진행률을 화면에 실시간으로 보여줍니다.

작업이 끝난 뒤에는 성공/실패 건수와 실패한 댓글 목록을 확인할 수 있고, 실패한 항목만 다시 처리할 수도 있습니다.

 

[6-4]. 1건 시험 삭제

대량 삭제 전에 정상적으로 동작이 되는 지 먼저 1건 시험 삭제를 넣어두었습니다. 세션 쿠키가 실제로 유효한지, 블로그 소유자 권한이 맞는지, 삭제 요청에 대해 티스토리가 어떤 응답을 돌려주는지 한 건으로 먼저 확인합니다.

 

위 사진과 같은 댓글에 대해서 테스트를 해보려고 합니다.

서비스에서 삭제하기 전에는 댓글 수정/삭제 버튼을 눌렀을 시 삭제가 나오는 것을 확인할 수 있습니다.

 

1건 시험 삭제에서 해당 댓글을 삭제해봤습니다. 실제 테스트에서 삭제가 성공했을 때 HTTP 200 응답이 오는 것을 확인했고, 동일한 댓글을 다시 조회했을 때 블로그에서도 사라진 것을 확인했습니다.

 

 

이제 정상적으로 삭제가 되는 것을 확인할 수 있습니다.

남은 것은 전체 삭제겠죠?


[7]. 시스템 아키텍처와 기술 구성

단순한 개인용 스크립트로 끝낼 수도 있었지만, 다른 분들도 사용할 수 있도록 만들기 위해 기능별로 레이어를 나눴습니다.

전체 구조는 아래와 같습니다.

 

app/
  __main__.py        # 서버 실행 및 브라우저 오픈
  server.py          # FastAPI 애플리케이션

  config/            # .env, rules.yaml, targets.yaml
  domain/            # 엔티티, enum, 도메인 예외

  infrastructure/
    db/              # SQLite + SQLAlchemy async
    tistory/         # HTTP client, parser, cookie, rate limit
    security/        # 쿠키 암호화
    logging_setup.py # 한국시간 로그

  services/          # 수집, 탐색, 분류, 백업, 삭제, 작업 관리
  api/               # API route, schema, exception handling
  web/               # Jinja2 template + CSS/JavaScript

 

의존 방향은 가능한 한 API → Service → Infrastructure → Domain 형태로 한 방향을 유지하도록 구성했습니다.

주요 기술 스택은 다음과 같습니다.

 

영역 기술 선택 이유
Web/API FastAPI + Uvicorn 비동기 처리와 가벼운 로컬 웹 서비스 구성
HTTP Client httpx AsyncClient 비동기 요청, connection pooling
Database SQLite(WAL) + SQLAlchemy async 별도 DB 서버 설치 없이 로컬 파일로 관리
HTML Parsing BeautifulSoup 댓글 HTML 파싱, 별도 컴파일 의존성 최소화
Frontend Jinja2 + Vanilla JavaScript Node.js/npm 없이 Python만으로 실행
Config pydantic-settings + YAML 환경설정 타입 검증과 규칙 편집
Security Fernet 로컬에 저장되는 세션 쿠키 암호화

 

[7-1]. 대량 요청을 위한 동시성 제어

댓글을 수천 건 처리한다고 해서 무작정 동시에 요청을 보내도록 만들지는 않았습니다. 토큰 버킷으로 초당 요청 수를 제한하고, 세마포어로 동시에 실행되는 요청 수를 별도로 제한했습니다. HTTP 429, 5xx, timeout과 같이 재시도할 가치가 있는 오류만 지수 백오프와 jitter를 적용해 재시도하고, 권한 오류나 존재하지 않는 댓글처럼 다시 보내도 의미가 없는 요청은 바로 실패로 처리하도록 했습니다.

기본 삭제 설정은 초당 4건, 동시 요청 3건입니다. 이 설정을 기준으로 약 5,500건을 처리하면 이론적으로 20분 안팎이 필요합니다. 요청 속도를 더 높일 수는 있지만 티스토리 서비스에 불필요한 부담을 주거나 계정에 제한이 생길 가능성을 고려해서 기본값을 보수적으로 잡았습니다.


[8]. Claude Code를 활용한 개발과 교차 검증

이번 도구는 Claude Code를 활용해서 개발했습니다.

다만 바로 코드를 생성하는 방식으로 진행하지 않고, 먼저 요구사항과 시스템 구조를 정리한 뒤 기능을 나누고 병렬로 구현하는 방식으로 작업했습니다. 큰 흐름은 계획 → 설계 문서 → 골격 구현 → 기능별 병렬 작업 → 교차 검증 순서였습니다.

 

수집/탐색, 스팸 규칙 엔진과 백업, 웹 UI, 인프라 테스트 등을 나눠서 작업했고, 이후 별도의 리뷰 역할을 둬서 단순히 "잘 동작하는가"가 아니라 "어떤 상황에서 깨지는가"를 찾도록 했습니다.

이 과정에서 실제 운영 시 문제가 될 수 있는 여러 케이스를 발견했습니다.

 

발견한 문제 수정한 내용
필터를 바꿔도 이전 선택이 남음 필터 변경 시 선택을 초기화하고, 서버에서도 정상 댓글이 섞이면 다시 확인하도록 변경
재분류 작업이 DB 쓰기 잠금을 오래 점유 일괄 UPDATE와 트랜잭션 분할로 처리 시간을 크게 단축
세션 만료가 대량 실패로 이어질 수 있음 연속 실패를 감지하는 서킷 브레이커와 세션 오류 판정 강화
1건 시험 삭제가 보호 규칙을 우회할 수 있음 시험 삭제에도 운영자/화이트리스트 보호와 감사 로그 적용
실패로 기록했지만 실제로는 삭제된 경우 작업 후 해당 게시글을 다시 수집해서 실제 상태와 대조

 

최종적으로 Python 3.9와 3.12 환경에서 테스트 462개를 통과하는 것까지 확인했습니다.

개인적으로 이번 작업에서는 기능을 빠르게 추가하는 것보다, 삭제처럼 복구가 어려운 작업에서 실패 시나리오를 먼저 찾는 것이 훨씬 중요하다는 것을 다시 느꼈습니다.


[10]. 설치 및 사용 방법

Python 3.9 이상 환경이면 실행할 수 있습니다.

별도의 데이터베이스 서버나 Node.js는 필요하지 않습니다.

 

[10-1]. 설치

git clone https://github.com/lsjsj92/tistory-comment-spam-cleaner.git
cd tistory-comment-spam-cleaner

python -m venv .venv

# macOS / Ubuntu
source .venv/bin/activate

# Windows PowerShell
# .\.venv\Scripts\Activate.ps1

pip install -r requirements.txt

 

이미 사용하고 있는 conda 환경이 있다면 별도의 가상환경을 새로 만들지 않고 해당 환경에 설치해도 됩니다.

 

[10-2]. 실행

python -m app

 

실행하면 기본적으로 http://127.0.0.1:8765 주소로 서버가 실행되고 브라우저가 열립니다.

첫 실행 시 필요한 설정 파일이 생성됩니다.

다른 티스토리 블로그에서 사용할 때 기본적으로 확인해야 할 설정은 아래 세 가지입니다.

 

1. .envAPP_BLOG_URL: 본인 티스토리 블로그 주소

2. 수집 대상 게시글: 직접 등록하거나 sitemap 스캔 사용

3. config/rules.yamlattack-window: 본인이 실제로 도배를 당한 시간대

 

그다음 댓글을 수집하고 대시보드에서 유입 패턴을 확인한 뒤, 스팸 분류 결과를 검토하고 1건 시험 삭제를 먼저 수행하는 것을 권장합니다.

실제 대량 삭제는 반드시 본인이 삭제 대상을 충분히 확인한 뒤 실행하시는 것이 좋습니다.


[11]. 실제 사용 후기

실제로 서비스를 사용해서 전체 댓글을 삭제해보려고 합니다. 제 게시글 중 가장 많은 댓글이 달린 Claude code agent 를 소개하는 글로 테스트 해보려고 합니다.

 

 

해당 게시글은 1000건이 넘는 댓글이 달렸습니다. 이 댓글들을 전부 삭제하려고 합니다.

 

 

한 번 최종 컨펌을 받습니다. 정말 삭제할 것인가죠. 왜냐하면 삭제를 하게 되면 되돌릴 수가 없기 때문입니다.

다른 실제 독자들의 댓글이 있는지 확인해보시고, 삭제 실행을 눌러봅니다.

 

 

진행 상황은 위와 같이 계속 모니터링 할 수 있습니다. 숫자가 바뀌는 것도 확인 가능하며, 프로그레스바에서도 확인할 수 있죠.

전부 완료가 되면 댓글 삭제 완료가 나오게 됩니다.

 

 

이와 같이 댓글이 전부 깔끔히 삭제되었습니다.


마무리

이번 포스팅에서는 제 티스토리 블로그에 발생한 대규모 댓글 도배와, 이를 정리하기 위해 만든 티스토리 댓글 정리 서비스에 대해서 소개했습니다. 

개인적으로 필요해서 시작한 프로젝트이지만, 티스토리에서 비슷한 형태의 댓글 도배를 겪으신 분들이라면 설정값과 규칙만 자신의 상황에 맞게 수정해서 사용하실 수 있도록 코드를 공개해두었습니다. 다만 삭제는 되돌리기 어렵기 때문에 전체 실행 전에 반드시 분류 결과와 삭제 대상을 확인하고, 1건 시험 삭제부터 진행하시길 권장드립니다.

 

비록 부족한 글이지만 저와 비슷한 일을 겪고 계신 분들에게 도움이 되시길 바랍니다.

긴 글 읽어주셔서 감사합니다.

 

혹시라도 저에게 연락을 주시고 싶으시다면,

 

으로 연락 남겨주시면 됩니다!

반응형
Comments