# Python_final_project **Repository Path**: chen_ke_715/python_final_project ## Basic Information - **Project Name**: Python_final_project - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-01-22 - **Last Updated**: 2021-01-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## Python期末项目之2020年考研调剂信息数据报告 [pythonanywhere部署地址](http://colleenproject.eu.pythonanywhere.com/) ![功能框架结构图](https://gitee.com/chen_ke_715/python_final_project/raw/master/image/functional_structure.JPG) ## 问题及解决方案表述 - 加/价值主张宣言 Python项目中的数据集是针对国内考研网爬取调剂大学的专业等信息,可以分析对比调剂最多的专业获学校信息,且能够通过可视化直观地对比专业与学校,获取学校往年接受的专业和名额、调剂考生的背景情况等关键信息。 [数据集来源](https://www.kesci.com/mw/project/60066af97ed5ab0015f0a68b) - 问题:许多考研党或者准备考研的同学对调剂学校信息关注度高,但是缺乏信息过于庞杂,信息不对称,缺乏统一且便捷的信息输入。 - 解决方案: 出发点:通过python从国内考研调剂信息中进行爬取,且依据[中国考研网]( http://www.chinakaoyan.com/)权威网站获取的数据,整合处理后将其进行数据可视化后呈现在web页面,这样可以让他们更加直观地对比专业与学校,分析调剂大学的专业信息,为其选择并进入合适且更好的读研平台提供一定的参考性。 技术点:利用pycharm将数据集进行筛选、转换后进行数据可视化,然后通过python flask框架将数据呈现在web,方便考研党/准备考研的同学们参考。最后,通过pythonanywher部署web项目,用户即可直接通过网址查看数据报告。 ## 编程功能基本描述 ### 数据集处理-文本处理 ``` #导入模块 import pandas as pd import numpy as np df_info = pd.read_csv('大学信息_整理后.csv',encoding='utf-8') #读取整理后的csv文件 df_info.head() def transform_attr(x): #转换学校属性 if '211' in x and '985' not in x: return 211 elif '985' in x: return '985' else: return '双非' def transform_type(x): #转换学校类型 if '理工类' in x or '理工类院校' in x or '理工科' in x or '理工、教学研究型大学' in x or '理工类\n[4]' in x or '理工\n[6]' in x: return '理工' elif '综合类' in x or '综合性大学\n[3]' in x or '综合类(应用型大学)' in x or '综合、研究教学型大学' in x or '综合类大学' in x or '综合师范类' in x: return '综合' elif '师范类院校' in x or '师范类' in x or '师范类(综合类)' in x or '师范(综合)' in x or '地方师范院校' in x: return '师范' elif '农林类' in x or '农业类' in x: return '农林' elif '医药类' in x: return '医药' elif '民族类' in x: return '民族' elif '未知' in x or '国有企业' in x or '科技型企业' in x or '公立大学' in x: return '其他' else: return x # 筛选字段 df_info= df_info[['school','province','school_level','school_types']] # 处理省份数据 df_info.loc[(df_info.school=='北京工商大学')&(df_info.province=='未知'), 'province']= '北京' df_info.loc[(df_info.school=='哈尔滨工程大学')&(df_info.province=='未知'), 'province']= '哈尔滨' df_info.loc[(df_info.school=='江苏大学')&(df_info.province=='未知'), 'province']= '江苏' df_info.loc[(df_info.school=='青岛大学')&(df_info.province=='未知'), 'province']= '山东' df_info.loc[(df_info.school=='北京石油化工学院')&(df_info.province=='未知'), 'province']= '北京' df_info.loc[(df_info.school=='齐鲁工业大学')&(df_info.province=='未知'), 'province']= '山东' df_info.loc[(df_info.school=='江苏科技大学')&(df_info.province=='未知'), 'province']= '江苏' df_info.loc[(df_info.school=='浙江农林大学')&(df_info.province=='未知'), 'province']= '浙江' df_info.loc[(df_info.school=='燕山大学')&(df_info.province=='未知'), 'province']= '河北' df_info.loc[(df_info.school=='福州大学')&(df_info.province=='未知'), 'province']= '福建' df_info.loc[(df_info.school=='内蒙古科技大学')&(df_info.province=='未知'), 'province']= '内蒙古' #删除重复数据 df_info = df_info.drop_duplicates() df_info.head() # 查看缺失数据 df_all.isnull().sum() ``` - 数据可视化 ``` import pyecharts from pyecharts.charts import Line from pyecharts import options as opts from pyecharts.charts import Pie from pyecharts.globals import ThemeType from pyecharts.charts import Bar from pyecharts.charts import Map #调剂信息发布时间走势图 line1 = Line(init_opts=opts.InitOpts(width='1280px',height='600px')) line1.add_xaxis(pub_time.index.tolist()) line1.add_yaxis('发布热度',pub_time.values.tolist(), areastyle_opts=opts.AreaStyleOpts(opacity=0.5), label_opts=opts.LabelOpts(is_show=True)) line1.set_global_opts(title_opts=opts.TitleOpts(title='调剂信息发布时间走势图'), toolbox_opts=opts.ToolboxOpts(), visualmap_opts=opts.VisualMapOpts()) line1.render_notebook() #学校层次 level_perc = df_all.school_level.value_counts() / df_all.school_level.value_counts().sum() display(level_perc) level_perc = np.round(level_perc * 100 ,2) level_perc #绘制饼图 pie1 = Pie(init_opts=opts.InitOpts(width='500px',height='600px')) pie1.add("", [*zip(level_perc.index, level_perc.values)], radius=["40%","75%"]) pie1.set_global_opts(title_opts=opts.TitleOpts(title='学校层次分布'), legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"), toolbox_opts=opts.ToolboxOpts()) pie1.set_series_opts(label_opts=opts.LabelOpts(formatter="{c}%")) pie1.render_notebook() #学校类型 type_perc = df_all.school_types.value_counts() / df_all.school_types.value_counts().sum() type_perc = np.round(type_perc*100,2) pie2 = Pie(init_opts=opts.InitOpts(theme=ThemeType.WONDERLAND, width='1280px', height='650px')) pie2.add("", [*zip(type_perc.index, type_perc.values)], radius=["40%","75%"]) pie2.set_global_opts(title_opts=opts.TitleOpts(title='学校类型分布'), legend_opts=opts.LegendOpts(orient="vertical", pos_top="15%", pos_left="2%"), toolbox_opts=opts.ToolboxOpts()) pie2.set_series_opts(label_opts=opts.LabelOpts(formatter="{c}%")) pie2.render_notebook() # 条形图 bar1 = Bar(init_opts=opts.InitOpts(width='1280px', height='1000px')) bar1.add_xaxis(province_num.index.tolist()) bar1.add_yaxis("省份", province_num.values.tolist()) bar1.set_global_opts(title_opts=opts.TitleOpts(title="调剂信息发布数省份分布"), toolbox_opts=opts.ToolboxOpts(), visualmap_opts=opts.VisualMapOpts(max_=110)) bar1.set_series_opts(label_opts=opts.LabelOpts(position='right')) # 标签 bar1.reversal_axis() bar1.render_notebook() c = Map(init_opts=opts.InitOpts(width='800px', height='750px')) c.add('',[list(z) for z in zip(province_num.index.tolist(), province_num.values.tolist())], 'china') c.set_global_opts(title_opts=opts.TitleOpts('调剂信息省份分布地图'), toolbox_opts=opts.ToolboxOpts(is_show=True), visualmap_opts=opts.VisualMapOpts(max_=110)) c.render_notebook() #词云 import jieba.analyse #cmd pip install jieba from pyecharts.charts import WordCloud from pyecharts.globals import SymbolType,ThemeType word1 = WordCloud(init_opts=opts.InitOpts(width='1280px', height='750px')) word1.add("", [*zip(key_words.words, key_words.num)], word_size_range=[20, 200], shape='diamond') word1.set_global_opts(title_opts=opts.TitleOpts(title="调剂专业分布"), toolbox_opts=opts.ToolboxOpts()) word1.render_notebook() ``` ### web项目启动文件app.py - 导入所需模块(在pycharm运行时需要安装相应模块,也可通过cmd命令pip install package模块包) ``` from flask import Flask, render_template, request, flash, redirect, url_for, session from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from models import User import hashlib import pymysql import numpy as np import pandas as pd import jieba.analyse import logging ``` - Mysql数据库 ``` pymysql.install_as_MySQLdb() engine = create_engine("mysql://root:123456@127.0.0.1/work?charset=utf8mb4") dbsession = sessionmaker(bind=engine) db = dbsession() ``` - def函数调用及实现web页面 ``` def load_data(): df = pd.read_csv('考研调剂数据-3.09.csv', encoding='utf-8') df_info = pd.read_csv('大学信息_整理后.csv', encoding='utf-8') return df, df_info def create_app(): app = Flask(__name__) app.config['SECRET_KEY'] = "as11sad" return app app = create_app() @app.route("/") def home(): return render_template('home.html') #用户登陆页面 @app.route('/login/', methods=["GET", "POST"]) def login(): try: if request.method == "GET": session.clear() return render_template("login.html") name = request.form.get("name") password = request.form.get("password") if name == "": flash("用户名不能为空") return redirect(url_for('login')) if password == "": flash("密码不能为空") return redirect(url_for('login')) user = db.query(User).filter_by(name=name).all() if user == []: flash("该用户不存在,请先注册。") return redirect(url_for('login')) md5 = hashlib.md5() md5.update(password.encode("utf-8")) if md5.hexdigest() != user[0].password: flash("密码错误,请重新输入密码。") return redirect(url_for('login')) flash("登录成功!") session["id"] = user[0].id session["name"] = name app.logger.info('%s用户登录成功' % name) return redirect(url_for('index')) except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/index/', methods=["GET", "POST"]) def index(): try: user_id = session.get("id") name = session.get("name") if name == None: return redirect(url_for('home')) df, df_info = load_data() df_info = df_info.drop_duplicates() # 删除重复数据 df_2020 = df[df['time'].str.contains('2020')].copy() df_all = pd.merge(df_2020, df_info, how='left', on='school') df_all = df_all[['school', 'name', 'time', 'province', 'school_level', 'school_types']] level_perc = df_all.school_level.value_counts() / df_all.school_level.value_counts().sum() level_perc = np.round(level_perc * 100, 2) index = level_perc.index.tolist() data = level_perc.tolist() datas = [] for k,v in zip(index, data): data_d = {} data_d["value"] = v data_d["name"] = k datas.append(data_d) print(datas) app.logger.info('%s用户进入主页' % name) return render_template('index.html', index=index, datas=datas) except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/register/', methods=["GET", "POST"]) #用户注册 def register(): try: if request.method == "GET": return render_template('register.html') name = request.form.get("name") password = request.form.get("password") re_password = request.form.get("re_password") if name == "": flash("用户名不能为空") return redirect(url_for('register')) if password == "": flash("密码不能为空") return redirect(url_for('register')) if re_password == "": flash("确认密码不能为空") return redirect(url_for('register')) if len(password) <= 6 or len(password) >= 18: flash("密码长度必须大于6位小于18位") return redirect(url_for('register')) if password != re_password: flash("两次密码输入不一致") return redirect(url_for('register')) user = db.query(User).filter_by(name=name).all() if user != []: flash("改用户名已被占用") return redirect(url_for('register')) md5 = hashlib.md5() md5.update(password.encode("utf-8")) user = User(name=name, password=md5.hexdigest()) db.add(user) db.commit() flash("注册成功!") app.logger.info('%s用户注册成功' % name) return redirect(url_for('index')) except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/logout/', methods=["GET", "POST"]) def logout(): try: user_id = session["id"] name = session["name"] session.clear() flash("注销成功!") app.logger.info('%s用户注销成功' % name) return redirect(url_for('home')) except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/school_level/', methods=["GET", ]) def school_level(): try: user_id = session.get("id") name = session.get("name") if name == None: return redirect(url_for('home')) app.logger.info('%s用户查看学校等级模块成功' % name) return render_template('index.html') except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/school_type/', methods=["GET", ]) def school_type(): try: user_id = session.get("id") name = session.get("name") if name == None: return redirect(url_for('home')) df, df_info = load_data() df_info = df_info.drop_duplicates() # 删除重 df_2020 = df[df['time'].str.contains('2020')].copy() df_all = pd.merge(df_2020, df_info, how='left', on='school') df_all = df_all[['school', 'name', 'time', 'province', 'school_level', 'school_types']] type_perc = df_all.school_types.value_counts() / df_all.school_types.value_counts().sum() type_perc = np.round(type_perc * 100, 2) index = type_perc.index.tolist() data = type_perc.values.tolist() datas = [] for k, v in zip(index, data): data_d = {} data_d["value"] = v data_d["name"] = k datas.append(data_d) print(datas) app.logger.info('%s用户查看学校类型模块成功' % name) return render_template('school_type.html', index=index, datas=datas) except Exception as e: app.logger.warning(e) app.logger.error(e) @app.route('/num_dis_info/', methods=["GET", ]) def num_dis_info(): try: user_id = session.get("id") user_name = session.get("name") if user_name == None: return redirect(url_for('home')) df, df_info = load_data() df_info = df_info.drop_duplicates() # 删除重 df_2020 = df[df['time'].str.contains('2020')].copy() df_all = pd.merge(df_2020, df_info, how='left', on='school') df_all = df_all[['school', 'name', 'time', 'province', 'school_level', 'school_types']] province_num = df_all.province.value_counts().sort_values() name = province_num.index.tolist() num = province_num.values.tolist() datas = [] for k,v in zip(name, num): l = [] l.append(v) l.append(k) datas.append(l) print(datas) app.logger.info('%s用户查看调剂信息发布数省份成功' % user_name) return render_template('num_dis_info.html', datas=datas) except Exception as e: app.logger.warning(e) app.logger.error(e) #日志管理系统 @app.route('/log/') def hello_world(): with open(r'flask.log', 'r', encoding='utf-8') as f: data = f.readlines() datas = [] for i in data: d = {} detail = i.split('-') d["time"] = detail[0] + "-" + detail[1] + "-" + detail[2] d["level"] = detail[3] d["function"] = detail[5] d["line"] = detail[6] d["message"] = detail[7][0:-1] print(d) datas.append(d) return render_template('log.html', datas=datas) if __name__ == '__main__': app.debug = True handler = logging.FileHandler('flask.log', encoding='UTF-8') handler.setLevel(logging.INFO) # 设置日志记录最低级别为DEBUG,低于DEBUG级别的日志记录会被忽略,不设置setLevel()则默认为NOTSET级别。 logging_format = logging.Formatter( '%(asctime)s-%(levelname)s-%(filename)s-%(funcName)s-%(lineno)s-%(message)s') handler.setFormatter(logging_format) app.logger.addHandler(handler) app.run() ``` ## 云端项目部署的基本描述 - 参考文档 [手把手教你在pythonanywhere上部署Flask项目](https://www.jianshu.com/p/5d120cfd386e) ### 各个HTML页面简要介绍 此网站主题为“2020年考研调剂信息数据报告”,主要通过数据可视化表格、数据交互更直观、灵活地呈现数据。一共有十个页面:注册页面、注册成功页面、登陆页面、六个数据可视化图表页面、注销页面。以下提供部分URL链接。 - [登录页面](http://colleenproject.eu.pythonanywhere.com/) ☺经过测试,请您按照注册-登陆的顺序查阅报告。点击右上角,您可以进行[注册](http://colleenproject.eu.pythonanywhere.com/register/),注册成功后进入[登陆页面](http://colleenproject.eu.pythonanywhere.com/login/),输入账号密码即可报告首页。 进入首页后,您可以点击‘查看图表’查看其他数据可视化图表。 退出时,请您点击右上角的‘注销’按钮,出现注销成功后即您已退出登陆状态。 有时候需要刷新后重新注册才不会出现error页面喔! - [调剂信息发布时间走势图](http://colleenproject.eu.pythonanywhere.com/time_chart/) 考研调剂信息发布数量有两个明显的高峰值 原因: 2020年考研成绩公布时间|2月左右 ---|--- 2020年考研国家线公布时间|3月下旬-4月初 2020年考研复试调剂时间|集中于3-4月份 (1)二月中下旬考研成绩公布阶段,所以各高校、各媒体陆续发布与调剂相关的信息; (2)四月份左右,各高校陆续进入考研复试调剂阶段,有关调剂的信息也陆续增多。 - [学校层次分布图](http://colleenproject.eu.pythonanywhere.com/index/) 接受调剂学校层次以双非学校为主,211学校其次,985学校最后。 - [学校类型分布](http://colleenproject.eu.pythonanywhere.com/school_type/) 接受调剂学校类型排行前三的为:理工类、综合类以及师范类。 - [调剂信息发布数省份分布](http://colleenproject.eu.pythonanywhere.com/num_dis_info/) 调剂信息发布数量最多的省份/市为北京市,其次是辽宁省,再次是广东省。 - [调剂省份分布地图](http://colleenproject.eu.pythonanywhere.com/map/) 同时,我们也可以发现北京市、辽宁省、广东省恰恰接受调剂院校分布最多的地区。 - [调剂专业分布词云](http://colleenproject.eu.pythonanywhere.com/word_cloud/) 调剂专业排行前三的是材料科学与工程、工程、材料。 原因:化工材料专业考生与招生人数众多,所以造成学校考研名额不平衡,进行考研调剂。 ### 心得 确保所使用模块都安装在所属环境下,python安装版本应为python3以上,python2.7容易出现安装错误,不停地报错T_T,pip安装命令行不通时,多去百度,可以直接复制ERROR搜索,很多有效的经验贴,然后部署时记得确保文件夹路径正确,所需要的虚拟环境已经创建完毕,Mysql数据库也应跟本地保持一致。最后就可以部署了,部署不成功记得常看操作日志文件,找寻错误,一般都是因为模块漏装导致报错。 ## 数据传递描述 ### app.py 引用了hashlib/pymysql/numpy/pandas/jieba.analyse/logging/pyecharts模块 - 登陆页面 ``` def login(): try: if request.method == "GET": session.clear() return render_template("login.html") name = request.form.get("name") password = request.form.get("password") if name == "": flash("用户名不能为空") return redirect(url_for('login')) if password == "": flash("密码不能为空") return redirect(url_for('login')) user = db.query(User).filter_by(name=name).all() if user == []: flash("该用户不存在,请先注册。") return redirect(url_for('login')) md5 = hashlib.md5() md5.update(password.encode("utf-8")) if md5.hexdigest() != user[0].password: flash("密码错误,请重新输入密码。") return redirect(url_for('login')) flash("登录成功!") session["id"] = user[0].id session["name"] = name app.logger.info('%s用户登录成功' % name) return redirect(url_for('index')) except Exception as e: app.logger.warning(e) app.logger.error(e) ``` - 读入数据,放上数据图表代码,并实现将其呈现在html页面中。 ``` def school_type(): try: user_id = session.get("id") name = session.get("name") if name == None: return redirect(url_for('home')) df, df_info = load_data() df_info = df_info.drop_duplicates() # 删除重复 df_2020 = df[df['time'].str.contains('2020')].copy() df_all = pd.merge(df_2020, df_info, how='left', on='school') df_all = df_all[['school', 'name', 'time', 'province', 'school_level', 'school_types']] type_perc = df_all.school_types.value_counts() / df_all.school_types.value_counts().sum() type_perc = np.round(type_perc * 100, 2) index = type_perc.index.tolist() data = type_perc.values.tolist() datas = [] for k, v in zip(index, data): data_d = {} data_d["value"] = v data_d["name"] = k datas.append(data_d) print(datas) app.logger.info('%s用户查看学校类型模块成功' % name) return render_template('school_type.html', index=index, datas=datas) except Exception as e: app.logger.warning(e) app.logger.error(e) ``` - 前端与后端的相互传值。将csv数据表格生成表单html,读取数据图表的值,返回html/title/数据图表/数据表格 ``` def load_data(): df = pd.read_csv('考研调剂数据-3.09.csv', encoding='utf-8') df_info = pd.read_csv('大学信息_整理后.csv', encoding='utf-8') return df, df_info ``` 日志管理系统位于flask.log文件 ## 学习/实践心得总结及感谢 ### 获得 - 学会利用百度搜索 - 学会寻找有效经验贴 - 学会询问前辈 - 和鲸社区有许多有效数据集以及项目经验,还有制图小技巧,可以多多学习 ### 挑战 - 云端部署不断踩坑,但是通过百度以及前人经验贴顺利部署 - 跑代码是缩进问题很常见,要多多注意 ### 经验帖分享 - [Mysql数据库连接问题](https://blog.csdn.net/u012604745/article/details/80632860) - [阿里云服务器-Python的web部署到服务器](https://blog.csdn.net/liul99/article/details/106199829?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control) - [Python3数据可视化模块Matplotlib](https://blog.csdn.net/asialee_bird/article/details/79585869) - [谈谈Python实战数据可视化之matplotlib模块(基础篇)](https://blog.51cto.com/12731497/2154195) - [pythonanywhere部署资料](https://www.jianshu.com/p/058b778500de) - [Pythonanywhere 部署 Django](https://www.jianshu.com/p/937694906ec0) - [学习flask的番外5之Pythonanywhere部署Flask项目](https://www.jianshu.com/p/9974701034ef) - [python 封装自己的log日志系统](https://blog.csdn.net/mr_hui_/article/details/103517031) - [PythonWEB框架之Flask](https://www.so.com/link?m=bL4khQ9nh1q2rDQkko7XDnf9uOG02iCbrdKlsH8mOlR5UT8NX%2BNXx93PnbKEcyGOSXGeCmUtWIlQznZPMYBV9MYalzroJmrhbHhgCcuj2PYfNcagA%2F%2B%2FrzszbJ37WedUUVRiD23IuI5y7bkbjcOT1YI0zEzfSklwaRRtbY3tpzMrEuV%2FRnD%2BJPkD%2B8cU%3D) - [python-def函数](https://www.cnblogs.com/derezzed/articles/8119592.html)