CoderCastrov logo
CoderCastrov
Парсер

Парсинг данных об играх и общего обзора отзывов с Steam Curator List

Парсинг данных об играх и общего обзора отзывов с Steam Curator List
просмотров
7 мин чтение
#Парсер

Read in English

Мне понадобились данные об индонезийских играх в Steam для статьи о данных в журналистике статьи для финального проекта курса. Лучшее, что я смог найти, это списки кураторов. Затем мне просто нужно было получить данные. В пакете pypi есть библиотека Steam API, но я не смог разобраться, как использовать ее. После множества поисков в Google, я спарсил список для получения appid, а затем получил детали и общий обзор с помощью веб-API. Кстати, я использую Python на Google Colab.

1. Скрапинг списка кураторов

1.1. Поиск данных

Кажется, что HTML-объекты Steam генерируются с помощью JavaScript. Я выбрал список и выбрал игру, скажем, Dreadout 2. Ее URL выглядит так:

[https://store.steampowered.com/app/**945710**/DreadOut_2/?curator_clanid=25278687&curator_listid=37980](https://store.steampowered.com/app/945710/DreadOut_2/?curator_clanid=25278687&curator_listid=37980)

Как видно из URL, идентификатор игры - 945710. Списковая страница должна содержать эту информацию где-то. Первым шагом будет просмотр исходного кода страницы (щелкнуть правой кнопкой мыши, просмотреть исходный код) и проверка, есть ли она там вместе с другими играми. Если ее там нет, мне, возможно, придется проверить вызовы API. К счастью, она есть, в атрибуте HTML. Он очень длинный и HTML-экранированный, поэтому я попытался найти его с помощью инструмента "Инспектировать элемент".

Вот оно. Оно находится в атрибуте data-curatorlistdata элемента #application_config div.

1.2. Получение данных

Похоже, что это JSON-строка. Поэтому сначала мне нужно получить этот JSON. Я сделал это с помощью requests-html. Вы можете установить его с помощью pip.

pip install requests-html

Если вы используете Google Colab или Jupyter, добавьте «!» в начало.

**!**pip install requests-html

Импортируйте HTMLSession из него. Также импортируйте пакет json.

from requests_html import HTMLSession
import json

Создайте объект сеанса и получите страницу списка кураторов.

session = HTMLSession()
r = session.get(url)

Получите div с помощью .html.find. Передайте #application_config в качестве аргумента CSS-селектора. Передайте first=True, чтобы он вернул только один объект.

application_config = r**.html.find**('**#application_config**', first=True)

Наконец, получите атрибут в виде строки с помощью .attrs, затем преобразуйте его в объект JSON с помощью json.loads.

data = **json.loads**(application_config**.attrs**["**data-curatorlistdata**"])

1.3. Очистка данных

Теперь у нас есть JSON. Вы можете его распечатать и скопировать в онлайн-просмотрщик JSON, чтобы просмотреть его структуру. Вы можете видеть, что список игр находится в:

data[0]["multi_detail_lists"][0]["apps"]["recommended_app"]

Я не знаю, почему они находятся в списках, когда списки имеют только один элемент, но я буду играть на безопасе и просто сделаю это вместо этого. Я также отсортирую их с использованием поля sort_order.

data = [x for l in data for x in l["multi_detail_lists"]]
data = [x for l in data for x in l["apps"]
data = sorted(data, key=lambda x: x["sort_order"])
data = [x["recommended_app"] for x in data]

Вот. У нас есть список краткой информации об играх из списка куратора. Однако, поскольку я все равно получу подробности, я просто возьму appid. Обзор куратора находится в поле "blurb", кстати.

data = [x["appid"] for x in data]

2. Получение информации об игре

2.1. Получение данных

Теперь, когда у нас есть идентификаторы приложений, мы можем получить полную информацию об игре. Мы получим информацию об игре с помощью этого эндпоинта. Он возвращает JSON-строку. Мы будем использовать пакет requests для ее получения. Также мы будем использовать пакет datetime для преобразования даты.

!pip install requests
import requests
import datetime

details_endpoint = "https://store.steampowered.com/api/appdetails?appids={0}&json=1&cc=ID"

Сначала вставьте идентификатор приложения в URL, а затем получите его с помощью requests.get. Получите результат в виде JSON-объекта с помощью .json().

url = details_endpoint.format(appid)
response = requests.get(url)
details = response.json()

Это словарь с ключом в виде строки идентификатора приложения, поэтому используйте строку идентификатора приложения, чтобы получить данные. Наконец, фактические детали находятся в поле "data".

details = details[str(appid)]
details = details["data"]

2.2. Очистка данных

JSON-данные содержат множество полей. В моем проекте я использовал только следующие поля:

keys = [
    "categories",
    "controller_support",
    "developers",
    "dlc",
    "genres",
    "is_free",
    "metacritic",
    "name",
    "platforms",
    "price_overview",
    "publishers",
    "release_date",
    "required_age",
    "steam_appid",
    "supported_languages",
    "type",
    "website",
    "support_info"
]

Вот как я отфильтровал JSON с помощью списка имен полей.

details = {k: details[k] for k in keys if k in details}

Теперь мы очищаем поля. Некоторые из полей являются списками тегов. Каждый тег имеет идентификатор и название (описание). Однако названия уникальны, поэтому я просто возьму их. Помните, что если у игры нет определенного поля, то поле просто не будет существовать.

if "categories" in details:
    details["categories"] = [x["description"] for x in details["categories"]]
if "genres" in details:
    details["genres"] = [x["description"] for x in details["genres"]]

Некоторые из полей являются просто списками строк. Это поля разработчиков и издателей. Пока мы их оставим.

Некоторые игры имеют оценку Metacritic. Она состоит из оценки и URL. Мы просто возьмем число оценки. Однако в итоге я не использовал это.

if "metacritic" in details:
    details["metacritic"] = details["metacritic"]["score"]

Некоторые поля являются объектами/словарями. Чтобы сохранить их в формате CSV, нам нужно их распаковать.

if "price_overview" in details:
    details["price_currency"] = details["price_overview"]["currency"]
    details["price_initial"] = details["price_overview"]["initial"]
    del details["price_overview"]
if "support_info" in details:
    for k, v in details["support_info"].items():
        details["support_{0}".format(k)] = v
    del details["support_info"]
if "release_date" in details and type(details["release_date"]) is dict:
    if "coming_soon" in details["release_date"]:
        details["coming_soon"] = details["release_date"]["coming_soon"]
    if "date" in details["release_date"]:
        details["release_date"] = details["release_date"]["date"]

Поле platforms состоит из словаря платформ и информации о том, поддерживаются ли они или нет. Мы распакуем их в список поддерживаемых платформ.

details["platforms"] = [k for k, v in details["platforms"].items() if v]

Некоторые поля являются HTML-текстом, поэтому я удалю HTML-теги. Также есть некоторый ненужный текст, который нужно удалить.

CLEANR = re.compile('<.*?>|&([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-f]{1,6});')
def clean_html(raw_html):
    clean_text = re.sub(CLEANR, '', raw_html)
    return clean_textlangs = details["supported_languages"]
langs = clean_html(langs).replace("*languages with full audio support", "").strip()
langs = [x.strip() for x in langs.split(",")]
details["supported_languages"] = langs

Затем я хочу преобразовать формат даты выпуска в формат YYYY-MM-DD.

if type(details["release_date"]) is str:
    try:
        details["release_date"] = datetime.datetime.strptime(
            details["release_date"], "%d %b, %Y"
        ).strftime("%Y-%m-%d")
    except ValueError:
        pass

2.3. CSV

В конце концов, мы преобразуем списки в строки, разделенные запятыми. Чтобы это сделать, я удалил каждую запятую в элементах списка. Вы можете использовать другие разделители, чтобы избежать удаления запятых. Вы также можете просто преобразовать список в строку JSON с помощью json.dumps.

def flatten_list(l):
    ls = [str(x).replace(",", "") for x in l]
    return ",".join(ls)def details_flatten_list(details):
    return {
        k: flatten_list(v) if type(v) is list else v 
        for k, v in details.items()
    }details = details_flatten_list(details)

И мы закончили. Теперь он должен хорошо подходить для CSV. Если я что-то упустил, пожалуйста, скажите мне в комментариях.

Помните, что при загрузке CSV вы должны разделить поля, разделенные запятыми, обратно в список.

df[col] = df[col].str.split(',')

3. Получение общего обзора игры

Последнее, что нужно получить, это обзоры игры. Однако мне нужен только общий обзор, поэтому я установил параметр num_per_page равным 0. Если вы хотите получить индивидуальные обзоры, вы можете установить этот параметр на не более 20. Если вы хотите фактически получить все обзоры, вам может потребоваться установить параметр языка на all.

review_endpoint = "https://store.steampowered.com/appreviews/{0}?json=1&language=all&num_per_page=0"

3.1. Получение данных

Хорошо, получаем данные так же, как и раньше в разделе 2. Мы будем использовать библиотеки requests и json.

url = review_endpoint.format(appid)
response = requests.get(url)
reviews = response.json()

Общий обзор или краткое описание обзора находится в поле query_summary. Если вы хотите получить отдельные отзывы, они находятся в поле reviews.

reviews = response.json()["query_summary"]

3.2. Очистка данных

Я удаляю поле num_reviews, потому что я не получаю никаких отзывов (num_per_page=0).

del reviews["num_reviews"]

Процент положительных отзывов не включен, поэтому я вычисляю его вручную.

if "total_reviews" in reviews and reviews["total_reviews"] > 0:
    reviews["positive_rate"] = reviews["total_positive"] / reviews["total_reviews"] * 100

И мы закончили. JSON с обзором уже готов для преобразования в CSV, и я использую все поля (кроме num_reviews), поэтому нет необходимости в большой очистке, как раньше.

Заключение

Хорошо, это в основном то, что я сделал для парсинга деталей игры и общего обзора на Steam. Это на самом деле довольно просто, если вам не нужно сохранять данные в формате CSV. Они уже хорошо отформатированы в JSON. Хотя я не знаю, поддерживает ли библиотека Steam в pypi это; я не смог найти, как это сделать.

Кстати, эндпоинт с деталями игры, кажется, имеет ограничение на количество запросов. Если вы его превысите, страница будет пустой, и код выдаст ошибку. Обработайте это так, как вам удобно.

Спасибо за чтение.