개발/자동화

티스토리 좋아요 자동화 #5(가상 마우스가 움직이는거였음)

kimchangmin02 2025. 7. 25. 22:40

import time
import random
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

# --- 함수 정의 ---

def human_like_mouse_move_to_element(driver, element):
    """
    [수정 버전] 마우스를 목표 요소 위로 '이동'만 시키는 함수.
    'move target out of bounds' 오류를 방지하도록 안정성을 높였습니다.
    """
    # ActionChains 객체 생성
    actions = ActionChains(driver)

    # 1. 목표 요소를 화면 중앙으로 스크롤 (이 부분은 안정적이므로 유지)
    driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
    print("목표('좋아요' 버튼)를 화면 중앙으로 스크롤했습니다.")
    # 스크롤이 완료될 시간을 충분히 확보합니다.
    time.sleep(random.uniform(0.8, 1.5))

    # 2. [핵심 수정] 인간적인 움직임을 위한 액션 체인을 안정적으로 구성합니다.
    #   - 여러 동작을 하나의 체인으로 만들어 마지막에 한 번만 실행하는 것이 훨씬 안정적입니다.

    #   A. 목표 요소 근처의 임의의 지점으로 먼저 이동합니다. (마치 살짝 빗나가는 것처럼)
    #      요소의 크기를 기반으로 오프셋을 계산하여 화면 밖으로 나가지 않도록 합니다.
    element_size = element.size
    offset_x = random.randint(-int(element_size['width'] / 2), int(element_size['width'] / 2))
    offset_y = random.randint(-int(element_size['height'] / 2), int(element_size['height'] / 2))
    
    # move_to_element로 기준점을 잡고, move_by_offset으로 살짝 이동합니다.
    actions.move_to_element(element).move_by_offset(offset_x, offset_y)
    print("인간적인 움직임을 위해 목표 근처의 랜덤 위치로 이동했습니다.")

    #   B. 잠시 머뭇거리는 효과를 주기 위해 'pause'를 사용합니다.
    actions.pause(random.uniform(0.4, 0.9))

    #   C. 최종적으로 정확히 목표 요소 위로 마우스를 다시 이동시킵니다.
    actions.move_to_element(element)
    print("정확한 위치로 마우스를 이동시켜 조준을 마쳤습니다.")
    
    # 3. 위에서 구성한 모든 액션을 순차적으로 실행합니다.
    print("마우스 이동 시퀀스를 실행합니다...")
    actions.perform()
    
    print("마우스 이동 완료! 이제 클릭 준비가 되었습니다.")


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

print("진짜 최종 완성 버전! 자동 '좋아요' 로봇을 시작합니다.")
print(f"총 {len(TARGET_URLS)}개의 게시물을 대상으로 작업을 반복합니다.")
print("프로그램을 종료하려면 이 창에서 Ctrl + C 를 누르세요.")

