Парсинг json файла в excel таблицу python
Есть json файл, у каждой записи есть свой id. В каждой записи есть много title (много title принадлежат одному id). Как делать так, чтобы к одному id цеплять все его title из json файла. Данные должны отобразиться в excel таблице.
Есть код который может заносить в excel таблицу id или title отдельно. Не совсем понимаю как это делается
import ijson
import xlsxwriter
from collections import defaultdict
//Создаю excel файл
workbook = xlsxwriter.Workbook('main.xlsx')
worksheet = workbook.add_worksheet()
//Даю имена двум столбцам
worksheet.write(0, 0, 'ID')
worksheet.write(0, 1, 'title')
//Читаю json файл
f = open("sanya.json", 'r', encoding="utf8")
//Произвожу запись id из json файла в excel
'''
i = 1
for n in ijson.items(f, "item._id.$oid"):
worksheet.write(i, 0, n)
i += 1
'''
//Произвожу запись title из json файла в excel
j = 1
for k in ijson.items(f, "item.methods.item.subcategories.item.title"):
worksheet.write(j, 1, k)
j +=1
workbook.close()
Как уже написал, сейчас могу заносить либо только id, либо только title, а хотелось все и сразу
Как пример сейчас вывожу title, и 10 из них принадлежат одному id.

