본문 바로가기
파이썬

인스타그램 좋아요 봇 만들기 8 : 인친들 최신 피드 누르기(2023.09.22. 업데이트)

by fecu 2022. 7. 23.
728x90

최근의 댓글 중에 인스타 메인 페이지에 올라오는 인친들의 최신 피드의 좋아요를 누르고 싶다는 글을 보았다. 사람들과 소통하는 느낌도 들고 좋을 것 같아서 한번 구현해보기로 했다. 기본적인 코드는 아래 글을 참고 바란다.

 

인스타그램 좋아요 봇 만들기 7 : 댓글 달기 기능

2022.06.18. 업데이트 driver.get을 통해 인스타그램 페이지를 불러올 때, 페이지가 모두 로드되지 않은 상태에서 요소를 찾다가 오류를 내는 경우가 빈번하여 driver.implicitly_wait 함수를 추가했다. 2022.0

fecu.tistory.com


2022.07.28. ~ 2022.08.12. 업데이트. 업데이트

안정화 업데이트. 로그인 함수가 실제로 로그인이 되었는지 확인하도록 만듦. 팔로잉 함수가 더 안정적으로 작동하도록 변경함. 또한 새롭게 인스타그램 주소로 접속했을 때, 새 게시물 보기가 있으면 클릭하도록 만들었다. 쉐도우 밴이 우려되어 좋아요를 누르는 인터벌을 조금 늘림. 태그 전환 시 5분~20분 사이를 쉬도록 time.sleep 값을 변경.

 

2023.01.25. 업데이트

쉐도우 밴 우회를 위해 randrange 함수를 대부분 uniform으로 변경함. 그리고 코드의 변수들을 가장 상단에 정리함. except에 대한 예외처리를 알아볼 수 있도록 수정함.

 

2023.07.24. 업데이트

로그인 후 로그인 여부를 자신의 이미지 태그 내부에 있는 자신의 아이디를 통해 확인하도록 변경

islogin = driver.find_element(By.XPATH, f'//*[contains(@alt, "{yourid}")]')

 

2023.09.22. 좋아요 버튼 xpath 변경.

모든 좋아요 버튼의 html 속성이 button 에서 div로 변경. 좋아요 하트의 2번째 조상 div를 찾도록 코드 변경


1. 좋아요 버튼 누르기

그냥 생각으로는 find_elements를 통해 '좋아요' 버튼을 찾고, for문을 통해 버튼들을 하나씩 클릭해주면 될 것 같았다. 먼저 로그인 후 화면부터 시작해보자.

셀레니움으로 로그인을 하면 로그인 정보를 저장하겠냐는 질문이 있는데, 이는 인스타그램 주소로 다시 접속을 하면 통과할 수 있다. driver.get을 통해 인스타그램으로 다시 돌아가준다. 혹은 '나중에 하기'를 다시 눌러줄 수도 있다.

인스타그램으로 접속하면 몇 번을 접속해도 알람 설정에 대한 질문이 나온다. 웹 요소들 중 '나중에 하기'를 포함한 것을 눌러 통과한다.

driver.find_element(By.XPATH, '//*[text() = "나중에 하기"]').click()

이제 좋아요를 누르면 되는데, 문제는 '좋아요'라는 텍스트를 포함한 button이 한두개가 아니었다. 그냥 무턱대고 '좋아요'를 포함한 버튼을 다 눌러 봤더니, 피드 뿐만 아니라 댓글에 있는 모든 글에도 하트를 죄다 눌러버렸다. 이를 장비하기 위해 하트 이미지의 높이가 '24'인 것을 찾아 누르도록 만드려고 했다. 그런데 보니... 좋아요 하트에도 똑같은 요소가 2개 포함되어 있어, find_elements를 이용하니 클릭을 받아줄 수 있는 요소도 포함되어 있었다.

해법은 간단하다. 누를 수 없으면 누르지 않으면 된다. find_elements를 이용하여 높이가 24인 좋아요를 모두 찾고, for문을 통해 요소들을 꺼내 try 구문으로 클릭해본 뒤 클릭할 수 없다면 그냥 넘어간다.

