python版 —— 验证码校验 打码兔平台的使用介绍
2018-07-27 本文已影响0人
Py_Explorer
1. 背景
验证码(CAPTCHA)的全称是全自动区分计算机和人类的图灵测试(Completely Automated Public Turing Test to tell Computers and Humans Apart),是一种用于区分人与计算机自动程序的挑战应答系统测试。CAPTCHA可通过设置一些人类很容易执行而自动程序很难完成的任务来区分人类和自动程序。
CAPTCHA经常被用来阻止自动程序使用博客来影响搜索引擎排名、签署电子邮箱帐户发送垃圾邮件或参与网上投票等。
通常,CAPTCHA是一个轻微扭曲的字母数字字符图像文件,人通常可以很容易读取图像中的字符。而自动程序能够识别该内容包含一个图像,但不知道是什么图像。考虑到弱视群体,一些CAPTCHA使用音频文件。在这样一个系统中,人可以听到一个字母或短句并打出他所听到的,从而证明他不是自动程序。
-
一般来说,自动化处理验证码有两种方式:
- 光学字符识别(Optical Character Recognition, OCR),从图像中抽取文本。主要的库有pytesser,tesseract。一般来说,对于复杂的验证码图像,需要先修改验证码图像,去除其中的背景噪音,只保留文本部分,再送入库中进行解析。而且识别率不是很高,想提高识别率,需要通过长时间机器学习的训练,代价较大。而且对于极其复杂的验证码,OCR甚至无法使用。
- 使用验证码处理服务(借助于专业的打码平台)。付费调用他们提供的API。当把验证码图片传给他们的API时,会有人进行人工查看,并在HTTP响应中给出解析后的文本内容。一般来说,整个解析过程在10s(打码兔平台),最长60s,而且价位不高,1元 = 50~100个验证码(根据验证码类型进行收费)。如果验证码不多,为了提高识别效率和简化操作,就可以选择这种方式。
2. 环境
- python 3.6.1
- 系统:win7
- IDE:pycharm
- 安装过chrome浏览器
- 配置好chromedriver
- selenium 3.7.0
3. 打码兔平台的使用流程
3.1. 原理
- 将验证码图片,打码平台账号,密码等按照指定格式调用API(访问URL),得到返回的结果。
- 打码兔平台地址:http://www.dama2.com/
- 打码兔平台开发者文档:http://wiki.dama2.com/index.php
3.2. 流程
-
第一步:注册一个开发者帐号。
image.png
-
第二步:生成使用验证码的软件的ID和key
登录开发者账号,进入 我的软件,点击创建软件,填好资料提交就会生成一个软件key的。
image.png -
备注:
- 把你的软件key填进相应的代码参数里面,然后把软件key发给打码兔的在线客服,他会帮你解除限制,你就可以用账号test,密码test测试了。
- 如图所示,这就是软件ID,软件名称,软件KEY,都是程序会用到的参数。
- 当别人使用你的软件ID和软件Key进行验证码消费时,作为软件的开发者是能拿到分成的。这也是抢票软件的赚钱的方式之一。也就是说,开发者帐号用于软件开发,赚钱。
- 软件不需要公开。而且开发者帐号不能进行付费打码。
-
第三步:注册一个普通用户的帐号,并进行充值,用这个帐号进行付费打码。
也就是说,开发者帐号用于赚钱,普通用户帐号用于付费打码
4. 自定义方法
# 对打码兔平台提供的代码进行了一些改动,更加适应本软件的需求
import hashlib
import urllib.request
import urllib
import json
import base64
import requests
# md5加密字符串
def md5str(str):
m = hashlib.md5(str.encode(encoding="utf-8"))
return m.hexdigest()
# md5加密byte
def md5(byte):
m = hashlib.md5(byte)
return m.hexdigest()
# 打码兔API类
class DamatuApi():
# 关联的开发者帐号
# 此帐号关联的验证码类型,默认是1~8位随机英文和数字组合
ID = '51773' # 开发者帐号 软件的AppID
KEY = 'fbfb9022a1499b0c6436f223f98b714e' # 开发者帐号 软件的key, 我的软件 生成的Key
# 打码平台host
HOST = 'http://api.dama2.com:7766/app/'
def __init__(self, username, password, limitCount):
self.username = username
self.password = password
# 限制每个实例请求验证码的次数,防止意外,导致请求验证码过多,消费不可控
self.limitCount = limitCount
# 用于统计验证码请求次数
self.count = 0
# 计算用户签名,按照一定的规则对 key和userName 进行加密
def getSign(self, param=b''):
return (md5(bytes(self.KEY, encoding="utf8") + bytes(self.username, encoding="utf8") + param))[:8]
# 获得加密后的密钥 key , userName, password
def getPwd(self):
return md5str(self.KEY + md5str(md5str(self.username) + md5str(self.password)))
# 向打码平台提交请求
def post(self, urlPath, formData = {}):
'''
:param urlPath: 用于构造url,向不同的地址请求不同的数据
:param formData: 提交的数据
:return:
'''
url = self.HOST + urlPath
try:
response = requests.request(method='post',
url=url,
data=formData,
timeout=60
)
print(f"text = {response.text}")
# 余额,balance, text = {"ret":0,"balance":"9957","sign":"2428e5f9"}
# 验证码,captcha, text = {"ret":0,"id":558899326,"result":"230876","sign":"8d55bdb1"}
return response.text
except Exception as e:
print(f"postRequest error. exception = {e}, urlPath = {urlPath}, formData = {formData}")
return {"ret": -1}
# 查询余额 return 是正数为余额, 如果为负数 则为错误码
def getBalance(self):
data = {
'appID': self.ID,
'user': self.username,
'pwd': self.getPwd(),
'sign': self.getSign()
}
res = self.post('d2Balance', data)
jres = json.loads(res)
if jres['ret'] == 0:
return (True, jres['balance'])
else:
return (False, jres['ret'])
# 上传验证码图片
def decode(self, filePath, type):
'''
:param filePath: 验证码图片路径 如d:/1.jpg
:param type: 验证码类型,查看 http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc
:return: 元组,result[0] = True为成功,False为错误码
'''
if self.count >= self.limitCount:
print(f"decode: 请求验证码数量超过限制自定义数量。")
return (False, False)
# 拿到验证码图片的数据
f = open(filePath, 'rb')
fdata = f.read()
filedata = base64.b64encode(fdata)
f.close()
data = {
'appID': self.ID,
'user': self.username,
'pwd': self.getPwd(),
'type': type,
'fileDataBase64': filedata,
'sign': self.getSign(fdata)
}
res = self.post('d2File', data)
jres = json.loads(res)
self.count += 1
if jres['ret'] == 0:
# 注意这个json里面有ret,id,result,cookie,根据自己的需要获取
return (True, jres['result'])
else:
return (False, jres['ret'])
# url地址打码,提供验证码链接
def decodeUrl(self, url, type):
'''
:param url: 验证码地址
:param type: 验证码类型 http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc
:return: 元组,result[0] = True为成功,False为错误码
'''
if self.count >= self.limitCount:
print(f"decodeUrl: 请求验证码数量超过限制自定义数量。")
return (False, False)
data = {
'appID': self.ID,
'user': self.username,
'pwd': self.getPwd(),
'type': type,
'url': urllib.parse.quote(url),
'sign': self.getSign(url.encode(encoding="utf-8"))
}
res = self.post('d2Url', data)
jres = json.loads(res)
self.count += 1
if jres['ret'] == 0:
# 注意这个json里面有ret,id,result,cookie,根据自己的需要获取
return (True, jres['result'])
else:
return (False, jres['ret'])
# 报错,暂时先不关心。 参数id(string类型)由上传打码函数的结果获得 return 0为成功 其他见错误码
def reportError(self, id):
data = {
'appID': self.ID,
'user': self.username,
'pwd': self.getPwd(),
'id': id,
'sign': self.getSign(id.encode(encoding="utf-8"))
}
res = self.post('d2ReportError', data)
res = str(res, encoding="utf-8")
jres = json.loads(res)
return jres['ret']
5. 嵌入代码中使用
验证码截图,请参考文章:http://blog.csdn.net/zwq912318834/article/details/78605486
- 第一步,确保余额充足
# 打码兔API类 实例化,参数是打码兔用户账号和密码. 最后一个是限制验证码的次数,防止出故障时,无限刷验证码,消费过大
# dmt=DamatuApi("test","test")
# 目前本软件验证码定位为: 1~8位数字英文混搭, 题分21分
dmt = DamatuApi("ancode", "ancode2017", 200)
# 先查看余额是否充足
# BalanceRes = (True, '9931')
balanceRes = dmt.getBalance() # 查询余额
if balanceRes[0] == True and int(balanceRes[1]) > 0:
# 余额充足, 可以放心爬取
print(f"main: balanceRes = {balanceRes}")
# 开始爬取数据
#......
- 第二步,验证码校验。
检测验证码是否出现。
对验证码进行截图。
发送验证码图片进行解码。
向网站提交验证码解析结果。
检测验证码是否校验成功。
注意:每一次页面的变动,都可能让之前获取的元素失焦,需要重新获取。
# 部分代码
# 通过Image处理图像,截取验证码图片
imgCaptcha.save('clawerImgs/captcha.png')
# 将验证码送往打码兔进行解码
# codeRes = (True, 'FMAE')
# 56 代表验证码类型,1~8位数字英文组合
# 参考:http://wiki.dama2.com/index.php?n=ApiDoc.Pricedesc
damatuRes = DamatuInstance.decode('clawerImgs/captcha.png', 56)
while(damatuRes[0] == False):
if damatuRes[1] == False:
raise Exception(f"打码兔超出自定义最大限制数,终止软件. damatuRes = {damatuRes}")
else:
# 验证码请求失败,查询下余额,看是否充足
balanceRes = DamatuInstance.getBalance()
if balanceRes[0] == True and int(balanceRes[1]) > 0:
damatuRes = DamatuInstance.decode('clawerImgs/captcha.png', 56)
time.sleep(2)
else:
# 打码兔 余额不足, 抛出异常,终止软件
raise Exception(f"打码兔余额不足,或者超出自定义最大限制数,终止软件. balanceRes = {balanceRes}")
# 点击提交按钮
input = browser.find_element_by_xpath("//div[@class='input']/input[@id='J_CodeInput']")
input.clear()
input.send_keys(damatuRes[1])
print(f"发送验证码为: damatuRes = {damatuRes}")
time.sleep(3)
# 提交
submit = browser.find_element_by_xpath("//button[@id='J_submit']")
submit.click()
time.sleep(5)
################################################
# 处理完,提交完验证码之后,回归到主页面,继续抓取商品
browser.switch_to.default_content()
time.sleep(2)
browser.refresh() # 处理完之后进行页面刷新
time.sleep(5)
# 然后重新检测验证码是否存在,如果还在就循环重复处理,如果不在,下面这个流程也不会继续
captchaHandler(browser, DamatuInstance)
感谢支持:https://blog.csdn.net/zwq912318834/article/details/78616462