SG刷题工具

开发步骤-软件官网

  1. 登陆过程
  2. 对比答案
  3. 提交答案
  4. 尾声

登陆过程

在放暑假前萌生了这个想法,于是我开始思考我该如何实现在这个功能。最开始开发的初心是为了可以使做题目的时候可以方便点(现在当我没说)。

最开始的时候比较没有方向,不知道要先实现什么功能,于是我重新分析了一下网页的代码。(希望你们看得懂)

登陆页面.png

代码界面.png

我们知道想要刷题最重要的步骤就是登陆(没有登陆玩个鸡),现在可以很清晰的看到登陆界面的两个文本域。

因为表单需要递交给服务器,服务器验证通过后我们才算登陆成功,所以我们要先看这个表单是用post还是用get提交的(在network里面或者是表单标签里可以看到)

表单标签

表单标签.png

此为登陆前的页面

查看登陆页面表单递交方式.png

此为登陆后的登陆页面

登陆后的页面.png

选择章节页面.png

我们可以看到登陆前是get,登陆之后变成302状态,选择章节页面则为get

所以得知这个表单是post到服务器的,但是单单知道这个也只是完成了登陆思路的1/3嗯? ,我们还需要知道表单域中的其他表单对象的value,所以回到源代码分析。

可疑的类名.png

一个很可疑的class名称

隐藏域.png

果不其然,有3个隐藏域对象,看一下后面的value,是不是一头雾水呆了(没关系我也看不懂)我们只需要知道隐藏域是给服务器看到,不是给人看的,所以这些信息只有服务器才看得懂。

我想了想会不会这些值是随机生成的,于是我换了几个浏览器试了一下

一模一样.png

发现是一样的,这时候我就有点迷糊了,难道大家的这些值都是一模一样的吗?我尝试登陆了一下

蒙了.png

嗯??噗!我有点懵了,可是这一看发现就是这几个的值变化了 __VIEWSTATE ,__VIEWSTATEGENERATOR ,__EVENTVALIDATION 随后我重新用其他人的账号登陆了一下多个账号尝试登陆.png

发现这个__VIEWSTATEGENERATOR是每个人的标识号,其他的__VIEWSTATE__EVENTVALIDATION 都是不同的并不简单

在知道了这些关键的信息之后就可以着手发开了,本来是想用C#来写的,但是部分依赖要自己写,自己在这方面还有点欠缺,所以用了除了不能生小孩其他都能干的万能Python来写。

PS.在这里说一下,微软的轻量级IDE Visual Studio Code真的是好用

依赖.png

安装好插件后就开始写代码啦!我的python是3.7.3版本的

request是Python中的网络请求的依赖,我们先导入到项目

1
2
3
4
5
6
7
import requests

yname=input('请输入你的姓名:')
ypass=input('请输入你的学号')
url="http://ks.zj.cn/Default.aspx" #登陆地址
S = requests.Session() #创建一-个session会话, 用来保持连接
res1= S.get("http://ks.zj.cn/Default.aspx") #连接到登录页面

因为需要获取res1这个内容中的几个隐藏域的内容,所以我们要用到正则表达式在python中最好用的正则表达式依赖是etree,所以我们也要导入

1
2
3
4
5
6
7
8
9
import requests
from lxml import etree

yname=input('请输入你的姓名:')
ypass=input('请输入你的学号')
url="http://ks.zj.cn/Default.aspx" #登陆地址
S = requests.Session() #创建一-个session会话, 用来保持连接
res1= S.get("http://ks.zj.cn/Default.aspx") #连接到登录页面
html = etree.HTML(res1.text) #把登陆页面的内容保存到htm1中

我们要用到的是etree的lxml以及stree本身,正则表达式和通配符的意义差不多,我们需要到网页代码中进行查找,需要知道隐藏域的id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
from lxml import etree

yname=input('请输入你的姓名:')
ypass=input('请输入你的学号')
url="http://ks.zj.cn/Default.aspx" #登陆地址
S = requests.Session() #创建一-个session会话, 用来保持连接
res1= S.get("http://ks.zj.cn/Default.aspx") #连接到登录页面
html = etree.HTML(res1.text) #把登陆页面的内容保存到htm1中


state=html.xpath('//*[@id="__VIEWSTATE"]')[0].get('value')
sta = html.xpath('//*[@id="__EVENTVALIDATION"]')[0].get('value')
dation = html.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0].get('value')

你是不是已经忘记了一个细节,就是怎么用姓名和学号啊

我们需要一个字典来保存这些东西,还要把这些东西交给服务器了,这些requests库已经有现成的代码可以使用了所以代码变成这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
from lxml import etree

yname=input('请输入你的姓名:')
ypass=input('请输入你的学号')
url="http://ks.zj.cn/Default.aspx" #登陆地址
S = requests.Session() #创建一-个session会话, 用来保持连接
res1= S.get("http://ks.zj.cn/Default.aspx") #连接到登录页面
html = etree.HTML(res1.text) #把登陆页面的内容保存到htm1中

state=html.xpath('//*[@id="__VIEWSTATE"]')[0].get('value')
sta = html.xpath('//*[@id="__EVENTVALIDATION"]')[0].get('value')
dation = html.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0].get('value')

n_login = {
'__VIEWSTATE':state,
'__EVENTVALIDATION':sta,
'__VIEWSTATEGENERATOR':dation,
"xm":yname,
"xh":ypass,
'Button1':'登录', #模拟点击登陆按钮
}
r = S.post(url,headers=headers,data=n_login) #把字典内容post到url

