戒酒的李白

More convenient project initialization, with database initialization added to app.py.

  1 +2025-01-09 23:29:06,246 [INFO] 尝试连接到数据库: root@localhost:3306/Weibo_PublicOpinion_AnalysisSystem
  2 +2025-01-09 23:29:06,346 [ERROR] 数据库连接失败: (1045, "Access denied for user 'root'@'localhost' (using password: YES)")
1 -from flask import Flask, session, request, redirect, render_template 1 +import os
2 import re 2 import re
3 -from apscheduler.schedulers.background import BackgroundScheduler 3 +import logging
  4 +import getpass
  5 +import pymysql
4 import subprocess 6 import subprocess
5 -import os 7 +from flask import Flask, session, request, redirect, render_template
  8 +from apscheduler.schedulers.background import BackgroundScheduler
6 from pytz import utc 9 from pytz import utc
7 -import logging 10 +
  11 +# 初始化日志记录
  12 +logging.basicConfig(
  13 + level=logging.INFO,
  14 + format='%(asctime)s [%(levelname)s] %(message)s',
  15 + handlers=[
  16 + logging.FileHandler("app.log"),
  17 + logging.StreamHandler()
  18 + ]
  19 +)
  20 +
  21 +def get_db_connection_interactive():
  22 + """
  23 + 通过终端交互获取数据库连接参数,若按回车则使用默认值。
  24 + 返回一个连接对象。
  25 + """
  26 + print("请依次输入数据库连接信息(直接按回车使用默认值):")
  27 +
  28 + host = input(" 1. 主机 (默认: localhost): ") or "localhost"
  29 + port_str = input(" 2. 端口 (默认: 3306): ") or "3306"
  30 + try:
  31 + port = int(port_str)
  32 + except ValueError:
  33 + logging.warning("端口号无效,使用默认端口 3306。")
  34 + port = 3306
  35 +
  36 + user = input(" 3. 用户名 (默认: root): ") or "root"
  37 + password = getpass.getpass(" 4. 密码 (默认: 12345678): ") or "12345678"
  38 + db_name = input(" 5. 数据库名 (默认: Weibo_PublicOpinion_AnalysisSystem): ") or "Weibo_PublicOpinion_AnalysisSystem"
  39 +
  40 + logging.info(f"尝试连接到数据库: {user}@{host}:{port}/{db_name}")
  41 +
  42 + try:
  43 + connection = pymysql.connect(
  44 + host=host,
  45 + port=port,
  46 + user=user,
  47 + password=password,
  48 + database=db_name,
  49 + charset='utf8mb4',
  50 + cursorclass=pymysql.cursors.DictCursor # 返回字典格式
  51 + )
  52 + logging.info("数据库连接成功。")
  53 + return connection
  54 + except pymysql.MySQLError as e:
  55 + logging.error(f"数据库连接失败: {e}")
  56 + exit(1)
  57 +
  58 +def initialize_database(connection, sql_file_path):
  59 + """
  60 + 执行 SQL 文件中的语句以初始化数据库。
  61 +
  62 + :param connection: 已建立的数据库连接
  63 + :param sql_file_path: SQL 文件的路径
  64 + """
  65 + try:
  66 + with open(sql_file_path, 'r', encoding='utf8') as file:
  67 + sql_commands = file.read()
  68 +
  69 + with connection.cursor() as cursor:
  70 + for statement in sql_commands.split(';'):
  71 + statement = statement.strip()
  72 + if statement:
  73 + cursor.execute(statement)
  74 + connection.commit()
  75 + logging.info("数据库初始化成功。")
  76 + except FileNotFoundError:
  77 + logging.error(f"SQL 文件未找到: {sql_file_path}")
  78 + exit(1)
  79 + except pymysql.MySQLError as e:
  80 + logging.error(f"执行 SQL 时出错: {e}")
  81 + connection.rollback()
  82 + exit(1)
  83 + except Exception as e:
  84 + logging.error(f"初始化数据库时出错: {e}")
  85 + connection.rollback()
  86 + exit(1)
  87 +
  88 +def prompt_first_run():
  89 + """
  90 + 询问用户是否首次运行,需要初始化数据库。
  91 +
  92 + :return: Boolean,True 表示需要初始化数据库
  93 + """
  94 + while True:
  95 + choice = input("是否首次运行该项目,需要初始化数据库?(Y/n): ").strip().lower()
  96 + if choice in ['y', 'yes', '']:
  97 + return True
  98 + elif choice in ['n', 'no']:
  99 + return False
  100 + else:
  101 + print("请输入 Y 或 N。")
