코딩 몰라도 가능한.. ChatGPT 활용법: 자동 언팔로우 프로그램 만들기

글이 길어요 ㅜㅜ 죄송합니다.



00. 발단 : 저는 현재 SNS로 Threads를 이용하고 있습니다.

여기 문제가, Threads는 내가 팔로우 할 수 있는 계정 숫자에 제한이 있더라구요, 약 7500명. 제가 비공개로 지인끼리 SNS를 쓸 때와는 달리, 딱히 생각 없이 오는 팔로우를 다 받았습니다. 그러다 보니 금새 7500명에 도달했고 더 이상 팔로우 버튼을 눌러도 정상 작동하지 않더라구요. 아무래도 팔로잉 계정 정리를 좀 해야겠다는 생각이 뒤늦게 들었습니다. 근데 7500개를 언제 다 보나 하고 차일피일 미뤘습니다 사실.

그러던 어느 날, 좋은 팁을 알게 되었습니다. Meta에서 내 Threads 계정 정보를 이메일로 받아볼 수 있었습니다. 이 정보에는 내가 팔로우한 사람과 나를 팔로우한 사람이 포함되어 있어서, 대충 비교해보니 맞팔 관계가 아닌 사람을 추려낼 수 있었습니다. 그래서 그 결과, 약 2000개 계정을 언팔로우해야겠더라구요. 7500개 계정 확인보단 당연히 낫지만! 약 2000개도! 이걸 어느 세월에 봐요... 하루에 계정 20개씩 확인을 해도 100일인데...


01. 전개 : 그러다 지피터스에서 좋은 정보글을 봤습니다.

열심히 찾았는데 정확히 어떤 글이었는지는 찾지 못했습니다.. 작성자님께 정말 감사합니다😭

[ 1) 반복 작업 싫다, 2) 근데 코딩은 잘 모른다, 3) Claude3 똑똑하다더라 ] 이런 글로 기억합니다.

그 분의 경우 Claude에게 상황설명을 하니 Claude가 알맞은 Python 코드를 대답해 주고, 이걸 직접 테스트 해보고, 또 다시 Claude에게 수정을 부탁하며 프로그램을 만드신 것 같았습니다.

전 이때까지만 해도, 제가 원하는 게 그냥 정말 단순하게, PC용 앱플레이어에서 자동 클릭 매크로 만드는 수준이라고 생각했습니다. (그 녹화 딸깍딸깍 그거요..) '언팔 계정 리스트.txt' 에서 계정 하나하나 복사 붙여넣기 하고 언팔로우 버튼 누르면 되는 것 아닌가?


1) Claude3을 먼저 켜 봤습니다.

직접 나와 맞팔관계인지 알아보는 방법을 Claude에게 설명해줬습니다. 대충 알아들은 것 같아요!

근데 claude 무료판 메세지 개수가 좀 많이 적어서, 그대로 chatgpt로 갔습니다. gpt-4 사용했습니다.


02. 절정

1) 제가 Python이란 단어가 있다는 것만 기억이 나요.

GPT-4:
안녕하세요! 프로그램 만들기에 관심이 있으시다니 정말 반갑습니다. 말씀하신 것처럼 단순 반복 작업을 자동화하는 매크로를 만드는 것은 효율적인 방법 중 하나입니다. 여기서 필요한 프로그램은 웹 자동화 도구를 사용해서 만들 수 있어요. 예를 들어 Python 언어로 Selenium이나 Puppeteer 같은 도구를 사용할 수 있습니다.

일단 기본적인 접근 방식을 설명드리겠습니다:
1. 환경 설정: Python과 Selenium을 설치하고, 필요한 웹드라이버(크롬 드라이버 등)를 설치합니다.
2. 로그인 자동화: 프로그램이 웹사이트에 자동으로 로그인할 수 있도록 만듭니다.
3. 팔로우 목록 수집: 자신의 팔로우 목록과 팔로워 목록을 수집합니다.
4. 비교 및 해제: 자신이 팔로우하고 있는데 상대방이 팔로우하지 않는 계정을 찾아 팔로우를 해제합니다.

간단한 코드 예제를 보여드릴게요. 아래는 Python과 Selenium을 사용해 로그인하는 예제 코드입니다.
실제로 동작하기 위해서는 웹사이트에 맞게 요소를 선택하고, 사용자 이름과 비밀번호를 입력해야 합니다.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time

# 웹드라이버 설정
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://threads.net/login')

# 로그인 과정
username = driver.find_element(By.ID, 'login_id')
username.send_keys('your_username')
password = driver.find_element(By.ID, 'password')
password.send_keys('your_password')
login_button = driver.find_element(By.ID, 'login_button')
login_button.click()

# 팔로잉 목록 접근
driver.get('https://threads.net/your_profile/following')
time.sleep(2)

