개발/자동화

[코드] 티스토리 자동 좋아요+비로그인 댓글(#9)

kimchangmin02 2025. 7. 26. 07:07

import time
import random
import string
import traceback
from selenium import webdriver
from seleniuhttp://m.webdriver.common.by import By
from seleniuhttp://m.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from seleniuhttp://m.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from seleniuhttp://m.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import NoSuchElementException, TimeoutException

# --- 헬퍼 함수 정의 ---

def human_like_mouse_move(driver, element):
    """지정한 요소로 사람처럼 마우스를 부드럽게 이동시킵니다."""
    actions = ActionChains(driver)
    
    element_location = element.location
    element_size = element.size
    center_x = element_location['x'] + element_size['width'] / 2
    center_y = element_location['y'] + element_size['height'] / 2

    start_x = random.randint(0, driver.get_window_size()['width'])
    start_y = random.randint(0, driver.get_window_size()['height'])
    
    actions.move_by_offset(start_x, start_y)
    actions.pause(random.uniform(0.2, 0.4))
    actions.move_to_element_with_offset(element, random.randint(-20, 20), random.randint(-20, 20))
    actions.pause(random.uniform(0.3, 0.6))
    actions.move_to_element(element)
    
    actions.perform()
    print("    ㄴ 사람처럼 마우스 커서를 목표 지점으로 이동했습니다.")
    time.sleep(random.uniform(0.5, 1.0))

def extract_post_text(driver):
    """게시물 본문 내용을 추출하여 단어 리스트로 반환합니다."""
    print("게시물 본문 내용을 추출합니다...")
    try:
        wait = WebDriverWait(driver, 10)
        post_body = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.contents_style")))
        text = post_body.text
        translator = str.maketrans(string.punctuation, ' ' * len(string.punctuation))
        words = text.translate(translator).split()
        meaningful_words = [word for word in words if len(word) > 1]
        
        if not meaningful_words:
            print("    ㄴ 경고: 의미있는 단어를 찾지 못했습니다.")
            return []
        print(f"    ㄴ 성공: {len(meaningful_words)}개의 의미있는 단어를 추출했습니다.")
        return meaningful_words
    except (NoSuchElementException, TimeoutException):
        print("    ㄴ 실패: 본문 내용 영역을 찾지 못했습니다.")
        return []
    except Exception as e:
        print(f"    ㄴ 실패: 본문 내용 추출 중 오류 발생 - {e}")
        return []

def generate_random_comment(word_list):
    """단어 리스트에서 무작위로 단어를 선택해 댓글을 생성합니다."""
    print("추출된 단어를 바탕으로 랜덤 댓글을 생성합니다...")
    if not word_list:
        return "포스팅 잘 보고 갑니다. 좋은 하루 되세요."
    num_words = random.randint(4, 9)
    if len(word_list) < num_words:
        num_words = len(word_list)
    random_words = random.sample(word_list, num_words)
    comment = " ".join(random_words)
    print(f"    ㄴ 생성된 댓글: \"{comment}\"")
    return comment

def generate_random_credentials():
    """랜덤으로 비로그인용 이름과 비밀번호를 생성합니다."""
    print("비로그인용 랜덤 이름과 비밀번호를 생성합니다...")
    name = ''.join(random.choices(string.ascii_lowercase, k=random.randint(5, 8)))
    password = ''.join(random.choices(string.ascii_letters + string.digits, k=random.randint(8, 12)))
    print(f"    ㄴ 생성된 정보: 이름={name}, 비밀번호={password}")
    return name, password

# --- 메인 코드 ---
BASE_URL = "https://kimchangmin02.tistory.com"
TARGET_URLS = [f"{BASE_URL}/{i}" for i in range(1, 55)]
WAIT_TIMEOUT = 15

print("="*60)
print(" TISTORY '좋아요' + '댓글' 자동화 봇을 시작합니다. (통합 최종 버전) ")
print(f" 총 {len(TARGET_URLS)}개의 게시물을 대상으로 무작위 작업을 반복합니다.")
print(" 프로그램을 종료하려면 이 창에서 Ctrl + C 를 누르세요.")
print("="*60)

while True:
    driver = None
    try:
        target_page = random.choice(TARGET_URLS)
        print(f"\n--- [작업 시작] '{target_page}' 게시물을 대상으로 새 작업을 시작합니다. ---")
        
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--start-maximized")
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        wait = WebDriverWait(driver, WAIT_TIMEOUT)
        
        driver.get(target_page)
        print(f"'{target_page}' 페이지로 성공적으로 이동했습니다.")
        
        # 1. 글을 읽는 것처럼 행동 (랜덤 스크롤 및 대기)
        print("\n--- 1. 페이지 탐색 (스크롤) ---")
        reading_time = random.uniform(5, 12)
        print(f"사람처럼 보이기 위해 {reading_time:.2f}초 동안 글을 읽는 척합니다...")
        
        scroll_count = random.randint(2, 4)
        for i in range(scroll_count):
            scroll_depth = (i + 1) / (scroll_count + 1)
            driver.execute_script(f"window.scrollTo({{ top: document.body.scrollHeight * {scroll_depth}, behavior: 'smooth' }});")
            time.sleep(random.uniform(1.5, 3.0))
        print("페이지 스크롤을 완료했습니다.")

        # 2. '좋아요' 누르기
        print("\n--- 2. '좋아요' 클릭 ---")
        try:
            like_button_selector = "div[id^='reaction-'] button.btn_post"
            like_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, like_button_selector)))
            
            driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", like_button)
            time.sleep(random.uniform(1, 2))
            
            human_like_mouse_move(driver, like_button)
            like_button.click()
            print("    ㄴ 성공! '좋아요' 버튼을 클릭했습니다.")
            time.sleep(random.uniform(0.5, 1.5))
        except Exception as e:
            print(f"    ㄴ '좋아요' 버튼을 찾거나 클릭할 수 없습니다. (이미 눌렀거나, 버튼이 없을 수 있음) - {e}")

        # 3. 댓글 작성
        print("\n--- 3. 댓글 작성 ---")
        words = extract_post_text(driver)
        if not words:
            raise Exception("본문 단어 추출에 실패하여 이번 작업을 중단합니다.")
            
        comment_text = generate_random_comment(words)
        user_name, user_password = generate_random_credentials()

        print("댓글 작성 영역으로 이동합니다...")
        try:
            comment_area_selector = "button.tt-btn_register"
            comment_form_area = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, comment_area_selector)))
            driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", comment_form_area)
            time.sleep(random.uniform(1, 2))
            human_like_mouse_move(driver, comment_form_area)
        except Exception:
             raise Exception("댓글 작성 영역을 찾지 못했습니다. CSS 선택자를 확인하세요.")

        print("댓글 폼에 정보를 입력합니다...")
        try:
            name_input_selector = "input[placeholder='이름']"
            password_input_selector = "input[placeholder='비밀번호']"
            comment_textarea_selector = "div.tt-cmt"

            name_input = driver.find_element(By.CSS_SELECTOR, name_input_selector)
            password_input = driver.find_element(By.CSS_SELECTOR, password_input_selector)
            comment_textarea = driver.find_element(By.CSS_SELECTOR, comment_textarea_selector)

            for char in user_name:
                name_input.send_keys(char)
                time.sleep(random.uniform(0.05, 0.15))
            time.sleep(random.uniform(0.5, 1.0))
            for char in user_password:
                password_input.send_keys(char)
                time.sleep(random.uniform(0.05, 0.15))
            time.sleep(random.uniform(0.5, 1.0))
            for char in comment_text:
                comment_textarea.send_keys(char)
                time.sleep(random.uniform(0.06, 0.18))
            print("    ㄴ 이름, 비밀번호, 댓글 내용 입력을 완료했습니다.")
        except Exception:
            raise Exception("이름/비밀번호/댓글 입력 필드를 찾지 못했습니다.")

        print("댓글 '등록' 버튼을 클릭합니다...")
        try:
            submit_button_selector = "button.tt-btn_register"
            submit_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, submit_button_selector)))
            driver.execute_script("arguments[0].click();", submit_button)
            print("    ㄴ 성공! 댓글을 성공적으로 등록했습니다.")
        except TimeoutException:
            raise Exception("댓글 등록 버튼이 시간 내에 활성화되지 않았습니다.")
        except Exception:
            raise Exception("댓글 등록 버튼을 찾거나 클릭하는 데 실패했습니다.")

        final_wait_time = random.uniform(3, 6)
        print(f"\n작업 완료! {final_wait_time:.2f}초 더 머문 뒤 브라우저를 닫습니다.")
        time.sleep(final_wait_time)

    except Exception as e:
        print(f"[오류 발생] 작업 중 예기치 않은 오류가 발생했습니다.")
        print(f"오류 내용: {e}")
        error_sleep_time = random.uniform(5, 10)
        print(f"오류로 인해 {error_sleep_time:.2f}초 대기합니다.")
        time.sleep(error_sleep_time)

    finally:
        if driver:
            print("브라우저를 종료합니다.")
            driver.quit()

    break_time = random.uniform(30, 90)
    print(f"\n>>> 다음 작업을 위해 {break_time:.2f}초간 충분히 휴식합니다...")
    time.sleep(break_time)

 

 

 

 

 

 

