AI/정책 댓글 반응 NLP

[1] 커뮤니티 게시물 크롤러_2. 루리웹닷컴

eraser 2020. 4. 6. 03:31
반응형

# 사전 작업

 

 

 루리웹닷컴의 경우, 검색어를 통합검색 창에 검색했을 때 뉴스, 게시글, 댓글, 마이피, 구글 통합검색 결과가 모두 검색되어 한 화면에 결과로 나온다. 뉴스와 구글 통합검색 결과는 애초에 팀의 크롤링 대상이 아니기 때문에 제외한다. 마이피는 루리웹닷컴만의 게시판인 것 같은데, 루리웹 회원들만의 공간으로 판단된다. 회원 아이디가 없으면 접근할 수 없는 경우도 있고, 공론장에 게시된 글이나 댓글으로 판단하기에는 무리가 있기 때문에 크롤링하지 않는다. 따라서 검색 결과 창에서 게시글과 댓글만을 크롤링하기로 한다.

 

 

 

 

 그러나 의사결정이 필요한 부분에 부딪혔다. 문제는 다음과 같았다. 첫째, 댓글로 검색된 결과 중 게시글 검색 결과에 포함된 글이 있을 수 있다. 둘째, 댓글로 검색된 결과 중, 실제 클릭해 봤을 때 게시물이 '주52시간'과 연관이 없는 것이 있다. 글 내용은 게임 관련 게시물인데, 유머성 댓글로 '주52시간'을 언급해 놓은 경우가 그 예이다.

 

 첫 번째 문제의 경우 링크가 중복되는지를 검사해 주는 부분만 코드에 추가하면 될 것이다. 두 번째 문제에 대해 고민했으나, 일단 팀원들과 논의를 통해 모든 댓글을 다 수집하기로 했다. 데이터를 최대한 많이 수집하는 게 좋을 것이며, 애초에 팀에서 크롤링 기준을 naive하게 검색어로 설정했기 때문에, 세세하게 확인해서 배제하는 것이 시간적으로 비효율적이라고 판단했기 때문이다.

 

 


 

# 크롤링 코드

 

 MLBPARK 크롤링 코드와 크게 달라지지 않는다. 다만, 댓글 게시물 페이지를 추출하는 부분이 포함되었다. 링크를 추출해 저장할 때는 중복된 링크를 제외하기 위해 자료구조로 set을 사용했다. 전체 크롤링 코드는 다음과 같다.

 

import requests
from bs4 import BeautifulSoup
from urllib.parse import urlencode
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from fake_useragent import UserAgent
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
import time
import csv
import re
from datetime import datetime as dtime


# 변수 설정
QUERY = "주52시간"
search_QUERY = urlencode({'q' : QUERY}, encoding = 'utf-8')
URL = f"https://bbs.ruliweb.com/search?{search_QUERY}"
POST_PAGES = 37
COMMENT_PAGES = 49

# 게시글 링크 가져오기 : 게시글 검색 결과, 댓글 검색 결과 모두에서 루리웹으로 시작하는 주소 가져 오기.
def get_posts(post_pages, comment_pages):
    global URL
    LINKS = set() # 중복된 링크를 제외하기 위해 set 사용
    
    for page in range(post_pages):
        url = f"{URL}&page={page+1}"
        print(url)
        req = requests.get(url)
        print(req.status_code) # 36개 나와야 함.
        soup = BeautifulSoup(req.text, 'lxml')
        links = soup.find_all('a', {'class':'title text_over'})
        for link in links[:15]:
            matched = re.match("https\:\/\/bbs\.ruliweb\.com", link['href'])
            if matched:
                LINKS.add(link['href'])
        time.sleep(3)

    print(f"게시판 검색 결과 총 {len(LINKS)}개의 글 링크를 찾았습니다.")

    for page in range(comment_pages):
        url = f"{URL}&c_page={page+1}"
        print(url)
        req = requests.get(url)
        print(req.status_code) # 49개 나와야 함.
        soup = BeautifulSoup(req.text, 'lxml')
        links = soup.find_all('a', {'class':'title text_over'})
        for link in links:
            matched = re.match("https\:\/\/bbs\.ruliweb\.com", link['href'])
            if matched:
                LINKS.add(link['href'])

    print(f"댓글까지 검색한 결과 총 {len(LINKS)}개의 글 링크를 찾았습니다.")

    LINKS = list(LINKS)

    # 게시글 링크 csv로 저장
    post_file = open(f"ruliweb_{QUERY}_inner_links.csv", mode='w', encoding='utf-8')
    writer = csv.writer(post_file)
    for LINK in LINKS:
        writer.writerow([LINK])
    post_file.close()

    return LINKS

