教学相关工具

从超星导出压缩包提取某些类型文件,如.docx、.doc等文件

使用场景:如期末收取学生实习报告,从超星导出后进行处理,几秒钟就处理完毕,不再是几小时;也不再需要课代表统一收集整理,事后再整理。

源码

import os
import pathlib
import py7zr
import rarfile
import shutil
import tempfile
import zipfile

# 文件名计数器,在一个压缩包里可能有多个相同后缀的文档,解压后形成 学号-姓名.ext 学号-姓名-1.ext 等等
name_counter = {}


def main():
    # 请修改下面参数
    extract([], "2.zip", [".doc", ".docx"], "实习报告")


def extract(layers=[], filename=None, exts=[], dst=None, delete=False):
    """从指定的压缩文件中提取指定的文件类型。
    layers: list[str] 当前所处的层,即压缩包文件名列表
    filename: str 压缩包名
    exts: list[str] 文件类型列表
    dst: str 目标路径
    delete: 提取完毕后是否删除压缩包
    """

    print(filename)

    # 创建路径
    if dst and (not os.path.exists(dst)):
        os.mkdir(dst)

    # 添加到新的层,防止嵌套干扰
    newlayers = []
    for name in layers:
        newlayers.append(name)
    newlayers.append(filename)
    if filename.endswith(".7z"):
        extract_7z(newlayers, filename, exts, dst, delete)
    if filename.endswith(".rar"):
        extract_rar(newlayers, filename, exts, dst, delete)
    if filename.endswith(".zip"):
        extract_zip(newlayers, filename, exts, dst, delete)
    if delete:
        os.remove(filename)


# 获取文件名,如:"/path/test.txt" => "test.txt"
def get_name(filename):
    return pathlib.Path(filename).name


# 获取文件名主干,如:"/path/test.txt" => "test"
def get_stem(filename):
    return pathlib.Path(filename).stem


# 获取文件名后缀,如:"/path/test.txt" => ".txt"
def get_suffix(filename):
    return pathlib.Path(filename).suffix


# 处理文件列表,列表中所有文件已经解压
def process_files(temppath, layers, files, exts, dst, delete):
    for file in files:
        src = os.path.join(temppath, file)
        ext = get_suffix(file)
        if ext in exts:
            newname = get_name(file)
            if len(layers) >= 2:
                stem = get_stem(layers[1])
                newname = stem + ext
                if newname in name_counter:
                    count = name_counter[newname]
                    newname = stem + "-" + str(count) + ext
                    name_counter[newname] = count + 1
                else:
                    name_counter[newname] = 1
            if dst:
                shutil.copyfile(src, os.path.join(dst, newname))
            else:
                shutil.copyfile(src, newname)
            os.remove(src)
        if ext in [".7z", ".rar", ".zip"]:
            extract(layers, src, exts, dst, True)


def extract_7z(layers, filename, exts, dst, delete):
    files = []
    temppath = tempfile.TemporaryDirectory().name
    with py7zr.SevenZipFile(filename) as sz:
        for file in sz.getnames():
            ext = get_suffix(file)
            if ext in exts + [".7z", ".rar", ".zip"]:
                files.append(file)
        sz.extract(path=temppath, targets=files)
    process_files(temppath, layers, files, exts, dst, delete)


def extract_rar(layers, filename, exts, dst, delete):
    files = []
    temppath = tempfile.TemporaryDirectory().name
    with rarfile.RarFile(filename) as rf:
        for file in rf.namelist():
            ext = get_suffix(file)
            if ext in exts + [".7z", ".rar", ".zip"]:
                files.append(file)
                rf.extract(file, temppath)
    process_files(temppath, layers, files, exts, dst, delete)


def extract_zip(layers, filename, exts, dst, delete):
    files = []
    temppath = tempfile.TemporaryDirectory().name
    with zipfile.ZipFile(filename, allowZip64=True, metadata_encoding="gbk") as zf:
        for file in zf.namelist():
            ext = get_suffix(file)
            if ext in exts + [".7z", ".rar", ".zip"]:
                files.append(file)
                zf.extract(file, temppath)
    process_files(temppath, layers, files, exts, dst, delete)


