본문 바로가기

부트캠프/멀티캠퍼스_퍼포먼스 마케팅과 데이터 분석

[멀티캠퍼스 부트캠프 6주차(1)] 데이터 분석 기본_웹 크롤링(selenium)

데이터 분석 기본_웹 크롤링(selenium)


필요 라이브러리 설치 및 불러오기

1. 크롬에서 주피터 노트북 실행

-아나콘다 프롬프트 → jupyter notebook --browser=chrome

-주피터와 드라이버의 브라우저 달라도 실행 가능(ex.주피터는 엣지, 드라이버는 크롬)

 

 

2. 라이브러리 불러오기

import selenium
from selenium import webdriver  # 웹 브라우저 자동화
from selenium.webdriver.common.by import By  # 요소 찾을 때 검색 방법 지정하는 기준 스위치

import chromedriver_autoinstaller  # 자동으로 크롬드라이버 설치
from tqdm import tqdm  # for문 진행사항 파악

import time  # 시간 관련 작업 (ex.2초 지연)
import os  # 컴퓨터의 운영체제 제어(폴더 생성, 파일 경로 확인, 파일 삭제 등)
import sys  # 파이썬 인터프리터 제어(프로그램 강제 종료, 시스템 경로 확인 등)

import warnings
warnings.filterwarnings('ignore')  # 워닝 무시

 

 

3. 크롬 드라이버 설치 및 불러오기

-크롬 드라이버: 파이썬이 크롬 브라우저를 통제할 수 있도록 도와주는 프로그램

chrome_path = chromedriver_autoinstaller.install(cwd=True)
# >>> 현재 작업중인 폴더에 144(크롬드라이버 버전)라는 폴더 생성

※크롬과 크롬 드라이버의 버전 맞춰줘야 함

   나의 경우 크롬 드라이버는 144.x, 크롬은 143.x → chrome://settings/help에서 크롬 다시시작

 


 

실습용 티스토리 크롤링

1. 티스토리 1

-https://ai-dev.tistory.com/1

1) 창 띄우기

 -webdriver.브라우저명(): 해당 브라우저를 내 파이썬 코드로 제어하겠다고 선언

  → 스크롤, 버튼 클릭, 글자 입력 등 제어 가능

 -time.sleep(n): n초간 정지(로딩 시간 확보)

driver = webdriver.Chrome()  # 크롬 브라우저 사용

driver.get('https://ai-dev.tistory.com/1')
time.sleep(2)

 

2) 글 제목, 글쓴이, 글 쓴 시간 크롤링

 -driver: 앞서 실행한 브라우저에서 찾겠다

 -find_element(): 조건에 맞는 요소 하나만 찾음

  ※find_elements(): 조건에 맞는 요소 여러 개 찾음(리스트로 반환) → p태그/li태그에 주로 사용

 -By.CSS_SELECTOR: CSS 선택자 방식 사용하겠다고 선언

# 글 제목
selector = '#content > div > div.hgroup > h1'
title = driver.find_element(By.CSS_SELECTOR, selector).text
title
# >>> '크롤링의 세계에 오신 것을 환영합니다.'
# 글쓴이
author = driver.find_element(By.CSS_SELECTOR, ".author").text
author
# >>> '로스카츠'

※id 선택자 대신 클래스 선택자 사용(클래스가 명확한(unique한) 것은 클래스 사용 용이)

# 글 작성 시간
selector = ".date"
time = driver.find_element(By.CSS_SELECTOR, selector).text
time
# >>> '2021. 1. 21. 14:06'
# 글 내용
selector = ".tt_article_useless_p_margin.contents_style"
text = driver.find_element(By.CSS_SELECTOR, selector).text
text
# >>> 'Hello, world!'

※클래스 선택자 이용시 띄어쓰기를 "."으로 바꿔줘야 함

 

3) 댓글 글쓴이, 댓글 내용, 댓글 날짜 리스트로 저장 후 데이터프레임으로 변환