like_btn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" or @aria-label="좋아요 취소"] //ancestor :: div[2]')
for like_btn in like_btns :
	try :
		like_btn.click()
		print('좋아요')
		time.sleep(5)
	except:
		pass

이렇게 하니 스크롤을 따로 하지 않아도 최대 4개의 좋아요를 눌렀다. 이제 스크롤을 내린 뒤 좋아요를 누르는 행위를 반복하면 된다.

2. 스크롤 하기

구글링을 통해 찾아보니 셀레니움을 통해 좋아요를 누르는 방법은 다양하다. 아래의 4가지 방법 중 마음에 드는 것을 하나를 골라 스크롤을 한 뒤, 좋아요 버튼을 찾고 다시 눌러주는 행위를 반복하면 된다. 나는 2)의 방법을 이용해 보려고 한다.

1) 원하는 높이 Y까지 스크롤

driver.execute_script("window.scrollTo(0, Y)")

2) 문서의 끝까지 1회 스크롤

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

3) 문서의 제일 끝까지 스크롤

SCROLL_PAUSE_TIME = 0.5

# Get scroll height
last_height = driver.execute_script("return document.body.scrollHeight")

while True:
    # Scroll down to bottom
    driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # Wait to load page
    time.sleep(SCROLL_PAUSE_TIME)

    # Calculate new scroll height and compare with last scroll height
    new_height = driver.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

4) Page Down 키를 이용한 스크롤

driver.find_element(By.XPATH, '/html/body').send_keys(Keys.PAGE_DOWN)

5) 내가 원하는 요소가 가운데 오도록 스크롤하기

driver.execute_script("arguments[0].scrollIntoView({block : 'center'});", 원하는 요소)

6) 실행문

while문을 통해 화면 최대 아래까지 스크롤 후 좋아요를 클릭하도록 반복해 보았다.

while following_likenum < following_stopnum:
    try : 
        following_likebtn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" and @height = "24"] //ancestor :: button')
        driver.execute_script("arguments[0].scrollIntoView({block : 'center'});", following_likebtn)
        following_likebtn.click()
        following_likenum += 1
        print(f'팔로워 새피드 좋아요 {following_likenum}개 누름')
        time.sleep(round(random.randrange(20)))
    except : 
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        driver.implicitly_wait(10)

클릭이 잘 된다. 최대로 스크롤 후 요소를 클릭하면 4개 정도를 누르니, 만약 20개 정도를 누르고 싶다면 5번 스크롤 하면 될 것이다. 

3. 광고 거르기

어차피 좋아요 프로그램의 목적은 최대한 많은 사람들에게 나의 계정을 노출시키는 것이므로, 광고만 아니라면 누구의 피드를 눌러도 괜찮다고 생각했다. 그래서 광고 계정만을 걸러 보기로 했다.

그런데 재미있는점은 데스크탑 환경에서 아무리 스크롤을 내려도 '광고'가 잘 나오지 않는다는 점. 웬만한 것들은 모두 클릭해 주어도 상관없을 것 같았다. 혹시 누르지 않았으면 하는 내용이 있다면 댓글 바란다.

4. 함수 만들기

위의 내용을 바탕으로 인스타그램에 접속 후 스크롤을 하면서 좋아요를 누르는 코드를 작성해보았다. 아래 함수를 적절한 시기에 본래의 함수 사이에다가 끼워주면 될 것 같다. following함수의 괄호 안에는 최대로 누르고 싶은 좋아요의 수를 넣어주면 된다.

def following(following_stopnum):
    driver.get('https://instagram.com')
    driver.implicitly_wait(10)
    try : 
        driver.find_element(By.XPATH, '//*[text() = "나중에 하기"]').click()
        driver.implicitly_wait(10)    
    except :
        pass
    
    try: 
        driver.find_element(By.XPATH, '//div[text() = "새 게시물"] //ancestor :: button').click()
    except:
        pass
    
    following_likenum = 0

    while following_likenum < following_stopnum:
        try : 
            following_likebtn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" and @height = "24"] //ancestor :: button')
            driver.execute_script("arguments[0].scrollIntoView({block : 'center'});", following_likebtn)
            following_likebtn.click()
            following_likenum += 1
            print(f'팔로워 새피드 좋아요 {following_likenum}개 누름')
            time.sleep(round(random.randrange(20)))
        except : 
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            driver.implicitly_wait(10)

