Recent Posts
Recent Comments
Archives
반응형
250x250
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Today
Yesterday

Total
04-27 00:00
관리 메뉴

Hey Tech

[Python] 구글 플레이 스토어 크롤러 코드 Version 2.0.3 본문

AI & 빅데이터/데이터 엔지니어링

[Python] 구글 플레이 스토어 크롤러 코드 Version 2.0.3

Tony Park 2022. 1. 26. 16:47
728x90
반응형

안녕하세요!

오늘은 파이썬(주피터 노트북)을 기반으로 직접 코딩한 구글 플레이 스토어 웹 크롤러 코드를 공유합니다.

📝 목차

1.  업데이트 Log
2.  주요 기능
3.  전체 코드
4.  필수 초기 세팅
5.  코드 및 설명

1.  업데이트 Log

* 미국(US) 구글 플레이 스토어 크롤러는 Github를 참고해 주시길 바랍니다.

📌 Last Updated @2022-08-24('더보기'로 확인가능✅)

더보기

Last Updated @2022-08-21

더보기
  • @K1ddong 님께서 selenium 버전에 따라 구문이 다르다는 사실을 공유해 주셨으며, 이러한 문제를 방지하고 패키지 dependency를 고려하기 위해 pipenv 가상환경을 사용하였습니다.

(1) find_element 구문 수정

  • 데이터 크롤링 섹션 내 16 line 수정
  • selenium 4.3.0 이후 버전 find_element_by_* 구문이 제거됨에 따른 구문 수정
    • 변경 전: driver.find_element_by_xpath(all_review_button_xpath).click()
    • 변경 후: driver.find_element(By.XPATH,all_review_button_xpath).click()

(2) pipenv 가상환경 추가

  • dependency 고려 위함

@2022-07-19

더보기

1) 업데이트 내역

  • 크롤링 작업 이후인 'HTML 데이터 저장' 섹션 직전 로직에 driver.quit() 코드 추가

2) 업데이트 배경

  • 실행된 이력이 있는 크롬 드라이버를 완벽하게 종료하지 않고 새로운 크롬 드라이버를 오픈하여 크롤링하는 경우 메모리 누수 발생
  • 크롤링이 완료되면 모든 크롬 드라이버를 강제 종료하여 메모리 누수 방지

@2022-06-22

더보기

1) 업데이트 내역

  • 웹 크롤링 함수 내 '리뷰 모두 보기' 버튼 path 수정
    • AS-IS
      • all_review_button_xpath = '/html/body/c-wiz[2]/div/div/div[1]/div[2]/div/div[1]/c-wiz[3]/section/div/div/div[5]/div/div/button/span'
    • TO-BE (정상 동작 확인)
      • all_review_button_xpath = '/html/body/c-wiz[2]/div/div/div[1]/div[2]/div/div[1]/c-wiz[4]/section/div/div/div[5]/div/div/button/span'

2) 업데이트 배경

  • 구글 플레이 스토어 내 '리뷰 모두 보기' 버튼의 HTML Path 변경(@Tony.P 님의 댓글 제보😊)

@2022-01-27

더보기

1) 업데이트 내역

  • 구글 플레이 스토어 웹 페이지 내 '리뷰 모두 보기' 버튼 클릭 시 리뷰 데이터가 출력되는 Modal Window 자동 무한 스크롤 기능 추가
    • 무한 자동 스크롤 불가 문제 해결
    • 수집 가능한 최대 리뷰 개수 제한 해결
  • 개편된 웹 페이지 구조를 고려한 코드 수정
    • 데이터 가져올 클래스명 수정
    • 기존에 존재했던 장문 리뷰의 '모두 보기' 기능이 삭제됨에 따라 단문/장문에 따라 리뷰 내용 가져오는 코드 삭제
  • 코드 리팩토링
    • 데이터 종류별로 배열을 사용하지 않고, 하나의 리뷰당 하나의 배열 원소로 사용할 수 있도록 수정

2) 업데이트 배경