# 잘못된 예시(class의 비유일성)
selector = ".tt_desc"
review = driver.find_element(By.CSS_SELECTOR, selector).text
review 
# >>> '로스카츠 님의 블로그입니다.'  (댓글이 안나옴)
# calss는 여러개가 있을 수 있으므로(동명이인 개념) 더 자세한 정보(copy selector 등) 필요
# 댓글 글쓴이
reviewer_list = []

for i in range(1, 17):
    selector = f"#entry1Comment > div > div > div > div.tt-area-reply > ul > li:nth-child({i}) \
            > div > div.tt-box-content > div.tt-box-meta"
    reviewer = driver.find_element(By.CSS_SELECTOR, selector).text
    reviewer_list.append(reviewer)
reviewer_list
# 댓글 내용
review_list = []

for i in range(1, 17):  # 1부터 시작
# 대댓글은 무시해야 하므로 댓글은 16개라고 봐야 함
    selector = f"#entry1Comment > div > div > div > div.tt-area-reply > ul > li:nth-child({i})\
    > div > div.tt-box-content > div.tt-wrap-desc > p"
    review_raw = driver.find_element(By.CSS_SELECTOR, selector).text
    review_list.append(review_raw)
review_list
# 댓글 날짜
review_time_list = []

for i in range(1, 17):
    selector = f"#entry1Comment > div > div > div > div.tt-area-reply > ul > li:nth-child({i}) \
            > div > div.tt-box-content > div.tt-wrap-info > span.tt_date"
    review_time = driver.find_element(By.CSS_SELECTOR, selector).text
    review_time_list.append(review_time)
review_time_list
df = pd.DataFrame({'댓글':review_list, '댓글러':reviewer_list, '댓글 시간':review_time_list})
df

 

 

2. 티스토리 2

-https://ai-dev.tistory.com/2

1) 창 띄우기

driver = webdriver.Chrome()
driver.get('https://ai-dev.tistory.com/2')
time.sleep(2)

 

2) 본문 내용 수집

# 본문 내용 크롤링
selector = "#article-view p"
# 한 칸 띄어쓰기: id 하위 자손(몇 칸 아래인지는 모름)

p_list = driver.find_elements(By.CSS_SELECTOR, selector)
p_list
# 리스트 컴프리헨션 사용하여 p_list에 들어있는 p태그들을 제거하고 text만 추출
p_list = [p.text for p in p_list]
p_list
# 리스트 컴프리헨션 사용하여 공백 요소 제거
p_list = [i for i in p_list if i != ' ']  # 공백이 아닐 때만 리스트에 넣어 출력
p_list

 

3) 표 데이터 추출

# selector 구성 확인
# selector = "#article-view > div.tt_article_useless_p_margin.contents_style > div > table > tbody > tr:nth-child(1) > td:nth-child(1)"
# tbody-tr-rd 순 구성

# 표 데이터 추출
selector = "tbody tr td"
td_raw = driver.find_elements(By.CSS_SELECTOR, selector)
td_raw
# 표에 포함된 td 데이터만 크롤링
td_list = []

for td in td_raw[:18]:  # 표가 3*6이므로 18개 데이터 존재
    td_list.append(td.text)
td_list
# 리스트 컴프리헨션으로 3개씩 나누기
rows = [td_list[i:i+3] for i in range(0, len(td_list), 3)]
rows

 

4) 데이터프레임으로 변환

pd.DataFrame(rows[1:], columns=rows[0])
# rows[1:]: [상품 색상 가격]은 제외하겠다

※중괄호{}는 리스트를 데이터프레임으로 변환할 때 필요(변수명으로 넣을 때는 필요 x)

 


 

네이버 검색 블로그 크롤링

1. 창 띄우기

chrome_path = chromedriver_autoinstaller.install(cwd=True)
driver = webdriver.Chrome()

driver.get('https://www.naver.com/')
time.sleep(2)

 

 

2. 검색어 입력 후 옵션 선택