5. 사용 예제

아래의 예제처럼 사용하면 된다. bot 함수 위쪽에 following 함수를 넣고, 제일 아래쪽의 함수 본문에 following 함수를 적당한 print 문구 사이에 집어넣었다. 문구는 본인이 원하는 형태로 해도 괜찮을 것 같다. 그럼 잘 쓰길 바란다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
from random import randrange, uniform, shuffle
import unicodedata
from selenium.webdriver.common.by import By

driver = webdriver.Chrome('chromedriver.exe')

# 자신의 아이디, 비밀번호 입력
yourid : str = "자신의 아이디"
yourpassword : str = "자신의 비밀번호"

# 좋아요를 1개 누르는 최소, 최대 시간 간격(1분 내외로 설정)
clickLikePauseMin : float = 50.0
clickLikePauseMax : float = 70.0

# 페이지 로딩까지 대기하는 시간(10초내외로 설정)
pageLoadingWaitMin : float = 3.0
pageLoadingWaitMax : float = 7.0
 
# 인친들 피트 좋아요 누르는 개수 설정
clickLikeFriendFeed : int = 2
 
# 검색을 원하는 태그 입력
tags : list = ['원하는', '태그를', '리스트', '형태로 입력']

# 태그 하나당 좋아요를 누르는 최소, 최대 개수 설정
clickLikeInTagMin : int = 15
clickLikeInTagMax : int = 20

# 다음 태그로 넘어가는 쉬는 시간
tagIntervalmin : float = 300.0
tagIntervalmax : float = 1200.0

# 입력하고싶은 댓글 내용 입력
comments : list = ['♡', '원하는 댓글을', '리스트 형태로 입력', '우리 서로 맞팔해요~']

# 좋아요를 누른 총 개수 카운터(손대지 말 것)
totalLikeCount : int = 0 

def login(id : str, password : str):
    print('로그인 진행중...')
    driver.implicitly_wait(6)
    ur_id = driver.find_element(By.XPATH, '//input[@aria-label="전화번호, 사용자 이름 또는 이메일"]')
    ur_id.send_keys(id)

    # 아이디 입력 후 쉬는시간
    time.sleep(uniform(1.0, 3.0))
    ur_password = driver.find_element(By.XPATH, '//input[@aria-label="비밀번호"]')
    ur_password.send_keys(password)
    
    #비밀번호 입력 후 쉬는시간
    time.sleep(uniform(1.0, 3.0))
    ur_password.send_keys(Keys.ENTER)
    
    #엔터키 입력 후 쉬는시간
    time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
    while True:
        try:
            islogin = driver.find_element(By.XPATH, f'//*[contains(@alt, "{yourid}")]')
            print("로그인 완료")
            break
        except:
            relogin = input('로그인에 실패했습니다. 수동으로 로그인 후 y를 눌러주세요.')
            if relogin == 'y' or 'Y':
                pass

def detect_ad():
    ad_list = ['아이디 입력', '재테크', '투자', '부업', '집테크', '고수입', '수입', '억대연봉', '억대', '연봉', '순수익', '초기금액', '초기 금액', '금액', '입금']
    article = driver.find_elements(By.XPATH, '//article//div[1]/span')
    for texts in article :
        text = unicodedata.normalize('NFC',texts.get_attribute('innerText'))
        for ad in ad_list :
            if text.find(ad) == -1 :
                continue
            else :
                print(f'광고 발견. 발견된 광고단어 : {ad}')
                return True
            
