Анализ большого файла Excel в MERN вызывает ошибку «Куча из стека». Должен ли я преобразовать его в Python?Python

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Анализ большого файла Excel в MERN вызывает ошибку «Куча из стека». Должен ли я преобразовать его в Python?

Сообщение Anonymous »

У меня есть работающее приложение MERN, которое анализирует файл Excel и вставляет его в MongoDB. Он использует обещания и массовую вставку. Однако ему необходимо читать большие файлы: когда он читает менее 200 000 строк, он завершается более 7 минут. Однако при содержании более 200 000 строк произойдет сбой при чтении файла (недостаточно памяти в куче), даже не вставив его в базу данных. Проверив загрузку, он задействовал 100% процессора и около 60% памяти.
Вот моя функция MERN, она возвращает текстовый файл с записями строк, которые не были успешно вставлены в БД:

Код: Выделить всё

...
import fs from 'fs';
import { join } from 'path';
import path from 'path';
import ExcelJS from 'exceljs';
...
const parseExcelFile = async (req, res) => {
const { fileName } = req.params;
const filePath = path.join(directoryPath, fileName);
const discrepancies = []; // holder for errors in parsing row
let totalUpsertedCount = 0;

const upsertEntries = async (entries) => {
for (let i = 0; i < entries.length; i += BATCH_SIZE) {
const batch = entries.slice(i, i + BATCH_SIZE);
const operations = await Promise.all(batch.map(async (entry) => {
const existingRecord = await Respondent.findOne({ IDNumber: entry.IDNumber });

if (existingRecord) {
const fieldsToCheck = ['regionName', 'provinceName', 'municipalityName', 'municipalityId', 'barangayName'];
let parentGroup = existingRecord.parentGroup;

for (const field of fieldsToCheck) {
if (existingRecord[field] !== entry[field]) {
parentGroup = null;
break;
}
}

entry.parentGroup = parentGroup;
}

return {
updateOne: {
filter: { IDNumber: entry.IDNumber },
update: { $set: entry },
upsert: true
}
};
}));

try {
const result = await Respondent.bulkWrite(operations);
totalUpsertedCount += result.upsertedCount + result.modifiedCount;
} catch (error) {
console.error('Error performing upsert:', error);
}
}
};

try {
const workbook = new ExcelJS.Workbook();
const stream = fs.createReadStream(filePath);
await workbook.xlsx.read(stream);
const worksheet = workbook.getWorksheet(1);
let validEntries = [];
let rowPromises = [];

worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
if (rowNumber !== 1) {
const rowPromise = new Promise(async (resolve, reject) => {
const rowValues = row.values;
const entry = {
regionName: rowValues[1],
provinceName: rowValues[2],
municipalityName: rowValues[3],
barangayName: rowValues[4],
purokName: rowValues[5] ? rowValues[5].trim() : '',
IDNumber: rowValues[6],
...
};

try {
const locationMatch = await Location.findOne({
regionName: new RegExp('^' + entry.regionName + '$', 'i'),
provinceName: new RegExp('^' + entry.provinceName + '$', 'i'),
municipalityName: new RegExp('^' + entry.municipalityName + '$', 'i'),
barangayName: new RegExp('^' + entry.barangayName + '$', 'i')
});

if (!locationMatch) {
discrepancies.push({
lineNumber: rowNumber,
IDNumber: entry.IDNumber,
regionName: entry.regionName,
provinceName: entry.provinceName,
municipalityName: entry.municipalityName,
barangayName: entry.barangayName
});
resolve('Discrepancy');
} else {
entry.regionName = locationMatch.regionName;
entry.provinceName = locationMatch.provinceName;
entry.municipalityName = locationMatch.municipalityName;
entry.municipalityId = locationMatch.municipalityId;
entry.barangayName = locationMatch.barangayName;

validEntries.push(entry);
resolve('Valid Entry');
}
} catch (error) {
reject(error);
}
});

rowPromises.push(rowPromise);
}
});

await Promise.all(rowPromises);
await upsertEntries(validEntries);

// Sort discrepancies by lineNumber
discrepancies.sort((a, b) => a.lineNumber - b.lineNumber);

