Загрузка данных
import os
import re
import glob
import pandas as pd
# ================= НАСТРОЙКИ =================
MAIN_FILE = 'result.xlsx' # Ваша основная таблица
DUMP_FOLDER = './выгрузка' # Папка с 40+ файлами выгрузки
OUTPUT_FILE = 'result_updated.xlsx' # Итоговый файл
# Если нужно добавлять из выгрузки ТОЛЬКО физкультуру — оставьте True.
# Если нужно добавить вообще ВСЕ недостающие предметы (заочников и очно-заочников) из 40 файлов — поставьте False.
ONLY_PE_FROM_DUMPS = False
# =============================================
def find_col(columns, keywords):
"""Находит точное название столбца по ключевым словам"""
for col in columns:
col_low = str(col).lower().strip()
if any(k in col_low for k in keywords):
return col
return None
def clean_str(val):
"""Очищает строку для точного сравнения (убирает лишние пробелы и невидимые символы)"""
if pd.isna(val) or str(val).strip().lower() in ['nan', 'none', '']:
return ''
return re.sub(r'\s+', ' ', str(val).strip()).lower()
def process_dataframe(df, cols_map, only_pe_explode=False):
"""Разбивает группы, правит факультеты, проставляет форму обучения и кафедру"""
if df.empty:
return df
c_inst = cols_map['inst']
c_grp = cols_map['grp']
c_form = cols_map['form']
c_lvl = cols_map['lvl']
c_subj = cols_map['subj']
c_teach = cols_map['teach']
c_dept = cols_map['dept']
# 1. Разбиваем группы через "|" на отдельные строки
if c_grp and c_grp in df.columns:
if only_pe_explode and c_subj in df.columns:
# В основной таблице разбиваем "|" только у физкультуры (или везде, где есть "|")
mask_pipe = df[c_grp].astype(str).str.contains(r'\|', na=False)
df.loc[mask_pipe, c_grp] = df.loc[mask_pipe, c_grp].astype(str).str.split('|')
df = df.explode(c_grp).reset_index(drop=True)
else:
df[c_grp] = df[c_grp].astype(str).str.split('|')
df = df.explode(c_grp).reset_index(drop=True)
df[c_grp] = df[c_grp].astype(str).str.strip()
# 2. Меняем ИГРИК -> ФГРИК и ставим ПШ ИД для 261-1011, 264-1011
if c_inst and c_inst in df.columns:
df[c_inst] = df[c_inst].replace({'ИГРИК': 'ФГРИК'})
if c_grp and c_grp in df.columns:
mask_pshid = df[c_grp].isin(['261-1011', '264-1011'])
df.loc[mask_pshid, c_inst] = 'ПШ ИД'
# 3. Определяем Форму обучения и Уровень образования по 3-й цифре группы (256-333 -> 6)
code_map = {
'1': ('Очная', 'Бакалавриат'),
'2': ('Очно-заочная', 'Бакалавриат'),
'3': ('Заочная', 'Бакалавриат'),
'4': ('Очная', 'Магистратура'),
'5': ('Очно-заочная', 'Магистратура'),
'6': ('Заочная', 'Магистратура')
}
def apply_group_logic(row):
grp = str(row[c_grp]).strip() if c_grp else ''
prefix = grp.split('-')[0]
if len(prefix) >= 3 and prefix[:3].isdigit():
third_digit = prefix[2]
if third_digit in code_map:
if c_form:
row[c_form] = code_map[third_digit][0]
if c_lvl:
row[c_lvl] = code_map[third_digit][1]
# 4. Если столбец "Кафедра" пустой, но в "Преподаватель" есть "(ИМП)" -> вытаскиваем кафедру
if c_teach and c_dept and c_teach in row and c_dept in row:
dept_val = str(row[c_dept]).strip()
teach_val = str(row[c_teach]).strip()
if (pd.isna(row[c_dept]) or dept_val in ['', 'nan', 'None']) and '(' in teach_val and ')' in teach_val:
match = re.search(r'\(([^)]+)\)\s*$', teach_val)
if match:
row[c_dept] = match.group(1).strip()
return row
df = df.apply(apply_group_logic, axis=1)
return df
# --- ШАГ 1. Читаем все листы вашей основной таблицы ---
if not os.path.exists(MAIN_FILE):
raise FileNotFoundError(f"Файл {MAIN_FILE} не найден!")
print(f"Загрузка файла {MAIN_FILE}...")
all_main_sheets = pd.read_excel(MAIN_FILE, sheet_name=None)
# Берем первый (или активный) лист как основной рабочий
target_sheet_name = list(all_main_sheets.keys())[0]
main_df = all_main_sheets[target_sheet_name].copy()
main_df.columns = main_df.columns.astype(str).str.strip()
print(f"Рабочий лист: '{target_sheet_name}', исходных строк: {len(main_df)}")
# Определяем столбцы по вашей фотографии
cols_map = {
'inst': find_col(main_df.columns, ['факультет', 'институт']),
'grp': find_col(main_df.columns, ['групп']),
'form': find_col(main_df.columns, ['форма']),
'lvl': find_col(main_df.columns, ['уровень']),
'subj': find_col(main_df.columns, ['наименование', 'дисциплин', 'предмет']),
'teach': find_col(main_df.columns, ['преподавател', 'фио', 'ппс']),
'dept': find_col(main_df.columns, ['кафедр'])
}
print("Сопоставлены столбцы:", {k: v for k, v in cols_map.items() if v})
# Обрабатываем основную таблицу (разбиваем '|', правим ИГРИК и ПШ ИД, НИЧЕГО НЕ УДАЛЯЯ)
main_df = process_dataframe(main_df, cols_map, only_pe_explode=True)
print(f"Строк в основной таблице после разделения групп через '|': {len(main_df)}")
# Автоматический справочник: Группа -> Институт (чтобы заполнять институт, если в выгрузке он пустой)
group_to_inst = {}
if cols_map['grp'] and cols_map['inst']:
for _, r in main_df[[cols_map['grp'], cols_map['inst']]].dropna().iterrows():
g_val = str(r[cols_map['grp']]).strip()
i_val = str(r[cols_map['inst']]).strip()
if g_val and i_val and i_val != 'nan':
group_to_inst[g_val] = i_val
# --- ШАГ 2. Собираем данные из 40+ файлов выгрузки ---
dump_files = glob.glob(os.path.join(DUMP_FOLDER, '*.xls*'))
print(f"\nНайдено файлов в папке '{DUMP_FOLDER}': {len(dump_files)}")
new_rows_list = []
for fpath in dump_files:
if os.path.abspath(fpath) == os.path.abspath(MAIN_FILE) or '~$' in fpath:
continue
try:
xls = pd.read_excel(fpath, sheet_name=None, header=None)
for s_name, raw_df in xls.items():
if raw_df.empty:
continue
# Ищем строку с шапкой таблицы (первые 20 строк)
header_idx = None
for idx, row in raw_df.head(20).iterrows():
row_text = ' '.join(row.dropna().astype(str)).lower()
if 'групп' in row_text and any(w in row_text for w in ['дисциплин', 'предмет', 'наименование']):
header_idx = idx
break
if header_idx is None:
continue
df_s = raw_df.iloc[header_idx + 1:].copy()
df_s.columns = raw_df.iloc[header_idx].astype(str).str.strip()
# Ищем столбцы в текущем файле выгрузки
s_inst = find_col(df_s.columns, ['факультет', 'институт'])
s_grp = find_col(df_s.columns, ['групп'])
s_form = find_col(df_s.columns, ['форма'])
s_lvl = find_col(df_s.columns, ['уровень'])
s_subj = find_col(df_s.columns, ['наименование', 'дисциплин', 'предмет'])
s_teach = find_col(df_s.columns, ['преподавател', 'фио', 'ппс'])
s_dept = find_col(df_s.columns, ['кафедр'])
if not s_grp or not s_subj:
continue
# Переименовываем столбцы под формат вашей основной таблицы
rename_map = {}
if s_inst and cols_map['inst']: rename_map[s_inst] = cols_map['inst']
if s_grp and cols_map['grp']: rename_map[s_grp] = cols_map['grp']
if s_form and cols_map['form']: rename_map[s_form] = cols_map['form']
if s_lvl and cols_map['lvl']: rename_map[s_lvl] = cols_map['lvl']
if s_subj and cols_map['subj']: rename_map[s_subj] = cols_map['subj']
if s_teach and cols_map['teach']: rename_map[s_teach] = cols_map['teach']
if s_dept and cols_map['dept']: rename_map[s_dept] = cols_map['dept']
df_s = df_s.rename(columns=rename_map)
# Убираем пустые строки без группы или дисциплины
df_s = df_s.dropna(subset=[cols_map['grp'], cols_map['subj']], how='any')
if df_s.empty:
continue
# Если включен фильтр только на физкультуру
if ONLY_PE_FROM_DUMPS:
mask_pe = df_s[cols_map['subj']].astype(str).str.lower().str.contains('физическая культура', na=False)
df_s = df_s[mask_pe].copy()
if df_s.empty:
continue
# Добавляем недостающие столбцы из главной таблицы
for c in main_df.columns:
if c not in df_s.columns:
df_s[c] = None
df_s = df_s[main_df.columns].copy()
new_rows_list.append(df_s)
except Exception as e:
print(f"Ошибка при чтении {os.path.basename(fpath)}: {e}")
# --- ШАГ 3. Проверка на дубликаты и добавление ТОЛЬКО новых строк ---
if new_rows_list:
dump_df = pd.concat(new_rows_list, ignore_index=True)
dump_df = process_dataframe(dump_df, cols_map, only_pe_explode=False)
# Подтягиваем институт по номеру группы, если в файле выгрузки не было столбца "Институт"
if cols_map['inst'] and cols_map['grp']:
def fill_missing_inst(row):
inst_val = str(row[cols_map['inst']]).strip()
grp_val = str(row[cols_map['grp']]).strip()
if (pd.isna(row[cols_map['inst']]) or inst_val in ['', 'nan', 'None']) and grp_val in group_to_inst:
row[cols_map['inst']] = group_to_inst[grp_val]
return row
dump_df = dump_df.apply(fill_missing_inst, axis=1)
# Создаем уникальный ключ для проверки: Группа + Дисциплина + Преподаватель
def make_key(df_in):
g = df_in[cols_map['grp']].apply(clean_str) if cols_map['grp'] else ''
s = df_in[cols_map['subj']].apply(clean_str) if cols_map['subj'] else ''
t = df_in[cols_map['teach']].apply(clean_str) if cols_map['teach'] and cols_map['teach'] in df_in else ''
return g + "||" + s + "||" + t
main_df['_check_key'] = make_key(main_df)
dump_df['_check_key'] = make_key(dump_df)
# Убираем пустые группы и дубликаты внутри самой выгрузки
dump_df = dump_df[dump_df[cols_map['grp']].apply(clean_str) != '']
dump_df = dump_df.drop_duplicates(subset=['_check_key'])
# СТРОГАЯ ПРОВЕРКА: оставляем только те строки, которых НЕТ в main_df
existing_keys = set(main_df['_check_key'])
rows_to_append = dump_df[~dump_df['_check_key'].isin(existing_keys)].copy()
# Удаляем технический столбец ключа
main_df.drop(columns=['_check_key'], inplace=True)
rows_to_append.drop(columns=['_check_key'], inplace=True)
print(f"\nНайдено абсолютно новых строк для добавления: {len(rows_to_append)}")
# Дописываем новые строки в конец вашей таблицы
final_df = pd.concat([main_df, rows_to_append], ignore_index=True)
else:
print("\nВ папке выгрузки не найдено подходящих таблиц.")
final_df = main_df
# --- ШАГ 4. Сохраняем результат (со всеми остальными вкладками) ---
all_main_sheets[target_sheet_name] = final_df
with pd.ExcelWriter(OUTPUT_FILE, engine='openpyxl') as writer:
for s_name, s_df in all_main_sheets.items():
s_df.to_excel(writer, sheet_name=s_name, index=False)
print(f"Готово! Итоговое количество строк на листе '{target_sheet_name}': {len(final_df)}")
print(f"Файл сохранен как: {OUTPUT_FILE}")