Python으로 모바일 스토어 스크래핑하기

BK·2022년 12월 13일
1

스크래핑 개발 이유?

회사에서 대내/외 로 사용중인 모바일 애플리케이션 정보를 자동으로 수집하기 위해 스크래핑에 대한 내용을 찾아보게 되었다.

1. 개요

스크래핑으로 얻을 정보

  • 구글 플레이 스토어, iOS 앱스토어 에서 앱 정보 획득
  • 앱 명칭/ 패키지정보(bundleId)/ 앱 버전/ 업데이트 날짜/ 출시 날짜

Package Library

Python에서 스크래핑을 위한 여러 패키지 라이브러리가 많이 개발되어 있었다. 그 중 가장 널리 쓰이고 있고 많은 구글링에서 보여주고 있어서, 우선 순위로 생각하고 있던 것이 아래 두개 라이브러리이다.

하지만 위 패키지를 적용하다 보니, 내가 원하는 데이터를 가져오지 못하는 이슈가 있었다.

  • google-play-scraper : 각 앱별로 버전 정보를 가져와야 했지만, 구글 플레이에서 앱 Version에 대한 정보를 기기별로 다르게 제공하는 앱들은 버전 정보를 제대로 가져올 수 없음 ("version": "Varies with device")
  • app-store-scraper : iOS app store의 리뷰 정보를 추출하는데 특화되어 있지만, 지금 개발하는 사항에는 부합하지 않고, 오히려 itunes lookup을 통해 정보를 가져오는 것이 원하는 데이터를 얻기 용이함.

Selenium, BeutifulSoup 사용

Selenium

  • Selenium은 다른 스크래핑 라이브러리와 달리 직접 웹브라우저를 가동하여, 키 입력이나 버튼 클릭 등의 액션을 가능하게 해주는 라이브러리이다.
  • 사용이유 : Google Play에 앱 버전이나 앱 오픈일자, 업데이트 일자 등 개발에서 얻고자 하는 내용이 상세 페이지(팝업)에 포함되어 있어, 해당 모듈을 이용해 구글 로그인 후 상세 페이지를 열어 정보를 가져 왔다.

BeautifulSoup

  • html 요청/응답 후 parsing 을 해주는 HTML Parser 이다. (JAVA에는JSoup이 있다)
  • 사용이유 : itunes Lookup API를 이용하여 받은 응답 정보를 파싱해서 사용하기 간편하다.

2. Mac O/S 파이썬 개발환경 세팅

1) Python 다운로드 및 설치

참조 링크

  • Python 사이트에서 O/S에 맞게 최신 버전 다운로드 및 설치
  • 사용하는 터미널 타입에 맞게 .zshrc 혹은 .bash_profile에 환경 변수 설정
# Python PATH
alias python="python3"
PATH="/Library/Frameworks/Python.framework/Version/3.11/bin:${PATH}" export PATH
  • 환경 변수 설정이 끝나면 터미널을 열어 정상적으로 python 커맨드 수행되는지 확인
python -V

2) Pycharm 다운로드 및 설치

  • Pycharm 사이트에서 무료 Pycharm 다운로드 및 설치( JetBrains에서 만든 Python 용 IDE )
    - Android Studio 만든 곳에서 만들었다 보니깐 GUI가 아예 똑같음. 친숙한 느낌이 들어 사용해 보았다.

  • 새로운 프로젝트 만들기

  • 기본적으로 메인.py에 샘플코드가 들어가 있다. 바로 Configuration을 run 해보면 코드가 수행된다.

3. 프로젝트 패키지 관리

1) python dependency 관리?

참조 : https://24hours-beginner.tistory.com/122

  • 파이썬은 다른 gradle, cocoapods, npm 등으로 패키지를 관리하는게 따로 없다고 함
  • 위 참조 URL에서 text 파일로 관리하는 내용이 나와있음.

2) 필요한 패키지 install

  • pip를 이용하여 install 한다

    ex) BeautifulSoup 패키지 설치 시
    https://pypi.org/project/beautifulsoup4/ 이동 해서 install command ctrl+c

    terminal 에 ctrl+v

    추가된 패키지 확인 가능

4. Python 코딩 및 실행