// Create discrepancy file content
const discrepanciesContent = discrepancies.map(d =>  `Line ${d.lineNumber}, Household Number: ${d.IDNumber}, Region: ${d.regionName}, Province: ${d.provinceName}, Municipality: ${d.municipalityName}, Barangay: ${d.barangayName}`).join('\n');
...

res.status(200).send(discrepanciesContent);
} catch (error) {
res.status(500).send({
message: "Failed to parse Excel file",
error: error.message
});
}
};
Я попробовал Microsoft CoPilot преобразовать функцию в скрипт Python. Каким-то образом он преобразовал его, но он работал около 45 минут с тем же файлом, содержащим менее 200 000 строк. Однако ресурсы, которые он использует, очень малы: 10 % для ЦП и 40 % для памяти.
Вот скрипт Python с отредактированными изменениями

Код: Выделить всё

import os
import pandas as pd
from pymongo import MongoClient, UpdateOne
from concurrent.futures import ThreadPoolExecutor
from dotenv import load_dotenv
import sys
import time

def to_title_case(value):
if isinstance(value, str):
return value.title()
return value

def process_entries(entries, batch_size):
operations = []
for entry in entries:
existing_record = db.respondents.find_one({"IDNumber": entry['IDNumber']})
if existing_record:
fields_to_check = ['regionName', 'provinceName', 'municipalityName', 'municipalityId', 'barangayName']
parent_group = existing_record.get('parentGroup')
for field in fields_to_check:
if existing_record.get(field) != entry[field]:
parent_group = None
break
entry['parentGroup'] = parent_group

operations.append(UpdateOne(
{'IDNumber': entry['IDNumber']},
{'$set': entry},
upsert=True
))

if operations:
result = db.respondents.bulk_write(operations)
print(f"\rInserted {result.upserted_count} and updated {result.modified_count} documents")  # Status update
return result.upserted_count + result.modified_count
return 0

def parse_excel_file(file_path, batch_size):
discrepancies = []
total_upserted_count = 0

# Check if the file exists
if not os.path.exists(file_path):
print(f"File not found: {file_path}")
return

df = pd.read_excel(file_path)
valid_entries = []

for index, row in df.iterrows():
sys.stdout.write(f"\rProcessing row {index + 2}")  # Status update
sys.stdout.flush()
entry = {
'regionName': row.iloc[0],
'provinceName': row.iloc[1],
'municipalityName': row.iloc[2],
'barangayName': row.iloc[3],
'purokName': row.iloc[4].strip() if pd.notna(row.iloc[4]) else '',
'ID': row.iloc[5],
'firstname': to_title_case(row.iloc[6]),
...
}

location_match = db.locations.find_one({
'regionName': {'$regex': f'^{entry["regionName"]}$', '$options': 'i'},
'provinceName': {'$regex': f'^{entry["provinceName"]}$', '$options': 'i'},
'municipalityName': {'$regex': f'^{entry["municipalityName"]}$', '$options': 'i'},
'barangayName': {'$regex': f'^{entry["barangayName"]}$', '$options': 'i'}
})

if not location_match:
discrepancies.append({
'lineNumber': index + 2,
'IDNumber': entry['IDNumber'],
'regionName': entry['regionName'],
'provinceName': entry['provinceName'],
'municipalityName': entry['municipalityName'],
'barangayName': entry['barangayName']
})
else:
entry['regionName'] = location_match['regionName']
entry['provinceName'] = location_match['provinceName']
entry['municipalityName'] = location_match['municipalityName']
entry['municipalityId'] = location_match['municipalityId']
entry['barangayName'] = location_match['barangayName']
valid_entries.append(entry)

print(f"\nProcessed {len(valid_entries)} valid entries")  # Status update

with ThreadPoolExecutor() as executor:
future_to_entry = {executor.submit(process_entries, valid_entries[i:i + batch_size], batch_size): i for i in range(0, len(valid_entries), batch_size)}
for future in future_to_entry:
try:
total_upserted_count += future.result()
except Exception as e:
print(f"Error processing batch {future_to_entry[future]}: {e}")

discrepancies.sort(key=lambda x: x['lineNumber'])