근데 가끔 댓글 영역을 못찾음

 

지연 로딩 (Lazy Loading)과의 눈치 싸움

"지연 로딩"이란 웹 페이지의 성능을 높이기 위해 사용하는 최신 기술입니다.
페이지를 처음 열 때 화면에 보이지 않는 무거운 부분(예: 댓글창, 고화질 이미지)은 일단 로드하지 않고, 사용자가 스크롤을 내려서 그 근처에 도달했을 때 비로소 로드하는 방식입니다.

이것이 바로 "될 때도 있고, 안 될 때도 있는" 현상의 원인입니다.

  • 성공하는 경우 (우리가 이겼을 때):
    1. 네트워크 상태가 좋거나 다른 이유로 페이지의 모든 스크립트가 빨리 로드됩니다.
    2. 우리 코드가 스크롤을 내렸을 때, 댓글창을 로드하는 스크립트가 이미 준비되어 즉시 댓글창을 만들어 줍니다.
    3. 코드는 성공적으로 댓글창을 찾아 작업을 수행합니다.
  • 실패하는 경우 (지연 로딩이 이겼을 때):
    1. 네트워크가 미세하게 느리거나, 다른 스크립트 처리가 늦어집니다.
    2. 우리 코드가 스크롤을 내려 댓글창을 찾으려고 합니다.
    3. 하지만 바로 그 순간, 아직 브라우저의 '지연 로딩' 스크립트가 댓글창 HTML을 다 만들지 못했습니다.
    4. 코드는 "아직 댓글창이 없네?" 라고 판단하고, 15초를 기다리다가 결국 시간 초과(Timeout) 오류를 발생시킵니다.