# 팔로잉 목록에서 사용자 이름 추출 및 프로필 방문
followings = driver.find_elements(By.CSS_SELECTOR, '.user-name-class')
for user in followings:
    user_name = user.text
    driver.get(f'https://threads.net/{user_name}')
    time.sleep(2)
    
    # '팔로워 삭제' 버튼의 유무 확인
    try:
        more_options = driver.find_element(By.CSS_SELECTOR, 'img.profile-menu')
        more_options.click()
        unfollow_button = driver.find_element(By.XPATH, '//button[text()="팔로워 삭제"]')
        unfollow_button.click()  # 상대방이 나를 팔로우하지 않는 경우, 팔로우 해제
        print(f"Unfollowed {user_name} because they do not follow back.")
    except:
        print(f"{user_name} follows back or button not found.")​

이 스크립트는 각 사용자의 프로필 페이지로 이동하여 '팔로워 삭제' 버튼을 찾고, 버튼이 존재할 경우 클릭하여 팔로우를 해제합니다. 이때, 예외 처리를 통해 버튼이 없는 경우 (즉, 상대방이 당신을 팔로우하지 않는 경우) 언팔로우를 진행합니다.

실제 웹사이트의 구조에 따라 CSS 선택자 및 XPath를 조정할 필요가 있으므로 개발자 도구를 활용하여 정확한 요소를 확인하고 코드를 수정하세요. 또한, 자동화 작업은 웹사이트의 이용 약관을 확인한 후 적법하게 사용해야 합니다. 자동화 도구 사용이 계정 정지나 다른 제제를 초래할 수 있으므로 주의 깊게 접근하시기 바랍니다.

왠지 모르게 무서운 말이랑, 어려운 말이 잔뜩 나왔습니다.

무슨 말인지 모르겠지만 저게 코드라는 것은 알겠으니까, 저대로 복사-붙여넣기-저장-실행 했습니다.

안되는데요?


GPT-4:
Selenium 설치 및 환경 설정 Python 환경에 Selenium과 웹드라이버(ChromeDriver 등) 설치

pip install selenium​

저걸 명령 프롬프트 라는 곳에 써 넣고 엔터 치라고 하더라구요! 얘는 python이 아니네!

웹드라이버는 또 뭐냐고 했더니 친절하게 다운로드 받는 방법을 알려주더라구요!

그래서 다시 저 2가지를 하고 실행을 했더니, 뭔가 되는 듯 하다가…


2) Thread 웹페이지가 자동으로 켜지긴 하는데 로그인이 안 된다.

Thread 로그인 페이지가 나오자마자 무슨 에러가 바로 나오더라구요.

나:
그럼 여기서 어떤 부분을 수정해야 해? 초록색 부분은 다 내가 내 상황에 맞춰서 수정해야 하는거야?


GPT-4:
네, 초록색으로 표시된 부분은 당신의 개인 정보 및 Threads.net 웹사이트에 특정적인 요소를 반영하여 수정해야 하는 부분들입니다. 각 요소에 맞게 수정하는 방법을 상세히 설명드리겠습니다.

웹사이트 URL: driver.get('https://threads.net/login'): 이 부분은 로그인 페이지의 정확한 URL로 수정해야 합니다. 만약 URL이 https://threads.net/login이 맞다면 그대로 두시면 됩니다.

HTML 요소 식별자: By.ID, 'login-id', By.ID, 'password-id', By.ID, 'login-button-id': 이 부분들은 Threads.net 로그인 페이지의 HTML 요소들의 ID입니다. 이 ID들은 실제 웹사이트의 입력 폼과 로그인 버튼에 할당된 실제 ID로 변경해야 합니다. 이 ID들은 웹 페이지의 HTML 코드를 검사(보통 웹 브라우저에서 오른쪽 클릭 후 "검사" 선택)하여 찾을 수 있습니다.

사용자 정보: 'your_username', 'your_password': 여기에는 당신의 Threads.net 계정의 사용자 이름과 비밀번호를 입력합니다. 이는 문자열 형태로 입력되며, 직접 코드에 적어주셔야 합니다.

아, 개인정보를 내가 직접 고쳐 써야 되는 거였구나..

그래서 아이디랑 비밀번호만 고쳐서 다시 실행했습니다.


나:
안되는데 (스크린샷 첨부)

GPT-4:
스크린샷을 확인해보니, 코드 실행 중에 "NoSuchElementException" 에러가 발생했습니다. 이 에러는 지정된 요소를 찾을 수 없을 때 발생합니다. 에러 메시지는 사용자 이름 입력 필드를 찾는 데 실패했음을 나타내고 있습니다. 이는 ... (생략)

지정된 요소? css selector? xpath? (이걸 알았어야 했는데 !!)

모르겠습니다, 그냥 아이디랑 비밀번호 넣고 로그인 버튼 누르기는 제가 직접 한다고 했습니다.


나:
아니면 지금 threads.net/login 페이지까지는 들어가지니까,
30초의 대기시간을 주고, 내가 직접 그 30초동안 아이디와 비밀번호를 입력하고 로그인 버튼을 누르는 건 어때.

GPT-4:
직접 로그인 과정을 수동으로 처리하는 것은 스크립트를 테스트하거나 개발하는 초기 단계에서 유용한 방법이 될 수 있습니다. 이 방식은 특히 자동화 프로세스에서 로그인 매커니즘을 디버깅할 때 좋은 임시 해결책이 될 수 있습니다. 또한, 자동화 스크립트가 웹사이트의 보안 조치로 인해 로그인을 수행하지 못하는 경우에도 유용하게 사용할 수 있습니다.

