[무료 배포] 티스토리 맞구독 확인 프로그램, 답답해서 직접 만들었습니다!
안녕하세요! 여러분의 투자 파트너이자 'WeJump 투자전략연구소'입니다. 😊
티스토리 블로그를 정성껏 운영하시면서 '맞구독' 관리, 다들 어떻게 하고 계시나요? 구독자는 점점 늘어가는데, "과연 이 중에서 나를 계속 구독해주고 계신 분은 누구일까?", "나는 구독했는데 상대방은 취소한 건 아닐까?" 궁금할 때가 참 많죠.
하나하나 수작업으로 찾아보자니 눈이 빠질 것 같고, 구글에 아무리 검색해 봐도 티스토리용 맞구독 체크 프로그램은 마땅한 게 없더라고요.
"없으면 어떡한다? 내가 직접 만든다!"
그래서 제 블로그에 방문해 주시는 소중한 이웃분들과 구독자님들을 위해, 제가 직접 '티스토리 맞구독 확인 프로그램 (Tistory Follow Checker)'을 개발했습니다. 물론 조건 없이 무료로 배포합니다! 🎉
💡 WeJump표 맞구독 확인 프로그램의 장점
단순히 화면에 보이는 글자만 대충 긁어오는 허술한 프로그램이 아닙니다. 확실하게 만들었습니다.
- 소름 돋는 정확성 (데이터 기반)
- 겉보기에만 의존하는 크롤링 방식이 아닙니다. Playwright라는 강력한 도구를 사용해 티스토리 내부 API 데이터를 직접 비교하므로 결과가 매우 정확합니다.
- 절대적인 안전성 (내 PC에서만!)
- 혹시 내 계정 정보가 어디로 새어나가지 않을까 걱정되시나요? 이 프로그램은 오직 여러분의 로컬 PC 안에서만 돌아갑니다. 비밀번호나 개인정보를 절대 가로채지 않으니 안심하고 사용하세요.
🚀 왕초보도 따라 하는 초간단 사용 방법
파이썬(Python)이나 터미널(CMD) 같은 단어가 낯설어도 겁먹지 마세요! 아래 순서대로 천천히 따라 하시면 됩니다.
1단계: Python 설치하기
- PC에 Python 3.7 이상 버전이 설치되어 있어야 합니다. (없으시다면 파이썬 공식 홈페이지에서 다운로드해 주세요!)
2단계: 필수 패키지 설치하기
- 윈도우 검색창에 cmd를 검색해서 '명령 프롬프트(터미널)'를 열어줍니다.
- 그리고 아래 명령어를 복사해서 붙여넣고 엔터를 쳐주세요.
pip install -r requirements.txt
playwright install chromium
3단계: 프로그램 실행하기
- 패키지 설치가 끝났다면, 터미널 창에 아래 명령어를 입력해서 프로그램을 실행합니다.
python checker.py
4단계: 브라우저 로그인 & 자동 분석
- 프로그램이 실행되면서 인터넷 브라우저 창이 하나 짠! 하고 열릴 겁니다.
- 평소처럼 티스토리에 로그인을 해주세요.
- 로그인 후 피드(Feed) 페이지로 넘어가면, 프로그램이 알아서 척척 분석을 시작합니다. (이때는 가만히 지켜보시면 됩니다 😎)
5단계: 결과 확인하기
- 분석이 끝나면 터미널 창에 '내가 구독했지만 나를 맞구독하지 않은' 블로그 목록이 출력됩니다.
- 상세한 결과는 폴더 안에 tistory_follow_report.txt 파일로 예쁘게 저장되니 언제든 열어보실 수 있습니다.
보너스 단계: 대시보드 만들기
- 보기 좋은 웹페이지 형태로 결과를 보고 싶으시다면, 터미널에 아래 명령어를 한 번 더 입력해 주세요.
- 예쁜 대시보드 파일이 뚝딱 생성됩니다!
python make_html.py
🎁 다운로드 및 마무리
블로그를 키워가는 과정은 혼자만의 외로운 싸움일 때가 많습니다. 하지만 이렇게 서로 소통하고 좋은 도구를 나누다 보면 훨씬 수월하고 즐겁게 운영하실 수 있을 거라 믿습니다.
프로그램 사용해 보시고 유용하셨다면, 혹은 궁금한 점이 있으시다면 언제든 편하게 댓글 남겨주세요! 여러분의 공감과 댓글은 제가 더 좋은 자료를 나누는 데 큰 힘이 됩니다.
그럼 오늘도 성투하시고, 즐거운 블로깅 하세요!
📄 부록: 파이썬 소스 코드 (checky.py, 전체 파일은 위 다운로드 파일에)
import os
import sys
import time
from playwright.sync_api import sync_playwright
def run_persistent_checker():
with sync_playwright() as p:
print("="*60)
print("티스토리 전수 조사 (Playwright 로딩 검증 강화 모드)")
print("="*60)
profile_dir = os.path.join(os.getcwd(), "tistory_session")
if not os.path.exists(profile_dir): os.makedirs(profile_dir)
browser_context = p.chromium.launch_persistent_context(
user_data_dir=profile_dir,
headless=False,
args=["--no-sandbox"]
)
page = browser_context.new_page()
print("1. 티스토리 피드로 이동합니다.")
page.goto("https://www.tistory.com/feed")
try:
page.wait_for_selector(".link_info, .txt_num, a:has-text('로그아웃')", timeout=10000)
print("✅ 로그인 확인 완료.")
except:
print("❗ 로그인이 필요합니다. 로그인을 완료해 주세요.")
page.wait_for_selector(".link_info, .txt_num, a:has-text('로그아웃')", timeout=300000)
print("✅ 로그인 확인 완료.")
def collect_data(base_url, target_name):
print(f"\n[{target_name}] 수집 시작...")
# 페이지 내 아이템 로드를 대기하는 보조 함수
def wait_for_page_items(timeout=15):
deadline = time.time() + timeout
while time.time() < deadline:
has_items = page.evaluate("!!document.querySelector('a.info_g, a.box_desc, a.link_info')")
if has_items:
return True
time.sleep(0.5)
return False
# 현재 페이징 컨트롤에서 active 페이지 번호를 읽는 보조 함수
def get_active_page_num():
return page.evaluate("""
(() => {
let em = document.querySelector('.paging_tistory em.link_paging');
if (!em) return null;
return em.innerText.replace('현재페이지', '').trim();
})()
""")
# 첫 페이지 로드
page.goto(base_url)
if not wait_for_page_items(20):
print(" [WARN] 첫 페이지 아이템 로드가 지연되어 추가 대기합니다...")
time.sleep(10)
time.sleep(5) # 렌더링 안정화
all_urls = set()
current_page = 1
while True:
# 1. 하단 콘텐츠까지 로드하기 위해 스크롤
page.evaluate("window.scrollBy(0, 5000)")
time.sleep(3)
# 2. 블로그 URL 수집
urls = page.evaluate("""
Array.from(document.querySelectorAll('a.info_g, a.box_desc, a.link_info'))
.map(a => a.href)
.filter(href => href && href.includes('.tistory.com') && !href.includes('www.tistory.com'))
""") or []
prev_count = len(all_urls)
for u in urls:
clean_url = u.split('?')[0].split('/manage')[0].rstrip('/')
if clean_url:
all_urls.add(clean_url)
new_found = len(all_urls) - prev_count
print(f" > {current_page}페이지: {len(urls)}개 발견, 신규 {new_found}개 (누적: {len(all_urls)}명)")
# 3. 다음 페이지 버튼 탐색 및 클릭
next_page = current_page + 1
next_page_str = str(next_page)
click_script = f"""
(() => {{
let links = document.querySelectorAll('.paging_tistory a.link_paging');
for (let link of links) {{
if (link.innerText.trim() === '{next_page_str}') {{
link.click();
return 'NUM_{next_page_str}';
}}
}}
let nextBtn = document.querySelector('.paging_tistory .ico_next');
if (nextBtn) {{
nextBtn.click();
return 'NEXT_BATCH';
}}
return 'NOT_FOUND';
}})()
"""
click_result = page.evaluate(click_script)
# 버튼을 찾지 못한 경우, 스크롤 후 재시도 (최대 3회)
retry_count = 0
while (not click_result or click_result == "NOT_FOUND") and retry_count < 3:
print(f" [!] {next_page_str}페이지 버튼을 찾지 못해 스크롤 후 재시도 중... ({retry_count + 1}/3)")
page.evaluate("window.scrollBy(0, 3000)")
time.sleep(3)
click_result = page.evaluate(click_script)
retry_count += 1
if not click_result or click_result == "NOT_FOUND":
print(" 버튼을 찾지 못했습니다. 마지막 페이지입니다.")
break
print(f" 액션: {click_result} 클릭됨. 페이지 전환 대기...")
# 4. 페이지 전환 감지 로직 (<em class="link_paging"> 변화 추적)
target_num = next_page_str if click_result.startswith("NUM_") else None
transitioned = False
for _ in range(40): # 최대 20초 대기 (0.5초 간격)
active = get_active_page_num()
if target_num:
# 숫자 버튼 클릭 시: 정확한 번호 일치 확인
if active == target_num:
transitioned = True
break
else:
# '다음페이지' 클릭 시: 현재 페이지 번호에서 벗어남을 확인
if active and active != str(current_page):
next_page = int(active) # 실제 이동한 페이지 번호 반영
transitioned = True
break
time.sleep(0.5)
if transitioned:
print(f" ✅ {next_page}페이지 전환 확인 완료.")
# 전환 후 새로운 아이템들이 렌더링될 때까지 대기
wait_for_page_items(10)
time.sleep(2)
else:
print(f" [WARN] 페이지 전환 감지 실패 (active={get_active_page_num()}). 5초 추가 대기 후 계속합니다.")
time.sleep(5)
wait_for_page_items(10)
current_page = next_page
print(f" > {target_name} 최종 수집 완료: 총 {len(all_urls)}명")
return all_urls
# 실행
subscribers = collect_data("https://www.tistory.com/feed?type=follower", "나를 구독 중")
followings = collect_data("https://www.tistory.com/feed?type=following", "내가 구독 중")
not_mutual = sorted(list(followings - subscribers))
print("\n" + "="*50)
print(f"📊 최종 분석 결과")
print(f"- 내가 구독 중: {len(followings)}명")
print(f"- 나를 구독 중: {len(subscribers)}명")
print(f"- 맞구독 안 함: {len(not_mutual)}명")
print("="*50)
if not_mutual:
print(f"\n⚠️ 맞구독하지 않은 블로그 ({len(not_mutual)}명):")
for url in not_mutual:
print(f"- {url}")
with open("tistory_final_report.txt", "w", encoding="utf-8") as f:
f.write(f"티스토리 맞구독 결과 (정밀 분석)\n")
f.write(f"구독 중({len(followings)}) | 구독자({len(subscribers)})\n\n")
for url in not_mutual:
f.write(f"{url}\n")
else:
print("\n✅ 모두 맞구독 중입니다!")
input("\n모든 분석이 완료되었습니다. 엔터를 눌러 종료하세요...")
browser_context.close()
if __name__ == "__main__":
run_persistent_checker()

