본문 바로가기
SW 개발/Data 분석 (RDB, NoSQL, Dataframe)

Apple App Store 사용자 댓글(리뷰) 데이터 수집하기 (Sample code 포함)

by Kibua20 2021. 7. 14.
728x170

이전 포스팅 (https://kibua20.tistory.com/192)에서는 Python Selenium과 BeatifulSoup을 사용해서 Google Play Store에서 App에 대한 사용자, 작성일, 좋아요, 리뷰 내용의 데이터를 크롤링하는 방법에 대해서 설명했습니다. Apple App Store에서는 사용자 리뷰 정보를 웹 페이지와 RSS Feed로 제공하고 있으며, 이를 수집하는 방법을 설명합니다. 

 

Apple App 앱 스트에서 사용자 리뷰는 아래 그림과 같이 사용자 리뷰 내용을 제공하고 있습니다. 아래 사이트를 Selenium을 사용해서 크롤링도 가능하지만,  RSS Feed가 보다 정형적인 데이터를 제공하고 구현이 쉽게 때문에 RSS Feed를 사용합니다.  예를 들어 Youtube의 RSS Feed와 사용자 리뷰 사이트는 아래와 같습니다.

Apple App Store의 리뷰 내용

 

 

애플이 제공하는 RSS Feed를 활용해서 사용자 리뷰 정보를 얻어오는 방법은 다음과 같이 3단계가 필요하고,  각 단계별로 상세하게 설명하도록 하겠습니다.   

  1. Apple Appostore에서 App id 확인하기
  2. AppID의 RSS Feed 사이트에서 사용자 리뷰 내용을 xlm 파일로 받기
  3. XML 파일을 Parsing 해서  사용자, 입력 날짜, 평점, 좋아요 데이터를 얻어 CSV 파일로 저장 

 

App ID 확인: Apple 앱 스토어 사이트에서 검색하여 URL 확인

애플의 App ID를 확인하는 방법은 Apple site에서 앱 이름으로 검색하면 상단 URL에서 App ID를 알 수 있습니다.  예를 들어 Youtube을 App site에서 검색하고 App Store URL을 확인하면 URL의 마지막 부분인 ID 544007644가 Youtube의 App ID입니다. 

 

Apple Appstore의 Youtube ID: https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664 

 

Apple App ID 확인하기

 

AppID의 RSS Feed 사이트에서 사용자 리뷰 내용을 xlm 파일로 받기

RSS Feed는 JSON과 XML 데이터 형식으로 제공합니다. JSON과 XML의 내용은 동일하지만, JSON에서는 리뷰 작성일이 빠져 있어 XML 데이터를 활용하는 것을 추천드립니다.   특정 App의 RSS의 URL은 아래와 같이 언어 코드와 App ID 구성되어 있어 App ID를 먼저 확인해야 합니다. (참고 링크: https://stackoverflow.com/questions/1090282/api-to-monitor-iphone-app-store-reviews)

 

▣ RSS Feed URL: http://itunes.apple.com/[언어]/rss/customerreviews/id=[you app id]/xml
e.g)  https://itunes.apple.com/kr/rss/customerreviews/id=544007664/xml

 

Apple RSS 사이트에서 사용자 리뷰를 Python requests를 사용해서 다운로드 받습니다.  Apple의 RSS Feed는 사용자 리뷰를 50개씩 나눠서 최대 10개의 URL Page에 나눠서 다운로드할 수 있습니다. 

1st Page Feed: https://itunes.apple.com/kr/rss/customerreviews/page=1/id=544007664/sortby=mostrecent/xml
2nd Page Feed: https://itunes.apple.com/kr/rss/customerreviews/page=2/id=544007664/sortby=mostrecent/xml
...
n-th Page Feed: URL: https://itunes.apple.com/kr/rss/customerreviews/page=n/id=544007664/sortby=mostrecent/xml

 

애플 RSS Feed사이트에서 XML형식으로 다운로드한 내용은 아래와 같습니다.  XML 내용에는 사용자 리뷰의 시작 URL과 마지막 URL이 <link> tag에 명시되어 있고, <entry> 하위에 사용자 리뷰 내용이 정리되어 있습니다.  <updated> tag에 사용자가 업데이트한 날짜를 가지고 있고, 제목은  <title> tag, 사용자는 <author> tag에 저장되어 있고, 제일 중요한 사용자 리뷰 내용은 <content>에서 가져올 수 있습니다. 

AppID의 RSS Feed 사이트에서 사용자 리뷰 내용을 xlm 파일로 받기

 

XML 파일을 Parsing 해서  사용자, 입력 날짜, 평점, 좋아요 데이터를 얻어 CSV 파일로 저장 

Python에서는 requests 모듈을 사용해서 XML을 다운로드하고, response를 xlmtodict 모듈로 dictionary형태로 변환 후 Pandas Dataframe으로 저장합니다.  최종적으로는 아래와 같이 CSV 파일로 저장합니다.  세부적인 내용은 Python code와 함께 Comment로 추가하였습니다.

XML 파일을 Parsing 해서  사용자, 입력 날짜, 평점, 좋아요 데이터를 얻어 CSV 파일로 저장 

 

 

Python code로 옮기면 get_url_index()와 appstore_crawler()로 정리할 수 있고 코드에 대한 설명은 아래와 같습니다.

 

# 리뷰 내용에서 마지막 page의 Index 가져 오는 함수

def get_url_index(url):

    #  requests에 response에 대해서 한글 깨짐을 방지하기 위해서 UTF8로 decoding

     response = requests.get(url).content.decode('utf8')

 

    #xml 을 dict로 변환 - https://kibua20.tistory.com/146 에서 xmltodict 설명을 참고하세요.

    xml = xmltodict.parse(response)

 

    # 사용자 리뷰 Page의  last Index 가져오기

    last_url = [l['@href'] for l in xml['feed']['link'] if (l['@rel'] == 'last')][0]

    last_index = [int(s.replace('page=', '')) for s in last_url.split('/') if ('page=' in s)][0]

return last_index

 

# AppStore에서 Review 전체를 가져오는 함수

def appstore_crawler(appid, outfile='./appstore_reviews.csv'):

    url = 'https://itunes.apple.com/kr/rss/customerreviews/page=1/id=%i/sortby=mostrecent/xml' % appid

 

    # 예외 처리 1:  Review가 전혀 없는 경우 

    try:

        last_index = get_url_index(url)

    except Exception as e:

        print ('\tNo Reviews: appid %i' %appid)

        print ('\tException:', e)

        return

 

    # Apple RSS Feed URL을 Page 1 ~ last index page까지 Iteration

    result = list()

    for idx in range(1, last_index+1):

          url = "https://itunes.apple.com/kr/rss/customerreviews/page=%i/id=%i/sortby=mostrecent/xml?urlDesc=/customerreviews/id=%i/sortBy=mostRecent/xml" % (idx, appid, appid)

     

         # 사용자 리뷰 xlm을 다운로드  (한글 깨짐 방지를 위해서 utf8로 인코딩)

          response = requests.get(url).content.decode('utf8')

   

       # xml을  dict로 변환, Apple Site에서 xml이 깨져 있는 경우가 있어  예외처리 추가

        try:

                xml = xmltodict.parse(response)

         except Exception as e:

                print ('\tXml Parse Error %s\n\tSkip %s :' %(e, url))

                continue

 

      # 사용자 Review가 존재하는지 확인, 리뷰가 없으면 이후 처리를 하지 않음

      try:

              num_reivews= len(xml['feed']['entry'])

       except Exception as e:

             print ('\tNo Entry', e)

             continue

 

     # 사용자 리뷰가 단 1개인 경우 XML 처리 시 에러 발생 방지

      try:

              xml['feed']['entry'][0]['author']['name']

               single_reviews = False

       except:

              #print ('\tOnly 1 review!!!')

              single_reviews = True

       pass

 

   # 사용자 리뷰를 list에 저장 

    if single_reviews:

        result.append({

              'USER': xml['feed']['entry']['author']['name'],

              'DATE': xml['feed']['entry']['updated'],

              'STAR': int(xml['feed']['entry']['im:rating']),

             'LIKE': int(xml['feed']['entry']['im:voteSum']),

             'TITLE': xml['feed']['entry']['title'],

             'REVIEW': xml['feed']['entry']['content'][0]['#text'],

          })

         else:

               for i in range(len(xml['feed']['entry'])):

                    result.append({

                           'USER': xml['feed']['entry'][i]['author']['name'],

                            'DATE': xml['feed']['entry'][i]['updated'],

                            'STAR': int(xml['feed']['entry'][i]['im:rating']),

                            'LIKE': int(xml['feed']['entry'][i]['im:voteSum']),

                            'TITLE': xml['feed']['entry'][i]['title'],

                           'REVIEW': xml['feed']['entry'][i]['content'][0]['#text'],

                  })

 

    # 사용자 리뷰 결과 list를 DataFrame을 생성 

    res_df = pd.DataFrame(result)

    # DATE Column은 String에서 Dataframe의 Date로 변환

    res_df['DATE'] = pd.to_datetime(res_df['DATE'], format="%Y-%m-%dT%H:%M:%S")

    # CSV  파일로 저장, 한글 깨짐을 방지하고 위해서 'utf-8-sig'로 저장, Index column은 저장하지 않음

    res_df.to_csv(outfile, encoding='utf-8-sig', index=False)

 

 

GitHub Sample Code

Apple Appstore에서 사용자 댓글을 얻어오는 코드의 전체 코드는 아래와 같고 Git Hub에서 반영했습니다.

 

소스 코드: https://github.com/kibua20/devDocs/tree/master/appstore_crawler

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import pandas as pd
import xmltodict
import requests
import os


def get_url_index(url):
    response = requests.get(url).content.decode('utf8')
    xml = xmltodict.parse(response)

    last_url = [l['@href'] for l in xml['feed']['link'] if (l['@rel'] == 'last')][0]
    last_index = [int(s.replace('page=', '')) for s in last_url.split('/') if ('page=' in s)][0]

    return last_index

# https://stackoverflow.com/questions/1090282/api-to-monitor-iphone-app-store-reviews
def appstore_crawler(appid, outfile='./appstore_reviews.csv'):
    url = 'https://itunes.apple.com/kr/rss/customerreviews/page=1/id=%i/sortby=mostrecent/xml' % appid

    try:
        last_index = get_url_index(url)
    except Exception as e:
        print (url)
        print ('\tNo Reviews: appid %i' %appid)
        print ('\tException:', e)
        return

    result = list()
    for idx in range(1, last_index+1):
        url = "https://itunes.apple.com/kr/rss/customerreviews/page=%i/id=%i/sortby=mostrecent/xml?urlDesc=/customerreviews/id=%i/sortBy=mostRecent/xml" % (idx, appid, appid)
        print(url)

        response = requests.get(url).content.decode('utf8')
        try:
            xml = xmltodict.parse(response)
        except Exception as e:
            print ('\tXml Parse Error %s\n\tSkip %s :' %(e, url))
            continue

        try:
            num_reivews= len(xml['feed']['entry'])
        except Exception as e:
            print ('\tNo Entry', e)
            continue

        try:
            xml['feed']['entry'][0]['author']['name']
            single_reviews = False
        except:
            #print ('\tOnly 1 review!!!')
            single_reviews = True
            pass

        if single_reviews:
                result.append({
                    'USER': xml['feed']['entry']['author']['name'],
                    'DATE': xml['feed']['entry']['updated'],
                    'STAR': int(xml['feed']['entry']['im:rating']),
                    'LIKE': int(xml['feed']['entry']['im:voteSum']),
                    'TITLE': xml['feed']['entry']['title'],
                    'REVIEW': xml['feed']['entry']['content'][0]['#text'],
                })
        else:
            for i in range(len(xml['feed']['entry'])):
                result.append({
                    'USER': xml['feed']['entry'][i]['author']['name'],
                    'DATE': xml['feed']['entry'][i]['updated'],
                    'STAR': int(xml['feed']['entry'][i]['im:rating']),
                    'LIKE': int(xml['feed']['entry'][i]['im:voteSum']),
                    'TITLE': xml['feed']['entry'][i]['title'],
                    'REVIEW': xml['feed']['entry'][i]['content'][0]['#text'],
                })

    res_df = pd.DataFrame(result)
    res_df['DATE'] = pd.to_datetime(res_df['DATE'], format="%Y-%m-%dT%H:%M:%S")
    res_df.to_csv(outfile, encoding='utf-8-sig', index=False)
    print ('Save reviews to file: %s \n' %(outfile))


if __name__ == '__main__':
    # https://apps.apple.com/us/app/youtube-watch-listen-stream/id544007664
    app_id = 544007664
    outfile = os.path.join('appstore_' + str(app_id)+'.csv')
    appstore_crawler(app_id, outfile=outfile)

 

관련 글:

[SW 개발/Python] - Python: JSON 개념과 json 모듈 사용법

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Panda Dataframe 날짜 기준으로 데이터 조회 및 처리하기

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Pandas Dataframe 여러 열과 행에 apply() 함수 적용 (Sample code 포함)

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Python Selenium과 BeautifulSoup을 활용하여 Google PlayStore 리뷰 가져오기

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - CSV 파일에서 MariaDB(또는 MySQL)로 데이터 가져오는 방법

[SW 개발/Android] - Python BeautifulSoup으로 만든 Apk download 자동화 (Sample code)

[SW 개발/Python] - Python: xmltodict를 활용하여 XML를 JSON으로 변환하는 방법

[SW 개발/Python] - Python 명령어 처리: Argparse 모듈 (ArgumentParser 사용 예제)

[SW 개발/Python] - Python 표준 입출력(stdin/stdout) 활용 - 리눅스 프로그램과 연동

[SW 개발/Data 분석 (RDB, NoSQL, Dataframe)] - Jupyter Notebook의 업그레이드: Jupyter Lab 설치 및 extension 사용법

그리드형



댓글4

  • 00 2021.07.15 17:20

    좋은 글 감사합니다. 샘플 코드가 많은 도움이 될 것 같습니다. 최고에요!!!
    답글

  • onge 2022.05.16 14:59

    안녕하세요! 도움이 많이 되었습니다. 감사해요.

    질문이 있는데, 저장된 데이터프레임과 csv파일을 찾을 수가 없는데 제가 놓치고 있는 부분이 있을까요?ㅠㅠ
    답글