8 102
9 # 初始化 Flask 应用 103 # 初始化 Flask 应用
10 app = Flask(__name__) 104 app = Flask(__name__)
@@ -14,25 +108,13 @@ app.secret_key = 'this is secret_key you know ?' # 设置 Flask 的密钥,用 @@ -14,25 +108,13 @@ app.secret_key = 'this is secret_key you know ?' # 设置 Flask 的密钥,用
14 from views.page import page 108 from views.page import page
15 from views.user import user 109 from views.user import user
16 app.register_blueprint(page.pb) # 注册页面蓝图 110 app.register_blueprint(page.pb) # 注册页面蓝图
17 -app.register_blueprint(user.ub) # 注册用户蓝图 111 +app.register_blueprint(user.ub) # 注册用户蓝图
18 112
19 # 首页路由,清空 session 113 # 首页路由,清空 session
20 @app.route('/') 114 @app.route('/')
21 def hello_world(): 115 def hello_world():
22 - return session.clear() # 清空 session,用户退出登录  
23 -  
24 -"""  
25 -@app.before_request  
26 -def before_reuqest():  
27 - pat = re.compile(r'^/static') # 正则匹配静态文件路径  
28 - if re.search(pat, request.path): # 如果是静态文件,直接返回  
29 - return  
30 - elif request.path == '/user/login' or request.path == '/user/register': # 登录或注册页面无需验证  
31 - return  
32 - elif session.get('username'): # 如果 session 中有用户名,则允许继续  
33 - return  
34 - return redirect('/user/login') # 否则重定向到登录页面  
35 -""" 116 + session.clear() # 清空 session,用户退出登录
  117 + return "Session Cleared"
36 118
37 # 中间件:处理请求前的逻辑 119 # 中间件:处理请求前的逻辑
38 @app.before_request 120 @app.before_request
@@ -79,6 +161,19 @@ def run_script(): @@ -79,6 +161,19 @@ def run_script():
79 161
80 # 主程序入口 162 # 主程序入口
81 if __name__ == '__main__': 163 if __name__ == '__main__':
  164 + # 检测是否需要初始化数据库
  165 + if prompt_first_run():
  166 + # 获取数据库连接
  167 + connection = get_db_connection_interactive()
  168 +
  169 + # 执行数据库初始化
  170 + sql_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'createTables.sql')
  171 + initialize_database(connection, sql_file)
  172 +
  173 + # 关闭数据库连接
  174 + connection.close()
  175 + logging.info("数据库连接已关闭。")
  176 +