이 방법의 장단점:

1. 장점:
- 간단한 디버깅: 로그인 매커니즘이 복잡하거나 추가 보안 조치가 있을 때 스크립트를 간단히 테스트하고 진행할 수 있습니다.
- 개발 단계에서 유연성 제공: 초기 개발 단계에서 스크립트의 다른 부분이 정상적으로 작동하는지 확인할 수 있습니다.

2. 단점:
- 자동화의 목적 상실: 자동화의 주된 목적은 인간의 개입 없이 작업을 수행하는 것입니다. 이 방법을 사용하면 자동화의 이점을 일부 상실하게 됩니다.
- 비효율적: 매번 수동으로 로그인을 해야 하므로, 반복적인 작업에는 적합하지 않습니다. 이 방법을 사용하여 원하는 결과를 얻을 수 있으면, 나중에 로그인 과정을 자동화할 방법을 추가로 모색하는 것이 좋습니다. 예를 들어, 로그인 토큰을 사용하거나, 쿠키를 활용하여 세션을 유지하는 방법 등이 있습니다. 이렇게 하면 전체 프로세스의 자동화를 완성할 수 있습니다.

비효율이 무슨 상관이에요, 시작조차 안 되는데..


3) 지정된 요소 모르면 못 넘어가 ( css? xpath? )

근데 어려운 말은 보기 싫어서, 괜히 다른 곳이 문제 아닌가 하고 트집 잡고 있었습니다.

나:
그리고 지금 로그인 이후의 python 코드가 이렇게 되어 있는데, 이러면 최초 1번만 수행하고 끝나는 거 아니야?

# 팔로잉 목록 페이지로 이동
driver.get('https://threads.net/your_profile/following')

# 팔로잉 목록 수집 및 언팔로우 처리
following_links = driver.find_elements(By.CSS_SELECTOR, 'a.profile-link')
for link in following_links:
    driver.get(link)
    try:
        unfollow_button = driver.find_element(By.XPATH, '//button[text()="언팔로우"]')
        unfollow_button.click()
    except:
        print("Already unfollowed or button not found")

# 드라이버 종료
driver.quit()

왜냐하면 내가 현재 Follow한 사람 숫자가 약 7500명이 넘어.
이중에서 나를 Follow하지 않는 사람을 걸러내는 작업이 되야 하잖아.
그러면 적어도 약 7500번의 반복을 해야 하는데, 뭔가 잘못된거 아니야?


GPT-4:
맞습니다, 제시해주신 코드는 현재 구조에서 팔로잉 목록 페이지로 한 번 이동하여 페이지에 나타난 모든 링크에 대해서만 반복을 수행하고, 각 링크에 대해 한 번씩만 접근하여 '언팔로우' 버튼을 클릭하는 방식입니다. 이렇게 되면 전체 팔로잉 목록 중 현재 페이지에 보이는 목록에 대해서만 작업을 수행하고 종료됩니다. 7500명의 팔로잉 목록을 처리하려면, 페이지를 넘기면서 반복적으로 목록을 수집하고 처리하는 로직이 필요합니다.


나:
그럼 생각을 잠깐 해보자. 어차피 나만 쓸 프로그램이잖아.
프로그램이 일일히 약 7500명을 다 검사하게 하지 말자.

내가 나와 '맞팔'이 아닌 계정만 찾아서, 이걸 메모장에 그 계정만 적어서 txt 파일로 줄 수 있어. 
그럼 그 계정들만 검색해서 언팔로우 하면 더 간단하겠지?

0. 내가 너한테 언팔로우 할 계정들의 목록(txt 파일)을 준다.
1. python 코드에서, threads.net/login을 열고, 60초를 기다려.
2. 내가 직접 60초 동안 직접 아이디와 비밀번호를 입력하고 로그인 버튼을 눌러.
3. 내가 직접 계정 검색 페이지까지 들어가 줄 수 있어.
4. 그럼 60초가 지난 후, 계정 검색 페이지부터 프로그램이 자동 작업을 시작해.
5. 자동 작업1: txt파일에서 목록의 첫 번째 계정을 복사 붙여넣기 해서 찾아낸 다음 언팔로우한다.
6. 자동 작업2: 언팔로우 했으면, 다시 txt파일로 돌아가 두 번째 계정을 복사 붙여넣기 해서 찾아낸 다음 언팔로우한다.
7. 자동 작업 3: 이 작업을 목록 n번째 계정을 언팔로우할때까지 반복한다.
8. 마지막 n번째 작업이 끝나면 프로그램을 종료한다

어때?


GPT-4:
지금 말씀하신 방법은 특정 부분에서 사용자의 개입을 통해 보안 기능을 우회하고, 그 이후에 자동화를 진행하는 혼합 접근 방식을 사용하는 것입니다. 이 방식은 프로그램을 통한 대량의 언팔로우 과정을 간소화하면서도 로그인과 초기 단계에서 발생할 수 있는 보안 문제를 효과적으로 회피할 수 있습니다. 사용자가 직접 로그인하고 초기 설정을 수행한 후에 자동화 스크립트가 나머지 작업을 처리하는 방식은 특히 개인적으로 사용하기에 적합합니다.