if __name__ == "__main__":
    main()

删除无用目录,如android源码中的build、.idea、.gradle等目录

学生提交的作品中总有一些不需要的内容,用这个脚本方便处理。

源码

import os
import shutil


def main():
    # 请修改下面参数
    rmdirs(
        "/run/media/znw/Ventoy/刻盘/移动平台应用开发-2023计算机科学与及技术(专升本)2班-卓能文/7.实验报告资料/",
        [".git", ".idea", ".gradle", "build"],
    )


def rmdirs(top, deldirs):
    """
    删除指定目录下,部分子目录(可以嵌套在层次很深的子目录下)
    top: 根目录
    deldirs: 需要删除的目录列表
    """
    for root, dirs, files in os.walk(top, topdown=False):
        for name in dirs:
            if name in deldirs:
                print(root, "/", name, sep="")
                shutil.rmtree(os.path.join(root, name), ignore_errors=True)


if __name__ == "__main__":
    main()

对文件及目录统一命名

学生提交的文档无论怎么强调,收上来的名字五花八门。文件名中只要包含学生的学号或名字,统一命名成“学号-姓名.ext”形式。

源码

from os import path
import os
import pandas as pd


def main():
    # 请修改下面参数
    tidynames("data.xlsx", "源码")


def nametidy(data, root, filename):
    """
    目录或文件名标准化处理
    """
    dir = path.dirname(filename)
    base = path.basename(filename)
    name, ext = path.splitext(base)
    if "-" in name:
        parts = name.split("-")
        if len(parts) == 2:
            if parts[0] in data["学号"].values and parts[1] in data["姓名"].values:
                return

    for i, row in data.iterrows():
        if str(row["学号"]) in name or row["姓名"] in name:
            newname = f"{row['学号']}-{row['姓名']}{ext}"
            newfilename = path.join(root, dir, newname)
            os.rename(path.join(root, filename), newfilename)


def tidynames(datafilename, top):
    """
    从datafilename excel文件中导入学号、姓名,然后在top目录下遍历所有的文件或目录,只要文件名或目录名包含学号或姓名,统一修改成“学号-姓名”或“学号-姓名.ext”格式。
    datefilename: xlsx文件,有两列,包含“学号、姓名”
    top: 从指定的目录开始处理
    """
    data = pd.read_excel(datafilename)
    for root, dirs, files in os.walk(top, topdown=False):
        for name in files:
            nametidy(data, root, name)
        for name in dirs:
            nametidy(data, root, name)


if __name__ == "__main__":
    main()

对实验报告统一打分

再也不需要打开,编辑、保存了。

源码

import docx
import os
import pandas as pd

# 实验报告所在路径
base = "/run/media/znw/Ventoy/刻盘/数据可视化-2022级计算机科学与技术(专升本)5班-卓能文/9.实验资料/1.平时实验资料/实验1- ECharts数据可视化(一)"

data = pd.read_excel("data.xlsx")
for i, datarow in data.iterrows():
    path = str(datarow.学号) + "-" + datarow.姓名
    filename = base + "/" + path + "/" + path + ".docx"
    if os.path.exists(filename):
        try:
            doc = docx.Document(filename)
            for table in doc.tables:
                for row in table.rows:
                    for cell in row.cells:
                        if cell.text.startswith("成绩评定"):  # 检查单元格内容
                            cell.text = ""  # 清空单元格内容
                            cell.paragraphs[0].add_run("成绩评定:").bold = True
                            cell.paragraphs[0].add_run("\n" + str(datarow.成绩))
                            doc.save(filename)
        except Exception as e:
            print(path + " 文件读取失败。原因: " + str(e))
        finally:
            continue
    else:
        print(path + " 不存在")

用数据及模板批量生成.docx文档

适合生成项目评分表之类的文档。准备一个template.docx文档,在需要用数据替换的地方,用{字段名}标注。程序将data.txt中的数据替换模板中的内容,并给每个学生成一个学号-姓名.docx文档。

源码 Windows版 Linux版,其它操作系统请自行编译。

package main

