Ежедневная таблица лидеров или данные отслеживания цен

#python #pandas #dataframe #web-scraping

#python #панды #фрейм данных #очистка веб-страниц

Вопрос:

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

Моя цель — очистить данные с веб-сайта (таблица лидеров за все время / скрытая), поместить их в файл .csv и обновлять его ежедневно в полдень.

Что мне удалось до сих пор: очистка данных.

  1. Пробовал выполнять очистку с помощью BS4, но поскольку данные скрыты, я не мог быть достаточно конкретным, чтобы получить только рекордные баллы. Я считаю, что это успех, потому что я могу получить таблицу со всеми необходимыми данными и датой в качестве заголовка. Моя проблема с этим решением заключается в том, что 1) данные, заполняющие csv, являются бесполезными 2) таблица вертикальная, а не горизонтальная
  2. Очищенные данные с помощью селектора CSS, но я отказался от этой идеи, потому что иногда страница не загружается, и данные не были удалены. Обнаружил, что есть файл json, содержащий данные сразу
  3. Очистка Json кажется лучшим вариантом, но возникают проблемы с созданием csv-файла, с которым можно составить график.

Это то, что подводит меня к тому, с чем я борюсь: хранение данных в таблице, которая выглядит следующим образом, где серая область — это точки, а ДАТА 1 — это момент, когда данные были очищены : Серая область = очки

Я бы не хотел слишком сильно манипулировать данными в файле csv. Если таблица будет выглядеть так, как я изобразил выше, то после этого будет проще составить график, но у меня возникли проблемы. Лучшее, что я сделал, это создал таблицу, которая выглядит так, и она вертикальная, а не горизонтальная.

 name,points,date
Dennis,52570,10-23-2020
Dinh,40930,10-23-2020
name,points,date
Dennis,52570,10-23-2020
Dinh,40930,10-23-2020
name,points,date
Dennis,52570,10-23-2020
Dinh,40930,10-23-2020
  

Спасибо за вашу помощь.

Вот код

 import pandas as pd
import time
timestr = time.strftime("%Y-%m-%d %H:%M")
url_all_time = 'https://community.koodomobile.com/widget/pointsLeaderboard?period=allTimeamp;maxResults=20amp;excludeRoles='
data = pd.read_json(url_all_time)
table = pd.DataFrame.from_records(data, index=['name'], columns=['points','name'])
table['date'] = pd.Timestamp.today().strftime('%m-%d-%Y')
table.to_csv('products.csv', index=True, encoding='utf-8')
  

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

Комментарии:

1. Я настоятельно рекомендую использовать облегченную базу данных (например, SQLite) для удобства обновления, поиска и запросов в будущем. Целевая таблица должна быть такой же простой, как что-то вроде (дата, ранг, пользователь, оценка).

2. Можете ли вы с этим справиться?

3. Веб-очистка (извлечение данных), предварительная обработка данных и хранение данных — это три отдельных этапа. Если я правильно понимаю, ваш вопрос касается предварительной обработки данных и, возможно, хранения. Я предполагаю, что вы собираетесь самостоятельно обрабатывать часть очистки ( pd.read_json часть уже отработана).

Ответ №1:

Итак, я немного поиграл с вашим вопросом, и вот что я придумал.

В принципе, ваш лучший выбор для хранения данных — это легкая база данных, как предложено в комментариях. Однако, с небольшим планированием, несколькими переходами и некоторым хакерским кодом, вы могли бы обойтись простым (вроде) JSON , который в конечном итоге выглядит как .csv файл, который выглядит следующим образом:

Примечание: значения такие же, как я не хочу ждать день или два, пока таблица лидеров действительно обновится.

введите описание изображения здесь

