暑假闲着没事第一弹:基于Django的长江大学教务处成绩查询系统
Posted Vito-Yan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了暑假闲着没事第一弹:基于Django的长江大学教务处成绩查询系统相关的知识,希望对你有一定的参考价值。
本篇文章涉及到的知识点有:Python爬虫,mysql数据库,html/css/js基础,selenium和phantomjs基础,MVC设计模式,ORM(对象关系映射)框架,django框架(Python的web开发框架),apache服务器,linux(centos 7为例)基本操作。因此适合有以上基础的同学学习。
声明:本博文只是为了纯粹的技术交流,敏感信息本文会有所过滤,大家见谅(由于任何缘故导致长江大学教务处网站出现问题,都与本人无关)。
实现思路:在没有教务处数据接口的前提下(学生的信息安全),那也只有自己写爬虫去模拟登陆教务处,然后爬数据,为了防止教务处网站崩溃,导致爬虫失败,可以进行数据缓存,下次可以直接从自己的数据库中取数据,而我们要做的就是定时更新数据与教务处实现同步。
技术架构:centos 7 + apache2.4 + mariadb5.5 + Python2.7.5 + mod_wsgi 3.4 + django1.11
------------------------------------------------------------------------
一、Python爬虫:
1、先看一下登录入口
我们这里用FireFox进行抓包分析,我们发现登录是post上去的,并且带有7个参数,发现有验证码,此时有两种解决办法,一种是运用现在很火的技术用DL做图片识别,一种是down下来让用户自己输。第一种成本比较高。。等不忙了可以试一下,记得Python有个库叫Pillow还是PIL可以做图片识别,,暑假用TF试一下。第二种很low就不说了。
2、 还有种高大上的方式,,,可以不用管验证码,这里就不细说了,我们模拟登陆上去:
#coding:utf8 from bs4 import BeautifulSoup import urllib import urllib2 import requests import sys reload(sys) sys.setdefaultencoding(\'gbk\') loginURL = "教务处登陆地址" cjcxURL = "http://jwc2.yangtzeu.edu.cn:8080/cjcx.aspx" html = urllib2.urlopen(loginURL) soup = BeautifulSoup(html,"lxml") __VIEWSTATE = soup.find(id="__VIEWSTATE")["value"] __EVENTVALIDATION = soup.find(id="__EVENTVALIDATION")["value"] data = { "__VIEWSTATE":__VIEWSTATE, "__EVENTVALIDATION":__EVENTVALIDATION, "txtUid":"账号", "btLogin":"%B5%C7%C2%BC", "txtPwd":"密码", "selKind":"1" } header = { # "Host":"jwc2.yangtzeu.edu.cn:8080", "User-Agent":"Mozilla/5.0 (Windows NT 10.0;… Gecko/20100101 Firefox/54.0", "Accept":"text/html,application/xhtml+x…lication/xml;q=0.9,*/*;q=0.8", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding":"gzip, deflate", "Content-Type":"application/x-www-form-urlencoded", # "Content-Length":"644", "Referer":"http://jwc2.yangtzeu.edu.cn:8080/login.aspx", # "Cookie":"ASP.NET_SessionId=3zjuqi0cnk5514l241csejgx", # "Connection":"keep-alive", # "Upgrade-Insecure-Requests":"1", } UserSession = requests.session() Request = UserSession.post(loginURL,data,header) Response = UserSession.get(cjcxURL,cookies = Request.cookies,headers=header) soup = BeautifulSoup(Response.content,"lxml") print soup
接下来我们可以看到:
再来post(此代码接上面):
__VIEWSTATE2 = soup.find(id="__VIEWSTATE")["value"] __EVENTVALIDATION2 = soup.find(id="__EVENTVALIDATION")["value"] AllcjData = { "__EVENTTARGET":"btAllcj", "__EVENTARGUMENT":"", "__VIEWSTATE":__VIEWSTATE2, "__EVENTVALIDATION":__EVENTVALIDATION2, "selYear":"2017", "selTerm":"1", # "Button2":"%B1%D8%D0%DE%BF%CE%B3%C9%BC%A8" } AllcjHeader = { # "Host":"jwc2.yangtzeu.edu.cn:8080", "User-Agent":"Mozilla/5.0 (Windows NT 10.0;… Gecko/20100101 Firefox/54.0", "Accept":"text/html,application/xhtml+x…lication/xml;q=0.9,*/*;q=0.8", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Encoding":"gzip, deflate", "Content-Type":"application/x-www-form-urlencoded", # "Content-Length":"644", "Referer":"http://jwc2.yangtzeu.edu.cn:8080/cjcx.aspx", # "Cookie":, "Connection":"keep-alive", "Upgrade-Insecure-Requests":"1", } Request1 = UserSession.post(cjcxURL,AllcjData,AllcjHeader) Response1 = UserSession.get(cjcxURL,cookies = Request.cookies,headers=AllcjHeader) soup = BeautifulSoup(Response1.content,"lxml") print soup
发现不行。。。这次get的页面还是原来的页面。。。我觉得有两种原因导致这次post失败:一是asp.net的__VIEWSTATE和__EVENTVALIDATION变量导致post失败,二是一个form多个button用了js做判断,导致爬虫失败,对于动态加载的页面,普通爬虫还是不行。。。。
3、再来点高大上的用selenium(web自动化测试工具,可以模拟鼠标点击)+ phantomjs(没有界面的浏览器,比chrome和Firefox都要快)
selenium安装:pip install selenium
phantomjs安装:
(1)地址:http://phantomjs.org/download.html(我下载的是Linux 64位的)
(2)解压缩:tar -jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2 /usr/share/
(3)安装依赖:yum install fontconfig freetype libfreetype.so.6 libfontconfig.so.1
(4)配置环境变量:export PATH=$PATH:/usr/share/phantomjs-2.1.1-linux-x86_64/bin
(5)shell下输入phantomjs,如果能进入命令行,安装成功。
请忽略我的注释:
#coding:utf8 from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.common.keys import Keys import time import urllib import urllib2 import sys reload(sys) sys.setdefaultencoding(\'utf8\') driver = webdriver.PhantomJS(); driver.get("教务处登录地址") driver.find_element_by_name(\'txtUid\').send_keys(\'账号\') driver.find_element_by_name(\'txtPwd\').send_keys(\'密码\') driver.find_element_by_id(\'btLogin\').click() cookie=driver.get_cookies() driver.get("http://jwc2.yangtzeu.edu.cn:8080/cjcx.aspx") #print driver.page_source #driver.find_element_by_xpath("//input[@name=\'btAllcj\'][@type=\'button\']") #js = "document.getElementById(\'btAllcj\').onclick=function(){__doPostBack(\'btAllcj\',\'\')}" #js = "var ob; ob=document.getElementById(\'btAllcj\');ob.focus();ob.click();)" #driver.execute_script("document.getElementById(\'btAllcj\').click();") #time.sleep(2) #让操作稍微停一下 #driver.find_element_by_link_text("全部成绩").click() #找到‘登录’按钮并点击 #time.sleep(2) #js1 = "document.Form1.__EVENTTARGET.value=\'btAllcj\';" #js2 = "document.Form1.__EVENTARGUMENT.value=\'\';" #driver.execute_script(js1) #driver.execute_script(js2) #driver.find_element_by_name(\'__EVENTTARGET\').send_keys(\'btAllcj\') #driver.find_element_by_name(\'__EVENTARGUMENT\').send_keys(\'\') #js = "var input = document.createElement(\'input\');input.setAttribute(\'type\', \'hidden\');input.setAttribute(\'name\', \'__EVENTTARGET\');input.setAttribute(\'value\', \'\');document.getElementById(\'Form1\').appendChild(input);var input = document.createElement(\'input\');input.setAttribute(\'type\', \'hidden\');input.setAttribute(\'name\', \'__EVENTARGUMENT\');input.setAttribute(\'value\', \'\');document.getElementById(\'Form1\').appendChild(input);var theForm = document.forms[\'Form1\'];if (!theForm) { theForm = document.Form1;}function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } }__doPostBack(\'btAllcj\', \'\')" #js = "var script = document.createElement(\'script\');script.type = \'text/javascript\';script.text=\'if (!theForm) { theForm = document.Form1;}function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); }}\';document.body.appendChild(script);" #driver.execute_script(js) driver.find_element_by_name("Button2").click() html=driver.page_source soup = BeautifulSoup(html,"lxml") print soup tables = soup.findAll("table") for tab in tables:
for tr in tab.findAll("tr"):
print "--------------------"
for td in tr.findAll("td")[0:3]:
print td.getText()
现在只能拿到必修课成绩。。。。。因为全部成绩是ASP生成的js触发的。。。而不是直接submit。。。正在寻找解决的办法。下面开始我们数据库的设计。。。
二、Mariadb学生数据库设计,,,这里引用了我们SQL server数据库原理上机的内容。。。
我的建库语句:
create database jwc character set utf8; use jwc; create table Student( Sno char(9) primary key, Sname varchar(20) unique, Sdept char(20), Spwd char(20) ); create table Course( Cno char(2) primary key, Cname varchar(30) unique, Credit numeric(2,1) ); create table SC( Sno char(9) not null, Cno char(2) not null, Grade int check(Grade>=0 and Grade<=100), primary key(Sno,Cno), foreign key(Sno) references Student(Sno), foreign key(Cno) references Course(Cno) );
三、Python web环境的搭建(LAMP):
1、因为这次选的http服务器时apache,所以要安装mod_wsgi(python通用网关接口)来实现apache和Python程序的交互。。。如果用nginx就要安装配置uwsgi。。。类似java的servlet和php的php-fpm。
安装:yum install mod_wsgi
配置:vim /etc/httpd/conf/httpd.conf
这个配置花费了我不少心思和时间。。。网上的有很多错误。。。最标准的Python web django开发配置。。。拿走不谢。
#config python web LoadModule wsgi_module modules/mod_wsgi.so <VirtualHost *:8080> ServerAdmin root@Vito-Yan ServerName www.yuol.onlne ServerAlias yuol.online Alias /media/ /var/www/html/jwc/media/ Alias /static/ /var/www/html/jwc/static/ <Directory /var/www/html/jwc/static/> Require all granted </Directory> WSGIScriptAlias / /var/www/html/jwc/jwc/wsgi.py # DocumentRoot "/var/www/html/jwc/jwc" ErrorLog "logs/www.yuol.online-error_log" CustomLog "logs/www.yuol.online -access_log" common <Directory "/var/www/html/jwc/jwc"> <Files wsgi.py> AllowOverride All Options Indexes FollowSymLinks Includes ExecCGI Require all granted </Files> </Directory> </VirtualHost>
2、下面来安装django。。。pip install django。。。。搞定。
查看django的版本:python -m django --version
官网地址:https://www.djangoproject.com
新建项目:django-admin.py startproject jwc(我的是在/var/www/html下建的,apache的网站根目录)
3、apcehe的配置:就不贴了,把上面的jwc改成jwc2,然后端口改成9000,然后Listen 9000(为什么用9000呢,第一个项目jwc用的是8080,django自带的服务器用python manage.py runserver可以开启,它的默认端口是8000,所以不用8000,以免冲突,我的jsp项目的tomcat服务器用的是9090端口,以免冲突,最好不用,常见的就9000端口了,其他不敢乱用)。
4、 settings.py的配置:
DEBUG = True 调试开启
ALLOWED_HOSTS = [\'192.168.47.128\'] 添加主机
5、wsgi.py配置,不要问我为什么。。。我也不知道。。用apache服务器启动django项目这样做就行了。。。如果用django自带的server就不用改了。。。
""" WSGI config for jwc2 project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ """ #import os #from django.core.wsgi import get_wsgi_application #os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jwc2.settings") #application = get_wsgi_application() import os from os.path import join,dirname,abspath PROJECT_DIR = dirname(dirname(abspath(__file__))) import sys sys.path.insert(0,PROJECT_DIR) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jwc2.settings") from django.core.wsgi import get_wsgi_application application = get_wsgi_application()
然后就大功告成。。。。Python web环境算是搭建完成。。。
四、开启我们的第一个django项目应用。。。
1、新建成绩查询的应用 python manage.py startapp cjcx
2、在settings.py中添加应用
3、在views.py里写下写下第一行代码。。。。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.http import HttpResponse from django.shortcuts import render # Create your views here. def index(request): return HttpResponse("Hello,YUOL!")
4、在urls.py下添加url
from django.conf.urls import url from django.contrib import admin import cjcx.views as cj urlpatterns = [ url(r\'^admin/\', admin.site.urls), url(r\'^cjcx/\',cj.index), ]
5、Hello,YUOL!
6、刚刚上面的4还可以换种方法。。。。
在cjcx应用下面新建urls.py
from django.conf.urls import url from . import views urlpatterns = [ url(r\'^$\', views.index), ]
修改jwc2下面的urls.py(项目根路径)
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r\'^admin/\', admin.site.urls), url(r\'^cjcx/\', include(\'cjcx.urls\')), ]
7、写前端页面。。。。。
在cjcx应用下面新建templates文件夹放我们的html文件(请暂时忽略动态加载的代码,我懒得删了)
<html> <head> <title>YUOL成绩查询系统</title> <style type="text/css"> #border { margin: 0 auto; width: 500px; min-height: 500px; background-color: #FFFFFF; border: 1px solid #000000; } #button {} </style> </head> <body style="text-align:center"> <div id="border"> <h1>YUOL成绩查询系统</h1><br/> <form action="" method="post"> 账号: <input type="text" id="xuehao" name="Sno" /><br/> 密码: <input type="password" id="pwd" name="Spwd" /><br/><br/> <input type="submit" value="查询" id="submit" /><br/> <div style="text-align:left;padding-left:50px;"> -----------------------------------------------------------<br/> 姓名:{{ student.Sname }}<br/> 学号:{{ student.Sno}}<br/> 班级:{{ student.Sdept }}<br/> </div> -----------------------------------------------------------<br/> <div> <br> <div style="display:inline-block;width:150px;"> 科目:<br> {{ course.Cname }} </div> <div style="display:inline-block;width:150px;"> 成绩:<br> {{ sc.Grade }} </div> <div style="display:inline-block;width:150px;"> 学分:<br> {{ course.Credit }} </div> </div> </form> </div> </body> </html>
修改views.py:
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.http import HttpResponse from django.shortcuts import render # Create your views here. def index(request): return render(request, \'jwcjcx.html\')
然后就成这样了。。。。。
8、根据jwc数据库设计Models。。。。
django默认支持的是sqllite,,现在换成 mariadb,修改settings.py
DATABASES = { \'default\': { # \'ENGINE\': \'django.db.backends.sqlite3\', # \'NAME\': os.path.join(BASE_DIR, \'db.sqlite3\'), \'ENGINE\': \'django.db.backends.mysql\', \'NAME\': \'jwc2\', \'USER\':\'root\', \'PASSWORD\':\'你的密码\', \'HOST\':\'localhost\', \'PORT\':\'3306\', } }
9、去models.py下面建表吧。。。。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models # Create your models here. class Student(models.Model): Sno=models.CharField(max_length=9,primary_key=True) Sname=models.CharField(max_length=20,unique=True) Sdept=models.CharField(max_length=20) Spwd=models.CharField(max_length=20) class Course(models.Model): Cno=models.CharField(max_length=2,primary_key=True) Cname=models.CharField(max_length=30,unique=True) Credit=models.DecimalField(max_digits=2, decimal_places=1) class SC(models.Model): Sno=models.CharField(max_length=9) Cno=models.CharField(max_length=2) Grade=models.IntegerField() def __unicode__(self): return self.Sno
这种ORM免去了写sql语句的麻烦,直接把表封装成一个类继承model.Model,查询字段直接‘点’操作。。。很方便。
然后生成数据模型表:python manage.py makemigrations
再将数据表迁移到mariadb数据库:python manage.py migrate
生成cjcx_三个表,其他是django默认的不用管,另外数据库要自己先建(create database jwc2 charset=utf8;)
10、使用django admin做数据管理。。。。Admin真心好用这是django框架最显著的一个优势。。。
创建用户:python manage.py createsuperuser
然后在主机后面加/admin就可以登录。。。我们发现它的css和img丢失了
解决办法:
在jwc2下面建一个静态文件夹:static
修改settings.py。。。在最后一行添加STATIC_ROOT = "/var/www/html/jwc2/static/",LANGUAGE_CODE = \'zh-Hans\'(改成中文的admin)
执行命令 :python manage.py collectstatic
上面apache的静态文件配置取消注释。。。
这样进去看不到数据表,需要修改admin.py引入models
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.contrib import admin import models # Register your models here. admin.site.register(models.Student) admin.site.register(models.Course) admin.site.register(models.SC)
可以直接操作数据库了。。。django的强大之处。。
11、下面开始我们最重要的业务逻辑。。
数据入库(MVC中的M,models):我这里把Course表的Cno给删了,把SC表的Cno换成Cname了。。。和上面有所不同,只需要把库删了重新生成数据表即可。。。
#encoding=utf-8 from bs4 import BeautifulSoup from selenium import webdriver from selenium.webdriver.common.keys import Keys import MySQLdb import time import urllib import urllib2 import sys reload(sys) sys.setdefaultencoding(\'utf8\') conn= MySQLdb.connect( host=\'localhost\', port = 3306,以上是关于暑假闲着没事第一弹:基于Django的长江大学教务处成绩查询系统的主要内容,如果未能解决你的问题,请参考以下文章