본문 바로가기
파이썬

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

by fecu 2022. 6. 11.
728x90

2022.07.12. 업데이트
아래의 selenium 홈페이지를 참고하여 find_element 구문을 정규식으로 수정했다.

 

Finding web elements

Locating the elements based on the provided locator values.

www.selenium.dev


2022.07.16.~ 2022.07.17.
오후부터 인스타그램에서 태그 게시물을 클릭할 수 없는 현상을 발견함. 확인 결과 일반 크롬에서도 클릭할 수 없음을 확인함. 인스타그램 측에서 업데이트 대기 중, 17일 15:30 확인 결과 이상없이 작동함. 코드 수정사항 없음.

2022.07.19. 업데이트
태그 검색 후 인기 게시물을 누르던 것을 최신 게시물로 누르도록 수정했다. 수정 전의 내용은 아래와 같다.

new_feed = driver.find_element(By.XPATH, '//article//img //ancestor :: div[2]')

이렇게 설정할 경우 태그를 검색하고, img가 있는 첫번째 개시물을 찾게 된다. 최근 게시물을 찾기 위해서는 img가 있는 요소들을 찾고, 그 요소들 중 10번째를 클릭하도록 만들어야한다. 그래서 코드를 아래와 같이 수정해보았다.

new_feed = driver.find_elements(By.XPATH, '//article//img //ancestor :: div[2]')[9]

위와 같이 설정할 경우, img가 있는 게시물 중 10번째 게시물을 선택한다. 이제 최신 게시물을 클릭할 것이다.

 

2022.08.01. ~ 2022.11.10. 업데이트

내부 코드를 조금씩 수정 했다. bot()함수 내부에 print()가 들어가도록 해서 전체적으로 코드가 깔끔해지도록 만들어보았다. 그리고 login 함수가 실제로 로그인에 성공했는지 확인하고 로그인 완료를 출력하도록 만들었다.

 

2023.01.25. 업데이트

코드를 다시한번 정리해 보았다. 


이번 글은 인스타그램 피드에 댓글을 다는 기능을 추가해보려고 한다. 이번 글에서는 이전의 코드를 한번 전체적으로 재정비하면서 모두 함수로 만들어서 앞으로 수정이 용이하도록 만들어보려고 한다.

인스타 댓글 봇

1. 댓글 달기

1) webdriver 옵션 제거

이때까지 쓴 글은 webdriver에 옵션들을 많이 넣어서 이용하고 있었다. 그런데 이번에 댓글 달기를 구현해보려고 하니 webdriver의 옵션들이 오히려 방해됐다. 그래서 옵션을 모조리 제거해보기로 했다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time
import random
import unicodedata

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

인스타 댓글 봇2

옵션들이 없어지니 리소스를 많이 잡아먹는 것 같기는 하지만 훨씬 더 깔끔하게, 그리고 원래 크롬에서 보는 것과 똑같이 보인다. 이 화면에서 댓글 기능을 한번 구현해보자.

2) 댓글 달기

댓글 입력창의 태그 네임은 textarea이고 속성으로 '댓글 달기...'를 가지고 있다.

인스타 댓글 프로그램

댓글을 다는 방법은 간단하다. 태그네임이 textarea인 요소를 찾고 클릭을 한 뒤, 원하는 내용을 입력해주면 된다. 그리고 요소를 다시 찾은 다음 엔터를 돌려준다.

인스타 댓글 프로그램4
인스타 댓글 프로그램5

이것을 코드로 구현하면 아래와 같다.

comment_path = driver.find_element(By.XPATH, '//textarea[@aria-label="댓글 달기..."]')
comment_path.click()
comment_path = driver.find_element(By.TAG_NAME, 'textarea')
comment_path.send_keys('원하는 텍스트')
comment_path.send_keys(Keys.ENTER)
print(f'댓글을 달았습니다.')

그런데 가끔 댓글을 달 수 없는, 댓글 기능이 제한된 글이 있기도 하다. 이것을 try 구문으로된 함수로 만들어 보자.

def comment(text):
    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')
    comment_path.send_keys(f'{text}')
    comment_path.send_keys(Keys.ENTER)
    print(f'댓글을 달았습니다. 내용 : {text}')

try에서 댓글창을 찾고, 만일 댓글창을 찾을 수 없으면 함수는 정지된다. 댓글창이 있다면 댓글창을 클릭하여 글을 쓴 후 엔터키를 돌려줘서 댓글을 달아준다.