82 # 设置定时任务,定期执行爬虫脚本 177 # 设置定时任务,定期执行爬虫脚本
83 scheduler = BackgroundScheduler(timezone=utc) # 创建后台任务调度器 178 scheduler = BackgroundScheduler(timezone=utc) # 创建后台任务调度器
84 scheduler.add_job(run_script, 'interval', hours=5) # 每5小时执行一次爬虫脚本 179 scheduler.add_job(run_script, 'interval', hours=5) # 每5小时执行一次爬虫脚本
@@ -90,8 +185,6 @@ if __name__ == '__main__': @@ -90,8 +185,6 @@ if __name__ == '__main__':
90 scheduler.shutdown() # 确保在应用关闭时关闭调度器 185 scheduler.shutdown() # 确保在应用关闭时关闭调度器
91 186
92 # 设置日志记录,捕获应用的请求信息 187 # 设置日志记录,捕获应用的请求信息
93 -logging.basicConfig(level=logging.INFO) # 配置日志记录,设置日志级别为 INFO  
94 -  
95 @app.before_request 188 @app.before_request
96 def log_request_info(): 189 def log_request_info():
97 # 记录每次请求的信息,便于调试和监控 190 # 记录每次请求的信息,便于调试和监控
@@ -4,7 +4,7 @@ from sqlalchemy import create_engine @@ -4,7 +4,7 @@ from sqlalchemy import create_engine
4 from getpass import getpass 4 from getpass import getpass
5 import logging 5 import logging
6 6
7 -# 配置日志 7 +# 配置日志
8 logging.basicConfig( 8 logging.basicConfig(
9 level=logging.INFO, 9 level=logging.INFO,
10 format='%(asctime)s [%(levelname)s] %(message)s', 10 format='%(asctime)s [%(levelname)s] %(message)s',
@@ -14,95 +14,95 @@ logging.basicConfig( @@ -14,95 +14,95 @@ logging.basicConfig(
14 ] 14 ]
15 ) 15 )
16 16
17 -# 假设 articleAddr 和 commentsAddr 是绝对路径或相对于脚本的路径 17 +# 假设 articleAddr 和 commentsAddr 是绝对路径或相对于脚本的路径
18 from spiderDataPackage.settings import articleAddr, commentsAddr 18 from spiderDataPackage.settings import articleAddr, commentsAddr
19 19
20 def get_db_connection_interactive(): 20 def get_db_connection_interactive():
21 """ 21 """
22 - 通过终端交互获取数据库连接参数,若按回车则使用默认值。  
23 - 返回 SQLAlchemy 的数据库引擎。 22 + 通过终端交互获取数据库连接参数,若按回车则使用默认值。
  23 + 返回 SQLAlchemy 的数据库引擎。
24 """ 24 """
25 - print("请依次输入数据库连接信息(直接按回车使用默认值):") 25 + print("请依次输入数据库连接信息(直接按回车使用默认值):")
26 26
27 - host = input(" 1. 主机 (默认: localhost): ") or "localhost"  
28 - port_str = input(" 2. 端口 (默认: 3306): ") or "3306" 27 + host = input(" 1. 主机 (默认: localhost): ") or "localhost"
  28 + port_str = input(" 2. 端口 (默认: 3306): ") or "3306"
29 try: 29 try:
30 port = int(port_str) 30 port = int(port_str)
31 except ValueError: 31 except ValueError:
32 - logging.warning("端口号无效,使用默认端口 3306。") 32 + logging.warning("端口号无效,使用默认端口 3306。")
33 port = 3306 33 port = 3306
34 34
35 - user = input(" 3. 用户名 (默认: root): ") or "root"  
36 - password = getpass(" 4. 密码 (默认: 12345678): ") or "12345678"  
37 - db_name = input(" 5. 数据库名 (默认: Weibo_PublicOpinion_AnalysisSystem): ") or "Weibo_PublicOpinion_AnalysisSystem" 35 + user = input(" 3. 用户名 (默认: root): ") or "root"
  36 + password = getpass(" 4. 密码 (默认: 12345678): ") or "12345678"
  37 + db_name = input(" 5. 数据库名 (默认: Weibo_PublicOpinion_AnalysisSystem): ") or "Weibo_PublicOpinion_AnalysisSystem"
38 38
39 - # 构建数据库连接字符串 39 + # 构建数据库连接字符串
40 connection_str = f"mysql+pymysql://{user}:{password}@{host}:{port}/{db_name}?charset=utf8mb4" 40 connection_str = f"mysql+pymysql://{user}:{password}@{host}:{port}/{db_name}?charset=utf8mb4"
41 41
42 try: 42 try:
43 engine = create_engine(connection_str) 43 engine = create_engine(connection_str)
44 - # 测试连接 44 + # 测试连接
45 with engine.connect() as connection: 45 with engine.connect() as connection:
46 - logging.info(f"成功连接到数据库: {user}@{host}:{port}/{db_name}") 46 + logging.info(f"成功连接到数据库: {user}@{host}:{port}/{db_name}")
47 return engine 47 return engine
48 except Exception as e: 48 except Exception as e:
49 - logging.error(f"无法连接到数据库: {e}") 49 + logging.error(f"无法连接到数据库: {e}")
50 exit(1) 50 exit(1)
51 51
52 def saveData(engine): 52 def saveData(engine):
53 """ 53 """
54 - 从数据库和CSV文件读取数据,合并后去重并保存回数据库。  
55 - 最后删除CSV文件。 54 + 从数据库和CSV文件读取数据,合并后去重并保存回数据库。
  55 + 最后删除CSV文件。