with open(os.path.join(BENEFICIARIES_DIRECTORY, f'{file_name}_discrepancy.txt'), 'w') as f:
for discrepancy in discrepancies:
f.write(f"Line {discrepancy['lineNumber']}, ID Number: {discrepancy['IDNumber']}, Region: {discrepancy['regionName']}, Province: {discrepancy['provinceName']}, Municipality:  {discrepancy['municipalityName']}, Barangay: {discrepancy['barangayName']}\n")

print(f'Inserted {total_upserted_count} respondents. Check {file_name}_discrepancy.txt for rows with issues.')

def connect_to_mongo(env_file):
load_dotenv(env_file)  # Load environment variables from the specified file

mongodb_uri = os.getenv('MONGODB_URI')
if not mongodb_uri:
print("MONGODB_URI not found in the .env file")
return False

try:
client = MongoClient(mongodb_uri)
db_name = mongodb_uri.split('/')[-1]
global db
db = client[db_name]
print(f"Connected to DB: {db_name}")  # Debug print
# The 'server_info' method will throw an exception if the connection is not successful
client.server_info()
print("Successfully connected to MongoDB")
return True
except Exception as e:
print(f"Failed to connect to MongoDB: {e}")
return False

if __name__ == "__main__":
import argparse

parser = argparse.ArgumentParser(description='Parse Excel file and upsert data to MongoDB')
parser.add_argument('env_file', type=str, help='Path to the .env file')
parser.add_argument('excel_file', type=str, help='Path to the Excel file to be parsed')
parser.add_argument('--batch-size', type=int, default=1000, help='Batch size for upsert operations')
args = parser.parse_args()

load_dotenv(args.env_file)
...
file_name = os.path.basename(args.excel_file)
file_path = os.path.join(BENEFICIARIES_DIRECTORY, file_name)
print(f"Full path of the Excel file: {file_path}")  # Debug print
start_time = time.time() # Start the timer

if connect_to_mongo(args.env_file):
parse_excel_file(file_path, args.batch_size)
end_time = time.time() # End the timer
elapsed_time = end_time - start_time
hours, rem = divmod(elapsed_time, 3600)
minutes, seconds = divmod(rem, 60)
print(f"\nProcessing completed in {int(hours)} hours, {int(minutes)} minutes, and {int(seconds)} seconds")
print("Disconnected from MongoDB")

Можете ли вы просмотреть мою функцию Javascript и сценарий Python, чтобы узнать, можно ли их улучшить? Моя проблема с исходным Javascript заключается в нехватке памяти в куче, и он может обрабатывать только менее 200 000 строк, а сценарий Python слишком медленный. Может ли Python использовать больше ресурсов, чтобы работать быстрее?
Дополнение:
Файл Excel уже находится на сервере. Поскольку он большой, необходимость его загрузки приведет к тайм-ауту соединения. Функция Javascript имеет исполняемую версию Javascript, которую использовал CoPilot и преобразовал ее в скрипт Python

Подробнее здесь: https://stackoverflow.com/questions/792 ... -i-convert
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Анализ большого файла Excel в MERN вызывает ошибку «Куча из стека». Должен ли я преобразовать его в Python?
    Anonymous » » в форуме Python
    0 Ответы
    15 Просмотры
    Последнее сообщение Anonymous
  • ОЗУ, куча и память стека для платы STM32
    Anonymous » » в форуме C++
    0 Ответы
    3 Просмотры
    Последнее сообщение Anonymous
  • ОЗУ, куча и память стека для платы STM32
    Anonymous » » в форуме C++
    0 Ответы
    2 Просмотры
    Последнее сообщение Anonymous
  • Почему моя реализация рекурсивной POW (x, n) вызывает переполнение стека для большого N? [дублировать]
    Anonymous » » в форуме C++
    0 Ответы
    3 Просмотры
    Последнее сообщение Anonymous
  • Интеграция линейной регрессии в стек-приложение MERN: Python или JavaScript? [закрыто]
    Гость » » в форуме Python
    0 Ответы
    33 Просмотры
    Последнее сообщение Гость

Вернуться в «Python»