# 한 페이지에서 정보 가져오기
def extract_info(url, wait_time=3, delay_time=2):

    driver = webdriver.Chrome("C:/Users/user/PycharmProjects/Scraping/chromedriver.exe")

    driver.implicitly_wait(wait_time)
    driver.get(url)
    html = driver.page_source
    time.sleep(delay_time)  # 강제 연결 종료 방지

    driver.quit()

    soup = BeautifulSoup(html, 'lxml')

    try:
        site = soup.find('img', {'class': 'ruliweb_icon'})['alt'].strip()
    except:
        site = "루리웹"

    try:
        title = soup.find('span', {'class': 'subject_text'}).get_text(strip=True)

        user_id = soup.find('strong', {'class': 'nick'}).get_text(strip=True)

        post_time = soup.find('span', {'class': 'regdate'}).get_text(strip=True).replace('.','-').replace('(','').replace(')','') # datetime 형식으로 바꾸기 위한 작업
        post_time = dtime.strptime(post_time, '%Y-%m-%d %H:%M:%S')

        post = soup.find('div', {'class': 'view_content'}).get_text(strip=True).replace('\n', '').replace('\r', '').replace('\t', '')

        view_cnt = int(soup.find('div', {'class': 'user_info'}).find_all('p')[4].get_text(strip=True).split()[-1].replace(',',''))

        recomm_cnt = int(soup.find('div', {'class': 'user_info'}).find('span', {'class':'like'}).get_text(strip=True).replace('\n', '').replace('\r', '').replace('\t', '').replace(',', ''))

        reply_cnt = int(soup.find('strong', {'class':'reply_count'}).get_text(strip=True).replace('[','').replace(']','').replace(',',''))

        reply_content = []
        if reply_cnt != 0:
            replies = soup.find_all('span', {'class': 'text'})
            for reply in replies:
                reply_content.append(reply.get_text(strip=True).replace('\n', '').replace('\r', '').replace('\t', ''))
        reply_content = '\n'.join(reply_content)

        print(url, " 완료")

    except:
        print(url, "삭제된 게시물이거나, 오류가 있습니다.")
        title = None
        user_id = None
        post_time = None
        post = None
        view_cnt = None
        recomm_cnt = None
        reply_cnt = None
        reply_content = None

    return {'site': site, 'title': title, 'user_id': user_id, 'post_time': post_time, 'post' : post, 'view_cnt': view_cnt, 'recomm_cnt': recomm_cnt, 'reply_cnt': reply_cnt, 'reply_content': reply_content}

# 모든 게시물 링크에 대해 정보 가져오는 함수 호출
def get_contents():
    global ruliweb_results
    post_links = get_posts(POST_PAGES, COMMENT_PAGES)
    for post_link in post_links:
        content = extract_info(post_link)
        append_to_file(ruliweb_results, content)
    return print("모든 작업이 완료되었습니다.")

# 저장 파일 만드는 함수
def save_to_file():
    global QUERY
    global PAGES
    file = open(f"ruliweb_{QUERY}.csv", mode='w', encoding='utf-8')
    writer = csv.writer(file)
    writer.writerow(['site', 'title', 'user_id', 'post_time', 'post', 'view_cnt', 'recomm_cnt', 'reply_cnt', 'reply_content'])
    file.close()
    return file

# 파일 열어서 쓰는 함수
def append_to_file(file_name, dictionary):
    file = open(f"ruliweb_{QUERY}.csv", mode='a', encoding='utf-8') # 덮어 쓰기
    writer = csv.writer(file)
    writer.writerow(list(dictionary.values()))
    file.close()
    return

# 함수 실행
ruliweb_results = save_to_file()
get_contents()
반응형