| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 将 `人员清单列表@外聘.xlsx` 导入到临时表 temp_external_staff
- 用法:
- python import_external_staff.py
- 依赖:
- pandas sqlalchemy pymysql openpyxl
- 说明:
- - 从同目录或上级目录读取 db_config.py 中的 DB_CONFIG
- - 自动根据 Excel 表头创建临时表(所有列使用 TEXT)
- - 支持批量写入
- """
- from __future__ import annotations
- import os
- import sys
- import math
- import argparse
- import importlib.util
- from typing import List, Dict, Any
- import pandas as pd
- from sqlalchemy import create_engine, text
- try:
- import openpyxl
- except Exception:
- openpyxl = None
- def load_db_config_from_file(config_path: str) -> dict:
- spec = importlib.util.spec_from_file_location("db_config", config_path)
- module = importlib.util.module_from_spec(spec)
- spec.loader.exec_module(module) # type: ignore
- if not hasattr(module, "DB_CONFIG"):
- raise RuntimeError(f"No DB_CONFIG found in {config_path}")
- return getattr(module, "DB_CONFIG")
- def clean_column_name(col: str) -> str:
- if col is None:
- return ""
- s = str(col).strip()
- s = s.replace("(", "_").replace(")", "_").replace("(", "_").replace(")", "_")
- s = s.replace("、", "_").replace(" ", "_").replace("\n", "_").replace("\r", "_")
- # ensure valid identifier start
- if not s:
- s = "col"
- if not (s[0].isalpha() or s[0] == "_"):
- s = f"c_{s}"
- return s
- def build_engine_from_config(db_config: dict):
- user = db_config.get("user")
- password = db_config.get("password")
- host = db_config.get("host", "localhost")
- port = db_config.get("port", 3306)
- database = db_config.get("database")
- charset = db_config.get("charset", "utf8mb4")
- uri = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"
- return create_engine(uri, pool_pre_ping=True)
- def create_table(conn, table_name: str, columns: List[str]):
- # 先单独执行 DROP,再执行 CREATE(避免多语句执行限制导致语法错误)
- conn.execute(text(f"DROP TABLE IF EXISTS `{table_name}`"))
- lines: List[str] = []
- lines.append(f"CREATE TABLE `{table_name}` (")
- lines.append(" `id` BIGINT(20) NOT NULL AUTO_INCREMENT,")
- for col in columns:
- lines.append(f" `{col}` TEXT DEFAULT NULL,")
- lines.append(" `import_time` DATETIME DEFAULT CURRENT_TIMESTAMP,")
- lines.append(" PRIMARY KEY (`id`)")
- lines.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;")
- sql = "\n".join(lines)
- conn.execute(text(sql))
- def import_excel_to_table(engine, excel_path: str, table_name: str, chunk_size: int = 1000):
- if not os.path.exists(excel_path):
- raise FileNotFoundError(f"找不到文件: {excel_path}")
- # 读取表头和全部数据使用 pandas(适合中小文件)
- df = pd.read_excel(excel_path, engine="openpyxl")
- raw_cols = list(df.columns)
- clean_cols = [clean_column_name(c) for c in raw_cols]
- # 重命名 DataFrame 列为清理后的列名
- df.columns = clean_cols
- with engine.begin() as conn:
- # 创建表
- create_table(conn, table_name, clean_cols)
- # 分块写入
- total = 0
- for start in range(0, len(df), chunk_size):
- chunk = df.iloc[start:start+chunk_size].where(pd.notnull(df), None)
- chunk.to_sql(name=table_name, con=conn, index=False, if_exists="append", method="multi", chunksize=chunk_size)
- total += len(chunk)
- return total
- def main():
- parser = argparse.ArgumentParser(description="将人员清单(外聘)导入临时表")
- parser.add_argument("--file", type=str, default="人员清单列表@外聘.xlsx", help="Excel 文件名或路径")
- parser.add_argument("--table", type=str, default="temp_external_staff", help="临时表名")
- parser.add_argument("--db-config", type=str, default="../学校历史数据迁移/db_config.py", help="db_config.py 路径")
- args = parser.parse_args()
- base_dir = os.path.dirname(os.path.abspath(__file__))
- excel_path = args.file
- if not os.path.isabs(excel_path):
- excel_path = os.path.join(base_dir, excel_path)
- db_config_path = args.db_config
- if not os.path.isabs(db_config_path):
- db_config_path = os.path.join(base_dir, db_config_path)
- if not os.path.exists(db_config_path):
- # try parent folder
- db_config_path = os.path.join(os.path.dirname(base_dir), "学校历史数据迁移", "db_config.py")
- if not os.path.exists(db_config_path):
- print(f"找不到 db_config 文件: {db_config_path}")
- sys.exit(1)
- db_config = load_db_config_from_file(db_config_path)
- engine = build_engine_from_config(db_config)
- print(f"开始导入文件: {excel_path} 到表 {args.table}")
- total = import_excel_to_table(engine, excel_path, args.table, chunk_size=1000)
- print(f"导入完成,共插入 {total} 条记录到 {args.table}")
- if __name__ == "__main__":
- main()
|