Python 코드 구현 제안
1. 텍스트 파일 읽기: 언팔로우할 계정 목록이 저장된 텍스트 파일을 읽어서 리스트에 저장합니다.
2. 사용자 로그인 대기: 사용자가 로그인하고 필요한 페이지에 도달할 때까지 기다립니다.
3. 자동 언팔로우 수행: 텍스트 파일에 있는 계정 목록을 순차적으로 검색하고, 각 계정에 대해 언팔로우 작업을 수행합니다.

from selenium import webdriver
import time

# 드라이버 설정
driver = webdriver.Chrome()

# 파일에서 계정 목록 읽기
with open('unfollow_list.txt', 'r') as file:
    accounts = file.readlines()

# 로그인 페이지로 이동
driver.get('https://threads.net/login')

# 사용자가 로그인하고 계정 검색 페이지에 접근할 시간을 주기
time.sleep(60)  # 60초 동안 대기

# 계정 목록을 순회하며 언팔로우
for account in accounts:
    account = account.strip()  # 줄바꿈 문자 제거
    # 계정 검색 페이지에 계정 이름 입력
    search_input = driver.find_element_by_css_selector('input.search')
    search_input.clear()
    search_input.send_keys(account)
    search_input.send_keys(Keys.ENTER)

    time.sleep(5)  # 검색 결과 로드 시간 대기

    # 언팔로우 버튼 클릭
    try:
        unfollow_button = driver.find_element_by_xpath('//button[text()="언팔로우"]')
        unfollow_button.click()
        print(f"Unfollowed {account}")
    except:
        print(f"Could not unfollow {account} or button not found")

# 모든 작업 완료 후 드라이버 종료
driver.quit()


나:
그럼 위에 코드를 이용한다면,
내가 '언팔 계정 리스트'를 메모장에 옮겨적고 'unfollow_list.txt' 라고 저장만 하면,
내가 특정 경로를 지정하지 않아도, 컴퓨터 내 저장장치에 unfollow_list.txt를 자동으로 열어서
그 안의 정보를 한 줄 한 줄 차례대로 읽을 수 있어?


GPT-4:
네, 그렇습니다. 하지만 open('unfollow_list.txt', 'r') 부분에서 unfollow_list.txt 파일을 열 때 파일의 위치는 현재 파이썬 스크립트가 실행되는 디렉토리(작업 디렉토리)를 기준으로 합니다. 
즉, 이 코드가 정상적으로 작동하려면 unfollow_list.txt 파일이 파이썬 스크립트와 같은 폴더에 있어야 합니다.

뭔가 수정해줬어요! 그래서 다시 복사-붙여넣기를 하고,


그러니까 .py랑 unfollow_list.txt를 같은 폴더에 놓으란 것은 알아들었습니다.


4) 근데 계속해서 NoSuchElementException이 나옵니다, 당연하지…

왜냐면 그 ‘지정된 요소’가 무슨 말인지 계속 못 알아들었거든요!! css ??? xpath ???


나:
(스크린샷) 안되잖아


GPT-4:
에러 로그를 확인해 보니 "NoSuchElementException"이 발생하고 있어, 여전히 Selenium 스크립트가 요소를 찾지 못하고 있습니다. 이 문제는 여러 원인으로 발생할 수 있지만, 가장 흔한 원인은 다음과 같습니다:
1. 요소의 로드 시간: 페이지의 요소가 로드되기 전에 Selenium이 요소를 찾으려고 시도할 수 있습니다. 페이지가 완전히 로드될 때까지 기다리도록 스크립트에 명시적인 대기 로직을 추가해야 할 수 있습니다.
2. 잘못된 선택자: 선택자가 페이지의 요소와 정확히 일치하지 않을 경우 발생합니다. CSS 선택자를 다시 확인해야 합니다.
3. 동적 요소: 페이지에 자바스크립트에 의해 동적으로 로드되는 요소가 있을 수 있습니다. 이 경우, 요소가 DOM에 완전히 로드되고 나타날 때까지 대기해야 합니다.


나:
(스크린샷) 시간 더 늘려도 안되잖아


GPT-4:
요소의 선택자가 정확하게 설정되어 있고, 요소가 페이지에 존재하는지 다시 한 번 확인하세요. 필요하면 개발자 도구를 사용하여 페이지의 요소를 다시 검사하고, 사용 중인 선택자가 실제 요소와 일치하는지 확인해 보세요.

정확한 선택자 찾기
1. 개발자 도구 사용: 다시 한 번 개발자 도구를 열고, 로그인 버튼을 직접 클릭하거나 탐색하여 그 속성을 확인합니다.
2. 다른 속성으로 선택자 구성: 버튼에 고유한 class가 있거나, 특정 텍스트를 포함하고 있다면, 그 속성을 기반으로 선택자를 구성할 수 있습니다.
3. XPath로 시도: CSS 선택자로 요소를 찾는 데 어려움이 있다면, XPath를 사용해 볼 수 있습니다. 예를 들어, 버튼의 텍스트나 다른 특징을 기반으로 한 XPath를 사용할 수 있습니다

