大学で担当している講義のレポートや出欠状況、授業態度などを評価用のエクセルファイルにまとめるためのスクリプトを作った。
- 出欠状況:Sclassから出力できるcsvファイル
- 授業態度:Mentimeterの回答状況をダウンロード。学籍番号をtxtファイルに貼り付け
- レポート提出状況:Solaからレポートの一括ダウンロード。回答してr01のように変更
上記のデータを使って評価用のエクセルシートに情報をまとめる。
├── mentimeter
│ ├── m01.txt
│ └── m02.txt
├── report
│ ├── r01
│ └── r02
├── report.py
├── 基本情報処理2-2024評価.xlsx
├── sclass.csv
└── 評価バックアップ
├── sample_20240728_113740.xlsx
├── sample_20240728_114521.xlsx
├── sample_20240728_114817.xlsx
├── sample_20240728_115100.xlsx
└── sample_20240821_133018.xlsx必要なデータをダウンロードし、所定の場所に保存。
コマンドライン引数で処理したい講義の回を指定。1回目の講義の処理をしたければ01とする。
python report.py 01で実行すると、
import openpyxl
import jaconv
import re
import os
import shutil
import csv
from datetime import datetime
import argparse
# コマンドライン引数の処理
parser = argparse.ArgumentParser(description="授業回数の指定を行います")
parser.add_argument('lesson_num', type=str, help="処理を行う講義の回数(例: '01', '02'など)")
args = parser.parse_args()
# 授業回数を引数から取得
lesson_num = args.lesson_num
# パス設定
excel_file_path = '基本情報処理2-2024評価.xlsx' # 講義名簿ファイル
sclass_file_path = 'sclass.csv' # Sclassの出席データファイル:ShiftJISからUTF8に変換して保存すること!
mentimeter_file_path = f'./mentimeter/m{lesson_num}.txt' # Mentimeterからダウンロードしたファイルの場所
report_folder_path = f'./report/r{lesson_num}' # レポートファイルの場所
backup_folder_path = './評価バックアップ' # 処理前のバックアップ先
def get_report_check_dict(report_folder_path):
"""フォルダ名から学籍番号と氏名を抽出して辞書に保存"""
student_info = {}
try:
folders = os.listdir(report_folder_path)
except FileNotFoundError:
print(f"指定されたフォルダが見つかりません: {report_folder_path}")
return student_info
for folder in folders:
if len(folder) > 9:
student_id = folder[:7]
student_name = folder[8:].split('_')[0] # 9文字目以降
folder_path = os.path.join(report_folder_path, folder)
pdf_files = [file for file in os.listdir(folder_path) if file.endswith('.pdf')]
pdf_path = os.path.join(folder_path, pdf_files[0]) if len(pdf_files) == 1 else folder_path
student_info[student_id] = pdf_path
return student_info
def read_and_convert_text(input_file_path):
"""テキストを読み込み、全角文字を半角文字に、大文字に変換"""
try:
with open(input_file_path, 'r', encoding='utf-8') as file:
text = file.read()
except FileNotFoundError:
print(f"指定されたファイルが見つかりません: {input_file_path}")
return ""
return jaconv.z2h(text, kana=False, ascii=True, digit=True).upper()
def get_mentimeter_check_dict(input_file_path):
"""Mentimeterデータを正規表現でチェックし辞書に格納"""
converted_text = read_and_convert_text(input_file_path)
mentimeter_check_dict = {}
pattern = r'^T\d{6}$'
for line in converted_text.split('\n'):
if re.match(pattern, line.strip()):
mentimeter_check_dict[line.strip()] = line.strip()
elif line.strip():
print("エラー値:" + line.strip())
return mentimeter_check_dict
def get_target_column(ws, column_name):
"""Excelシートから指定した列名に一致する列を取得"""
for cell in ws[1]:
if cell.value == column_name:
return cell.column
raise ValueError(f"列名 '{column_name}' が見つかりませんでした。")
def write_attendance(ws, check_dict, target_column):
"""出欠情報をExcelに書き込む共通関数"""
for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
student_id = row[0].value
row[target_column-1].value = 1 if student_id in check_dict else 0
check_dict.pop(student_id, None)
def write_report(ws, student_info, target_column, link_column):
"""レポート情報をExcelに書き込む"""
for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
student_id = row[0].value
pdf_path = student_info.get(student_id)
if pdf_path:
symbol = "◎" if os.path.isdir(pdf_path) else "●"
row[target_column-1].value = symbol
row[target_column-1].hyperlink = pdf_path
row[link_column-1].value = symbol
row[link_column-1].hyperlink = pdf_path
del student_info[student_id]
else:
row[target_column-1].value = "_"
row[link_column-1].value = "_"
def create_sclass_check_dict(csv_file_path):
"""CSVファイルから出欠情報を辞書に格納"""
sclass_check_dict = {}
try:
with open(csv_file_path, mode='r', encoding='utf-8') as file:
reader = csv.reader(file)
for row in reader:
if row[12] == "出":
sclass_check_dict[row[4]] = row[5]
except FileNotFoundError:
print(f"指定されたファイルが見つかりません: {csv_file_path}")
return sclass_check_dict
def backup_excel_file(excel_file_path, backup_folder_path, max_backups=5):
"""Excelファイルをバックアップし、古いバックアップを削除"""
if not os.path.exists(backup_folder_path):
os.makedirs(backup_folder_path)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file_path = os.path.join(backup_folder_path, f'sample_{timestamp}.xlsx')
shutil.copy2(excel_file_path, backup_file_path)
backup_files = sorted(os.listdir(backup_folder_path), reverse=True)
for old_backup in backup_files[max_backups:]:
os.remove(os.path.join(backup_folder_path, old_backup))
def clear_data_from_column(ws, column_number):
"""指定された列番号のデータを削除"""
for row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=column_number, max_col=column_number):
for cell in row:
cell.value = None
# メイン処理
try:
# レポート・Mntimeter・Sclassのファイルを読み込み辞書に登録
report_check_dict = get_report_check_dict(report_folder_path)
mentimeter_check_dict = get_mentimeter_check_dict(mentimeter_file_path)
sclass_check_dict = create_sclass_check_dict(sclass_file_path)
# 書き込み用ブック・シートの準備
wb = openpyxl.load_workbook(excel_file_path)
ws = wb['Sheet1']
# 書き込み(mentimeter出欠)
target_column = get_target_column(ws, os.path.splitext(os.path.basename(mentimeter_file_path))[0])
clear_data_from_column(ws, target_column)
write_attendance(ws, mentimeter_check_dict, target_column)
# 書き込み(sclass出欠)
target_column = get_target_column(ws, f's{lesson_num}')
clear_data_from_column(ws, target_column)
write_attendance(ws, sclass_check_dict, target_column)
# 書き込み(レポート)
target_column = get_target_column(ws, os.path.basename(report_folder_path))
link_column = get_target_column(ws, 'LINK')
clear_data_from_column(ws, target_column)
clear_data_from_column(ws, link_column)
write_report(ws, report_check_dict, target_column, link_column)
# バックアップ作成と保存
backup_excel_file(excel_file_path, backup_folder_path)
wb.save(excel_file_path)
# エラーレポート
if mentimeter_check_dict:
print("未登録の学生:", mentimeter_check_dict)
else:
print("すべての学生が登録されています。")
print("処理完了")
except Exception as e:
print(f"エラーが発生しました: {e}")