[12306专题]第三篇12306的验证码的识别

1.再次处理12306的验证码

  • 需要明确,12306的验证码是没有办法绕过.
  • 目前仍没有什么模块可以进行解码
但这并不意味着,没有办法自动化识别验证码.这里采用第三方平台打码兔的方式进行在线解码.
  • 解码平台提供2中解码方式,一种是直接将图片的url交给打码平台,让打码平台自己打开图片的地址,然后识别后,返回验证码的正确值.
  • 第二种,是本地请求了图片,并把图片上传到打码平台,让平台根据图片返回正确的验证码.
  • 由于验证码绑定session,所以采用第二种方法,现在本地请求到验证码图片,获得会话并绑定验证码,再将图片上传到打码平台解码后返回.

2.使用打码兔dama2.com进行在线解码

首先需要http://dama2.com/Index/ureg注册一个用户账号,账号需要验证手机号

注册完成后,需要接收短信验证码认证,同时需要至少先充值至少1元钱.
  • 完成后,在验证码识别API接口维基,地址http://wiki.dama2.com/index.php?n=ApiDoc.Http界面,下载对应的API脚本,这里选择Python,对应下载地址[http://www.dama2.com/download/demo/damatuWeb-Python.zip](http://www.dama2.com/download/demo/damatuWeb-Python.zip)
  • 打码兔提供的是python3的接口脚本,这里附上python2脚本damatuWeb.py,脚本需要修改的地方是末尾的dmt=DamatuApi("c******s","t*******0"),将刚才注册的打码兔的普通用户账号的用户名密码即可,需要注意的是,帐户里必须至少充1元钱,否则无法解码.
#-*-coding:utf-8-*-
import hashlib
import http.client
import urllib2
import urllib
import json
import base64def md5str(str): #md5加密字符串
        m=hashlib.md5(str.encode(encoding = "utf-8"))
        return m.hexdigest()
        
def md5(byte): #md5加密byte
        return hashlib.md5(byte).hexdigest()
        
class DamatuApi():
    
    ID = '40838'
    KEY = 'ca9507e17e8d5ddf7c57cd18d8d33010'
    HOST = 'http://api.dama2.com:7766/app/'
    
    
    def __init__(self,username,password):
        self.username=username
        self.password=password
        
    def getSign(self,param=b''):
        return (md5(bytes(self.KEY) + bytes(self.username) + param))[:8]
        
    def getPwd(self):
        return md5str(self.KEY +md5str(md5str(self.username) + md5str(self.password)))
        
    def post(self,path,params={}):
        data = urllib.urlencode(params).encode('utf-8')
        url = self.HOST + path
        response = urllib2.Request(url,data)
        return urllib2.urlopen(response).read()
    
    #查询余额 return 是正数为余额 如果为负数 则为错误码
    def getBalance(self):
        data={'appID':self.ID,
            'user':self.username,
            'pwd':dmt.getPwd(),
            'sign':dmt.getSign()
        }
        res = self.post('d2Balance',data)
        res = str(res)
        jres = json.loads(res)
        if jres['ret'] == 0:
            return jres['balance']
        else:
            return jres['ret']
    
    #上传验证码 参数filePath 验证码图片路径 如d:/1.jpg type是类型,查看http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc  return 是答案为成功 如果为负数 则为错误码
    def decode(self,filePath,type):
        f=open(filePath,'rb')
        fdata=f.read()
        filedata=base64.b64encode(fdata)
        f.close()
        data={'appID':self.ID,
            'user':self.username,
            'pwd':dmt.getPwd(),
            'type':type,
            'fileDataBase64':filedata,
            'sign':dmt.getSign(fdata)
        }       
        res = self.post('d2File',data)
        res = str(res)
        jres = json.loads(res)
        if jres['ret'] == 0:
            #注意这个json里面有ret,id,result,cookie,根据自己的需要获取
            return(jres['result'])
        else:
            return jres['ret']
        
    #url地址打码 参数 url地址  type是类型(类型查看http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc) return 是答案为成功 如果为负数 则为错误码
    def decodeUrl(self,url,type):
        data={'appID':self.ID,
            'user':self.username,
            'pwd':dmt.getPwd(),
            'type':type,
            'url':urllib.quote(url),
            'sign':dmt.getSign(url.encode(encoding = "utf-8"))
        }
        res = self.post('d2Url',data)
        res = str(res)
        jres = json.loads(res)
        if jres['ret'] == 0:
            #注意这个json里面有ret,id,result,cookie,根据自己的需要获取
            return(jres['result'])
        else:
            return jres['ret']
    
    #报错 参数id(string类型)由上传打码函数的结果获得 return 0为成功 其他见错误码
    def reportError(self,id):
        #f=open('0349.bmp','rb')
        #fdata=f.read()
        #print(md5(fdata))
        data={'appID':self.ID,
            'user':self.username,
            'pwd':dmt.getPwd(),
            'id':id,
            'sign':dmt.getSign(id.encode(encoding = "utf-8"))
        }   
        res = self.post('d2ReportError',data)
        res = str(res)
        jres = json.loads(res)
        return jres['ret']