1) iOS 앱 데이터 스크래핑

  • 필요 패키지
 pip install beautifulsoup4
 pip install requests
 pip install datetime2
  • 코드

import json
import ssl

from bs4 import BeautifulSoup
import requests
from datetime import datetime

# iOS 앱 아이디 목록
app_id_list = [
    '541164041',  # microsoft-office
    '586449534',  # microsoft-powerpoint
    '586447913',  # microsoft-word
    '586683407',  # microsoft-excel
    '1113153706',  # microsoft-teams
    '477537958',  # microsoft-onedrive
    '1091505266',  # microsoft-sharepoint
    '951937596',  # microsoft-outlook
    '410395246',  # microsoft-onenote
    '1401013624',  # microsoft-stream
    '289559439',  # yammer
]


# 시간 포맷 변경
def convertDate(date_str):
    real_date = datetime.strptime(date_str.replace('T', ' ').replace('Z', ''), '%Y-%m-%d %H:%M:%S')
    return real_date.strftime("%Y%m%d")


# 앱 데이터 요청
def requestAppData():
    # SSL 통신 혀용
    ssl._create_default_https_context = ssl._create_unverified_context
    # SSL인증서 확인 무시, 경고 표시 없애기
    requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)

    for app_id in app_id_list:
        request_url = 'http://itunes.apple.com/kr/lookup?id=' + app_id
        source_code = requests.get(request_url, verify=False)
        html = source_code.text.encode('utf-8', 'replace')
        soup = BeautifulSoup(html, "html.parser")
        json_object = json.loads(str(soup))
        # resultJson = json.dumps(jsonObject, indent="\t", ensure_ascii=False)
        # print(resultJson)

        for j_key, j_value in json_object.items():
            if j_key == 'results':
                for key, value in j_value[0].items():
                    if key == 'trackName':
                        name = value
                    if key == 'bundleId':
                        bundle_id = value
                    if key == 'version':
                        version = value
                    if key == 'releaseDate':
                        open_date = convertDate(value)
                    if key == 'currentVersionReleaseDate':
                        update = convertDate(value)

        print('id : ' + app_id)
        print('name : ' + name)
        print('bundleId : ' + bundle_id)
        print('version : ' + version)
        print('update : ' + update)
        print('openDate : ' + open_date)
        print('appOs : ' + 'IOS')
        print()

        # TODO: - 정보 확보 후 처리할 동작 추가


requestAppData()
  • output 확인

2) AOS 앱 데이터 스크래핑

  • AOS의 경우, 기기가 인증 되어 있는 구글 계정이 별도로 필요하다. (앱 버전을 확인하기 위해선 실제 디바이스 정보가 있는 구글계정으로 로그인 해야 버전 정보가 웹에 표시된다.)
  • 필요 패키지
 pip install selenium
 pip install undetected-chromedriver
  • 코드
from time import sleep

from selenium.webdriver.common.by import By
import undetected_chromedriver as uc


aos_pkg_list = [
        'com.microsoft.office.officehubrow',
        'com.microsoft.office.powerpoint',
        'com.microsoft.office.word',
        'com.microsoft.office.excel',
        'com.microsoft.teams',
        'com.microsoft.skydrive',
        'com.microsoft.sharepoint',
        'com.microsoft.office.outlook',
        'com.microsoft.office.onenote',
        'com.microsoft.stream',
        'com.yammer.v1',
    ]