최근에 구글 플레이 스토어 웹페이지가 개편됨에 따라, 이전 포스팅에서 업로드했던 구글 플레이 스토어 웹 크롤러가 작동하지 않는다는 연락을 많이 받았습니다. 자동 스크롤이 동작하지 않거나 최대 수집 가능한 리뷰 개수가 40개로 제한된다는 등의 문의였습니다. 저의 포스팅이 누군가에게 조금이나마 도움을 줄 수 있다는 점에 참 뿌듯합니다. 이러한 많은 관심에 화답하고자 개편된 웹페이지 구조에 맞춰 크롤러를 업데이트하였습니다.

2.  주요 기능

1) 구글 플레이 스토어 App 사용자 리뷰 자동 수집(그림 1)

  • 리뷰 등록일
  • 작성자 닉네임
  • 리뷰 평점
  • 리뷰 내용

그림 1. App 리뷰 수집을 위한 브라우저 자동 제어 모습

(2) Parsing 한 데이터 html 문서로 저장

(3) 수집 데이터는 아래와 같이 데이터프레임 형태로 포매팅(그림 2)

그림 2. 수집 데이터 포맷

2.  전체 코드

크롬 드라이버 세팅, 필요 패키지 설치, 간단한 파일 경로 편집이 가능하신 분은

이 문서(master branch)를 참고하셔서 필요한 부분을 이용하시면 되겠습니다 :)

https://github.com/park-gb/playstore-review-crawler

 

GitHub - park-gb/playstore-review-crawler: The web crawler to collect app user reviews from Google Play Store

The web crawler to collect app user reviews from Google Play Store - GitHub - park-gb/playstore-review-crawler: The web crawler to collect app user reviews from Google Play Store

github.com

3.  필수 초기 세팅

이제부터 차근차근 웹 크롤러 구현 방법을 소개합니다.

혹시라도 잘 안 되는 부분이 있다면 아래에 👇👇👇 댓글 남겨주세요!

해결책을 찾아드릴 수 있도록 노력하겠습니다👨‍💻🔥

1)  코드 다운로드

Github 링크 이곳을 클릭하셔서 코드를 포함한 파일을 설치해 줍니다(그림 3). master 브랜치를 이용해 주세요.

그림 3. 소스코드 및 필요한 파일 다운로드

(1) 초록색 버튼 'Code'를 클릭

(2) 'Download ZIP' 클릭(git이 설치되어 있으신 분들은 clone 하셔도 됩니다)

(3) 다운로드한 알집 해제

2)  크롬 설치

크롬 미설치자는 이곳을 클릭하셔서 설치해 주시기 바랍니다. 크롬은 구글에서 개발한 브라우저입니다.

3)  크롬 드라이버 설치

(1) 이곳을 클릭해 사용 중인 크롬 버전을 확인합니다.

(2) 이곳을 클릭해 버전에 맞는 크롬 드라이버를 설치합니다.

(3) 설치한 크롬 드라이버는 Github에서 다운로드한 폴더 안으로 이동시킵니다.

4.  코드 및 설명

4.1. 기본 세팅

이제 Github에서 설치한 주피터 노트북을 오픈합니다. src 폴더 안에 있습니다.

1) 크롬 드라이버 설정

파일 확장자 이름 사용 여부에 따라 앞서 chromedriver 파일명을 수정합니다.

# chrome_driver = '../chromedriver.exe' # 파일 확장자 이름 표기
chrome_driver = '../chromedriver' # 파일 확장자 이름 미표기

2) 수집할 앱 주소 입력

구글 플레이 스토어에서 수집할 App 검색 후 App을 선택합니다. 아래 그림 4 빨간 박스처럼 선택한 App 소개 페이지의 URL 링크를 복사하여 소스코드 내 url 변수에 할당해 줍니다.

그림 4. 수집할 App URL 가져오기

 

URL = "https://play.google.com/store/apps/details?id=com.github.android"

3) pipenv 가상환경 설치

아래 포스팅을 참고하셔서 pipenv 가상환경을 설치합니다. 패키지 dependency를 고려하기 위함입니다.

 

4) 파이썬 패키지 설치

!pipenv install

가상환경 세팅 및 github 클론까지 모두 완료하셨다면 주피터 노트북 환경에서 위의 명령어를 통해 필요한 패키지를 모두 다운로드 받아주시길 바랍니다.

5) 패키지 불러오기

