본문 바로가기
Project

[프로젝트] 고인물의 스팀 게임추천 #2 데이터수집

by nothing-error 2023. 1. 12.

 

1. App list(아래 주소에서 api 정보 확인 가능)

https://partner.steamgames.com/doc/webapi/ISteamApps

 

ISteamApps Interface (Steamworks Documentation)

Documentation Resources News & Updates Support

partner.steamgames.com

GetAppList 으로 스팀에 있는 모든 게임목록을 알 수 있다. 

 

API 구조는 아래와 같다.

### 응답
- applist
    - apps- 응용 프로그램이 포함된 목록입니다.
        - appid- uint32 - 이 애플리케이션의 앱 ID입니다.
        - name- 문자열 - 이 애플리케이션의 이름입니다.

 

 master 노드에서 데이터 수집을 진행하였고

%spark_yarn.pyspark

import time 
import json
import requests
import pandas as pd
import pymongo
from tqdm import tqdm

applist=requests.get("https://api.steampowered.com/ISteamApps/GetAppList/v2")

pdf_applist=pd.DataFrame(applist.json()["applist"]["apps"])

pdf_applist=pdf_applist[pdf_applist['name'] != ""]

pdf_applist_final=pdf_applist[~pdf_applist["name"].str.contains('test')]

sdf_app_list=spark.createDataFrame(pdf_applist_final)

 

출력결과는 아래처럼  appid 와 name 으로 이뤄진 데이터프레임 형식이다.

 

데이터 저장을 위해서 mongoDB를 설치하였고 설정같은 아래와 같다. steam 이라는 DB를 만들었다.

mongo_id=""
mongo_password=""
server_ip="spark-master-01"
port=27317
conn = pymongo.MongoClient(f"mongodb://{mongo_id}:{mongo_password}@{server_ip}:{port}")
db = conn.steam

 

2. 게임 리뷰 & 플레이타임


아래 링크에서 게임 리뷰 api를 확인할 수 있다.
https://partner.steamgames.com/doc/store/getreviews

 

User Reviews - Get List (Steamworks Documentation)

Documentation Resources News & Updates Support

partner.steamgames.com

 

api를 살펴보면, 파라미터에 대한 설명이 잘되어 있다. 대부분 디폴트 값으로 설정하고 모든 데이터를 가져오는 방향으로 수집하였다. 언어의 경우에는 한국어로만 수집하였다.(게임 취향은 나라가 다르더라도 비슷할 것으로 예상되지만 15만개 이상의 게임의 데이터를 언어 제한 없이 모두 수집하기엔 물리적인 어려움이 있었다)

api를 사용하면서 약간 헤맸던 부분은 cursor 라는 파라미터였는데, 응답값을 확인해보니 다른 api의 page 개념이었다.

초기에만 cursor 값을 '*' 으로 주면 이후에 응답에 다음 페이지로 갈 수 있는 cursor 를 응답값으로 준다.

 

 

모든 게임 app 들을 돌아가면서 review를 수집해야 했기에  수집하는 부분은 함수로 만들었다. cursor 와 appid만 있으면 json 타입으로 리턴된다.

def collect_reviews(cursor, appid)-> [str,int]:
    
    params={
        "filter":"all",
        "language":"korean",
        "day_range":365,
        "cursor": cursor,
        "revuew_type":"all" ,
        "purchase_type":"steam" ,
        "num_per_page":100
    }
    reviews=requests.get(f"http://store.steampowered.com/appreviews/{appid}?json=1", params=params)
    return reviews.json()

 

 

모든  appid 를 돌면서 cursor의 값이 100이 이 아니거나 값이 동일하면 break 하고 그 외에는 review를 마지막 페이지까지 수집한다. 수집한 여러페이지의 데이터를  하나의 json으로 통합하여  steam DB에 reviews 라는 collection(table) 을 만들어서 넣었고 이후에 appid 로 검색하기 편하도록 appid 라는 key값을 추가했다.

%spark_yarn.pyspark

appid_list=pdf_applist_final["appid"].tolist()

for appid in tqdm(appid_list[0:10000]):
    try:
        cursor="*"
        temp=[]
        while True:
            time.sleep(0.3)
            reviews=collect_reviews(cursor, appid)
            if reviews2['query_summary']['num_reviews'] <100:
                temp.append(reviews2)
                break
            if cursor == reviews2['cursor']:
                break
            
            cursor=reviews2['cursor']
            temp.append(reviews2)

        temp2=[]
        for i in range(len(temp)):
            temp2+=temp[i]['reviews']
        reviews['appid']=appid
        reviews['reviews']=temp2
        
        
        steam_reviews = db.reviews
        
        steam_reviews.insert_one(reviews)
        pdf_applist_final.loc[pdf_applist_final['appid'] == appid, 'result' ]=1
    except Exception as ex:
        print(appid, ex)

 


3. mySQL vs mongoDB

mysql과 mongodb를 간략히 비교하면 아래와 같다.

mySQL mongoDB
database databs
table collection
row document

 