-.clear(): 입력창 안에 기존 텍스트 모두 삭제

-.send_keys(): 글자 입력

-submit(): 입력한 양식을 서버로 전송(<form> 태그 안에 들어있는 요소에 사용)

# 네이버 검색창에 "강남 맛집" 검색
query_txt = "강남 맛집"

selector = "#query"
element = driver.find_element(By.CSS_SELECTOR, selector)
element.clear()
element.send_keys(query_txt)

element.submit()
time.sleep(1)

 -By.LINK_TEXT: 링크가 걸려있는 텍스트(<a>태그) 중에서 찾겠다 

  ※By.PARTIAL_LINK: 글자의 일부분만 포함되어 있어도 찾음

 -.click(): 해당 요소를 마우스로 클릭

  ※submit vs. click

  .submit() .click()
대상 오직 'form'과 연결된 요소 클릭 가능한 모든 요소(버튼, 링크 등)
작동 원리 엔터 키를 쳐서 양식 제출 마우스 왼쪽 클릭
범용성 특정 양식 전송 시에만 사용 매우 高
# '블로그' 클릭
driver.find_element(By.LINK_TEXT, "블로그").click()
time.sleep(1)
# '옵션' 클릭
driver.find_element(By.LINK_TEXT, "옵션").click()
time.sleep(1)
# 검색기간 '3개월' 클릭
driver.find_element(By.LINK_TEXT, '3개월').click()

 

 

3. 글 개수 확인 후 스크롤

 -네이버 블로그에서 한번에 화면에 보여지는 글의 개수는 30개(더 많은 크롤링을 위해서는 스크롤 必)

def blog_cnt():
    selector = ".sds-comps-vertical-layout.sds-comps-full-layout.p8YLe6TZKiwO531wrWYT"
	# 글 제목 셀렉터
    blog_cnt = len(driver.find_elements(By.CSS_SELECTOR, selector))
    print(blog_cnt)
blog_cnt()
# >>> 30

 -.execute_script(JavaScript 문자열): 현재 페이지를 스크롤 다운(JavaScript 강제 주입)

 -(0, 9999): 제일 처음부터 마지막까지

   → 한번 실행할 때마다 로딩된 페이지 중 가장 마지막 글까지 내려감

driver.execute_script("window.scrollTo(0, 99999)")
n = 3  # 스크롤 할 횟수

i = 0  # 0, 1, 2
while i < n:
    driver.execute_script("window.scrollTo(0, 99999)")
    time.sleep(1)
    i = i+1
    
 blog_cnt
 # >>> 120 (기존 30개 + 스크롤 90개)

 

 

4. 블로그 제목, url 수집

# 글 제목 수집 후 리스트로 저장
selector = ".sds-comps-text.sds-comps-text-ellipsis.sds-comps-text-ellipsis-1.sds-comps-text-type-headline1.sds-comps-text-weight-sm"
titles_raw = driver.find_elements(By.CSS_SELECTOR, selector)

title_list = []
for title_raw in titles_raw:
    title = title_raw.text
    title_list.append(title)

print(len(title_list))
title_list

 -.get_attribute("속성명"): 속성값 가져옴

# 글 url 수집 후 리스트로 저장
selector = ".fender-ui_228e3bd1.Fup4JAiE19ubRE2YcX5w"
urls_raw = driver.find_elements(By.CSS_SELECTOR, selector)

url_list = []
for url_raw in urls_raw:   
    url = url_raw.get_attribute('href')  # 링크 주소 가져오기
    url_list.append(url)

print(len(url_list))
url_list

 -index=Fale: 데이터프레임 옆에 자동으로 붙는 행 번호(0, 1, 2, ...) 제외

# 데이터프레임 저장
df_title_url = pd.DataFrame({"글제목": title_list, "url":url_list })

# csv로 저장
df_title_url.to_csv("title_url.csv", encoding='utf-8-sig', index=False)

 

 

5. 각 url 별 내용 수집

1) 첫 번째 블로그 창 띄우기

