9  네이버 아카라 카페 웹크롤링

9.1 네이버카페 웹크롤링 템플릿

import time
from selenium import webdriver
import csv
import pandas as pd
from bs4 import BeautifulSoup as bs
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import pandas as pd


class NaverCafeCrawler:
    def __init__(self, driver_path, url, id, pw, baseurl, clubid, userDisplay, boardType):
        self.total_list = ['제목', '내용', '링크']
        self.driver_path = driver_path
        self.url = url
        self.id = id
        self.pw = pw
        self.baseurl = baseurl
        self.baseraw = "https://cafe.naver.com"
        self.clubid = clubid
        self.userDisplay = userDisplay
        self.boardType = boardType

    def initialize_file(self, file_path='content.csv'):
        with open(file_path, 'w', encoding='utf-8', newline='') as f:
            wr = csv.writer(f)
            wr.writerow([self.total_list[0], self.total_list[1], self.total_list[2]])

    def login(self, browser):
        browser.get(self.url)
        browser.implicitly_wait(2)
        browser.execute_script(f"document.getElementsByName('id')[0].value='{self.id}'")
        browser.execute_script(f"document.getElementsByName('pw')[0].value='{self.pw}'")
        browser.find_element(By.XPATH, '//*[@id="log.login"]').click()
        time.sleep(1)

    def crawl_page(self, browser, page_num):
        browser.get(f"{self.baseurl}ArticleList.nhn?search.clubid={self.clubid}&userDisplay={self.userDisplay}"
                    f"&search.boardType={self.boardType}&search.page={page_num}")
        browser.switch_to.frame('cafe_main')
        soup = bs(browser.page_source, 'html.parser')
        soup = soup.find_all(class_='article-board m-tcol-c')[1]
        datas = soup.find_all(class_='td_article')
        new_df = pd.DataFrame(columns=['제목', '내용', '링크'])
        
        #print(datas)
        for data in datas:
            article_title = data.find(class_='article')
            link = article_title.get('href')
            article_title = article_title.get_text().strip()
            content = self.get_content(browser, self.baseraw + link)
            new_df = pd.concat([new_df, pd.DataFrame({'제목': [article_title], '내용': [content], '링크': [self.baseraw + link]})],
                              ignore_index=True)
        return new_df

    def get_content(self, browser, link):
        browser.get(link)
        time.sleep(1)
        browser.switch_to.frame('cafe_main')
        soup = bs(browser.page_source, 'html.parser')
        content = soup.find("div",{"class":"article_viewer"})
        
        if content:
            #print(f"Content: {content.get_text().strip()}")
            #print("\n")
            return content.get_text().strip()
        else:
            return ""

    def run(self, max_pages=2, file_path='content.csv'):
        self.initialize_file(file_path)
        offset = 0
        i = offset
        while i < max_pages+offset:
            i += 1
            pageNum = i
            print(f"Page Number: {pageNum}")
            original_df = pd.read_csv(file_path, encoding='utf-8')
            chrome_options = Options()
            chrome_options.add_argument('--headless')
            chrome_options.add_argument(f'--webdriver-path={self.driver_path}')
            browser = webdriver.Chrome(options=chrome_options)
            self.login(browser)
            new_df = self.crawl_page(browser, pageNum)
            concat_df = pd.concat([original_df, new_df])
            concat_df = concat_df.drop_duplicates(keep=False)
            concat_df.to_csv(file_path, mode='a', header=False, index=False)
            browser.close()
            time.sleep(5)
        print("done completely....")

if __name__ == "__main__":
    driver_path = "xxxxxxxxxx"
    url = 'https://nid.naver.com/nidlogin.login'
    id = "xxxxxxxxx"
    pw = "xxxxx"
    baseurl = "https://cafe.naver.com/"
    clubid = xxxxxx 
    userDisplay = 10
    boardType = 'L'

    crawler = NaverCafeCrawler(driver_path, url, id, pw, baseurl, clubid, userDisplay, boardType)
    crawler.run(max_pages=1)
    df = pd.read_csv("content.csv")
    df.head(10)

9.2 주요 코드 분석

9.2.1 환경 변수 설정 및 MySQL. 연결


from dotenv import load_dotenv
load_dotenv()