즉, 우리 코드와 티스토리의 '지연 로딩' 스크립트 간에 미세한 타이밍 경쟁이 벌어지고 있는 것입니다.

해결책: 더 확실하게 로딩을 유도하고, 너그럽게 기다리기

이 문제를 해결하려면, 우리 코드가 좀 더 똑똑하게 행동해야 합니다.

  1. 확실한 로딩 유도: 페이지 맨 아래까지 스크롤하여, "여기까지 다 봤으니 숨겨진 거 전부 다 로드해!" 라는 신호를 브라우저에 확실하게 줍니다.
  2. 자바스크립트 실행 시간 부여: 스크롤 직후, 아주 짧은 강제 대기 시간(0.5초 등)을 주어 브라우저가 지연 로딩 스크립트를 실행할 시간을 벌어줍니다.
  3. 안정적인 기다림: 그 후에 WebDriverWait를 사용해 댓글창이 나타날 때까지 너그럽게 기다립니다.

결론부터 말씀드리면, "우리 봇이 너무 빨라서, 웹사이트가 미처 준비하기도 전에 댓글창을 내놓으라고 소리치기 때문"입니다.

이 상황을 **'셀프 서비스 로봇 가게'**에 비유해 보겠습니다.

  • 가게 주인 (웹사이트): 아주 효율적입니다. 손님이 들어오면 일단 가장 잘 보이는 상품(글 본문)만 진열해 둡니다. 무겁고 창고 깊숙이 있는 상품(댓글창)은 손님이 직접 창고 근처까지 걸어와서 달라고 할 때만 그제서야 꺼내서 조립해 줍니다. (이것을 **'지연 로딩'**이라고 합니다.)
  • 우리 봇 (손님): 성격이 매우 급합니다. 가게에 들어가자마자 창고 쪽으로 쏜살같이 달려가서 "댓글창 주세요!" 라고 외칩니다.