개발자 도구.. CSS.. XPath.. 결국 제가 이걸 이해해야 하나 봅니다..

개발자 도구는, 왼쪽 클릭 후 맨 밑에 '검사'를 누르면 된다고 합니다.. 눌렀더니

무시무시한게 나왔습니다. 왜 일이 이렇게 커졌나..? 그만 해야 하나..?

게임 매크로처럼 대충 클릭 아니였나..? 보통 이런 창은 막 엄청난 사람들만 보는 거 아닌가 ..?


근데 그만하기에는 제가 하루 이상 날려서 좀 아까웠습니다. 그리고 2000명 지우기 작업도 너무 무섭구요..

어피치 손가락 끝 위치처럼, 한 줄 한 줄 위에 커서를 대 보니까, 그 줄에 써 있는 <...>이것에 대응하는 부분이, 왼쪽 실제 화면에 파랗게 표시가 되는 거 같아요. 해당 줄을 자세히 보면 중간에 "..." 이라고 생략 된 곳이 있습니다.

그걸 클릭해보면 다음과 같이 더 세부적으로 나오는 모양입니다..?

차근히 "..."으로 생략된 부분을 열어서 파고들어보니, 드디어 [팔로잉] 버튼 부분을 찾았습니다..


이건가 봐요…

전 여전히 CSS selector나 XPath가 정확히 뭔지 모르겠는데, 대충 웹 사이트 속 요소의 주소..인가봐요…

이제 gpt-4가 준 코드에서

by_xpath(어쩌구 저쩌구)이 부분에, xpath든 selector든 복붙해서 써 넣으면 되겠죠? 제발 !!!


5) 문제가 좀 더 있었습니다.

(1) 언팔로우를 위한 [팔로잉] 버튼 위치만 찾을게 아니었습니다.

- 일단 검색 창을 클릭해야 한다.

- 그 다음 unfollow_list.txt에서 읽어온 계정명을 입력해야 한다.

- 로딩될때까지 잠깐 기다린다.

- 검색되어 나온 첫 번째 계정명과 unfollow_list.txt에서 읽어온 계정명이 동일한지 확인한다.

- 동일하면 [팔로잉] 버튼을 눌러서 언팔로우 수행, 동일하지 않으면 건너뛰기.

- 검색 창에 입력해놨던 계정명을 지운다.

- 대충 2000번 반복한다

즉, 검색 창 위치, 검색되어 나온 첫 번째 계정 이름 위치, [팔로잉] 버튼 위치, 지우기 버튼 위치를 전부 알아야 한다.


(2) 사실 저렇게 해서도 NoSuchElementException가 계속 나오더라구요.

제 생각에는,

저 검색 창 자체가 내가 클릭하면 스르륵 나오고, 클릭하지 않으면 스르륵 들어가는(?) 것이어서가 아닐까..

즉, 저 모든 위치가 나타났다 사라졌다 할 때도 있고, selector나 xpath가 계속 변경될 수도 있다?

다시 gpt-4에게 물어봤습니다. 어차피 전 질문만 할 줄 알아요 !

나:
그리고 너는 이제,
<div class="x6s0dn4 x78zum5"><span class="x6s0dn4 x78zum5 x1q0g3np"><div class=""><div><object type="nested/pressable"><a class="x1i10hfl xjbqb8w x1ejq31n xd10rxx x1sy0etr x17r0tee x972fbf xcfux6l x1qhh985 xm0m39n x9f619 x1ypdohk xt0psk2 xe8uvvx xdj266r x11i5rnm xat24cr x1mh8g0r xexx8yu x4uap5 x18d9i69 xkhd6sd x16tdsg8 x1hl2dhg xggy1nq x1a2a7pz xp07o12 xzmqwrg x1citr7e x1kdxza xt0b8zv" href="/@개인아이디" role="link" tabindex="0"><span class="x1lliihq x1plvlek xryxfnj x1n2onr6 x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x1i0vuye xjohtrz x1s688f xp07o12 x1yc453h" dir="auto" style="line-height: var(--base-line-clamp-line-height); --base-line-clamp-line-height: calc(1.4 * 1em);">개인아이디</span></a></object></div></div></span></div>
이걸 찾아낼 선택자를 나한테 알려줘야 한다.


GPT-4:
제가 본문에서 언급된 정보를 바탕으로 유추하자면, "개인아이디"는 특정 소셜 미디어 플랫폼의 사용자 이름일 가능성이 높습니다. 이 링크는 해당 사용자의 프로필 페이지로 이동하는 하이퍼링크 역할을 하며, 웹 페이지의 사용자와의 상호작용을 위해 설정된 요소 중 하나입니다. 이 링크를 통해 사용자는 "개인아이디"의 프로필을 볼 수 있으며, 여기에는 아마도 사진, 게시물, 팔로워 수, 팔로잉 수 등의 정보가 포함될 것입니다. 이와 같은 링크는 사용자들이 서로를 더 쉽게 찾고 소통할 수 있게 해주는 중요한 도구 중 하나입니다. 링크에 사용된 CSS 클래스와 구조는 웹 개발자가 스타일과 기능을 정의하기 위해 사용하는 것으로, 이러한 코드를 통해 웹 페이지의 시각적인 부분과 상호작용적인 부분을 제어합니다.