def comment(text : str):
    tringer = randrange(1,5)
    if tringer != 3:
        return
    try : 
        comment_path = driver.find_element(By.XPATH, '//textarea[@aria-label="댓글 달기..."]')
        pass
    except :
        print('댓글이 제한된 피드입니다.')
        return
    comment_path.click()
    comment_path = driver.find_element(By.TAG_NAME, 'textarea')
    driver.implicitly_wait(1)
    comment_path.send_keys(f'{text}')
    comment_path.send_keys(Keys.ENTER)
    print(f'댓글을 달았습니다. 내용 : {text}')
    time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))

def click_likebtn(like_num : int, stop_num : int):
    global totalLikeCount
    like_btn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" or @aria-label="좋아요 취소"] //ancestor :: div[2]')
    like_svg = like_btn.find_element(By.TAG_NAME, 'svg').get_attribute('aria-label')
    
    if like_svg == '좋아요' : 
        like_btn.click()
        like_num += 1 
        totalLikeCount += 1
        print(f'좋아요 {like_num}번째 : 좋아요 총 {totalLikeCount}개')
        
        # 댓글을 달고 싶다면 아래의 주석을 해제
        # comment(comments[randrange(len(comments))])
        
        time.sleep(randrange(clickLikePauseMin, clickLikePauseMax))
        return like_num, stop_num
    
    else :
        stop_num += 1
        print(f'이미 좋아요 작업한 피드 : {stop_num}개 중복')
        time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
        return like_num, stop_num

def next_btn():
    driver.find_element(By.XPATH, '//*[@aria-label="다음"] //ancestor :: button').click()

def following(following_stopnum : int):
    global totalLikeCount
    driver.get('https://instagram.com')
    driver.implicitly_wait(10)
    try : 
        driver.find_element(By.XPATH, '//*[text() = "나중에 하기"]').click()
        driver.implicitly_wait(10)    
    except :
        pass
    
    try: 
        driver.find_element(By.XPATH, '//div[text() = "새 게시물"] //ancestor :: button').click()
    except:
        pass
    
    following_likenum = 0

    while following_likenum < following_stopnum:
 
        try : 
            following_likebtn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" and @height = "24"]/ancestor :: div[2]')
            driver.execute_script("arguments[0].scrollIntoView({block : 'center'});", following_likebtn)
            following_likebtn.click()
            following_likenum += 1
            totalLikeCount += 1
            print(f'팔로워 새피드 좋아요 {following_likenum}개 누름 : 좋아요 총 {totalLikeCount}개')
            time.sleep(uniform(clickLikePauseMin, clickLikePauseMax))
        except Exception as e:
            print(e) 
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            driver.implicitly_wait(10)  
                    
def bot(insta_tag : str, how_many : int): 
    print(f'작업 태그는 {insta_tag}입니다.')
    driver.get(f'https://www.instagram.com/explore/tags/{insta_tag}/')
    
    #페이지 로딩 대기
    time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
    
    new_feed = driver.find_elements(By.XPATH, '//article//img //ancestor :: div[2]')[9]
    new_feed.click()
    
    #페이지 로딩 대기
    time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
       
    like = 0
    stop = 0
    
    for click in range(how_many):
        if detect_ad() == True:
            next_btn()
            #페이지 로딩 대기
            time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
 
        else :
            like, stop = click_likebtn(like, stop)
            next_btn()
            
            #페이지 로딩 대기
            time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
            
            if stop >= 4 :
                print(f'중복 피드가 많아 {insta_tag} 태그 작업 종료함')
                return like
    
    print(f'{insta_tag} 태그 작업완료')
    return like
    
driver.get('https://instagram.com')
login(yourid,yourpassword)
shuffle(tags)

for tag in tags:
    try : 
        #본문에 아래처럼 함수를 삽입합니다.
        following(clickLikeFriendFeed)
        bot(tag, randrange(clickLikeInTagMin,clickLikeInTagMax))
        time.sleep(uniform(tagIntervalmin, tagIntervalmax))
        
    except Exception as e :
        print(e)
        print('오류가 반복된다면 댓글로 문의해주세요.')
        time.sleep(uniform(pageLoadingWaitMin, pageLoadingWaitMax))
        driver.refresh()

driver.quit()
728x90