mongoDB의 장점:

  • 동적 스키마를 지원하여 데이터 구조를 쉽게 변경할 수 있습니다.
  • JSON 타입의 데이터를 저장할 수 있어 데이터 저장, 읽기가 쉽습니다.
  • 분산 환경에서 쉽게 사용할 수 있도록 해주는 설계 구조를 가지고 있습니다.
  • 컬렉션 수, 데이터 용량에 대한 제약이 없습니다.
  • 고성능 인덱싱, 쿼리 수행 등을 지원합니다.

mongoDB의 단점:

  • 모든 데이터를 메모리에 적재하지 않아 고정된 용량의 데이터를 저장할 수 없습니다.
  • 트랜잭션 지원이 제한적입니다.
  • 일반적인 RDBMS와 달리 JOIN 기능이 없습니다.
  • 관계형 데이터를 저장하기에는 적합하지 않을 수 있습니다.

(수정사항)

als 모델을 훈련하고 평가하는 과정에서 rmse 값이 상당히 높게 나오다보니 데이터를 조금 더 뜯어보는 과정에서

생각보다 상당히 많은 리뷰가 누락된것을 확인할 수 있었다(상당히 인기 많은 다수의 게임들 포함)

 

수집하는 코드를 전체적으로 수정하였다. 

변경1. user_agent 를 생성해주는 라이브러리를 통해서 api 요청시 header에 추가

변경2. 1개 서버에서 thread 4개로 수집( gcp 무료 크레딧으로 4개 서버에서 수집했었다가 정책위반으로 하둡이랑 스파크 환경 세팅해놓은거 다 날라갔었다.)

 

%pyspark
import threading
import time
import pandas as pd

def thread_collect_reviews(num):
    from tqdm import tqdm

    def collect_review(cursor, appid)-> [str,int]:
        from user_agent import generate_user_agent, generate_navigator
        navigator = generate_navigator()
        params={
            "filter":"all",
            "language":"korean", #한국어만 수집
            "day_range":365,
            "cursor": cursor, #page number
            "revuew_type":"all" ,
            "purchase_type":"steam" ,
            "num_per_page":100
        }
        reviews = requests.get(f"http://store.steampowered.com/appreviews/{appid}?json=1", params=params, headers=navigator)
        return reviews.json()
        
        
    #수집 앱 목록
    applist=requests.get("https://api.steampowered.com/ISteamApps/GetAppList/v2")
    pdf_applist=pd.DataFrame(applist.json()["applist"]["apps"])
    pdf_applist=pdf_applist[pdf_applist['name'] != ""]
    pdf_applist_final=pdf_applist[~pdf_applist["name"].str.contains('test')]
    pdf_applist_final_list=pdf_applist_final['appid'].tolist()

    temp_dic={
        1:pdf_applist_final_list[0:40000],
        2:pdf_applist_final_list[40000:80000],
        3:pdf_applist_final_list[80000:120000],
        4:pdf_applist_final_list[120000:]
        
    }
    
    #수집

    for appid in tqdm(temp_dic[num]): 
        try:
            cursor="*"
            temp=[]
            
            while True:  #리뷰 100건마다 페이지 넘기기
                time.sleep(0.3)
                reviews=collect_review(cursor, appid)
                if reviews['query_summary']['num_reviews'] < 100  :
                    temp.append(reviews)
                    break
                if cursor == reviews['cursor']:
                    break
                cursor=reviews['cursor']
                temp.append(reviews)
                
                
            #100개씩 모은 리뷰를 하나로 통합 후 appid 키 추가
            temp2=[]
            for i in range(len(temp)):
                temp2 += temp[i]['reviews']
            reviews['appid']=appid
            reviews['reviews']=temp2
    
            #mongoDB에 수집한 리뷰 넣기
            steam_appid = db.appids
            steam_appid.insert_one(reviews) # 데이터 1개 삽입
            
            #수집여부 check!
            pdf_applist_final.loc[pdf_applist_final['appid'] == appid, 'result' ] = 1 
            
        except Exception as ex:
            time.sleep(15*60)
            print(appid, ex)
            



t1 = threading.Thread(target=thread_collect_reviews, args=(1,))
t2 = threading.Thread(target=thread_collect_reviews, args=(2,))
t3 = threading.Thread(target=thread_collect_reviews, args=(3,))
t4 = threading.Thread(target=thread_collect_reviews, args=(4,))
t1.start()
t2.start()
t3.start()
t4.start()
t1.join()
t2.join()
t3.join()
t4.join()

 

 

 

 

mongoDB 설치는 아래 링크 참고해서 설치하였다.

https://velog.io/@seungsang00/Ubuntu-MongoDB-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-Ubuntu-20.04

설치 후 conf 파일은 ip 랑 port 정도만 수정

$sudo vi /etc/mongod.conf

 


다음글

2023.01.17 - [Project] - [프로젝트] 고인물의 스팀 게임추천 #3 데이터탐색

2023.01.25 - [Project] - [프로젝트] 고인물의 스팀 게임추천 #4 모델링

 

 

댓글