Python과 ChatGPT API를 활용한 자동화 로봇 개발

8_Python과 ChatGPT API를 이용한 블로그 자동화 프로그램 개발

atomicdev 2024. 8. 15. 16:20
728x90

강의 8: 자동화 프로그램 설계 및 구현

목표:
이 강의의 목표는 앞에서 학습한 내용을 종합하여, 티스토리 블로그 자동화 프로그램을 설계하고 구현하는 것입니다. 이 프로그램은 블로그의 글 목록을 가져와 특정 조건에 맞게 필터링하고, 공감을 클릭하고, ChatGPT API를 이용하여 댓글을 작성하는 기능을 포함합니다. 또한, 크롤링 진행 시간을 SQLite 데이터베이스에 저장하여, 다음 크롤링 시 효율적으로 데이터를 처리할 수 있게 합니다.

내용

  1. 프로그램의 전체 구조 설계
    • 블로그 글 목록 크롤링
    • 글 필터링
    • 공감 클릭
    • ChatGPT API를 이용한 댓글 작성
    • 크롤링 진행 시간 데이터베이스에 저장
    • 데이터베이스에서 마지막 처리일자 이후의 글만 처리
  2. 글 목록을 가져와 조건에 맞는 글 필터링하기
    • 글의 작성 날짜를 기준으로 필터링하여, 데이터베이스에 저장된 마지막 처리일자 이후에 작성된 글만 처리합니다.
  3. 공감 클릭 및 댓글 작성 기능 통합
    • Selenium을 사용하여 글 목록의 각 글에 대해 공감을 클릭합니다.
    • ChatGPT API를 이용하여 자동으로 댓글을 작성합니다.
  4. 크롤링 진행 시간 데이터베이스에 저장 (SQLite)
    • SQLite 데이터베이스에 마지막 크롤링 시간을 저장하고, 다음 실행 시 이 시간 이후에 작성된 글만 필터링합니다.
  5. 프로그램의 실행 및 디버깅
    • 프로그램을 실행하고, 발생하는 문제를 디버깅하며 최종적으로 완성된 프로그램을 구현합니다.
  6. 실습: 데이터베이스에 저장된 마지막 처리일자 이후 현재까지 작성된 모든 글에 공감과 ChatGPT 기반의 댓글을 자동으로 처리하는 프로그램 완성하기
    • SQLite 데이터베이스에 마지막 처리일자를 기록하고, 그 이후의 글만 처리하도록 합니다.

소스 코드

ReplyAgent.ipynb라는 노트북을 만들어서 작업을 진행합니다.
한번에 전체 실행되도록 하기 위해 한 Cell 안에 모든 소스를 구현하였습니다.
 
import openai
import sqlite3
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
import time

# OpenAI API 키 설정
openai.api_key = '발급받은 API Key'


