[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=''