# 크롬 드라이버 시작후, 구글 계정 로그인
def loginAccount(pkg_nm):

    driver = uc.Chrome()
    driver.get('https://play.google.com/store/apps/details?id=' + pkg_nm)
    driver.implicitly_wait(1)

    # 로그인 버튼 클릭
    login_button = driver.find_element(By.XPATH, '//*[@id="kO001e"]/header/nav/div/c-wiz/div/div/div[1]/button')
    login_button.click()
    driver.implicitly_wait(1)
    sleep(3)

    # 구글 계정 입력
    account_button = driver.find_element(By.XPATH,
                                         '//*[@id="kO001e"]/header/nav/div/c-wiz/div/div/div[2]/div/ul/li[1]/span[3]')
    account_button.click()
    driver.implicitly_wait(1)
    sleep(3)
    email_input = driver.find_element(By.XPATH, '//*[@id="identifierId"]')
    email_input.send_keys('본인 구글계정 기입')
    driver.implicitly_wait(1)
    next_button = driver.find_element(By.XPATH, '//*[@id="identifierNext"]/div/button')
    next_button.click()
    driver.implicitly_wait(1)
    sleep(3)

    # 비밀번호 입력
    password_input = driver.find_element(By.XPATH, '//*[@id="password"]/div[1]/div/div[1]/input')
    password_input.send_keys('본인 구글 패스워드 기입')
    driver.implicitly_wait(1)
    next_btn = driver.find_element(By.XPATH, '//*[@id="passwordNext"]/div/button')
    next_btn.click()
    driver.implicitly_wait(1)
    sleep(3)

    # 본인 확인을 위한 번호 정보
    try:
        check_number = driver.find_element(By.XPATH,
                                           '//*[@id="view_container"]/div/div/div[2]/div/div[1]/div/form/span/section/div/div/span/figure/samp')
        print('check number : ' + check_number.text)
        sleep(10)
    except:
        print('keep going')
    sleep(30)
    # 본인 핸드폰에서 본인 확인 번호 입력
    return driver


# 제일 낮은 버전 선택
def selectMinimumVersion(versions):
    # for version in versions:
    #     print(version.text)
    return versions[1].text


# 제일 낮은 업데이트 날짜 선택
def selectMinimumUpdate(updates):
    # for update in updates:
    #     print(update.text)
    return updates[1].text


# 날짜 포맷 변환
def convertDate(date):
    try:
        ymd_dates = date.split('.')
        yyyy = ymd_dates[0].strip()
        mm = ymd_dates[1].strip()
        dd = ymd_dates[2].strip()

        if int(mm) < 10:
            mm = '0' + mm.strip()

        if int(dd) < 10:
            dd = '0' + dd.strip()

        return yyyy + mm + dd
    except:
        return 'null'


# 앱의 버전정보, 업데이트 일자 등을 크롤링을 통해 가져온다.
def getUpdateInfoWithCrawl(driver, pkg_id):
    driver.get('https://play.google.com/store/apps/details?id=' + pkg_id)
    driver.implicitly_wait(1)

    # 앱 정보 상세 팝업 클릭
    try:
        button = driver.find_element(By.XPATH,
                                     '//*[@id="yDmH0d"]/c-wiz[2]/div/div/div[1]/div[2]/div/div[1]/c-wiz[6]/div/section/header/div/div[2]/button')
    except:
        button = driver.find_element(By.XPATH,
                                     '//*[@id="yDmH0d"]/c-wiz[2]/div/div/div[1]/div[2]/div/div[1]/c-wiz[2]/div/section/header/div/div[2]/button/i')
    button.click()
    sleep(1)
    driver.implicitly_wait(1)

    # 앱 명칭
    names = driver.find_element(By.XPATH, '//*[@id="yDmH0d"]/div[4]/div[2]/div/div/div/div/div[1]/div/div/h5')
    name = names.text
    # 앱 버전
    versions = driver.find_elements(By.XPATH, '//div[text()="버전"]/following-sibling::div')
    version = selectMinimumVersion(versions)
    # 업데이트 날짜
    updates = driver.find_elements(By.XPATH, '//div[text()="업데이트 날짜"]/following-sibling::div')
    update = convertDate(selectMinimumUpdate(updates))
    # 출시 날짜
    try:
        open_dates = driver.find_element(By.XPATH, '//div[text()="출시일"]/following-sibling::div')
        open_date = convertDate(open_dates.text)
    except:
        open_date = '00000000'

    print('name : ' + name)
    print('packageId : ' + pkg_id)
    print('version : ' + version)
    print('openDate : ' + open_date)
    print('upDate : ' + update)
    print('appOs : ' + 'AOS')
    print('')
    
    # TODO: - 정보 확보 후 처리할 동작 추가


driver = loginAccount(aos_pkg_list[0])
lists = []
for pkg_id in aos_pkg_list:
    getUpdateInfoWithCrawl(driver, pkg_id)

  • output 확인

전체 소스 링크 : https://github.com/doriwori/store-scrap-sample

profile
k-힙합을 사랑하는 개발자

0개의 댓글