나:
근데 "개인아이디"는 unfollow_list.txt에서 읽어올 때마다 그때그때 바뀌는 이름이다.
그러니까 다른 선택자가 필요하다


GPT-4:
아하, 이해했습니다! “개인아이디”와 같이 동적으로 변하는 사용자 이름을 다룰 때는 일반적인 검색자보다는 더 구체적인 방식이 필요하겠네요. 웹 스크래핑이나 자동화된 브라우저 작업을 할 때는 클래스 이름, ID, 또는 특정 HTML 구조를 기반으로 하는 것이 일반적입니다. 예를 들어, CSS 클래스나 상위 요소를 활용하여 고유한 패턴을 식별할 수 있습니다.

정확한 선택자를 만들기 위해서는 해당 웹페이지의 전체적인 HTML 구조를 더 자세히 분석할 필요가 있습니다. 만약 정적인 페이지가 아니라면, 동적으로 로딩되는 컨텐츠에 접근하기 위해 JavaScript를 실행할 수 있는 환경에서 스크래핑해야 할 수도 있습니다.


나:
너한테 어떻게 하면 이 웹페이지의 전체적인 HTML 구조를 알려줄 수 있을까????


GPT-4:
Selenium을 사용하여 웹 페이지의 전체 HTML 구조를 확인하는 방법은 간단합니다. 페이지의 전체 HTML을 가져오려면, Selenium WebDriver를 통해 페이지를 로드한 후 page_source 속성을 사용하여 페이지의 소스 코드를 얻을 수 있습니다. 여기에 Python을 사용한 간단한 예시를 들어 보겠습니다:

from selenium import webdriver

# 웹드라이버 초기화 (Chrome을 예로 들었습니다)
driver = webdriver.Chrome()

# 원하는 웹 페이지로 이동
driver.get("https://www.example.com")

# 페이지의 전체 HTML 소스 가져오기
html_source = driver.page_source

# HTML 소스 출력
print(html_source)

# 드라이버 종료
driver.quit()​

이 스크립트는 지정된 URL의 웹 페이지를 불러오고, 해당 페이지의 전체 HTML 소스를 출력합니다. 웹드라이버는 여러분의 시스템에 설치된 브라우저에 따라 webdriver.Chrome(), webdriver.Firefox() 등으로 변경할 수 있습니다. 또한, WebDriver를 사용하기 전에 해당 브라우저의 드라이버를 시스템에 설치해야 합니다. 위 코드를 실행하면, 웹 페이지의 전체 HTML 구조를 볼 수 있고, 이를 통해 필요한 요소를 찾기 위한 적절한 선택자를 개발할 수 있습니다.

음… 일단 저 코드를 따로 .py로 저장해서 실행한 후,

뭔가 나온 것을 전부 다 복붙해서 txt파일로 만들어서 다시 gpt-4에게 줘 봤습니다.


나:
자 내가 첨부한 remember.txt가 웹 페이지의 전체 HTML 구조야.

너는 그 중에서 나타났다 사라졌다 하는,
<div class="x6s0dn4 x78zum5"><span class="x6s0dn4 x78zum5 x1q0g3np"><div class=""><div><object type="nested/pressable"><a class="x1i10hfl xjbqb8w x1ejq31n xd10rxx x1sy0etr x17r0tee x972fbf xcfux6l x1qhh985 xm0m39n x9f619 x1ypdohk xt0psk2 xe8uvvx xdj266r x11i5rnm xat24cr x1mh8g0r xexx8yu x4uap5 x18d9i69 xkhd6sd x16tdsg8 x1hl2dhg xggy1nq x1a2a7pz xp07o12 xzmqwrg x1citr7e x1kdxza xt0b8zv" href="/@개인아이디" role="link" tabindex="0"><span class="x1lliihq x1plvlek xryxfnj x1n2onr6 x193iq5w xeuugli x1fj9vlw x13faqbe x1vvkbs x1s928wv xhkezso x1gmr53x x1cpjm7i x1fgarty x1943h6x x1i0vuye xjohtrz x1s688f xp07o12 x1yc453h" dir="auto" style="line-height: var(--base-line-clamp-line-height); --base-line-clamp-line-height: calc(1.4 * 1em);">개인아이디</span></a></object></div></div></span></div>
이 요소를 찾아내야 한다!!!!!!!!!!

근데 "개인아이디"같은 특정한 이름은 동적으로 바뀌기 때문에 소용이없다!!!

이거를 찾아낼 선택자를 만들어봐!!!!!!!!!!!!!!!!!!!!!!!!!


GPT-4:
그 웹페이지 HTML 내에서 동적으로 변하는 사용자 이름을 가진 링크 요소를 찾으려면 다음과 같이 CSS 선택자를 구성하여 검색할 수 있습니다:

div.x6s0dn4.x78zum5 > span.x6s0dn4.x78zum5.x1q0g3np > div > div > object[type="nested/pressable"] > a[href^="/@"]