이제 여기서 **두 가지 상황 (성공과 실패)**이 발생합니다.

  • 성공할 때: 그날따라 가게 주인의 손이 매우 빨랐습니다. 우리 봇이 "주세요!"라고 외치는 순간, 정확히 그 타이밍에 조립이 끝나서 "여기 있습니다!" 하고 댓글창을 건네줍니다.
  • 실패할 때 (지금 겪는 문제): 우리 봇이 "주세요!"라고 외쳤지만, 가게 주인은 이제 막 조립을 시작했습니다. 불과 0.5초만 기다리면 완성될 텐데, 성격 급한 우리 봇은 "에이, 없잖아!" 하고 그냥 가게를 나가버립니다. (이것이 바로 **시간 초과 오류(Timeout Exception)**입니다.)

"왜 될 때도 있고 안 될 때도 있는가?"
바로 이 0.5초의 미세한 타이밍 차이가 네트워크 상태나 컴퓨터의 컨디션에 따라 매번 달라지기 때문입니다. 봇과 웹사이트 간의 눈치 싸움에서 가끔 우리 봇이 지는 것입니다.

해결책은 무엇인가?
우리 봇의 성격을 바꿔주는 것입니다. "주세요!" 라고 한 번만 외치고 포기하는 대신, "그 자리에 서서 댓글창이 조립되어 나타날 때까지 10초 정도는 끈기 있게 기다려라" 라고 가르치는 것입니다. 제가 마지막에 드린 v3 코드가 바로 이 '끈기 있는 봇'을 만드는 코드입니다.

 

 

 

 

 

 

수정된 코드

 

import time
import random
import string
import traceback
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import NoSuchElementException, TimeoutException

# --- 헬퍼 함수 정의 (이전과 동일) ---
def human_like_mouse_move(driver, element):
    actions = ActionChains(driver)
    actions.move_to_element(element).perform()
    print("    ㄴ 사람처럼 마우스 커서를 목표 지점으로 이동했습니다.")
    time.sleep(random.uniform(0.5, 1.0))

def extract_post_text(driver):
    try:
        wait = WebDriverWait(driver, 10)
        post_body = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.contents_style")))
        text = post_body.text
        translator = str.maketrans(string.punctuation, ' ' * len(string.punctuation))
        words = text.translate(translator).split()
        return [word for word in words if len(word) > 1]
    except Exception:
        return []

def generate_random_comment(word_list):
    if not word_list: return "포스팅 잘 보고 갑니다. 좋은 하루 되세요."
    num_words = random.randint(4, 9)
    if len(word_list) < num_words: num_words = len(word_list)
    return " ".join(random.sample(word_list, num_words))

def generate_random_credentials():
    name = ''.join(random.choices(string.ascii_lowercase, k=random.randint(5, 8)))
    password = ''.join(random.choices(string.ascii_letters + string.digits, k=random.randint(8, 12)))
    return name, password

# --- 메인 코드 ---
BASE_URL = "https://kimchangmin02.tistory.com"
TARGET_URLS = [f"{BASE_URL}/{i}" for i in range(1, 60)]
WAIT_TIMEOUT = 15