728x90

2. 함수 재정비

이번에는 이전에 썼던 글의 webdriver 옵션들을 모두 제거하고, 글 속에 있던 내용들을 함수로 최대한 축약하여 실행문의 부피를 줄여보려고 한다.

1) import 모듈 및 webdriver 정의

그냥 모든 옵션을 빼버리고 필요없을 것 같은 모듈도 제거해보았다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
import time
import random
import unicodedata

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

2) login(id, password)

id, password를 변수로 받는다. 로그인과 비밀번호 박스를 찾아 아이디를 입력하고 엔터키를 눌러 로그인한다.

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, "//main//div[text()='로그인 정보를 저장하시겠어요?']")
            print("로그인 완료")
            break
        except:
            relogin = input('로그인에 실패했습니다. 수동으로 로그인 후 y를 눌러주세요.')
            if relogin == 'y' or 'Y':
                pass

3) detect_ad()

이전에 썼던 글에서 있던 광고를 넘기는 함수이다. 차이점이 있다면 사용자의 아이디를 넣어두었다. 셀레니움 옵션을 없애고 나니 get_attribute('innerText') 함수로 댓글창의 아이디, 내용까지 모두 긁어올 수 있게 되었다. 덕분에 댓글에 광고 태그가 있어도 잘 걸러질 수 있다. 만일 내가 댓글을 달았던 게시물이라면, 이 함수를 통해서 광고와 함께 거를 수 있다.

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

4) comment(text)

윗쪽에 있는 글과 비슷하지만 조금 다르다. 차이점은 tringger라는 램던한 숫자를 지정해 25%의 확률로 댓글을 달도록 지정했으며, 댓글이 달릴 수 있도록 약간의 대기 시간을 두었다는 것이다. 댓글 기능도 너무 많이 남발하면 좋지 않을 것 같아서 코드를 추가해보았다.

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))

5) click_likebtn(like_num, stop_num)

좋아요를 누르는 함수를 아예 만들어버렸다. 좋아요 버튼을 찾아서 좋아요를 누른 뒤 comment 함수를 실행한다. 만약 좋아요를 이미 누른 버튼이라면 그냥 넘어간다. 그리고 어떤 경우에서도 like_num과 stop_num을 반환한다. 실행문 내에서 좋아요를 누른 횟수, 이미 좋아요를 누른 횟수를 이용하여 다음 태그로 넘어갈 것인지를 결정하도록 할 것이다.

def click_likebtn(like_num : int, stop_num : int):
    global totalLikeCount
    like_btn = driver.find_element(By.XPATH, '//*[@aria-label="좋아요" or @aria-label="좋아요 취소"] //ancestor :: button')
    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

6) next_btn()

다음 버튼을 누르는 것이 길어서 알아보기 쉽고 작게 만들어보려고 함수로 지정했다.

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

7) bot(insta_tag, how_many)

bot 함수는 실제 몸통이 되는 함수이다. insta_tag, how_many를 변수로 받는다. insta_tag는 내가 좋아요를 누르고 싶은 태그를, how_many는 하나의 태그에서 좋아요를 누르고 싶은 횟수를 입력하면 된다. 설정된 태그에서 새로운 피드를 찾고 광고인지를 판단 후, 광고가 아닌 글에 좋아요 버튼을 누른다. 만일 이미 좋아요를 누른 글이 4회가 넘게 나온다면 다음 태그로 이동한다.

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

3. 실행문

함수를 제외한 본문이다. 인스타그램에 접속해서 로그인 후 좋아요를 누르고 댓글을 달아준다. 참고로 하나의 태그에서 다음 태그로 넘어가는 시간은 10분~30분 정도로 무척 길게 주었다. 인스타그램은 무작위적으로 좋아요를 마구 누르는 행위를 차단한다. 하루에 200~300개 이하로 누르고, 반드시 천천히 눌러야 한다.

for tag in tags:
    try : 
        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()

4. 전체코드

전체적으로 코드를 모두 수정하고, 재정비해보았다. 이때까지의 글은 이러한 코드들을 만들어나가는 과정이라고 생각하면서 따라해보면 좋을 것 같다. 유용하게 쓰길 바란다.

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
 
# 검색을 원하는 태그 입력
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 :: button')
    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 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 : 
        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