56 """ 56 """
57 try: 57 try:
58 - # 读取旧数据 58 + # 读取旧数据
59 oldArticle = pd.read_sql('SELECT * FROM article', engine) 59 oldArticle = pd.read_sql('SELECT * FROM article', engine)
60 oldComment = pd.read_sql('SELECT * FROM comments', engine) 60 oldComment = pd.read_sql('SELECT * FROM comments', engine)
61 - logging.info("成功从数据库读取旧的文章和评论数据。") 61 + logging.info("成功从数据库读取旧的文章和评论数据。")
62 62
63 - # 读取新数据 63 + # 读取新数据
64 newArticle = pd.read_csv(articleAddr) 64 newArticle = pd.read_csv(articleAddr)
65 newComment = pd.read_csv(commentsAddr) 65 newComment = pd.read_csv(commentsAddr)
66 - logging.info("成功从CSV文件读取新的文章和评论数据。") 66 + logging.info("成功从CSV文件读取新的文章和评论数据。")
67 67
68 - # 合并数据 68 + # 合并数据
69 mergeArticle = pd.concat([newArticle, oldArticle], ignore_index=True, sort=False) 69 mergeArticle = pd.concat([newArticle, oldArticle], ignore_index=True, sort=False)
70 mergeComment = pd.concat([newComment, oldComment], ignore_index=True, sort=False) 70 mergeComment = pd.concat([newComment, oldComment], ignore_index=True, sort=False)
71 - logging.info("成功合并新旧文章和评论数据。") 71 + logging.info("成功合并新旧文章和评论数据。")
72 72
73 - # 去重 73 + # 去重
74 mergeArticle.drop_duplicates(subset='id', keep='last', inplace=True) 74 mergeArticle.drop_duplicates(subset='id', keep='last', inplace=True)
75 mergeComment.drop_duplicates(subset='content', keep='last', inplace=True) 75 mergeComment.drop_duplicates(subset='content', keep='last', inplace=True)
76 - logging.info("成功去除重复的文章和评论数据。") 76 + logging.info("成功去除重复的文章和评论数据。")
77 77
78 - # 保存回数据库 78 + # 保存回数据库
79 mergeArticle.to_sql('article', con=engine, if_exists='replace', index=False) 79 mergeArticle.to_sql('article', con=engine, if_exists='replace', index=False)
80 mergeComment.to_sql('comments', con=engine, if_exists='replace', index=False) 80 mergeComment.to_sql('comments', con=engine, if_exists='replace', index=False)
81 - logging.info("成功将合并后的数据保存回数据库。") 81 + logging.info("成功将合并后的数据保存回数据库。")
82 82
83 except pd.errors.EmptyDataError as e: 83 except pd.errors.EmptyDataError as e:
84 - logging.error(f"读取CSV文件时出错: {e}") 84 + logging.error(f"读取CSV文件时出错: {e}")
85 except Exception as e: 85 except Exception as e:
86 - logging.error(f"保存数据时出错: {e}") 86 + logging.error(f"保存数据时出错: {e}")
87 else: 87 else:
88 - # 删除CSV文件 88 + # 删除CSV文件
89 try: 89 try:
90 os.remove(articleAddr) 90 os.remove(articleAddr)
91 os.remove(commentsAddr) 91 os.remove(commentsAddr)
92 - logging.info("成功删除CSV文件。") 92 + logging.info("成功删除CSV文件。")
93 except Exception as e: 93 except Exception as e:
94 - logging.warning(f"删除CSV文件时出错: {e}") 94 + logging.warning(f"删除CSV文件时出错: {e}")
95 95
96 def main(): 96 def main():
97 - # 获取数据库连接 97 + # 获取数据库连接
98 engine = get_db_connection_interactive() 98 engine = get_db_connection_interactive()
99 99
100 - # 保存数据 100 + # 保存数据
101 saveData(engine) 101 saveData(engine)
102 102
103 - # 关闭引擎(可选,因为SQLAlchemy引擎会自动管理连接池) 103 + # 关闭引擎(可选,因为SQLAlchemy引擎会自动管理连接池)
104 engine.dispose() 104 engine.dispose()
105 - logging.info("数据库连接已关闭。") 105 + logging.info("数据库连接已关闭。")
106 106
107 if __name__ == '__main__': 107 if __name__ == '__main__':
108 main() 108 main()
@@ -2,7 +2,7 @@ import getpass @@ -2,7 +2,7 @@ import getpass
2 import pymysql 2 import pymysql
3 import logging 3 import logging
4 4
5 -# 配置日志 5 +# 配置日志
6 logging.basicConfig( 6 logging.basicConfig(
7 level=logging.INFO, 7 level=logging.INFO,
8 format='%(asctime)s [%(levelname)s] %(message)s', 8 format='%(asctime)s [%(levelname)s] %(message)s',
@@ -14,24 +14,24 @@ logging.basicConfig( @@ -14,24 +14,24 @@ logging.basicConfig(
14 14
15 def get_db_connection_interactive(): 15 def get_db_connection_interactive():
16 """ 16 """
17 - 通过终端交互获取数据库连接参数,若按回车则使用默认值。  
18 - 返回一个连接对象。 17 + 通过终端交互获取数据库连接参数,若按回车则使用默认值。
  18 + 返回一个连接对象。
19 """ 19 """
20 - print("请依次输入数据库连接信息(直接按回车使用默认值):") 20 + print("请依次输入数据库连接信息(直接按回车使用默认值):")
21 21
22 - host = input(" 1. 主机 (默认: localhost): ") or "localhost"  
23 - port_str = input(" 2. 端口 (默认: 3306): ") or "3306" 22 + host = input(" 1. 主机 (默认: localhost): ") or "localhost"
  23 + port_str = input(" 2. 端口 (默认: 3306): ") or "3306"
24 try: 24 try:
25 port = int(port_str) 25 port = int(port_str)
26 except ValueError: 26 except ValueError:
27 - logging.warning("端口号无效,使用默认端口 3306。") 27 + logging.warning("端口号无效,使用默认端口 3306。")
28 port = 3306 28 port = 3306
29 29
30 - user = input(" 3. 用户名 (默认: root): ") or "root"  
31 - password = getpass.getpass(" 4. 密码 (默认: 312517): ") or "312517"  
32 - db_name = input(" 5. 数据库名 (默认: Weibo_PublicOpinion_AnalysisSystem): ") or "Weibo_PublicOpinion_AnalysisSystem" 30 + user = input(" 3. 用户名 (默认: root): ") or "root"
  31 + password = getpass.getpass(" 4. 密码 (默认: 12345678): ") or "12345678"
  32 + db_name = input(" 5. 数据库名 (默认: Weibo_PublicOpinion_AnalysisSystem): ") or "Weibo_PublicOpinion_AnalysisSystem"
33 33
34 - logging.info(f"尝试连接到数据库: {user}@{host}:{port}/{db_name}") 34 + logging.info(f"尝试连接到数据库: {user}@{host}:{port}/{db_name}")
35 35
36 try: 36 try:
37 connection = pymysql.connect( 37 connection = pymysql.connect(
@@ -41,29 +41,29 @@ def get_db_connection_interactive(): @@ -41,29 +41,29 @@ def get_db_connection_interactive():
41 password=password, 41 password=password,
42 database=db_name, 42 database=db_name,
43 charset='utf8mb4', 43 charset='utf8mb4',
44 - cursorclass=pymysql.cursors.DictCursor # 返回字典格式 44 + cursorclass=pymysql.cursors.DictCursor # 返回字典格式
45 ) 45 )
46 - logging.info("数据库连接成功。") 46 + logging.info("数据库连接成功。")
47 return connection 47 return connection
48 except pymysql.MySQLError as e: 48 except pymysql.MySQLError as e:
49 - logging.error(f"数据库连接失败: {e}") 49 + logging.error(f"数据库连接失败: {e}")
50 exit(1) 50 exit(1)
51 51
52 -# 获取数据库连接 52 +# 获取数据库连接
53 conn = get_db_connection_interactive() 53 conn = get_db_connection_interactive()
54 54
55 -# 获取游标 55 +# 获取游标
56 cursor = conn.cursor() 56 cursor = conn.cursor()
57 57
58 def query(sql, params=None, query_type="no_select"): 58 def query(sql, params=None, query_type="no_select"):
59 """ 59 """
60 - 执行SQL查询或操作。 60 + 执行SQL查询或操作。
61 61
62 - :param sql: SQL语句  
63 - :param params: SQL参数(可选)  
64 - :param query_type: 查询类型,默认为 "no_select"  
65 - 如果不是 "no_select",则执行 fetch 操作  
66 - :return: 如果是查询操作,返回数据列表;否则返回 None 62 + :param sql: SQL语句
  63 + :param params: SQL参数(可选)
  64 + :param query_type: 查询类型,默认为 "no_select"
  65 + 如果不是 "no_select",则执行 fetch 操作
  66 + :return: 如果是查询操作,返回数据列表;否则返回 None
67 """ 67 """
68 try: 68 try:
69 if params: 69 if params:
@@ -72,43 +72,43 @@ def query(sql, params=None, query_type="no_select"): @@ -72,43 +72,43 @@ def query(sql, params=None, query_type="no_select"):
72 else: 72 else:
73 cursor.execute(sql) 73 cursor.execute(sql)
74 74
75 - # 确保连接保持活跃 75 + # 确保连接保持活跃
76 conn.ping(reconnect=True) 76 conn.ping(reconnect=True)
77 77
78 if query_type != "no_select": 78 if query_type != "no_select":
79 data_list = cursor.fetchall() 79 data_list = cursor.fetchall()
80 conn.commit() 80 conn.commit()
81 - logging.info("查询成功,已获取数据。") 81 + logging.info("查询成功,已获取数据。")
82 return data_list 82 return data_list
83 else: 83 else:
84 conn.commit() 84 conn.commit()
85 - logging.info("操作成功,已提交事务。") 85 + logging.info("操作成功,已提交事务。")
86 except pymysql.MySQLError as e: 86 except pymysql.MySQLError as e:
87 - logging.error(f"执行SQL时出错: {e}") 87 + logging.error(f"执行SQL时出错: {e}")
88 conn.rollback() 88 conn.rollback()
89 return None 89 return None
90 90
91 def main(): 91 def main():
92 - # 示例用法 92 + # 示例用法
93 93
94 - # 执行查询操作 94 + # 执行查询操作
95 select_sql = "SELECT * FROM article LIMIT 5" 95 select_sql = "SELECT * FROM article LIMIT 5"
96 articles = query(select_sql, query_type="select") 96 articles = query(select_sql, query_type="select")
97 if articles: 97 if articles:
98 for article in articles: 98 for article in articles:
99 print(article) 99 print(article)
100 100
101 - # 执行插入操作(根据实际表结构修改) 101 + # 执行插入操作(根据实际表结构修改)
102 insert_sql = "INSERT INTO article (id, content) VALUES (%s, %s)" 102 insert_sql = "INSERT INTO article (id, content) VALUES (%s, %s)"
103 - new_article = (12345, "这是一条新的文章内容。") 103 + new_article = (12345, "这是一条新的文章内容。")
104 result = query(insert_sql, params=new_article, query_type="no_select") 104 result = query(insert_sql, params=new_article, query_type="no_select")
105 if result is None: 105 if result is None:
106 - logging.info("插入操作完成。") 106 + logging.info("插入操作完成。")
107 107
108 - # 关闭游标和连接 108 + # 关闭游标和连接
109 cursor.close() 109 cursor.close()
110 conn.close() 110 conn.close()
111 - logging.info("数据库连接已关闭。") 111 + logging.info("数据库连接已关闭。")
112 112
113 if __name__ == '__main__': 113 if __name__ == '__main__':
114 main() 114 main()