print("="*60)
print(" TISTORY 자동화 봇 (3회 반복 테스트 버전) ")
print(f" 총 {len(TARGET_URLS)}개의 게시물을 대상으로 3번의 작업을 수행합니다.")
print("="*60)

# --- [수정] 무한 루프를 3번 반복하는 for문으로 변경 ---
for i in range(3):
    driver = None
    try:
        # --- [수정] 몇 번째 작업인지 표시 ---
        print(f"\n--- [ {i+1} / 3 번째 작업 시작 ] ---")
        target_page = random.choice(TARGET_URLS)
        print(f"대상 URL: '{target_page}'")
        
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--start-maximized")
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        wait = WebDriverWait(driver, WAIT_TIMEOUT)
        
        driver.get(target_page)
        print(f"페이지로 성공적으로 이동했습니다.")
        
        print("\n--- 1. 페이지 탐색 (스크롤) ---")
        time.sleep(random.uniform(1, 2))
        scroll_count = random.randint(2, 4)
        for j in range(scroll_count):
            scroll_depth = (j + 1) / (scroll_count + 1)
            driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {scroll_depth});")
            time.sleep(random.uniform(1.0, 2.5))
        print("페이지 스크롤을 완료했습니다.")

        print("\n--- 2. '좋아요' 클릭 시도 ---")
        try:
            like_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div[id^='reaction-'] button.btn_post")))
            driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", like_button)
            time.sleep(random.uniform(1, 2))
            human_like_mouse_move(driver, like_button)
            like_button.click()
            print("    ㄴ 성공! '좋아요' 버튼을 클릭했습니다.")
        except TimeoutException:
            print("    ㄴ '좋아요' 버튼을 찾을 수 없거나 이미 비활성화 상태입니다.")
        
        print("\n--- 3. 댓글 작성 시도 ---")
        try:
            print("댓글창 로딩을 유도하기 위해 페이지 최하단으로 스크롤합니다.")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.uniform(1, 2))

            comment_container_selector = "div.tt-comment-cont"
            print(f"댓글 컨테이너({comment_container_selector})를 기다립니다...")
            comment_container = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, comment_container_selector))
            )
            print("    ㄴ 댓글 컨테이너를 찾았습니다.")

            words = extract_post_text(driver)
            if not words:
                print("    ㄴ 본문 단어 추출에 실패하여 댓글 작성을 건너뜁니다.")
            else:
                comment_text = generate_random_comment(words)
                user_name, user_password = generate_random_credentials()
                
                name_input = comment_container.find_element(By.CSS_SELECTOR, "input[placeholder='이름']")
                password_input = comment_container.find_element(By.CSS_SELECTOR, "input[placeholder='비밀번호']")
                comment_textarea = comment_container.find_element(By.CSS_SELECTOR, "div.tt-cmt")

                for char in user_name: name_input.send_keys(char); time.sleep(random.uniform(0.05, 0.12))
                time.sleep(random.uniform(0.5, 1.0))
                for char in user_password: password_input.send_keys(char); time.sleep(random.uniform(0.05, 0.12))
                time.sleep(random.uniform(0.5, 1.0))
                for char in comment_text: comment_textarea.send_keys(char); time.sleep(random.uniform(0.06, 0.15))
                print("    ㄴ 이름, 비밀번호, 댓글 내용 입력을 완료했습니다.")

                submit_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.tt-btn_register")))
                driver.execute_script("arguments[0].click();", submit_button)
                print("    ㄴ 성공! 댓글을 성공적으로 등록했습니다.")

        except TimeoutException:
            print("    ㄴ 댓글 영역을 시간 내에 찾지 못했습니다. (지연 로딩 실패 또는 다른 문제)")
        except Exception as comment_e:
            print(f"    ㄴ 댓글 작성 중 예상치 못한 오류 발생: {comment_e}")

        final_wait_time = random.uniform(3, 6)
        print(f"\n작업 완료! {final_wait_time:.2f}초 더 머문 뒤 브라우저를 닫습니다.")
        time.sleep(final_wait_time)

    except Exception as e:
        print(f"[전체 오류 발생] 작업 중 예기치 않은 오류가 발생했습니다: {e}")
        error_sleep_time = random.uniform(5, 10)
        print(f"오류로 인해 {error_sleep_time:.2f}초 대기합니다.")
        time.sleep(error_sleep_time)

    finally:
        if driver:
            print("브라우저를 종료합니다.")
            driver.quit()

    # for 루프의 마지막이 아니면 휴식
    if i < 2:
        break_time = random.uniform(10, 20) # 테스트를 위해 휴식 시간 단축
        print(f"\n>>> 다음 작업을 위해 {break_time:.2f}초간 휴식합니다...")
        time.sleep(break_time)

