抓取高考录取数据

2015年高考转瞬即逝,转眼间我也快大四了,不由感慨。学业未成,仍需努力。

写这个脚本的原因

  • 我们学校有一个高中的校友会,大家互相帮助,受益良多。每年的这个时候,校友会的同学都需要去一个个找看哪位师弟师妹录到我校了,然后就会主动去联系他们,让他们加入校友会这个大家庭。然而大家一个个问高中的师弟师妹不太科学,而高中的龙虎榜又需要开学才张贴,所以校友会的同学们的工作进展的不是很顺利。这个时候这个脚本的出来了。
  • 脚本的大部分是星尘大师兄去年写的,但当初比较赶,写的比较乱,而且数据没有持久化。这样不利于校友会后续的同学们继续使用。而今年星尘大师兄毕业工作了,所以完善工作就落在我身上了。

原理

  • 先说下大概思路吧。www.5184.com/gk里面提供了高考录取的查询接口,我们可以用Fiddler抓包,看HTTP请求的相关参数有什么。
  • 其实高中学子每个地区每个学校的考号段都是固定的,比如我们学校的就是前4位是1802,后6位也有分布规律。
  • 综上,使用Python来遍历某个考号段,获取返回的数据,判断是否是所需数据,是的话保存,不是的话丢弃。

具体代码

1.首先看核心代码 HttpRequestTool.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# number:the number of a student
# birthday: the student's birthday, like 9301
# try_time: the request reconnect times when it broken.
def get_admit_result_by_number_and_birthday(number, birthday, try_times=3):
if try_times == 0:
file = open('error.txt', 'a')
file.write(str(number + "-" + birthday))
return
# make the request header.
# all the header are copied from a normal HTTP request.
# maybe some header is not necessary.
cookie = "Hm_lvt_2be31ff7176cef646e9351788dc99055=1437448597;Hm_lpvt_2be31ff7176cef646e9351788dc99055=1437450310;PHPSESSID=fblmt7m54ehe2m0q65otdfbbg5"
url = "http://www.5184.com/gk/common/get_lq_edg.php"
post_data = urllib.parse.urlencode({'csny': birthday, 'zkzh': number, 'yzm': ''})
post_data = post_data.encode('utf-8')
request = urllib.request.Request(url)
request.add_header("Host", "www.5184.com")
request.add_header("Connection", "keep-alive")
request.add_header("Content-Length", "31")
request.add_header("Accept", "application/json, text/javascript, */*; q=0.01")
request.add_header("Origin", "http://www.5184.com")
request.add_header("X-Requested-With", "XMLHttpRequest")
request.add_header("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36")
request.add_header("Content-Type", "application/x-www-form-urlencoded")
request.add_header("Referer", "http://www.5184.com/gk/check_lq.html") # important. without referer, the request will return error.
request.add_header("Accept-Encoding", "gzip,deflate,sdch")
request.add_header("Accept-Language", "zh-CN,zh;q=0.8")
request.add_header("Cookie", cookie)
try:
f = urllib.request.urlopen(request, post_data)
r_data = f.read().decode('utf-8')
except:
get_admit_result_by_number_and_birthday(number, birthday, try_times - 1)
result_json = json.loads(r_data)
if result_json['flag'] == 1:
result = result_json['result']
if result['zymc'] == Data.SCHOOL:
file = open('admit.txt', 'a')
file.write(str(result['zkzh'] + ' ' + birthday + ' ' + result['zymc'] + ' ' + result['xm'] + '\n'))

get_admit_result_by_number_and_birthday()这个方法接受3个参数,number为考号,10位的数字。birthday为出生年月,用于5184的接口验证,相当于密码。try_time是失败重新请求次数。这里有个注意的地方,构造请求头部的时候,一定要加上Referer这个参数,否则5184将过滤掉这次请求。顺口吐槽下,5184的验证码其实是假的,请求的时候yzm这个参数置空就可以了,真弱鸡啊。
这个方法会去请求5184接口,返回的数据如果是我们想要的(代码第40行进行判断 ),则按照一定格式写入到文件admit.txt中。

2.多线程文件 ThreadPoolTool.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import threading
from Admit import Data, HttpRequestTool
# make one thread class to one task which get one student's result.
class ThreadTask(threading.Thread):
number = "0"
def __init__(self):
threading.Thread.__init__(self)
def run(self):
birthday_length = len(Data.BIRTHDAY_PREFIX)
for i in range(0, birthday_length - 1):
HttpRequestTool.get_admit_result_by_number_and_birthday(self.number,Data.BIRTHDAY_PREFIX[i])

在这里,大约需要进行180w次请求,如果单线程跑的话,速度太慢了。而且这还没计算上失败重连的次数,所以必须让多线程来跑。这个线程类ThreadTask负责对每个考号进行暴力请求,循环定义好的出生年月范围进行请求。

3.入口文件 Main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import time
import sys
from Admit import Data, ThreadPoolTool
def main():
sys.stderr = None
j = 0
for number_prefix in Data.NUMBER_PREFIX:
for number_suffix in range(0, Data.NUMBER_RANGE):
if number_suffix < 10:
number_suffix = '000' + str(number_suffix)
elif number_suffix < 100:
number_suffix = '00' + str(number_suffix)
elif number_suffix < 1000:
number_suffix = '0' + str(number_suffix)
else:
number_suffix = str(number_suffix)
thread = ThreadPoolTool.ThreadTask()
thread.number = str(number_prefix + number_suffix)
thread.start()
j += 1
if j % 50 == 0:
time.sleep(10)
j = 0
# the entrance
main()

入口方法main()主要做的就是构造考号,然后把每个考号分配给一个线程去执行。

写在最后

到这里程序就分析完了。目前抓到了63个师弟师妹,校友会的同学就可以有方向去拉人了哈。你们快到碗里来。。。
程序不复杂。但可以借助程序,做一点小事,也是非常开心的哈。最后希望校友会越来越好,大家一起加油!

完。


##附录
源码地址在Github