CoderCastrov logo
CoderCastrov
Парсер

Парсинг общего резюме обзоров и подробных данных об игре Steam из списка кураторов Steam

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

Читать на английском

Ранее мне понадобились данные об играх из Индонезии в Steam для статьи о журналистике данных для моего финального проекта в буткемпе. Лучшим, что я смог найти, был список кураторов. Затем мне просто нужно было получить эти данные. В pypi есть библиотека Steam API, но я не смог найти способ использовать ее для этого. После многочисленных поисков в Google я сделал парсинг списка кураторов, чтобы получить appid, а затем получил подробности и общий обзор с использованием веб-API Steam. Я использовал 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, идентификатор игры (game ID) равен 945710. Эта страница списка наверняка содержит эту информацию где-то. Первым шагом, который я сделал, было просмотреть исходный код страницы (щелкнуть правой кнопкой мыши, выбрать "Просмотреть исходный код") и найти, где находится идентификатор игры вместе с другими играми. Если его нет, мне, возможно, придется проверить API-вызов, который выполняется на этой странице. К счастью, идентификатор игры присутствует в атрибуте HTML. Этот атрибут очень длинный и HTML-экранированный, поэтому я попробовал найти его с помощью инструмента "Инспектировать элемент".

Вот оно. Идентификатор игры, или appid, находится в атрибуте data-curatorlistdata элемента div с идентификатором #application_config.

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. Укажите CSS-селектор #application_config в качестве аргумента. Также укажите 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. Mengambil detail game

2.1. Mengambil data

Karena kita sudah punya appid, kita bisa ambil detail game seutuhnya. Kita bisa ambil detail game dari endpoint ini. Ia akan menghasilkan sebuah JSON string. Kita akan gunakan package requests untuk mengambilnya. Kita juga akan menggunakan package datetime untuk mengkonversi tanggal.

!pip install requestsimport requests
import datetimedetails_endpoint = "https://store.steampowered.com/api/appdetails?appids={0}&json=1&cc=ID"

Pertama-tama, masukkan appid ke dalam url lalu ambil menggunakan requests.get. Ambil hasilnya sebagai objek JSON menggunakan .json().

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

Objek ini adalah sebuah dictionary dengan string sebagai kuncinya, jadi gunakan string appid untuk mengambil datanya. Terakhir, data detailnya ada dalam field “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}

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

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"]]

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

Некоторые игры имеют оценку 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. Если вы хотите получить все обзоры, вам может понадобиться установить параметр language на 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 это; я не нашел способа сделать это.

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

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