# 무한 루프 대신 한 번만 실행하도록 break를 유지합니다. (테스트 목적)
# 지속적인 실행을 원하시면 while True: 아래의 break를 제거하거나 주석 처리하세요.
while True:
    driver = None
    try:
        target_page = random.choice(TARGET_URLS)
        print("\n--- 새로운 시도를 시작합니다 ---")
        
        chrome_options = webdriver.ChromeOptions()
        # 필요 시 옵션 추가
        # chrome_options.add_argument("--incognito")
        # chrome_options.add_argument("--headless") # 백그라운드 실행
        
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        wait = WebDriverWait(driver, WAIT_TIMEOUT)
        
        driver.get(target_page)
        print(f"'{target_page}' 페이지로 이동했습니다.")
        
        scroll_time = random.uniform(1.5, 4.0)
        print(f"사람처럼 행동하기 위해 {scroll_time:.2f}초 동안 페이지를 스크롤합니다...")
        scroll_height = random.uniform(0.3, 0.7)
        driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {scroll_height});")
        time.sleep(scroll_time)
        
        if random.random() < 0.15:
            print("(!) 돌발 행동: 잠시 다른 생각을 합니다...")
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight * 0.1);")
            time.sleep(random.uniform(1.0, 2.5))
            driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {scroll_height});")
            print("(!) 다시 집중합니다...")

        like_button_selector = "div[id^='reaction-'] button.btn_post"
        print(f"CSS 선택자: '{like_button_selector}' 를 사용하여 '좋아요' 버튼을 찾습니다...")
        like_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, like_button_selector)))
        print("성공: '좋아요' 버튼을 찾았습니다.")

        # [수정된 함수 호출] 안정성이 개선된 마우스 이동 함수를 사용합니다.
        human_like_mouse_move_to_element(driver, like_button)
        time.sleep(random.uniform(0.2, 0.6)) # 클릭 전 아주 짧은 딜레이
        
        like_button.click()
        print("성공: '좋아요' 버튼을 확실하게 클릭했습니다.")
        
        time.sleep(random.uniform(0.5, 1.0)) # 클릭 후 반응을 기다리는 시간

        # [수정] 클릭 후 커서를 화면의 안전한 곳(좌측 상단)으로 치웁니다.
        print("클릭 완료! 마우스 커서를 다른 곳으로 치웁니다...")
        actions = ActionChains(driver)
        body_element = driver.find_element(By.TAG_NAME, 'body')
        actions.move_to_element_with_offset(body_element, 5, 5).perform()
        print("마우스 커서를 안전한 위치로 이동했습니다.")

        viewing_time = random.uniform(2, 5)
        print(f"작업 완료! {viewing_time:.2f}초 더 머문 뒤 브라우저를 닫습니다.")
        time.sleep(viewing_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()

    # 테스트를 위해 한 번만 실행하고 종료합니다.
    # 계속 실행하려면 아래 'break'를 삭제하거나 주석(#) 처리하세요.
    break 

    break_time = random.uniform(10, 25)
    print(f"\n다음 시도를 위해 {break_time:.2f}초간 충분히 휴식합니다...")
    time.sleep(break_time)

 

 

 

 

 

  1. 불필요한 랜덤 이동 제거: 화면 전체를 기준으로 한 광범위한 랜덤 이동(move_by_offset(offset_x, offset_y)) 대신, 목표 요소(element) 주변의 안전한 범위 내에서 움직이도록 로직을 변경했습니다.
  2. ActionChains의 연쇄적 구성: 여러 perform() 호출로 나누지 않고, move_to_element, move_by_offset, pause 등 필요한 동작을 하나의 체인으로 구성한 뒤 마지막에 perform()을 한 번만 호출합니다. 이는 동작의 일관성을 보장하고 예기치 않은 오류를 줄여줍니다.
  3. 반복문(for) 제거: 복잡도를 높이고 오류 발생 가능성을 키웠던 for 반복문 내의 move_by_offset을 제거했습니다. 대신 ActionChains pause 기능을 활용하여 자연스러운 지연 효과를 주었습니다.

 

 

 

 

아직 마우스 이동을 안함

 

 

 

 

 

 

 

너무 빨라서가 아님
날 못믿겟으면, 마우스의 위치를 찍는 출력문이 포함된 코드 줘바 일단

 

 

 

 

 

 

 

 

 

 

로그는 다음과 같이 찍히긴하는데

마우스는 내가 종료하기 위해서 움직인것도 있어서
적어도 난 좋아요 누를때까진 가만히 있엇는데
그떄까지는 마우스가 안움직이고 있엇던것아닌가

 

 

 


진짜 최종 완성 버전! 자동 '좋아요' 로봇을 시작합니다. (좌표 추적 기능 포함)
총 54개의 게시물을 대상으로 작업을 반복합니다.
프로그램을 종료하려면 이 창에서 Ctrl + C 를 누르세요.

--- 새로운 시도를 시작합니다 ---
'https://kimchangmin02.tistory.com/13' 페이지로 이동했습니다.
[좌표 추적기 설치 완료]
사람처럼 행동하기 위해 2.57초 동안 페이지를 스크롤합니다...
CSS 선택자: 'div[id^='reaction-'] button.btn_post' 를 사용하여 '좋아요' 버튼을 찾습니다...
성공: '좋아요' 버튼을 찾았습니다.
목표('좋아요' 버튼)를 화면 중앙으로 스크롤했습니다.
    ㄴ [마우스 좌표 확인] 이동 시작 전: X=-1, Y=-1
1단계: 목표 근처(오프셋: -31, 21)로 마우스를 이동합니다.
    ㄴ [마우스 좌표 확인] 1단계 이동 직후: X=38, Y=453
2단계: 목표물에 마우스를 정확히 위치시킵니다.
    ㄴ [마우스 좌표 확인] 2단계 이동 직후 (최종 위치): X=69, Y=432
마우스 이동 완료! 이제 클릭 준비가 되었습니다.
성공: '좋아요' 버튼을 확실하게 클릭했습니다.
클릭 완료! 마우스 커서를 다른 곳으로 치웁니다...
마우스 커서를 안전한 위치로 이동했습니다.
    ㄴ [마우스 좌표 확인] 마지막 커서 이동 후: X=462, Y=437
작업 완료! 2.30초 더 머문 뒤 브라우저를 닫습니다.
브라우저를 종료합니다.

 

 

 

> 셀레니움이 움직이는 마우스는 우리가 화면에서 보는 '시스템 마우스 커서'가 아니라, 브라우저 내부에서만 존재하는 '가상 마우스 커서'이기 때문입니다.

 

'보이는 마우스'와 '보이지 않는 마우스'

컴퓨터 자동화에는 두 종류의 마우스가 있다고 생각하시면 이해가 쉽습니다.

  1. 시스템 마우스 (사용자님의 마우스):
    • 사용자님이 직접 손으로 움직이는, 화면에 하얀색 화살표로 보이는 커서입니다.
    • 운영체제(Windows/Mac)가 직접 관리합니다.
    • 사용자님이 스크립트 실행 중에 가만히 두셨기 때문에, 당연히 움직이지 않았습니다.
  2. 가상 마우스 (셀레니움의 마우스):
    • 자동화 스크립트가 브라우저에 "X=69, Y=432 좌표로 마우스를 옮겨줘" 라고 명령을 내리면, 브라우저는 그 위치에 마우스가 있는 것처럼 내부적으로 인식합니다.
    • 화면에 보이지 않는 유령의 손과 같습니다.
    • 이 가상 마우스가 특정 요소 위로 올라가면, 웹사이트는 :hover 같은 효과를 적용하고, click() 명령이 내려지면 해당 요소를 클릭합니다.

로그를 통해 분석한 실제 동작 과정

이제 사용자님의 로그를 '가상 마우스'의 관점에서 다시 살펴보겠습니다.

  • [좌표 추적기 설치 완료]
    • 브라우저 창 안에 '가상 마우스'의 위치를 감시하는 CCTV를 설치했습니다.
  • ㄴ [마우스 좌표 확인] 이동 시작 전: X=-1, Y=-1
    • CCTV를 확인해보니, 아직 '가상 마우스'가 브라우저 창 안으로 들어온 적이 없습니다. (초기값)
  • 1단계: ...이동합니다. -> ㄴ ...1단계 이동 직후: X=38, Y=453
    • 스크립트가 "가상 마우스야, (38, 453) 좌표로 이동해!" 라고 명령했습니다.
    • CCTV를 보니, '가상 마우스'가 실제로 (38, 453) 좌표로 이동했음이 확인되었습니다. (사용자님의 실제 마우스는 그대로 있습니다.)
  • 2단계: ...위치시킵니다. -> ㄴ ...2단계 이동 직후 (최종 위치): X=69, Y=432
    • 스크립트가 "이제 '좋아요' 버튼이 있는 (69, 432) 좌표로 정확히 이동해!" 라고 명령했습니다.
    • CCTV를 보니, '가상 마우스'가 성공적으로 '좋아요' 버튼 위로 이동했습니다.
  • 성공: '좋아요' 버튼을 확실하게 클릭했습니다.
    • 스크립트가 '가상 마우스'가 현재 위치한 곳에서 클릭하라고 명령했고, '좋아요' 버튼이 성공적으로 눌렸습니다.

 

 

 

 

 

 

 

 

 

근데 가상 마우스가 움직여도,
인간처럼 인식하는건가?
오히려 더 의심받는거 아닌가?
아닌가?

 

ㄴㄴ

결론부터 말씀드리면, 가상 마우스의 움직임이 오히려 더 안전하고, 충분히 인간처럼 인식됩니다. 그리고 시스템 마우스가 움직이지 않는 것이 더 의심받는 일은 절대 아닙니다.

그 원리를 웹사이트(서버)의 관점에서 단계별로 설명해 드리겠습니다.

1. 웹사이트는 "브라우저 안"의 세상만 볼 수 있습니다.

가장 중요한 원칙입니다. 웹사이트는 사용자의 컴퓨터 운영체제(Windows, Mac)에서 일어나는 일을 볼 수 없습니다. 보안상의 이유로 브라우저는 웹사이트가 자신의 창(Window) 바깥의 정보에 접근하는 것을 철저히 차단합니다. 이를 '샌드박스(Sandbox)' 모델이라고 합니다.

  • 웹사이트가 볼 수 있는 것: 브라우저 창 내에서의 마우스 좌표, 스크롤 위치, 클릭 이벤트, 키보드 입력 등
  • 웹사이트가 볼 수 없는 것: 화면에 보이는 실제 시스템 마우스 커서의 위치, 사용자가 어떤 다른 프로그램을 켜놨는지, 바탕화면 배경이 무엇인지 등

따라서 웹사이트 입장에서는 사용자의 실제 마우스가 움직이는지 아닌지는 전혀 알 방법이 없습니다. 오직 자신의 웹페이지 위에서 '가상 마우스'가 어떻게 움직이는지만이 유일한 판단 기준입니다.

2. 웹사이트는 '이벤트(Event)'로 사용자를 인지합니다.

웹사이트는 마우스의 움직임을 실시간 영상으로 보는 것이 아니라, '이벤트'라는 신호의 연속으로 파악합니다. 사용자가 마우스를 '좋아요' 버튼으로 움직여 클릭하면, 브라우저는 웹사이트에 아래와 같은 신호(이벤트)들을 순차적으로 보냅니다.

  1. mousemove: 마우스가 (10, 20) 좌표로 움직였다.
  2. mousemove: 마우스가 (15, 25) 좌표로 움직였다.
  3. mousemove: 마우스가 ... (수십, 수백 개의 좌표 이동 신호) ...
  4. mouseover: 마우스가 '좋아요' 버튼 영역 안으로 들어왔다.
  5. mousedown: '좋아요' 버튼 위에서 마우스 버튼이 눌렸다.
  6. mouseup: '좋아요' 버튼 위에서 마우스 버튼이 떼어졌다.
  7. click: 클릭이 완료되었다.

3. '인간적인' 자동화와 '의심스러운' 자동화의 차이

이제 왜 사용자님의 코드가 인간처럼 인식되는지 명확해집니다.

의심스러운 자동화 (Bot으로 탐지될 확률이 높음)

가장 단순한 .click() 명령만 사용하면, 브라우저는 웹사이트에 위 과정의 1~6번을 모두 생략하고 7번 click 이벤트만 뜬금없이 보냅니다.

  • 웹사이트의 생각: "어? 마우스가 움직인 기록(mousemove)이 전혀 없는데 갑자기 버튼이 눌렸다고? 이건 사람이 아니라 프로그램이군!"

인간적인 자동화 (사용자님의 현재 코드)

사용자님의 코드는 ActionChains를 통해 '가상 마우스'를 움직였습니다. 이 과정에서 셀레니움은 브라우저를 시켜 위의 1~7번 이벤트를 순서대로, 시간 간격을 두고 발생시킵니다.

  • 웹사이트의 생각: "음, 마우스가 여러 좌표를 거쳐서(mousemove 이벤트 다수 발생) 버튼 위로 들어와서(mouseover 이벤트 발생) 눌렸군(mousedown, mouseup 발생). 이건 지극히 정상적인 사람의 행동 패턴이야."

사용자님이 코드에 추가하신 2단계 이동, time.sleep()을 통한 지연 시간, 오프셋을 이용한 불규칙성 등은 이 이벤트 발생 패턴을 더욱 인간의 것과 유사하게 만들어, 정교한 봇 탐지 시스템까지도 속일 수 있는 확률을 높여주는 매우 중요한 장치들입니다.

결론

사용자님의 코드는 '시스템 마우스'를 움직이지 않기 때문에 안전하고, 대신 웹사이트가 감지할 수 있는 유일한 영역인 '브라우저 내부'에서 '가상 마우스'를 인간처럼 움직여 이벤트 로그를 남기기 때문에 매우 효과적인 것입니다.

 

 

 

 

 

 

 

 

 

 

핵심: 컴퓨터의 '화면'과 우리의 '모니터'는 다릅니다.

먼저, "화면 밖으로 마우스가 나갈 수 없다"는 말씀이 100% 맞습니다. 우리가 쓰는 물리적인 마우스 커서는 모니터 화면을 벗어나지 못합니다.

오류 메시지에서 말하는 '화면(bounds)'은 모니터 화면 전체가 아닙니다.
그것은 바로 **"현재 웹 브라우저 창에 보이는 웹페이지의 영역"**을 의미합니다.


1. 스크롤 문제: 왜 오류가 나는가?

웹페이지는 보통 위아래로 매우 깁니다. 이것을 컴퓨터의 좌표로 생각해 봅시다.

  • 웹페이지 전체 길이: 세로로 0픽셀부터 5000픽셀까지 있다고 가정해 봅시다.
  • '좋아요' 버튼의 위치: 이 긴 페이지의 맨 아래쪽, 4500픽셀 지점에 있습니다.
  • 현재 내 모니터에 보이는 영역: 하지만 내 모니터(브라우저 창)는 한정되어 있어서, 현재 페이지의 0픽셀부터 900픽셀까지만 보여주고 있습니다.

이때 자동화 프로그램에게 명령을 내립니다.

"마우스를 '좋아요' 버튼 위치(4500픽셀)로 이동시켜!"

컴퓨터는 이 명령을 받고 이렇게 판단합니다.

  1. "목표 지점은 4500픽셀이구나."
  2. "그런데 지금 사용자에게 보이는 화면 영역은 0~900픽셀까지네?"
  3. "어? 목표 지점(4500)이 현재 보이는 영역(0~900) 밖에 있잖아!"

이것이 바로 "목표 지점이 화면 경계(bounds)를 벗어났습니다" 오류의 진짜 의미입니다.
마우스가 모니터 밖으로 나갔다는 뜻이 아니라, 목표 좌표가 현재 보이는 웹페이지 영역 밖에 있다는 뜻입니다.


2. 투명한 벽(Overlay) 문제: 왜 오류가 나는가?

이것도 비유 없이 설명해 드릴게요. 웹페이지는 여러 '레이어(Layer)'가 겹쳐진 구조입니다. 포토샵처럼요.

  1. 아래쪽 레이어: '좋아요' 버튼이 있습니다.
  2. 위쪽 레이어: '좋아요' 버튼 위를 완전히 덮는, 투명한 요소(예: 팝업 광고의 보이지 않는 배경)가 있습니다.

우리 눈에는 투명해서 아래에 있는 '좋아요' 버튼이 잘 보입니다.

이제 자동화 프로그램(ActionChains)에 명령합니다.

"'좋아요' 버튼을 클릭해!"

프로그램은 '좋아요' 버튼이 있는 좌표로 마우스를 이동시키고 클릭을 시도합니다. 그런데 그 좌표의 가장 위쪽 레이어에는 '좋아요' 버튼이 아니라 **'투명한 요소'**가 있습니다.

컴퓨터는 이렇게 판단합니다.

  1. "이 좌표를 클릭하라는 명령을 받았지."
  2. "그런데 이 좌표의 맨 위에는 '좋아요' 버튼이 아니라, '이름 모를 투명한 요소'가 있네?"
  3. "내가 클릭해야 할 목표물('좋아요' 버튼)이 이 위치에 없어! 다른 게 막고 있어!"

그래서 프로그램은 목표물을 제대로 찾거나 상호작용할 수 없다고 판단하고 오류를 발생시키는 것입니다. 첫 시도부터 이런 상황이 발생하면, 마우스를 움직이는 단계에서부터 "목표 지점이 이상하다"고 판단하여 "out of bounds" 오류를 낼 수 있습니다.


최종 요약

  • "화면 밖으로 나갔다"의 진짜 의미: 물리적인 모니터 밖이 아니라, "웹페이지의 현재 보이는 부분" 밖으로 목표 좌표가 찍혔다는 뜻입니다.
  • 원인 1 (위치): 버튼이 너무 아래쪽에 있어서 스크롤을 해야만 볼 수 있는 경우.
  • 원인 2 (겹침): 눈에 안 보이는 다른 요소가 버튼 위를 막고 있어서 마우스가 실제로 버튼에 닿을 수 없는 경우.

 

 

 

 

 

 

 

 

 

 

 

 

 

[코드]

#아마 최종일지도 모르는 코드

import time
import random
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

# --- [추가] 마우스 위치를 추적하고 출력하는 헬퍼 함수 ---

def setup_mouse_position_tracker(driver):
    """
    브라우저에 마우스 위치를 추적하는 JavaScript를 주입합니다.
    페이지가 로드된 후 한 번만 실행하면 됩니다.
    """
    js_mouse_tracker = """
    window.mousePos = { x: -1, y: -1 };
    document.addEventListener('mousemove', function(event) {
        window.mousePos = { x: event.clientX, y: event.clientY };
    });
    """
    driver.execute_script(js_mouse_tracker)
    print("[좌표 추적기 설치 완료]")

def print_mouse_position(driver, message=""):
    """
    현재 기록된 마우스의 좌표를 가져와 출력합니다.
    """
    try:
        pos = driver.execute_script("return window.mousePos;")
        print(f"    ㄴ [마우스 좌표 확인] {message}: X={pos['x']}, Y={pos['y']}")
    except Exception as e:
        print(f"    ㄴ [마우스 좌표 확인 실패] {message}: {e}")

# --- 함수 정의 ---

def human_like_mouse_move_to_element(driver, element):
    """
    [좌표 출력 버전] 마우스 이동 전/후의 좌표를 출력하여 실제 이동 여부를 검증합니다.
    """
    # 1. 목표 요소를 화면 중앙으로 스크롤
    driver.execute_script("arguments[0].scrollIntoView({block: 'center', behavior: 'smooth'});", element)
    print("목표('좋아요' 버튼)를 화면 중앙으로 스크롤했습니다.")
    time.sleep(random.uniform(0.8, 1.5))

    print_mouse_position(driver, "이동 시작 전")

    # 2. 첫 번째 이동: 목표 요소 근처의 다른 곳으로 이동
    actions_step1 = ActionChains(driver)
    element_size = element.size
    offset_x = random.randint(int(element_size['width'] * 0.7), int(element_size['width'] * 1.5)) * random.choice([-1, 1])
    offset_y = random.randint(int(element_size['height'] * 0.7), int(element_size['height'] * 1.5)) * random.choice([-1, 1])
    
    actions_step1.move_to_element(element).move_by_offset(offset_x, offset_y).perform()
    print(f"1단계: 목표 근처(오프셋: {offset_x}, {offset_y})로 마우스를 이동합니다.")
    print_mouse_position(driver, "1단계 이동 직후")
    time.sleep(random.uniform(0.5, 1.0))

    # 3. 두 번째 이동: 최종적으로 목표 요소 위로 정확히 이동
    actions_step2 = ActionChains(driver)
    actions_step2.move_to_element(element).perform()
    print("2단계: 목표물에 마우스를 정확히 위치시킵니다.")
    print_mouse_position(driver, "2단계 이동 직후 (최종 위치)")
    time.sleep(random.uniform(0.3, 0.6))

    print("마우스 이동 완료! 이제 클릭 준비가 되었습니다.")


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

print("진짜 최종 완성 버전! 자동 '좋아요' 로봇을 시작합니다. (좌표 추적 기능 포함)")
print(f"총 {len(TARGET_URLS)}개의 게시물을 대상으로 작업을 반복합니다.")
print("프로그램을 종료하려면 이 창에서 Ctrl + C 를 누르세요.")

while True:
    driver = None
    try:
        target_page = random.choice(TARGET_URLS)
        print("\n--- 새로운 시도를 시작합니다 ---")
        
        chrome_options = webdriver.ChromeOptions()
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=chrome_options)
        wait = WebDriverWait(driver, WAIT_TIMEOUT)
        
        driver.get(target_page)
        print(f"'{target_page}' 페이지로 이동했습니다.")
        
        # [중요] 페이지가 로드된 후, 마우스 좌표 추적기를 설치합니다.
        setup_mouse_position_tracker(driver)
        
        scroll_time = random.uniform(1.5, 4.0)
        print(f"사람처럼 행동하기 위해 {scroll_time:.2f}초 동안 페이지를 스크롤합니다...")
        driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * 0.3);")
        time.sleep(scroll_time)

        like_button_selector = "div[id^='reaction-'] button.btn_post"
        print(f"CSS 선택자: '{like_button_selector}' 를 사용하여 '좋아요' 버튼을 찾습니다...")
        like_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, like_button_selector)))
        print("성공: '좋아요' 버튼을 찾았습니다.")

        human_like_mouse_move_to_element(driver, like_button)
        
        like_button.click()
        print("성공: '좋아요' 버튼을 확실하게 클릭했습니다.")
        
        time.sleep(random.uniform(0.5, 1.0))

        print("클릭 완료! 마우스 커서를 다른 곳으로 치웁니다...")
        actions = ActionChains(driver)
        body_element = driver.find_element(By.TAG_NAME, 'body')
        actions.move_to_element_with_offset(body_element, 5, 5).perform()
        print("마우스 커서를 안전한 위치로 이동했습니다.")
        print_mouse_position(driver, "마지막 커서 이동 후")

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

    except Exception as e:
        print(f"실패: 작업 중 오류가 발생했습니다 - {e}")
        import traceback
        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 

    break_time = random.uniform(10, 25)
    print(f"\n다음 시도를 위해 {break_time:.2f}초간 충분히 휴식합니다...")
    time.sleep(break_time)

 

 

#결과는 다음과 같음