db_to_mongo/student_data_processor.py

168 lines
7.1 KiB
Python
Raw Normal View History

import os
import json
import pymysql
from pymysql.cursors import DictCursor
import pymongo
import requests
from dotenv import load_dotenv
from typing import List, Dict, Generator
# 加载环境变量
load_dotenv()
class StudentDataProcessorLMStudio:
def __init__(self):
# 初始化 MySQL 连接(支持批量遍历)
self.mysql_conn = pymysql.connect(
host=os.getenv("MYSQL_HOST"),
port=int(os.getenv("MYSQL_PORT")),
user=os.getenv("MYSQL_USER"),
password=os.getenv("MYSQL_PASSWORD"),
database=os.getenv("MYSQL_DB"),
charset="utf8mb4"
)
self.mysql_cursor = self.mysql_conn.cursor()
# 初始化 MongoDB 连接
self.mongo_client = pymongo.MongoClient(os.getenv("MONGO_URI"))
self.mongo_db = self.mongo_client[os.getenv("MONGO_DB")]
self.mongo_collection = self.mongo_db[os.getenv("MONGO_COLLECTION")]
# LM Studio 配置
self.lm_studio_api_url = os.getenv("LM_STUDIO_API_URL")
self.lm_studio_model = os.getenv("LM_STUDIO_MODEL_NAME")
self.lm_studio_api_token = os.getenv("LM_STUDIO_API_TOKEN")
self.batch_size = int(os.getenv("BATCH_SIZE"))
def batch_read_student_data(self) -> Generator[List[Dict], None, None]:
"""分批遍历 MySQL 所有学生数据(避免一次性加载过多数据)"""
try:
# 先获取总条数,用于分批
count_query = "SELECT COUNT(*) as total FROM student WHERE is_deleted = 0"
self.mysql_cursor.execute(count_query)
total = self.mysql_cursor.fetchone()["total"]
print(f"MySQL 中共计 {total} 条学生数据,将按每批 {self.batch_size} 条处理")
# 分批读取
offset = 0
while offset < total:
query = f"""
SELECT id, name, age, gender, class, phone, email, address
FROM student
WHERE is_deleted = 0
LIMIT {self.batch_size} OFFSET {offset}
"""
self.mysql_cursor.execute(query)
batch_data = self.mysql_cursor.fetchall()
if not batch_data:
break
yield batch_data
offset += self.batch_size
print(f"已读取第 {offset//self.batch_size} 批数据(累计 {offset} 条)")
except Exception as e:
print(f"分批读取 MySQL 数据失败:{str(e)}")
raise
def process_batch_with_lmstudio(self, batch_data: List[Dict]) -> List[Dict]:
"""调用 LM Studio 本地模型整理单批数据"""
if not batch_data:
return []
# 构造适配本地模型的提示词(更简洁,避免本地模型处理复杂指令出错)
prompt = f"""
请严格按照以下要求整理学生数据仅返回JSON数组不要添加任何额外文字解释或备注
整理规则
1. 标准化字段
- 姓名去除首尾空格仅保留中文" 李四 ""李四"
- 年龄转为整数非数字则设为null
- 手机号仅保留11位纯数字不符合则设为null
- 邮箱验证格式无效则设为null
- 性别""/""则设为"未知"
- 班级为空则设为"未分配"
2. 新增字段
- student_id格式为"2024"+补零后的id如id=5"202400005"id=123"202400123"
- data_quality"完整"所有字段非null/"部分缺失"1-2个字段null/"严重缺失"3个字段null
原始数据{json.dumps(batch_data, ensure_ascii=False, indent=2)}
"""
# 构造 LM Studio API 请求体(兼容 OpenAI 格式)
payload = {
"model": self.lm_studio_model,
"messages": [
{"role": "system", "content": "你是数据整理专家仅返回JSON格式的处理结果无其他内容"},
{"role": "user", "content": prompt}
],
"temperature": 0.1, # 低随机性保证结果稳定
"max_tokens": 2000 # 足够容纳批量数据的JSON
}
try:
# 调用 LM Studio 本地 API
response = requests.post(
self.lm_studio_api_url,
headers={"Authorization": f"Bearer {self.lm_studio_api_token}"},
json=payload,
timeout=60 # 本地模型处理稍慢,延长超时时间
)
response.raise_for_status() # 抛出HTTP错误
result = response.json()
# 解析本地模型返回的结果
processed_text = result["choices"][0]["message"]["content"].strip()
# 清理可能的多余字符(本地模型可能返回```json开头/结尾)
if processed_text.startswith("```json"):
processed_text = processed_text.replace("```json", "").replace("```", "").strip()
processed_data = json.loads(processed_text)
return processed_data
except json.JSONDecodeError:
print(f"本地模型返回非标准JSON跳过该批{processed_text[:200]}")
return []
except Exception as e:
print(f"调用LM Studio失败{str(e)}")
return []
def save_to_mongodb(self, processed_data: List[Dict]):
"""将整理后的数据存入MongoDB支持增量插入"""
if not processed_data:
return
try:
# 批量插入跳过重复id避免重复存储
for data in processed_data:
self.mongo_collection.update_one(
{"id": data["id"]}, # 以原始id为唯一键
{"$set": data}, # 存在则更新,不存在则插入
upsert=True
)
print(f"成功存入/更新 {len(processed_data)} 条数据到MongoDB")
except Exception as e:
print(f"存入MongoDB失败{str(e)}")
def close_connections(self):
"""关闭数据库连接"""
if self.mysql_cursor:
self.mysql_cursor.close()
if self.mysql_conn:
self.mysql_conn.close()
if self.mongo_client:
self.mongo_client.close()
print("所有连接已关闭")
def run(self):
"""执行完整流程分批读取→本地模型处理→存入MongoDB"""
try:
# 遍历所有批次数据
for batch in self.batch_read_student_data():
# 处理单批数据
processed_batch = self.process_batch_with_lmstudio(batch)
# 存入MongoDB
self.save_to_mongodb(processed_batch)
print("✅ 全量数据处理完成!")
except Exception as e:
print(f"程序执行异常:{str(e)}")
finally:
self.close_connections()
# 程序入口
if __name__ == "__main__":
processor = StudentDataProcessorLMStudio()
processor.run()