이 선택자는 클래스 x6s0dn4와 x78zum5를 포함하는 <div> 태그 내부에 위치한 <a> 태그를 타겟으로 하며, href 속성 값이 "/@"로 시작하는 요소를 찾습니다. 이 방식은 동적으로 변하는 사용자 이름을 가진 링크를 찾는데 유용하며, href의 기본 구조가 변하지 않는 한 유효합니다.

이후로 NoSuchElementException이 나올 때마다, 계속 위의 방법으로 선택자인지 뭔지를 찾으라고 시켰습니다. 스크린샷 찍어서 보여주면 바로 알려주더라구요 …!!!


결과적으로 챗지피티에게 꼬장 부려서, 전부 css?로 다 수정했습니다!!! 그 결과 !!!!!!!!!!!!!!!!!

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

import time

# 드라이버 설정
driver = webdriver.Chrome()

# 파일에서 계정 목록 읽기
with open('unfollow_list.txt', 'r') as file:
    accounts = file.readlines()

# 로그인 페이지로 이동
driver.get('https://threads.net/login')

# 사용자가 로그인하고 계정 검색 페이지에 접근할 시간을 주기
time.sleep(30)  # 60초 동안 대기
print(f"30 seconds")
time.sleep(20)
print(f"after 10 seconds, start")
time.sleep(10)

# 계정 목록을 순회하며 언팔로우
for account in accounts:
    account = account.strip()  # 줄바꿈 문자 제거

    
    # 검색 입력 필드 선택
    selector = "input.x1i10hfl.x9f619.xggy1nq.x1s07b3s.x1kdt53j.x1a2a7pz.x1ggkfyp.x972fbf.xcfux6l.x1qhh985.xm0m39n.xp07o12.x1i0vuye.xjohtrz.x5yr21d.x1yc453h.xh8yej3.x1e899rk.x1bn1fsv.xo6swyp.x1ad04t7.x1glnyev.x1ix68h3.x19gujb8[type='search']"
    search_input = driver.find_element(By.CSS_SELECTOR, selector)
    search_input.clear()
    
    search_input.send_keys(account)

    time.sleep(10)  # 검색 결과 로드 시간 대기
    print(f"after 5seconds, start")
    

    # 언팔로우 버튼 클릭
    try:

        # 검색 결과 텍스트와 입력한 계정 이름이 일치하는지 확인
        result_text = driver.find_element(By.CSS_SELECTOR, 'div.x6s0dn4.x78zum5 > span.x6s0dn4.x78zum5.x1q0g3np > div > div > object[type="nested/pressable"] > a[role="link"]').text

        if account == result_text:

            # 계정 이름과 검색 결과 텍스트가 완전히 같을 경우 실행되는 코드
            print("계정과 검색 결과가 정확히 일치합니다.")
            
            profile_link = driver.find_element(By.CSS_SELECTOR, 'div.x1i10hfl.xjbqb8w.x1ypdohk.xdl72j9.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xexx8yu.x18d9i69.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1q0g3np.x1lku1pv.x1a2a7pz.x6s0dn4.x1a2cdl4.xnhgr82.x1qt0ttw.xgk8upj.x9f619.x3nfvp2.x1s688f.x90ne7k.xl56j7k.x193iq5w.x1swvt13.x1pi30zi.x12w9bfk.x1g2r6go.x11xpdln.xz4gly6.x87ps6o.xuxw1ft.x19kf12q.x6bh95i.x1re03b8.x1hvtcl2.x3ug3ww.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x178xt8z.xm81vs4.xso031l.xy80clv.xu0ddkp.xwsj4vy[role="button"][tabindex="0"]')
            profile_link.click()

            time.sleep(10)

            try:
                # CSS 선택자를 사용하여 요소 찾기
                re_check = driver.find_element(By.CSS_SELECTOR, 'div.x1i10hfl.xjbqb8w.xjqpnuy.xa49m3k.xqeqjp1.x2hbi6w.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xeuugli.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1o1ewxj.x3x9cwd.x13rtm0m.x1q0g3np.x87ps6o.x1lku1pv.x1a2a7pz.x6s0dn4.x78zum5.x1iyjqo2.x1dnml7z.xl56j7k.x18xmwgd.x12v9rci[role="button"]')

                # 요소가 있으면 클릭
                re_check.click()
                print("비공계 계정과의 팔로우가 삭제되었습니다")

                search_field = driver.find_element(By.CSS_SELECTOR, 'div.x78zum5 > div.x1i10hfl.x1qjc9v5.xjbqb8w.xjqpnuy.xa49m3k.xqeqjp1.x2hbi6w.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xdl72j9.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xeuugli.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1o1ewxj.x3x9cwd.x1e5q0jg.x13rtm0m.x3nfvp2.x1q0g3np.x87ps6o.x1lku1pv.x1a2a7pz#typeahead-clear-button')
                search_field.click()

            except Exception:
                # 요소가 없으면 메시지 출력
                print("이상없습니다. 진행합니다")

                print(f"Unfollowed {account}")
            
                search_field = driver.find_element(By.CSS_SELECTOR, 'div.x78zum5 > div.x1i10hfl.x1qjc9v5.xjbqb8w.xjqpnuy.xa49m3k.xqeqjp1.x2hbi6w.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xdl72j9.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xeuugli.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1o1ewxj.x3x9cwd.x1e5q0jg.x13rtm0m.x3nfvp2.x1q0g3np.x87ps6o.x1lku1pv.x1a2a7pz#typeahead-clear-button')
                search_field.click()

            
        else:
            print(f"Account {account} not found or has left Threads.")
            
            search_field = driver.find_element(By.CSS_SELECTOR, 'div.x78zum5 > div.x1i10hfl.x1qjc9v5.xjbqb8w.xjqpnuy.xa49m3k.xqeqjp1.x2hbi6w.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xdl72j9.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xeuugli.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1o1ewxj.x3x9cwd.x1e5q0jg.x13rtm0m.x3nfvp2.x1q0g3np.x87ps6o.x1lku1pv.x1a2a7pz#typeahead-clear-button')
            search_field.click()

            
    except Exception as e:
        print(f"Could not unfollow {account} or button not found: {str(e)}")
        
        search_field = driver.find_element(By.CSS_SELECTOR, 'div.x78zum5 > div.x1i10hfl.x1qjc9v5.xjbqb8w.xjqpnuy.xa49m3k.xqeqjp1.x2hbi6w.x13fuv20.xu3j5b3.x1q0q8m5.x26u7qi.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xdl72j9.x2lah0s.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x2lwn1j.xeuugli.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x1n2onr6.x16tdsg8.x1hl2dhg.xggy1nq.x1ja2u2z.x1t137rt.x1o1ewxj.x3x9cwd.x1e5q0jg.x13rtm0m.x3nfvp2.x1q0g3np.x87ps6o.x1lku1pv.x1a2a7pz#typeahead-clear-button')
        search_field.click()