print("\n--- 3회 반복 테스트가 모두 종료되었습니다. ---")```

 

 

결론부터 말씀드리면, 대부분의 경우 print 문의 유무가 프로그램의 성능에 주는 부담은 무시할 수 있을 정도로 미미합니다. 하지만 특정 상황에서는 유의미한 차이를 만들 수도 있습니다.

질문하신 코드의 맥락에서 자세히 설명해 드릴게요.

1. 일반적인 상황: print는 거의 부담이 없다

사용자님의 코드처럼 Selenium time.sleep()을 사용하는 자동화 스크립트에서는 print문이 성능에 미치는 영향은 0에 가깝습니다.

  • 이유: 프로그램의 실행 시간 대부분은 print가 아닌 다른 곳에서 소요됩니다.
    • 네트워크 지연: driver.get(target_page) - 페이지를 불러오는 시간 (수백 ms ~ 수 초)
    • 강제 대기: time.sleep() - 의도적으로 프로그램을 멈추는 시간 (수백 ms ~ 수십 초)
    • DOM 렌더링 및 탐색: wait.until(...) - 브라우저가 요소를 그리고, Selenium이 그 요소를 찾는 시간 (수십 ms ~ 수 초)
    • CPU 연산: extract_post_text, generate_random_comment 
  • print문의 동작 시간: 터미널에 문자열을 출력하는 I/O(입출력) 작업은 매우 빠릅니다. 일반적으로 마이크로초(µs, 1/1,000,000초) 단위로 끝납니다.
  • 예를 들어 time.sleep(1.0)은 1,000,000 마이크로초를 기다리는 반면, print("...완료...")는 몇 마이크로초 만에 끝납니다. 둘 사이에는 수십만 배의 시간 차이가 나므로, print문의 시간은 전체 실행 시간에 거의 영향을 주지 않습니다.

결론: 자동화 스크립트에서는 개발 및 디버깅의 편의성을 위해 print문을 적극적으로 사용하는 것이 훨씬 이득입니다.


2. 예외적인 상황: print가 성능에 영향을 줄 수 있는 경우

print문이 성능 저하의 원인이 되는 경우는 매우 특수한 상황에 한정됩니다.

  • 극도로 짧은 시간 안에 수백만 번 이상 반복되는 루프:Generated python
    # 이런 코드에서는 print가 병목이 될 수 있습니다.
    for i in range(10_000_000):
        # 복잡한 계산...
        print(f"Processing item {i}") # 이 한 줄이 계산보다 오래 걸릴 수 있음
     
    Use code with caution.Python
    이런 경우는 과학 계산, 데이터 처리 등 순수 연산 위주의 작업에서 발생하며, 웹 자동화와는 거리가 멉니다.
  • 느린 터미널 환경:
    • 일부 IDE(통합 개발 환경)의 내장 터미널이나 원격 접속(SSH) 환경에서는 터미널에 텍스트를 출력하는 속도 자체가 느릴 수 있습니다.
    • 이런 환경에서 아주 많은 양의 로그를 계속해서 출력하면 프로그램이 미세하게 느려지는 것을 체감할 수 있습니다.
  • 파일 리디렉션(Redirection):
    • python my_script.py > log.txt 와 같이 프로그램의 출력을 파일로 저장할 때, 디스크 쓰기 속도가 느리면 print문이 병목이 될 수 있습니다.

사용자님 코드에 대한 최종 결론

사용자님의 코드는 time.sleep과 웹 페이지 로딩 대기가 주를 이루므로, print문을 모두 주석 처리한다고 해서 체감할 수 있는 성능 향상은 전혀 없습니다.

오히려 print문을 그대로 두는 것이 다음과 같은 큰 이점을 제공합니다.

  1. 진행 상황 파악: 스크립트가 현재 어떤 단계를 수행 중인지 명확히 알 수 있습니다.
  2. 디버깅 용이성: 오류가 발생했을 때, 어떤 print문까지 실행되었는지를 보고 문제의 원인을 훨씬 쉽게 찾을 수 있습니다. ("댓글 영역을 시간 내에 찾지 못했습니다." 같은 메시지가 없다면 왜 프로그램이 멈췄는지 알기 어렵습니다.)
  3. 신뢰성: 프로그램이 "살아있는지" 아니면 어딘가에서 "멈췄는지"를 시각적으로 확인할 수 있어 심리적인 안정감을 줍니다.

따라서 현재 코드의 print문은 그대로 유지하시는 것을 강력하게 추천합니다. 성능에 대한 걱정은 전혀 하지 않으셔도 됩니다.

 

 

 

 

 

 3번 반복하는 for 루프에서 다시 while True를 사용한 무한 루프로 변경

import time
import random
import string
import traceback
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import NoSuchElementException, TimeoutException

# --- 헬퍼 함수 정의 (이전과 동일) ---
def human_like_mouse_move(driver, element):
    actions = ActionChains(driver)
    actions.move_to_element(element).perform()
    print("    ㄴ 사람처럼 마우스 커서를 목표 지점으로 이동했습니다.")
    time.sleep(random.uniform(0.5, 1.0))

def extract_post_text(driver):
    try:
        wait = WebDriverWait(driver, 10)
        post_body = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "div.contents_style")))
        text = post_body.text
        translator = str.maketrans(string.punctuation, ' ' * len(string.punctuation))
        words = text.translate(translator).split()
        return [word for word in words if len(word) > 1]
    except Exception:
        return []

def generate_random_comment(word_list):
    if not word_list: return "포스팅 잘 보고 갑니다. 좋은 하루 되세요."
    num_words = random.randint(4, 9)
    if len(word_list) < num_words: num_words = len(word_list)
    return " ".join(random.sample(word_list, num_words))

def generate_random_credentials():
    name = ''.join(random.choices(string.ascii_lowercase, k=random.randint(5, 8)))
    password = ''.join(random.choices(string.ascii_letters + string.digits, k=random.randint(8, 12)))
    return name, password

# --- 메인 코드 ---
BASE_URL = "https://kimchangmin02.tistory.com"
TARGET_URLS = [f"{BASE_URL}/{i}" for i in range(1, 60)]
WAIT_TIMEOUT = 15
loop_count = 0  # 몇 번째 시도인지 카운트하기 위한 변수

print("="*60)
print(" TISTORY 자동화 봇 (무한 반복 버전) ")
print(f" 총 {len(TARGET_URLS)}개의 게시물을 대상으로 작업을 무한 반복합니다.")
print(" 프로그램을 종료하려면 이 창에서 Ctrl + C 를 누르세요.")
print("="*60)

# --- [수정] 3번 반복하는 for문에서 무한 루프로 변경 ---
while True:
    driver = None
    loop_count += 1
    try:
        # --- [수정] 몇 번째 시도인지 표시 ---
        print(f"\n--- [ {loop_count} 번째 시도 시작 ] ---")
        target_page = random.choice(TARGET_URLS)
        print(f"대상 URL: '{target_page}'")
        
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_argument("--start-maximized")
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        wait = WebDriverWait(driver, WAIT_TIMEOUT)
        
        driver.get(target_page)
        print(f"페이지로 성공적으로 이동했습니다.")
        
        print("\n--- 1. 페이지 탐색 (스크롤) ---")
        time.sleep(random.uniform(1, 2))
        scroll_count = random.randint(2, 4)
        for j in range(scroll_count):
            scroll_depth = (j + 1) / (scroll_count + 1)
            driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {scroll_depth});")
            time.sleep(random.uniform(1.0, 2.5))
        print("페이지 스크롤을 완료했습니다.")

        print("\n--- 2. '좋아요' 클릭 시도 ---")
        try:
            like_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div[id^='reaction-'] button.btn_post")))
            driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", like_button)
            time.sleep(random.uniform(1, 2))
            human_like_mouse_move(driver, like_button)
            like_button.click()
            print("    ㄴ 성공! '좋아요' 버튼을 클릭했습니다.")
        except TimeoutException:
            print("    ㄴ '좋아요' 버튼을 찾을 수 없거나 이미 비활성화 상태입니다.")
        
        print("\n--- 3. 댓글 작성 시도 ---")
        try:
            print("댓글창 로딩을 유도하기 위해 페이지 최하단으로 스크롤합니다.")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.uniform(1, 2))

            comment_container_selector = "div.tt-comment-cont"
            print(f"댓글 컨테이너({comment_container_selector})를 기다립니다...")
            comment_container = WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, comment_container_selector))
            )
            print("    ㄴ 댓글 컨테이너를 찾았습니다.")

            words = extract_post_text(driver)
            if not words:
                print("    ㄴ 본문 단어 추출에 실패하여 댓글 작성을 건너뜁니다.")
            else:
                comment_text = generate_random_comment(words)
                user_name, user_password = generate_random_credentials()
                
                name_input = comment_container.find_element(By.CSS_SELECTOR, "input[placeholder='이름']")
                password_input = comment_container.find_element(By.CSS_SELECTOR, "input[placeholder='비밀번호']")
                comment_textarea = comment_container.find_element(By.CSS_SELECTOR, "div.tt-cmt")

                for char in user_name: name_input.send_keys(char); time.sleep(random.uniform(0.05, 0.12))
                time.sleep(random.uniform(0.5, 1.0))
                for char in user_password: password_input.send_keys(char); time.sleep(random.uniform(0.05, 0.12))
                time.sleep(random.uniform(0.5, 1.0))
                for char in comment_text: comment_textarea.send_keys(char); time.sleep(random.uniform(0.06, 0.15))
                print("    ㄴ 이름, 비밀번호, 댓글 내용 입력을 완료했습니다.")

                submit_button = WebDriverWait(driver, 5).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button.tt-btn_register")))
                driver.execute_script("arguments[0].click();", submit_button)
                print("    ㄴ 성공! 댓글을 성공적으로 등록했습니다.")

        except TimeoutException:
            print("    ㄴ 댓글 영역을 시간 내에 찾지 못했습니다. (지연 로딩 실패 또는 다른 문제)")
        except Exception as comment_e:
            print(f"    ㄴ 댓글 작성 중 예상치 못한 오류 발생: {comment_e}")

        final_wait_time = random.uniform(3, 6)
        print(f"\n작업 완료! {final_wait_time:.2f}초 더 머문 뒤 브라우저를 닫습니다.")
        time.sleep(final_wait_time)

    except Exception as e:
        print(f"[전체 오류 발생] 작업 중 예기치 않은 오류가 발생했습니다: {e}")
        traceback.print_exc() # 오류의 상세 내용을 보기 위해 추가
        error_sleep_time = random.uniform(5, 10)
        print(f"오류로 인해 {error_sleep_time:.2f}초 대기합니다.")
        time.sleep(error_sleep_time)

    finally:
        if driver:
            print("브라우저를 종료합니다.")
            driver.quit()

    # 무한 루프의 각 시도 사이에 휴식
    break_time = random.uniform(10, 25) # 원래 휴식 시간으로 복원
    print(f"\n>>> 다음 작업을 위해 {break_time:.2f}초간 휴식합니다...")
    time.sleep(break_time)

# 이 부분은 무한 루프에서는 도달하지 않으므로 주석 처리하거나 삭제해도 됩니다.
# print("\n--- 모든 작업이 종료되었습니다. ---")