df = pd.read_csv('title_url.csv')
i = 0
url = df['url'][i]

driver = webdriver.Chrome()
driver.get(url)

time.sleep(1)

 

2) 제목, 글쓴이, 날짜 수집

 -driver.switch_to: 현재의 시선을 다른 곳으로 돌림

 -.frame(): 해당 액자 안으로 들어감

 -<body> 안 <iframe>: 프레임 안 프레임

  ※네이버 블로그처럼 프레임을 사용하는 사이트를 크롤링할 때 iframe 안으로 들어가지 않으면 요소 찾을 수 x

driver.switch_to.frame("mainFrame")
# 게시글(제목, 글쓴이, 날짜 등) 크롤링 후 저장할 공간 생성
target_info = {}
# 여러 형식이 있으므로 딕셔너리로 생성
# → 추후 여러 리스트를 모아 최종 딕셔너리를 만들 것임
# 제목 크롤링
selector = ".se-fs-.se-ff-nanummaruburi"
title = driver.find_element(By.CSS_SELECTOR, selector).text

target_info['title'] = title
# 글쓴이 크롤링
selector = ".nick"
nickname = driver.find_element(By.CSS_SELECTOR, selector).text

target_info['nickname'] = nickname
# 날짜 크롤링
selector = ".se_publishDate.pcol2"
datetime = driver.find_element(By.CSS_SELECTOR, selector).text

target_info['datetime'] = datetime

 

3) 글 내용 수집

# 반복문으로 모든 문단 텍스트 수집 후 딕셔너리에 저장
selector = ".se-component.se-text.se-l-default"
text_raw = driver.find_elements(By.CSS_SELECTOR, selector)
# find_elements임에 주의(text로 하나만 추출하려면 인덱싱 필요)

for review in text_raw:
    review_str = review_str + review.text.replace("\n", " ")

target_info['review'] = review_str

 

4) 전체 n개 블로그 글 수집

 -driver.close(): 브라우저 종료(창 닫기)

 -try~except: 예상치 못한 에러에 대응

total_dict = {}  # 전체 블로그 글 수집할 딕셔너리 선언 

for i in range(0, 3): 
    try:    
        driver = webdriver.Chrome()
        driver.get(df_url['url'][i])  # i가 바뀌면서 블로그 글도 바뀜
        time.sleep(1)
        
        driver.switch_to.frame('mainFrame')
        
        # 한 개 블로그(제목, 글쓴이, 날짜, 내용) 크롤링 후 저장할 딕셔너리
        target_info = {}
        
        # 제목
        selector = ".se-module.se-module-text.se-title-text"     
        title= driver.find_element(By.CSS_SELECTOR, selector).text      
        target_info['title'] = title
        
        # 글쓴이
        selector = ".nick"
        nickname = driver.find_element(By.CSS_SELECTOR, selector).text
        target_info['nickname'] = nickname 
        
        # 날짜
        selector = ".se_publishDate.pcol2"
        datetime = driver.find_element(By.CSS_SELECTOR, selector).text
        target_info['datetime'] = datetime
        
        # 내용
        review_str = ""
        selector = ".se-component.se-text.se-l-default"
        text_raw = driver.find_elements(By.CSS_SELECTOR, selector)
        for review in text_raw:
            review_str = review_str + review.text.replace("\n", " ")
        target_info['review'] = review_str
    
        print(target_info)
        total_dict[i] = target_info 
        print(i+1, "번째 데이터가 수집되었습니다.", "\n")

        time.sleep(2)

        driver.close()
        
    except:
        print(i+1, "번째 블로그는 건너 뜁니다.")
        driver.close()
        continue

 -.from_dict(): 딕셔너리를 데이터프레임으로 변환

 -'index': 행렬 변환

import pandas as pd
result_df = pd.DataFrame.from_dict(total_dict, 'index')
result_df

 


 

#부트캠프후기 #멀티캠퍼스부트캠프 # 데이터마케팅부트캠프