# 모든 작업 완료 후 드라이버 종료
driver.quit()

된다 !!!!!!!!!!!!!!


03. 결말

1) 일단 대략적인 작동 방식은 이렇습니다.

- 이 .py랑 같은 폴더 내의 'unfollow_list.txt'에서 계정 이름을 하나 읽어서 account라고 저장합니다.

- Thread 로그인 웹페이지가 자동으로 켜집니다

- 총 60초동안 느긋하게 로그인을 하고, 상단 돋보기 모양 아이콘을 눌러, 검색 창에 들어갑니다 (이것만 사용자가 직접…)

- 검색 창 속 입력 칸을 직접 요 프로그램이 찾고, 그 안에 account를 입력합니다.

- 보통 검색 첫 번째 프로필만 대조해보면 됩니다.

- 첫 번째 프로필의 계정명이 account와 같으면 [팔로잉] 버튼을 눌러 언팔로우를 수행합니다.

- 가끔 비공계 계정일 경우 뭔가 창이 하나 더 뜨는데, 알아서 확인을 찾아서 누릅니다.

- 첫 번째 프로필의 계정명이 account와 다르면 그냥 "이상없습니다, 진행합니다"가 나오고 건너뜁니다.

- 가끔 아예 계정 삭제를 하셔서 첫 번째 프로필도 안 뜰 때가 있습니다. "떠났다"고 알려주고 건너뜁니다.

- 아주 가끔 아예 뜬금없는 계정이 첫 번째 프로필로 뜰 때가 있습니다.

- 뭐라고 오류가 뜨는데 상관 없을 것 같아서 건너뛰게 뒀습니다.

- 위 경우 중 하나를 성공하면 입력했던 account를 지웁니다.

- 그리고 다시 unfollow_list.txt 내 다른 계정명을 account에 저장하고, 위 과정을 반복합니다

- 끝

2) 저걸 하는 과정에서

- 웹 페이지 켜지게 두고, 로그인하고, 에러 나서 끄고, 다시 켜지게 두고, 로그인하고,

- 이걸 수 차례 계속 반복을 했더니 계정에 이상한 게 감지되었다고 일시적으로 로그인이 안되더라구요..

3) 그래서 60초정도 시간을 주고 그냥 직접 로그인을 느긋하게 했는데,

- 이마저도 계속 반복을 하니까 계정 비밀번호를 바꾸라고 연락이 오더라구요..

4) 계정 아예 잠기기 전에 성공 할 수 있어서 행복했습니다..

5) 결국 중요한건, 스크린샷 캡쳐를 해서 보여주든 상황설명을 하고

- "왜 안되냐!!" 꼬장 부리면 ChatGPT가 뭔가 해 줍니다.

- 대답이 맘에 안들면 계속 이래저래 질문하다 보면 해결법이 나오는게 신기했습니다.


04. 횡설수설 장황한 글을 읽어주셔서 감사합니다.

저는 문과로 대학에 들어가서

10년 전에 기초 교양으로 python 강의 잠깐 들은게 다인데, 이게 되네요…


찐 개발자, 찐 고수분들이 보면 뭐 이런 바보(..)가 다 있어 싶겠지만

개인적으로 2000번 딸깍을 안하게 되어서 너무 좋아서… 사방팔방 떠들고 싶었습니다

감사합니다.

🙆‍♀️

10
11개의 답글

📚 모집 중인 AI 스터디