gaokao-site/scripts/migration_add_tuition_schid.py

275 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
migration_add_tuition_schid.py
高考志愿网站 v3 数据迁移
功能:
1. schools 表新增 sch_idTEXT和 tuitionTEXT字段
2. 从最新25年招生章程链接.xlsx 导入 schId2891所学校
3. 根据学校名称计算并写入 tuition 字段(静态学费标准)
数据来源:
- schId: 最新25年招生章程链接.xlsx阳光高考官方
- tuition: 河南省发改委高等教育收费标准 + 学校名称关键字推断
用法(本地测试):
python3 migration_add_tuition_schid.py --local
用法(服务器执行):
python3 migration_add_tuition_schid.py
(默认连接远程数据库)
"""
import sys
import os
import sqlite3
import json
import re
import argparse
# 导入学费数据模块
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from tuition_data import get_tuition_for_school, ALL_TUITION_RATES
# ============================================================
# 配置
# ============================================================
REMOTE_DB = "/www/wwwroot/gaokao/gaokao_henan.db"
SCHID_XLSX = "/tmp/最新25年招生章程链接.xlsx"
LOCAL_DB = os.path.expanduser("~/mc/gaokao-site/gaokao_henan.db")
def get_db_path(local=False):
if local:
if os.path.exists(LOCAL_DB):
return LOCAL_DB
# 尝试其他本地路径
alt = "/www/wwwroot/gaokao/gaokao_henan.db"
if os.path.exists(alt):
return alt
print(f"[警告] 本地数据库不存在: {LOCAL_DB}")
return None
return REMOTE_DB
def add_columns(conn):
"""给 schools 表添加 sch_id 和 tuition 列"""
cur = conn.cursor()
for col, dtype in [("sch_id", "TEXT"), ("tuition", "TEXT")]:
try:
cur.execute(f"ALTER TABLE schools ADD COLUMN {col} {dtype}")
print(f" [+] 新增列: {col}")
except sqlite3.OperationalError as e:
if "duplicate column" in str(e).lower() or "already exists" in str(e).lower():
print(f" [=] 列已存在: {col}")
else:
print(f" [!] 新增列失败: {col}: {e}")
conn.commit()
def load_schid_xlsx(path):
"""
从 xlsx 读取 schId 映射
返回: {(学校名, 省份): sch_id}
"""
try:
import openpyxl
except ImportError:
print("[!] 需要 openpyxl: pip install openpyxl")
return {}
if not os.path.exists(path):
print(f"[!] xlsx 文件不存在: {path}")
return {}
wb = openpyxl.load_workbook(path, data_only=True, read_only=True)
ws = wb.active
result = {}
for i, row in enumerate(ws.iter_rows(values_only=True)):
if i < 2: # 跳过表头
continue
if not row or not row[0]:
continue
school_name = str(row[0]).strip() if row[0] else ""
province = str(row[1]).strip() if len(row) > 1 and row[1] else ""
if not school_name or school_name == '院校名称':
continue
# 提取 schId从 URL 中 schId-数字 格式)
sch_id = None
if len(row) > 4 and row[4]:
url = str(row[4])
m = re.search(r'schId[=-]?(\d+)', url)
if m:
sch_id = m.group(1)
if sch_id:
result[(school_name, province)] = sch_id
wb.close()
print(f" [i] 从 xlsx 加载 {len(result)} 条 schId 映射")
return result
def write_schid_to_db(conn, schid_map):
"""将 schId 写入 schools 表"""
cur = conn.cursor()
# 先查一下现有学校
cur.execute("SELECT DISTINCT school_name FROM schools LIMIT 10")
print(f" [i] 数据库现有学校示例: {[r[0] for r in cur.fetchall()]}")
updated = 0
not_found = []
cur.execute("SELECT DISTINCT school_name FROM schools")
all_schools = [r[0] for r in cur.fetchall()]
for school in all_schools:
# 多轮匹配:精确 → 含郑州/河南前缀/后缀
sch_id = schid_map.get((school, "河南")) or \
schid_map.get((school, ""))
if not sch_id:
# 尝试模糊匹配(去掉"大学""学院"后缀)
aliases = [
school,
school.replace("大学", ""),
school.replace("学院", ""),
]
for alias in aliases:
for (sn, prov), sid in schid_map.items():
if sn == alias or alias in sn:
sch_id = sid
break
if sch_id:
break
if sch_id:
conn.execute(
"UPDATE schools SET sch_id = ? WHERE school_name = ? AND (sch_id IS NULL OR sch_id = '')",
(sch_id, school)
)
updated += 1
else:
not_found.append(school)
conn.commit()
print(f" [+] 写入 schId: {updated} 所学校")
if not_found[:10]:
print(f" [!] 未找到 schId (前10): {not_found[:10]}")
return updated, not_found
def write_tuition_to_db(conn):
"""根据学校名称计算并写入 tuition 字段"""
cur = conn.cursor()
cur.execute("SELECT DISTINCT school_name FROM schools")
schools = [r[0] for r in cur.fetchall()]
updated = 0
for school in schools:
tuition_info = get_tuition_for_school(school)
tuition_json = json.dumps(tuition_info, ensure_ascii=False)
conn.execute(
"UPDATE schools SET tuition = ? WHERE school_name = ? AND (tuition IS NULL OR tuition = '')",
(tuition_json, school)
)
updated += 1
conn.commit()
print(f" [+] 写入 tuition: {updated} 所学校")
return updated
def get_school_tuition_preview(conn, n=15):
"""打印 tuition 写入预览(不实际写入)"""
cur = conn.cursor()
cur.execute("SELECT DISTINCT school_name FROM schools ORDER BY school_name LIMIT ?", (n,))
schools = [r[0] for r in cur.fetchall()]
print(f"\n 学费预览(前{n}所):")
for s in schools:
info = get_tuition_for_school(s)
print(f" {s:<28}{info['annual']:,}元/年 ({info['category']})")
def main(local=False, preview_only=False):
db_path = get_db_path(local)
if not db_path:
print("[!] 无法找到数据库,退出")
return
print(f"\n{'='*60}")
print(f" 高考志愿网站 v3 - 学费 & schId 迁移")
print(f" 数据库: {db_path}")
print(f"{'='*60}")
conn = sqlite3.connect(db_path)
# Step 1: 添加列
print("\n[1/3] 添加 sch_id / tuition 列...")
add_columns(conn)
# Step 2: 导入 schId
print("\n[2/3] 导入 schId从 xlsx...")
schid_map = load_schid_xlsx(SCHID_XLSX)
# 手动添加几所河南重点学校的 schId已知数据
manual_schid = {
("郑州大学", "河南"): "361",
("河南大学", "河南"): "419",
("河南师范大学", "河南"): "471",
("河南科技大学", "河南"): "512",
("河南理工大学", "河南"): "478",
("华北水利水电大学", "河南"): "509",
("河南农业大学", "河南"): "473",
("河南工业大学", "河南"): "510",
("郑州轻工业大学", "河南"): "515",
("河南中医药大学", "河南"): "481",
("新乡医学院", "河南"): "527",
("河南财经政法大学", "河南"): "521",
}
schid_map.update(manual_schid)
print(f" [i] 合并后共 {len(schid_map)} 条 schId")
write_schid_to_db(conn, schid_map)
# Step 3: 写入 tuition
print("\n[3/3] 写入 tuition静态学费标准...")
get_school_tuition_preview(conn)
if preview_only:
print("\n [preview_only] 预览模式,未写入数据库")
conn.close()
return
write_tuition_to_db(conn)
# 验证
print("\n[验证] 随机抽查 5 所学校的写入结果:")
cur = conn.cursor()
cur.execute("""
SELECT school_name, sch_id, tuition
FROM schools
WHERE tuition IS NOT NULL AND tuition != ''
GROUP BY school_name
LIMIT 5
""")
for row in cur.fetchall():
sch, sid, tui = row
tui_parsed = json.loads(tui) if tui else {}
print(f" {sch}: schId={sid}, tuition={tui_parsed.get('annual')}元/年 ({tui_parsed.get('category', '')})")
conn.close()
print("\n[完成] 迁移成功!")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="高考志愿网站学费 & schId迁移")
parser.add_argument("--local", action="store_true", help="使用本地数据库而非远程")
parser.add_argument("--preview", action="store_true", help="仅预览,不写入数据库")
args = parser.parse_args()
main(local=args.local, preview_only=args.preview)