import requests
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from bs4 import BeautifulSoup
import time
from time import sleep
import random
import pandas as pd

4.2.  크롤링 코드

1) 무한 스크롤 함수

구글 플레이 스토어 내 리뷰는 Modal Window를 통해 무한 스크롤 형태로 제공됩니다. 

즉, 스크롤할 때마다 새로운 사용자 리뷰를 계속 보여주는 형태입니다.

따라서 모든 리뷰를 확인하려면 창 맨 아래까지 스크롤해야 합니다.

아래 코드는 이를 위한 함수입니다.

def scroll(modal):
    try:        
        # 스크롤 높이 받아오기
        last_height = driver.execute_script("return arguments[0].scrollHeight", modal)
        while True:
            pause_time = random.uniform(0.5, 0.8)
            # 최하단까지 스크롤
            driver.execute_script("arguments[0].scrollTo(0, arguments[0].scrollHeight);", modal)
            # 페이지 로딩 대기
            time.sleep(pause_time)
            # 무한 스크롤 동작을 위해 살짝 위로 스크롤
            driver.execute_script("arguments[0].scrollTo(0, arguments[0].scrollHeight-50);", modal)
            time.sleep(pause_time)
            # 스크롤 높이 새롭게 받아오기
            new_height = driver.execute_script("return arguments[0].scrollHeight", modal)
            try:
                # '더보기' 버튼 있을 경우 클릭
                all_review_button = driver.find_element_by_xpath('/html/body/div[1]/div[4]/c-wiz/div/div[2]/div/div/main/div/div[1]/div[2]/div[2]/div/span/span').click()
            except:
                # 스크롤 완료 경우
                if new_height == last_height:
                    print("스크롤 완료")
                    break
                last_height = new_height
                
    except Exception as e:
        print("에러 발생: ", e)

2) 리뷰 페이지 오픈

아래 코드는 초기에 설정한 앱 URL에 접근하고 페이지가 잘 로딩되어 있는지 확인합니다.

특정 앱의 모든 리뷰를 보는 페이지가 다른 URL로 구분하지 않고

'리뷰 모두 보기' 버튼을 클릭하여 Modal Window를 통해 출력됩니다.

따라서 해당 버튼을 클릭하는 코드 역시 추가하였습니다.

# 크롬 드라이버 세팅
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--disable-blink-features=AutomationControlled')
driver = webdriver.Chrome(chrome_driver, options = chrome_options)
# 페이지 열기
driver.get(URL)
# 페이지 로딩 대기
wait = WebDriverWait(driver, 5)

# '리뷰 모두 보기' 버튼 렌더링 확인(path 수정 @2022-06-22)
all_review_button_xpath = '/html/body/c-wiz[2]/div/div/div[1]/div[2]/div/div[1]/c-wiz[4]/section/div/div/div[5]/div/div/button/span'
button_loading_wait = wait.until(EC.element_to_be_clickable((By.XPATH, all_review_button_xpath)))
# '리뷰 모두 보기' 버튼 클릭
driver.find_element(By.XPATH,all_review_button_xpath).click()

# '리뷰 모두 보기' 페이지 렌더링 대기
all_review_page_xpath = '/html/body/div[4]/div[2]/div/div/div/div/div[2]'
page_loading_wait = wait.until(EC.element_to_be_clickable((By.XPATH, all_review_page_xpath)))

# 페이지 무한 스크롤 다운
modal = WebDriverWait(driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='fysCi']")))
scroll(modal)

3) 무한 스크롤 함수 호출

리뷰 데이터가 출력되는 Modal Window의 xpath 경로를 스크롤 함수에 전달하여,

해당 창의 맨 아래까지 자동으로 스크롤하는 함수를 호출합니다.

# 페이지 무한 스크롤 다운
modal = WebDriverWait(driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='fysCi']")))
scroll(modal)

4) HTML Parsing

이제 전체 웹 페이지 소스를 받아오고 Beautifulsoup 패키지를 활용해 parsing 합니다.

# html parsing하기
html_source = driver.page_source
soup_source = BeautifulSoup(html_source, 'html.parser')

이제 아래 그림 5와 같이 웹 페이지가 자동으로 열리고 모든 리뷰 데이터가 Modal Window에 로딩될 때까지 무한 스크롤이 진행됩니다.