你们可能会好奇,这个headers到底是什么,这些在浏览器中可以看到

headers.png

就是这些东西,他们代表着向服务器宣誓我是某某某浏览器,并不是一个网络请求,因为我们进行post或者get行为的时候一般没有headers都会被服务器认为是非法请求而把你拒绝掉,所以我们要加上这段代码(UA很关键,可以没有其他内容,UA建议一定要先写)

headers内容.png

这段代码你们可以去浏览器自己复制出来,同理一切的网页登陆操作都可以这样子写。

至此为止我们完成了登录的请求了,那么接下来就是找到答案并递交给服务器了


对比答案

这个时候已经快要放暑假了,大概在补课前夕吧

比较苦恼的是要如何实现后面的功能,于是我在qq群里问了很多大佬,的确对我的帮助挺大的

我最初是想用数据库来存答案的,可是一想操作数据库虽然简单,但是代码还是不够精简,于是我抛弃了这个想法改用Excel文件

头疼的是用xlsx格式好还是用csv的格式。后来我还是选择的前者,因为排列筛选比较方便。

要知道答案首先得知道题号,还记得前面的那个html吗?它存的就是整个网页的内容,所以和筛选隐藏域的value一样我们来筛选题号。

1
th=html.xpath('//span[@id="lb_tid"]/text()')

然后此时的答案是这个样子的[、"1024"] 所以我们要截取数字部分

1
2
3
4
5
6
7
8
th=html.xpath('//span[@id="lb_tid"]/text()')
th = str(th)
th=th.lstrip('[')
th=th.rstrip(']')
th=th.replace("'","")
th=th.replace("、","")
th = th.strip()
print(th)

这个th就是我们的题号1024

知道题号了以后就可以在Excel中找答案了,在此之前我们要知道程序找题号的原理

有3种获取sheet对象的方法:

  1. #通过sheet名字来获取,当然如果你知道sheet名字了可以直接指定
  2. #通过sheet索引序号获得sheet对象
  3. #指定索引获得sheet对象

这里我们用第三种方法,因为我们的答案库文件很简单

答案库.png

打开文件–指定索引–比对题号–获取答案 简单的流程就是这样,那么代码如何实现呢?请看

1
2
3
4
5
6
7
8
9
10
11
import xlrd #导入Excel模块
import shelve #Shelve使对象持久化保存
#在Excel找答案
answer = xlrd.open_workbook('test.xlsx') #打开Excel文件读取数据
table = answer.sheets()[0] #获取这sheet数据
t=shelve.open('ans') #数据存储
t['b']=table.row_values(1) #将第二列存入shelve中,0为第一列,1为第二列,以此类推...

print (t['b'])#取所在行内容
print (t['b'][2])#取答案
tans = t['b'][2]

tans就是答案了叉会腰.jpg就是这么轻松。


提交答案

让我们回到网页中,这一部分决定软件的实际功能。

02-1.png

02-02.png

很清楚的知道了 单选题 的input id ps.单选题和多选不同!

当我们选中这个A的时候他的value中就是“rb_A”这个值就准备好被递交了,所以我们只需要根据之前的dans这个答案来判断到底怎么选。

02-03.png

02-04.png

多选题的id则变成了cb ,所以我们可以这样想,先判断答案的长度,如果小于2则说明这答案是单选题的答案,反之则为多选,我们可以用class来规定对象,因为class它定义了该集合中每个对象所共有的属性和方法 资料引用 我们还是中规中矩的用函数来写。

这里的代码就不详细写了,最后写如何递交答案

还是和第一点的代码类似,用request依赖来递交,答案和隐藏域的值写进字典提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s = requests.session() #新建回话
r = s.get(post_url).text #获取页面内容
html = etree.HTML(r) #筛选
state=html.xpath('//*[@id="__VIEWSTATE"]')[0].get('value')
sta = html.xpath('//*[@id="__EVENTVALIDATION"]')[0].get('value')
dation = html.xpath('//*[@id="__VIEWSTATEGENERATOR"]')[0].get('value')
data = {
'__VIEWSTATE':state,
'__EVENTVALIDATION':sta,
'__VIEWSTATEGENERATOR':dation,
'rb':'rb_'+an, #单选题就是rb,多选为cb
'Button1':'参考答案',
}
r = s.post(url=post_url,data=data,headers=headers)

这里的post_url则是你这个考点的地址,例如你做2.3地址就是http://ks.zj.cn/lianxi.aspx?zhangjie=2.3 如果是5.1就是http://ks.zj.cn/lianxi.aspx?zhangjie=5.1 headers是我们的cookie ,cookie保存在session中,我们要拿着个这个cookie就cookie=S.cookie 简单粗暴,最后来个while循环让页面在递交答案后刷新一下,这样子就可以一直递交答案了。

希望这些可以让大家明白这个程序的原理

尾声

写在最后我也希望大家的刷题效率可以进一步提升,过度依赖程序只会害自己的技能无法提升,最后希望大家可以在暑假这有限的时间做一些自己爱好的实际内容,多学习一门语言,每天多记15个单词,养成每天指定计划的习惯,这样子才可以在以后的处理事件中轻车熟路,赶紧充实自己吧!

顺便扯皮一下,下一篇文章预告:swift实战开发iOS日常计划APP ps.没错,还是日常计划03.png

Screenshot_2019-08-07-21-35-57-800_com.dandelion..png

flag之王

-------------本文结束感谢您的阅读-------------
陈景跃 wechat
欢迎扫描二维码关注公众号一起成长呀~