# SQLite 데이터베이스 초기화
def init_db():
    conn = sqlite3.connect('crawling_history.db')
    cursor = conn.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS CrawlingHistory (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            last_crawled TIMESTAMP
        )
    ''')
    conn.commit()
    conn.close()

# 마지막 크롤링 시간을 가져오기
def get_last_crawled_time():
    conn = sqlite3.connect('crawling_history.db')
    cursor = conn.cursor()
    cursor.execute('SELECT last_crawled FROM CrawlingHistory ORDER BY id DESC LIMIT 1')
    last_time = cursor.fetchone()
    conn.close()
    if last_time:
        return datetime.strptime(last_time[0], '%Y-%m-%d %H:%M:%S')
    else:
        # 크롤링 기록이 없다면 7일 전을 기본값으로 사용
        return datetime.now() - timedelta(days=7)

# 크롤링 시간을 업데이트하기
def update_last_crawled_time():
    conn = sqlite3.connect('crawling_history.db')
    cursor = conn.cursor()
    cursor.execute('INSERT INTO CrawlingHistory (last_crawled) VALUES (?)', (datetime.now().strftime('%Y-%m-%d %H:%M:%S'),))
    conn.commit()
    conn.close()

# 티스토리 블로그에서 특정 페이지의 글 목록을 가져오기
def fetch_blog_posts(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.content, 'html.parser')
    articles = soup.find_all('article', class_='article-type-common')

    posts = []
    for article in articles:
        post_url_suffix = article.find('a', class_='link-article')['href']
        post_id = post_url_suffix.split('/')[-1]  # 게시글 ID 추출
        post_url = f"https://atomicdev.tistory.com{post_url_suffix}"
        title_tag = article.find('strong', class_='title')
        title = title_tag.get_text(strip=True)  # 글 제목
        date_str = article.find('span', class_='date').get_text(strip=True)  # 작성 날짜
        date = datetime.strptime(date_str, '%Y.%m.%d')
        posts.append({'id': post_id, 'title': title, 'url': post_url, 'date': date})

    return posts

# ChatGPT를 사용해 댓글 내용 생성
def generate_comment_content(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",  # 또는 gpt-4
        messages=[{"role": "user", "content": prompt}],
        max_tokens=400,
        n=1,
        stop=None,
        temperature=0.7
    )
    return response['choices'][0]['message']['content'].strip()

# 글 처리 함수
def process_post(driver, post):
    # 페이지로 이동
    driver.get(post['url'])
    
    # 공감 버튼 클릭 처리
    try:
        like_button_container = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "div.postbtn_like"))
        )
        
        like_button = like_button_container.find_element(By.CSS_SELECTOR, "div.uoc-icon")
        if "like_on" not in like_button.get_attribute("class"):
            like_button.click()
            print(f"공감(좋아요) 버튼을 클릭했습니다: {post['title']}")
        else:
            print(f"공감(좋아요) 버튼이 이미 클릭된 상태입니다: {post['title']}")
    except Exception as e:
        print(f"공감(좋아요) 버튼을 클릭하지 못했습니다: {post['title']}")
        print(e)

    # 페이지 로드와 공감(좋아요) 클릭이 완료될 때까지 대기
    time.sleep(2)

    # 본문 내용 추출
    try:
        content = driver.find_element(By.CSS_SELECTOR, "div.tt_article_useless_p_margin").text
        print(f"본문 내용을 성공적으로 추출했습니다: {post['title']}")
    except Exception as e:
        print(f"본문 내용을 추출하지 못했습니다: {post['title']}")
        print(e)
        return

    # 댓글 내용 생성
    prompt = f"경력 28년차의 프로그래머 입장에서 다음 글에 대한 평가를 간결하게 작성해줘: {content}"
    comment = generate_comment_content(prompt)
    print(f"Generated Comment: {comment}")

    # 댓글 작성 및 제출
    try:
        comment_input = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, "div[contenteditable='true'].tt-cmt"))
        )
        
        driver.execute_script("arguments[0].innerText = arguments[1]; arguments[0].dispatchEvent(new Event('input', { bubbles: true }));", comment_input, comment)
        
        submit_button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button.tt-btn_register"))
        )
        submit_button.click()

        print(f"댓글이 성공적으로 등록되었습니다: {post['title']}")
    except Exception as e:
        print(f"댓글을 등록하는 데 실패했습니다: {post['title']}")
        print(e)

# 메인 실행 함수
def main():
    # 데이터베이스 초기화 및 마지막 크롤링 시간 가져오기
    init_db()
    last_crawled = get_last_crawled_time()
    print(f"마지막 크롤링 시간: {last_crawled}")

    # 크롬 드라이버 설정 및 실행
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)

    # 1. 티스토리 메인 페이지 열기
    driver.get("https://atomicdev.tistory.com")

    # 2. 로그인 후 프롬프트 대기
    input("로그인 후 Enter 키를 눌러 주세요...")

    # 로그인 후 페이지로 이동
    page = 1
    process_next_page = True
    total_processed_posts = 0  # 총 처리된 게시글 수

    start_time = time.time()  # 시작 시간 기록

    while process_next_page:
        # 현재 페이지의 글 목록 가져오기
        blog_url = f"https://atomicdev.tistory.com/?page={page}"
        posts = fetch_blog_posts(blog_url)

        if not posts:
            print(f"{page}페이지에 더 이상 글이 없습니다.")
            break

        # 마지막 글의 날짜 확인
        oldest_post_date = posts[-1]['date']
        if oldest_post_date <= last_crawled:
            process_next_page = False  # 이전 페이지로 넘어가지 않음

        # 마지막 크롤링 시간 이후의 글만 처리
        for post in posts:
            if post['date'] > last_crawled:
                process_post(driver, post)
                total_processed_posts += 1
            else:
                process_next_page = False
                break

        page += 1

    # 크롤링 시간 업데이트
    update_last_crawled_time()

    # 브라우저 닫기
    driver.quit()

    # 총 처리 시간 계산
    end_time = time.time()
    total_time = (end_time - start_time) / 60  # 분 단위로 계산

    print(f"총 {total_processed_posts}개의 게시글을 처리했습니다.")
    print(f"총 처리 시간: {total_time:.2f}분")

if __name__ == "__main__":
    main()
 
 
아래와 같이 모든 게시글에 ghatgpt api를 이용한 자동 댓글이 작성되어 있는것을 확인할 수 있다.
 
한번 처리가 완료되고 현재 날짜(시간)으로 크롤링 된 시간이 업데이트 되면 지금 이시간 이전에 작성된 글에는 댓글이나 공감(좋아요) 클릭을 할 수 없다.
 
 
 
테스트가 더 필요할때 마지막 크롤링 된 시간을 7일 전으로 임의로 업데이트 하는 코드는 다음과 같다.
import sqlite3
from datetime import datetime, timedelta

# 마지막 크롤링 시간을 1주일 전으로 업데이트
def update_crawled_time_to_one_week_ago():
    conn = sqlite3.connect('crawling_history.db')
    cursor = conn.cursor()

    one_week_ago = datetime.now() - timedelta(days=7)
    cursor.execute('INSERT INTO CrawlingHistory (last_crawled) VALUES (?)', (one_week_ago.strftime('%Y-%m-%d %H:%M:%S'),))

    conn.commit()
    conn.close()
    print(f"마지막 크롤링 시간을 {one_week_ago.strftime('%Y-%m-%d %H:%M:%S')}로 업데이트했습니다.")

# 함수 실행
update_crawled_time_to_one_week_ago()
 
이렇게 마지막 크롤링된 시간을 임의로 수정해서 재실행할 경우 비슷한 댓글이 여러개 작성될 수 있으니 반드시 확인후 삭제 해주세요.

설명:

  1. SQLite 데이터베이스 초기화: 데이터베이스에 crawl_log라는 테이블을 생성하여, 마지막 크롤링 시간을 저장합니다.
  2. 마지막 크롤링 시간 가져오기: 데이터베이스에서 마지막으로 크롤링한 시간을 가져옵니다. 이 시간을 기준으로 이후의 글만 처리합니다.
  3. 글 목록 크롤링 및 필터링: 티스토리 블로그에서 글 목록을 가져오고, 마지막 크롤링 시간 이후에 작성된 글만 필터링하여 처리합니다.
  4. 공감 클릭 및 댓글 작성: Selenium을 사용하여 크롤링한 각 글에 대해 공감을 클릭하고, ChatGPT API를 통해 생성된 댓글을 작성합니다.
  5. 크롤링 시간 저장: 크롤링이 완료되면 현재 시간을 데이터베이스에 저장하여, 다음 크롤링 시에 사용됩니다.

실습:

프로그램을 실행하여 데이터베이스에 저장된 마지막 처리일자 이후에 작성된 모든 글에 공감과 ChatGPT 기반의 댓글을 자동으로 처리하는 프로그램을 완성합니다.

728x90