db_to_mongo/student_data_processor.py

168 lines
7.1 KiB
Python
Raw Permalink 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.

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()