import (
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/lukasjarosch/go-docx"
)

func main() {
	content, err := os.ReadFile("data.txt")

	if err != nil {
		log.Fatal(err)
	}

	lines := strings.Split(string(content), "\n")
	headers := strings.Fields(strings.TrimSpace(lines[0]))

	for _, line := range lines[1:] {
		replaceMap := docx.PlaceholderMap{}
		fields := strings.Fields(line)
		for i, header := range headers {
			replaceMap[header] = fields[i]
		}

		// read and parse the template docx
		doc, err := docx.Open("template.docx")
		if err != nil {
			panic(err)
		}

		// replace the keys with values from replaceMap
		err = doc.ReplaceAll(replaceMap)
		if err != nil {
			panic(err)
		}

		// write out a new file
		fileName := fmt.Sprintf("%s-%s.docx", fields[0], fields[1])
		err = doc.WriteToFile(fileName)
		if err != nil {
			panic(err)
		}
	}
}

教务系统期末成绩导入

将平时成绩或期末成绩按照学号、姓名、成绩在excel中准备好,替换源码中相应位置数据,然后将整个脚本复制到到浏览器中运行即完成成绩录入,方便、快捷、不易出错。

源码

// 为了避免在FireFox中多次运行出现重复定义问题
// 包装开始
(() => {
	// 修改下面的成绩,可以直接从excel复制粘贴,成绩的顺序必须和教务系统的名单一致
	const scoreRaw = `221124010014	吴芳	87
221124010062	邹敏	81
221124010105	李昌蔚	92
221124010114	余传奇	92
221124010155	刘长青	83
221124010172	马欢	91
221124010033	罗阿甲	92
221124010138	罗超	92
221124010161	江小朋	98
221124010166	毛呷明	86
221124010174	益西翁扎	81
221124010128	高梓晔	82
221124010147	王尧	95
221124010154	周含鸣	95
221124010167	吴坤生	94
221124010007	王樱蓉	83
221124010049	汪洋	89
221124010050	陈杰	84
221124010090	赵禹辰	81
221124010037	吴星怡	94
221124010064	郑龙	83
221124010111	陈天祺	95
221124010143	刘静	97
221124010148	刘雨煌	81
221124010183	纪垚林	86
221124010184	格桑卓玛	90
221124010035	许越	81
221124010063	刘威	81
221124010091	邓灵	83
221124010135	宋庆	93
221124010140	刘津良	83
221124010134	谯小峰	98
221124010139	陈飞	91
221124010162	王炯炯
221124010182	易小勇	94
221124010003	廖禹婷	87
221124010065	胡开红	98
221124010066	彭凯歌	84
221124010122	何家豪	82
221124010149	马清玲	98
`;
	// 分数
	const scores = {};

	// 按行进行切分
	const rows = scoreRaw.trim().split("\n");
	for (const row of rows) {
		const fields = row
			.trim()
			.split(/ |\t/)
			.map((e) => e.trim())
			.filter((e) => e !== "");
		scores[fields[0]] = fields;
	}

	// 找到录入成绩对应的文档
	let doc = document; // 默认为top文档,即当前页面所在的文档
	for (let i = 0; i < frames.length; i++) {
		const d = frames[i].document;
		if (d.title === "教学记录-YETHAN以专教学信息服务平台") {
			doc = d;
			break;
		}
	}

	// 填充到每个输入框
	let ids = doc.getElementsByName("studentId"); // 平时成绩录入时学号
	if (ids.length === 0) {
		ids = doc.getElementsByName("student_id"); // 期末成绩录入时学号
	}
	const marks = doc.getElementsByName("mark");
	for (let i = 0; i < marks.length; i++) {
		const id = ids[i].value;
		const row = scores[id];
		if (row) {
			// 检查是否有该学生记录
			const score = row[2];
			if (score) {
				// 判断成绩是否存在
				marks[i].value = score;
			} else {
				alert(`学号:${id} 学生无成绩!!!`);
			}
		} else {
			alert(`学号:${id} 学生无记录!!!`);
		}
	}

	// 包装结束
})();
posted @   卓能文  阅读(206)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示