def codeCal():
    code=dmt.decode('code.png',287)
    code=code.replace('|',',')
    code=code.split(',')
    x=code[0::2]
    y=code[1::2]
    s=''
    for i in zip(x,y):
        y=int(i[1])-30
        s+='%s,%s,'%(i[0],y)
    return s[:-1]
    
        
#调用类型实例:
#1.实例化类型 参数是打码兔用户账号和密码
dmt=DamatuApi("c******s","t*******0")
#2.调用方法:
#print(dmt.getBalance()) #查询余额
#print codeCal()
#print(dmt.decode('code.png',287)) #上传打码

3.上传验证码解码

通过前面的脚本,我们下载了一张验证码图片code.png,和脚本文件存放在同一目录

运行脚本后,得到上面的效果,上图菠萝只有一个,所以只有一对坐标点,说明解码是正确的.

打码平台打码的默认起点,是从左上角最上面的顶点开始识别,而12306是从下方图片位置开始,这中间存在着纵坐标30像素的差距,所以需要自定义函数codeCal,进行修复
def codeCal():
    code=dmt.decode('code.png',287) #上传本地图片code.png到打码平台,解码类型287鼠标对选题
    code=code.replace('|',',')      #平台返回的数据96,121|169,127是|分开的,所以要替换成96,121,169,127格式的验证码
    code=code.split(',')    #将96,121,169,127字符串格式转成[96,121,169,127]列表的形式以便对于纵坐标-30处理
    x=code[0::2]            #把列表[96,121,169,127]从第一个元素96开始,每2个取出一次,取出结果为横坐标[96,169]
    y=code[1::2]             #把列表[96,121,169,127]从第二个元素121开始,每2个取出一次,取出结果为横坐标[121,127]
    s=''                      #定义空字符串准备存放
    for i in zip(x,y):        #将x坐标[96,169]  ,y坐标[121,127] 每次各取一个值压缩[96,121]到一块成一对
        y=int(i[1])-30        #i[1]就是那一对[96,121]的纵坐标的121,减30是为了让坐标从下面开始计算.横坐标i[0]则不需处理
        s+='%s,%s,'%(i[0],y)  #把[96,121]处理成[96,91]后,封装成字符串'96,91,'后面留个逗号是为了再次接收后面的[169,127]
    return s[:-1]            #处理完毕的数据是'96,91,169,97,',所以要用s[:-1]把最后的逗号去掉,变成'96,91,169,97'

4.将打码兔嵌入到12306模块

#-*- coding:utf-8 -*-
from __future__ import unicode_literals
import urllib,urllib2,ssl,cookielib,json,damatuWeb  #在这里导入打码兔模块damatuWeb,该文件与当前脚本同一个目录,直接导入c=cookielib.LWPCookieJar()
cookie=urllib2.HTTPCookieProcessor(c)
opener=urllib2.build_opener(cookie)
urllib2.install_opener(opener)
ssl._create_default_https_context=ssl._create_unverified_contextdef getCode():  #下载验证码的函数
    req=urllib2.Request('https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand')
    codeimg=opener.open(req).read()
    with open('code.png','wb') as f:
        f.write(codeimg) 
def code():   #解码的函数
    getCode()         #在这里直接进行下载验证码,保存到code.png
    code=damatuWeb.codeCal() #调用打码兔模块,直接上传刚刚下载的code.png,由平台处理后,返回验证码直接处理
    print(code)
    data={
        'answer':code,      #把返回的验证码直接封装到data,发送给服务器校验
        'login_site':'E',
        'rand':'sjrand',
        }
    data=urllib.urlencode(data)
    req=urllib2.Request('https://kyfw.12306.cn/passport/captcha/captcha-check',data=data)
    h=opener.open(req).read()
    h=json.loads(h)
    if h['result_code']=='4':   #校验成功的状态码是'4',这时候执行登陆
        print(h['result_message'])
        login()
    else:                   #校验不成功,则重新递归调用函数本身,重新下载验证码
        print(h['result_message'])
        code()
def login():                #登陆12306的函数
    data={
        'username':'这里替换你的12306用户名',
        'password':'这里替换你的12306密码',
        'appid':'otn',
        }
    data=urllib.urlencode(data)
    req=urllib2.Request('https://kyfw.12306.cn/passport/web/login',data=data)
    h=opener.open(req).read()
    h=json.loads(h)
    if h['result_code']==0:     #状态码0是登陆成功.
        print(h["result_message"])
        return True
    else:
        print(h["result_message"])
        return Falseif __name__=='__main__':        #如果当前的脚本是直接运行的,就执行code()下载验证码并登陆的操作,如果是别的文件对它import,就不执行
    code()

登陆成功,验证码通过后,登陆成功,整个过程大约5~6秒,解码速度还是令人满意的.

总结

本次重点分析了12306网站的验证码解码方式,由于不可绕过,所以采取了第三方解码的方式,既有效的降低了成本,还能很好解决验证码的问题,同时解码的效率还得到保证.修复验证码的逻辑对于今后的其它解码应用有很大帮助.所以请重点掌握该思路.
赞(0) 打赏
未经允许不得转载:http://www.yueguangzu.net逗逗网 » [12306专题]第三篇12306的验证码的识别
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