#python #pandas #dataframe #web-scraping
#python #панды #фрейм данных #очистка веб-страниц
Вопрос:
Я просто начну с нуля, так как чувствую, что потерялся во всех разных возможностях. Я буду говорить о таблице лидеров, но это может быть применимо и к отслеживанию цен.
Моя цель — очистить данные с веб-сайта (таблица лидеров за все время / скрытая), поместить их в файл .csv и обновлять его ежедневно в полдень.
Что мне удалось до сих пор: очистка данных.
- Пробовал выполнять очистку с помощью BS4, но поскольку данные скрыты, я не мог быть достаточно конкретным, чтобы получить только рекордные баллы. Я считаю, что это успех, потому что я могу получить таблицу со всеми необходимыми данными и датой в качестве заголовка. Моя проблема с этим решением заключается в том, что 1) данные, заполняющие csv, являются бесполезными 2) таблица вертикальная, а не горизонтальная
- Очищенные данные с помощью селектора CSS, но я отказался от этой идеи, потому что иногда страница не загружается, и данные не были удалены. Обнаружил, что есть файл json, содержащий данные сразу
- Очистка 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. Не уверен, что я понимаю, что вы пытаетесь сказать