db_config = {
    'user': os.getenv('SQL_USER'),
    'password': os.getenv('SQL_PASSWORD'),
    'host': os.getenv('SQL_HOST'),
    'database': os.getenv('SQL_DATABASE'),
    'charset': 'utf8mb4',
    'collation': 'utf8mb4_general_ci'
}
  • dotenv 라이브러리를 사용해 .env 파일에서 MySQL 및 네이버 카페 로그인 관련 환경 변수를 불러온다.

  • 이를 통해 MySQL에 연결할 때 필요한 정보를 제공한다.

9.2.2 네이버 카페 크롤링 클래스 정의

class NaverCafeCrawler:
    def __init__(self, driver_path, url, id, pw, baseurl, clubid, userDisplay, boardType, db_config):
        self.total_list = ['registered_date', 'devices', 'title', 'question', 'answers']
        self.driver_path = driver_path
        self.url = url
        self.id = id
        self.pw = pw
        self.baseurl = baseurl
        self.clubid = clubid
        self.userDisplay = userDisplay
        self.boardType = boardType
        self.db_config = db_config
  • NaverCafeCrawler 클래스는 크롤링의 주요 기능을 수행한다.

  • 클래스는 로그인 정보, 크롤링할 URL, MySQL 연결 정보 등을 초기화한다.

9.2.3 로그인 함수

def login(self, browser):
    browser.get(self.url)
    browser.implicitly_wait(2)
    browser.execute_script(f"document.getElementsByName('id')[0].value='{self.id}'")
    browser.execute_script(f"document.getElementsByName('pw')[0].value='{self.pw}'")
    browser.find_element(By.XPATH, '//*[@id="log.login"]').click()
    time.sleep(1)
  • 네이버 카페에 자동으로 로그인하는 기능이다.

  • Selenium을 사용해 ID와 비밀번호를 입력하고 로그인 버튼을 클릭한다.

9.2.4 페이지 크롤링 함수

def crawl_page(self, browser, page_num):
    browser.get(f"{self.baseurl}ArticleList.nhn?search.clubid={self.clubid}&userDisplay={self.userDisplay}&search.boardType={self.boardType}&search.page={page_num}")
    browser.switch_to.frame('cafe_main')
    soup = bs(browser.page_source, 'html.parser')
    datas = soup.find_all(class_='td_article')
    ...
  • 지정된 페이지 번호의 게시글 목록을 크롤링한다.

  • Selenium을 사용해 카페 게시판의 특정 페이지로 이동하고, BeautifulSoup을 사용해 게시글의 제목, 카테고리, 링크 등을 파싱한다.

9.2.5

게시글 상세 내용 크롤링

def get_content(self, browser, link):
    browser.get(link)
    time.sleep(1)
    browser.switch_to.frame('cafe_main')
    soup = bs(browser.page_source, 'html.parser')
    content_text = soup.find("div", {"class": "article_viewer"}).get_text().strip() if content else ""
    ...
  • 각 게시글의 링크로 이동하여 상세 내용을 크롤링한다.

  • 게시글의 본문 내용과 등록 날짜, 댓글(답변)을 추출한다.

9.2.6 MySQL 저장 함수

def save_to_mysql(self, df):
    conn = mysql.connector.connect(**self.db_config)
    cursor = conn.cursor()

    for _, row in df.iterrows():
        check_sql = "SELECT COUNT(*) FROM aqara_cafe WHERE registered_date = %s AND title = %s"
        cursor.execute(check_sql, (row['registered_date'], row['title']))
        result = cursor.fetchone()

        if result[0] == 0:
            insert_sql = "INSERT INTO aqara_cafe (registered_date, devices, title, question, answers) VALUES (%s, %s, %s, %s, %s)"
            cursor.execute(insert_sql, tuple(row))
    
    conn.commit()
    cursor.close()
    conn.close()
  • 크롤링한 데이터를 DataFrame으로 받아 MySQL 데이터베이스에 저장하는 기능이다.

  • 중복된 데이터를 방지하기 위해 registered_date와 title을 기준으로 이미 데이터가 존재하는지 확인한 후, 존재하지 않으면 데이터를 삽입한다.

9.2.7 크롤러 실행

crawler = NaverCafeCrawler(driver_path, url, id, pw, baseurl, clubid, userDisplay, boardType, db_config)
crawler.run(max_pages=params_pages)
  • 설정된 매개변수로 크롤링을 실행한다.

  • 최대 페이지 수(max_pages) 만큼 페이지를 순차적으로 크롤링하고, 데이터를 CSV로 저장한 후 MySQL로 전송한다.