그림 5. App 리뷰 수집을 위한 브라우저 자동 제어 모습

5) 크롬 드라이버 종료

메모리 누수 방지를 위해 크롤링을 완료한 크롬 드라이버는 모두 강제 종료해 줄 필요가 있습니다.

driver.quit()

6) HTML 데이터 저장

parsing 한 데이터 자체를 html 파일로 저장합니다.

추후에 추가적인 크롤링 작업 없이도 해당 웹 페이지의 전체 데이터를 활용하기 위함입니다.

# html 데이터 저장
with open("../dataset/data_html.html", "w", encoding = 'utf-8') as file:
    file.write(str(soup_source))

4.3. 데이터 프레임 변환

이제 리뷰 등록일, 작성자 닉네임, 리뷰 평점, 리뷰 내용 데이터를 데이터프레임 형태로 변환해 보겠습니다.

리뷰 등록일의 경우, 'yyyymmdd' 포맷과(e.g., 20211001), 연도, 월, 일 정보를 각각 나누어 저장하겠습니다.

그 이유는 시계열(Time Series) 분석에 활용하기 위함입니다.

필요에 따라 원하는 유형의 데이터를 사용하시길 바랍니다.

# 리뷰 데이터 클래스 접근
review_source = soup_source.find_all(class_ = 'RHo1pe')
# 리뷰 데이터 저장용 배열
dataset = []
# 데이터 넘버링을 위한 변수
review_num = 0 
# 리뷰 1개씩 접근해 정보 추출
for review in tqdm(review_source):
    review_num+=1
    # 리뷰 등록일 데이터 추출
    date_full = review.find_all(class_ = 'bp9Aid')[0].text
    date_year = date_full[0:4] # 연도 데이터 추출
    # 해당 단어가 등장한 인덱스 추출
    year_index = date_full.find('년')
    month_index = date_full.find('월')
    day_index = date_full.find('일')
    
    date_month = str(int(date_full[year_index+1:month_index])) # 월(Month) 데이터 추출
    # 월 정보가 1자리의 경우 앞에 0 붙이기(e.g., 1월 -> 01월)
    if len(date_month) == 1:
        date_month = '0' + date_month
    
    date_day = str(int(date_full[month_index+1:day_index])) # 일(Day) 데이터 추출 
    # 일 정보가 1자리의 경우 앞에 0 붙여줌(e.g., 7일 -> 07일)
    if len(date_day) == 1:
        date_day = '0' + date_day
    
    # 리뷰 등록일 full version은 최종적으로 yyyymmdd 형태로 저장
    date_full = date_year + date_month + date_day
    user_name = review.find_all(class_ = 'X5PpBb')[0].text # 닉네임 데이터 추출
    rating = review.find_all(class_ = "iXRFPc")[0]['aria-label'][10] # 평점 데이터 추출
    content = review.find_all(class_ = 'h3YV2d')[0].text # 리뷰 데이터 추출

    data = {
        "id": review_num, 
        "date": date_full,
        "dateYear": date_year,
        "dateMonth": date_month,
        "dateDay": date_day,
        "rating": rating,
        "userName": user_name,
        "content": content
    }
    dataset.append(data)

데이터프레임 저장

이제 추출한 데이터를 데이터프레임 형태로 바꿔주고 이를 csv 파일로 저장합니다.

df = pd.DataFrame(dataset)
df.to_csv('../dataset/review_dataset.csv', encoding = 'utf-8-sig') # csv 파일로 저장

데이터 불러오기

앞서 저장한 데이터가 정상적으로 저장되었는지 확인합니다(그림 6).

# 저장한 리뷰 정보 불러오기
df = pd.read_csv('../dataset/review_dataset.csv', encoding = 'utf-8-sig')
df = df.drop(['Unnamed: 0'], axis = 1) # 불필요한 칼럼 삭제
df

그림 6. 수집 데이터 포맷


포스팅 내용에 오류가 있을 경우 아래에 👇👇👇 댓글 남겨주시면 감사드리겠습니다 :)

그럼 오늘도 즐겁고 건강한 하루 보내시길 바랍니다.

고맙습니다.

728x90
반응형
Comments