Что я сделал, так это переставил данные, которые возвращались из запроса в API, и построил структуру, которая выглядит следующим образом:

     "BobTheElectrician": {
        "id": 7160010,
        "rank": 14,
        "score_data": {
            "2020-10-24 18:45": 4187,
            "2020-10-24 18:57": 4187,
            "2020-10-24 19:06": 4187,
            "2020-10-24 19:13": 4187
        }
  

Каждый игрок — это ваш главный ключ, который, помимо прочего, имеет scores_data значение. Это, в свою очередь dict , points значение, которое сохраняется для каждого дня, когда вы запускаете скрипт.

Теперь хитрость в том, чтобы заставить это JSON выглядеть так, как .csv вы хотите. Вопрос в том, как?

  • Ну, поскольку вы намерены обновить данные всех игроков (я просто предположил, что), все они должны иметь одинаковое количество записей для score_data .

  • Ключами для score_data являются ваши временные метки. Возьмите score_data ключи любого игрока, и у вас будут заголовки даты, верно?

  • Сказав это, вы можете строить свои .csv строки таким же образом: берите имя игрока и все его значения очков score_data . Это должно дать вам список списков, верно? Правильно.

Затем, когда у вас есть все это, вы просто сбрасываете это в .csv файл, и вот оно у вас есть!

Собрать все это вместе:

 import csv
import json
import os
import random
import time
from urllib.parse import urlencode

import requests


API_URL = "https://community.koodomobile.com/widget/pointsLeaderboard?"
LEADERBOARD_FILE = "leaderboard_data.json"


def get_leaderboard(period: str = "allTime", max_results: int = 20) -> list:
    payload = {"period": period, "maxResults": max_results}
    return requests.get(f"{API_URL}{urlencode(payload)}").json()


def dump_leaderboard_data(leaderboard_data: dict) -> None:
    with open("leaderboard_data.json", "w") as jf:
        json.dump(leaderboard_data, jf, indent=4, sort_keys=True)


def read_leaderboard_data(data_file: str) -> dict:
    with open(data_file) as f:
        return json.load(f)


def parse_leaderboard(leaderboard: list) -> dict:
    return {
        item["name"]: {
            "id": item["id"],
            "score_data": {
                time.strftime("%Y-%m-%d %H:%M"): item["points"],
            },
            "rank": item["leaderboardPosition"],
        } for item in leaderboard
    }


def update_leaderboard_data(target: dict, new_data: dict) -> dict:
    for player, stats in new_data.items():
        target[player]["rank"] = stats["rank"]
        target[player]["score_data"].update(stats["score_data"])
    return target


def leaderboard_to_csv(leaderboard: dict) -> None:
    data_rows = [
        [player]   list(stats["score_data"].values()) 
        for player, stats in leaderboard.items()
    ]
    random_player = random.choice(list(leaderboard.keys()))
    dates = list(leaderboard[random_player]["score_data"])
    with open("the_data.csv", "w") as output:
        w = csv.writer(output)
        w.writerow([""]   dates)
        w.writerows(data_rows)


def script_runner():
    if os.path.isfile(LEADERBOARD_FILE):
        fresh_data = update_leaderboard_data(
            target=read_leaderboard_data(LEADERBOARD_FILE),
            new_data=parse_leaderboard(get_leaderboard()),
        )
        leaderboard_to_csv(fresh_data)
        dump_leaderboard_data(fresh_data)
    else:
        dump_leaderboard_data(parse_leaderboard(get_leaderboard()))


if __name__ == "__main__":
    script_runner()

  

Скрипт также проверяет, есть ли у вас JSON файл, который выдает себя за правильную базу данных. Если нет, он запишет данные таблицы лидеров. При следующем запуске скрипта он обновит JSON и выдаст новый .csv файл.

Надеюсь, этот ответ подтолкнет вас в правильном направлении.

Комментарии:

1. Именно то, что я искал. Я не думал, что можно изменить порядок данных, полученных из json. Определенно не было знаний, чтобы придумать это. В итоге я создал файл JSON для каждого имени пользователя, а затем вручную объединил эти файлы в другом excel. Это решение определенно лучше. Будет ли идентификатор обновления говорить, что кто-то получает больше очков, чем другой? или данные будут записываться в одно и то же место каждый раз?

2. При этом обновляются только оценка и ранг, но вы можете легко адаптировать это для записи любых данных. Если вы найдете мой ответ полезным, пожалуйста, проголосуйте и / или примите его.

3. До сих пор удалось успешно заставить его работать в cronjob. Всякий раз, когда скрипт запускается в cron, он выдает ошибку ключа, но отлично работает в PyCharm. Я в замешательстве. Я ничего не менял.

4. Я не могу помочь вам без отслеживания ошибок. Опубликуйте проблему как отдельный вопрос.

Ответ №2:

Эй, поскольку вы загружаете его в фрейм panda, это делает операции довольно простыми. Я сначала запустил ваш код

  import pandas as pd
import time
timestr = time.strftime("%Y-%m-%d %H:%M")
url_all_time = 'https://community.koodomobile.com/widget/pointsLeaderboard?period=allTimeamp;maxResults=20amp;excludeRoles='
data = pd.read_json(url_all_time)
table = pd.DataFrame.from_records(data, index=['name'], columns=['points','name'])
table['date'] = pd.Timestamp.today().strftime('%m-%d-%Y')
  

Затем я добавил еще несколько строк кода, чтобы изменить таблицу фреймов panda в соответствии с вашими потребностями.

 idxs = table['date'].index
for i,val in enumerate(idxs):
    table.at[ val , table['date'][i] ] = table['points'][i]
table = table.drop([ 'date', 'points' ], axis = 1)
  

В приведенном выше фрагменте я использую фреймы pandas для присвоения значений с использованием индексов. Итак, сначала я получаю значения индекса для столбца даты, затем просматриваю каждый из них, чтобы добавить столбец для требуемой даты (значения из столбца даты) и получить соответствующие баллы в соответствии с индексами, которые мы извлекли ранее

Это дает мне следующий результат:

 name    10-24-2020
Dennis  52570.0
Dinh    40930.0
Sophia  26053.0
Mayumi  25300.0
Goran   24689.0
Robert T    19843.0
Allan M 19768.0
Bernard Koodo   14404.0
nim4165 13629.0
Timo Tuokkola   11216.0
rikkster    7338.0
David AKU   5774.0
Ranjan Koodo    4506.0
BobTheElectrician   4170.0
Helen Koodo 3370.0
Mihaela Koodo   2764.0
Fred C  2542.0
Philosoraptor   2122.0
Paul Deschamps  1973.0
Emilia Koodo    1755.0
  

Затем я могу сохранить это в csv, используя последнюю строку из вашего кода. Аналогично, вы можете извлекать данные для большего количества дат и форматировать их, чтобы добавить их в тот же фрейм panda

 table.to_csv('products.csv', index=True, encoding='utf-8')
  

Комментарии:

1. Я не уверен, что понимаю, как добавить больше данных. Если я запускаю скрипт, данные из последнего запуска перезаписываются

2. Вам нужно будет выполнить pandaframename.append(newdataframe). Возможно, вы каждый раз переопределяете одну и ту же переменную, что приводит к потере старых извлеченных данных. Сбросьте данные в csv в конце кода, сначала создайте весь необходимый фрейм panda.

3. Не уверен, что я понимаю, что вы пытаетесь сказать