Код ошибки для первой ревизии ответа, где join ругался на уже существующие колонки:
Traceback (most recent call last):
File "C:\Users\ROG\Desktop\new_script_python\main.py", line 12, in <module>
df = df.join(df_x[['name_title', 'name_recordId']].rename(columns={'name_title': 'title'}))
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\frame.py", line 9734, in join
return self._join_compat(
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\frame.py", line 9773, in _join_compat
return merge(
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\reshape\merge.py", line 158, in merge
return op.get_result(copy=copy)
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\reshape\merge.py", line 807, in get_result
result = self._reindex_and_concat(
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\reshape\merge.py", line 759, in _reindex_and_concat
llabels, rlabels = _items_overlap_with_suffix(
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\core\reshape\merge.py", line 2572, in _items_overlap_with_suffix
raise ValueError(f"columns overlap but no suffix specified: {to_rename}")
ValueError: columns overlap but no suffix specified: Index(['title', 'name_recordId'], dtype='object')
Переход между id в json файле
...
"name": "Муниципальный земельный контроль",
"recordId": "0af4cd2e-78cb-109b-8178-e981366403f4",
"status": "NOT_MODERATION_PUBLISHED",
"slug": "2307"
},{
"_id": {
"$oid": "61c49773d06bd238714e7805"
},...
Код новой ошибки
Traceback (most recent call last):
File "C:\Users\ROG\Desktop\new_script_python\main.py", line 8, in <module>
df_x = pd.json_normalize(row.methods, record_path=['subcategories'], record_prefix='name_')
File "C:\Users\ROG\Desktop\new_script_python\venv\lib\site-packages\pandas\io\json\_normalize.py", line 447, in json_normalize
raise NotImplementedError
NotImplementedError
Ответы (2 шт):
import ijson
import xlsxwriter
from collections import defaultdict
# Создаю excel файл
workbook = xlsxwriter.Workbook('main.xlsx')
worksheet = workbook.add_worksheet()
# Даю имена двум столбцам
worksheet.write(0, 0, 'ID')
worksheet.write(0, 1, 'title')
# Функция для записи titles, используя рекурсию
def write_titles(id, titles, row):
if not titles:
worksheet.write(row, 0, id)
worksheet.write(row, 1, '')
return row
title = titles[0]
worksheet.write(row, 0, id)
worksheet.write(row, 1, title)
row += 1
return write_titles(id, titles[1:], row)
# Читаю json файл
with open("sanya.json", 'r', encoding="utf8") as f:
id_to_titles = defaultdict(list)
for item in ijson.items(f, "item"):
if not isinstance(item, dict):
continue
id = item.get('_id', {}).get('$oid')
if not id:
continue
titles = []
for method in item.get('methods', []):
if not isinstance(method, dict):
continue
subcategories = method.get('subcategories', [])
for subcategory in subcategories:
if not isinstance(subcategory, dict):
continue
title = subcategory.get('title')
if title:
titles.append(title)
id_to_titles[id].extend(titles)
# Произвожу запись id и titles из json файла в excel, используя функцию write_titles
row = 1
for id, titles in id_to_titles.items():
row = write_titles(id, titles, row)
# Закрываем excel файл
workbook.close()
Задача решается буквально в несколько строк с pandas.
Получим первичную табличку через pd.json_normalize(), прекрасной функцией для работы c json данными и преобразованием их в табличный формат.
import json
import pandas as pd
with open('sanya.json', 'r') as f:
data = json.load(f)
df = pd.json_normalize(data)
print(df)
Вывод:
methods name recordId status slug _id.$oid
0 [{'category': ['Профилактические мероприятия']... Муниципальный контроль в сфере благоустройства 0af4cd2e-78cb-109b-8178-e98136ad03fc NOT_MODERATION_PUBLISHED 2311 61c49773d06bd238714e7809
В целом можно сказать, что результат чудесен. Ну правда неплох, обработалось вложение _id ⇛ $oid, всё остальное у нас находится на первом уровне, кроме methods. Там творится полный бардак, очень много списков, вложенных словарей и т.д. Раскрывать это даже в рекурсии мартышкин труд, но в целом можно сделать из этого реляционную БД, желательно не в Excel.
Ладно, если я правильно понял ваш скриншот в вопросе, вы хотите забрать первый уровень, а также title и record_id из subcategories, которому вы почему-то поставили префикс name_.
Будем придерживаться данной концепции, поехали:
df.set_index('_id.$oid'):
Ставим ID на место индекса, по не нему потом будем делать join.df[['methods']].iterrows()цикл:
Идём по всем строкам по колонке methods, ведь их у вас в json будет больше, и:берём
row.methods, колонка из которой надо достать наш бардак, сразу идём вsubcategoriesт.к. искомые колонки находятся там и делаем префикс name_ как на скриншоте. Т.к. колонкиrecordIdповторяются, префикс крайне необходим.ставим индекс из
iterrows(), т.е. строчка с которой работаем в цикледелаем
.join()по индексу, тут не надо переживать, что мы добавим в искомыйdfновые строчки и несколько раз по ним пройдёмся, нет,iterrows()создаёт копию в памяти, так что на нём изменения не отразятся
Тут же меняем titile с перфиксом на обычный.дальше
ifи то что я изначально забыл, мы обсудили в комментариях. Для первого разаjoinотработает отлично, для последующих колонки уже будут вdf, новые добавятся с суффиксом, чтобы не было конфликта.если лишние есть (второй и последующие разы), собираем всё в одну колонку и убираем созданные лишние
elseесли проходимся первый раз, то уберём изdfколонку methods, чтобы немного сэкономить память. Как мы помним она так и так останется вiterrows
Делаем фильтр по колонкам, тем самым убираем ненужные и сортируем
Пишем в excel.
df = df.set_index('_id.$oid')
for ix, row in df[['methods']].iterrows():
df_x = pd.json_normalize(row.methods, record_path=['subcategories'], record_prefix='name_')
df_x.index = [ix] * len(df_x)
df = df.join(df_x[['name_title', 'name_recordId']].rename(columns={'name_title': 'title'}), rsuffix='_right')
if 'title_right' in df.columns:
for i in range(2):
df.iloc[:,-4+i] = df.iloc[:,-4+i].fillna(df.iloc[:,-2+i])
df = df.iloc[:,:-2]
else:
df = df.drop(columns=['methods'])
df = df[['title', 'recordId', 'name', 'name_recordId', 'status', 'slug']]
df.index.names = ['ID']
df.to_excel("main.xlsx")
