一、简介
这个是最新的京东轨迹验证码,需要用户根据轨迹画出对应的曲线。这个和传统的验证码有较大的差异,有非常大的难度。经过长时间的研究,现在终于解决了它的识别问题。
这个是识别效果,和真实轨迹基本上重合,所以轨迹识别正确率在95%左右。
在线免费测试效果:得塔云
二、识别代码
下面是识别这个轨迹验证码的样例代码
这个是通过验证码图片,然后获得识别的曲线轨迹点的代码,是识别最基础代码,不包含任何业务逻辑。大家可以根据这个基础代码,结合自己的业务逻辑进行修改。
import base64
import requests
import datetime
from io import BytesIO
from PIL import Image
t1 = datetime.datetime.now()
#PIL图片保存为base64编码
def PIL_base64(img, coding='utf-8'):
img_format = img.format
if img_format == None:
img_format = 'JPEG'
format_str = 'JPEG'
if 'png' == img_format.lower():
format_str = 'PNG'
if 'gif' == img_format.lower():
format_str = 'gif'
if img.mode == "P":
img = img.convert('RGB')
if img.mode == "RGBA":
format_str = 'PNG'
img_format = 'PNG'
output_buffer = BytesIO()
# img.save(output_buffer, format=format_str)
img.save(output_buffer, quality=100, format=format_str)
byte_data = output_buffer.getvalue()
base64_str = 'data:image/' + img_format.lower() + ';base64,' + base64.b64encode(byte_data).decode(coding)
# base64_str = base64.b64encode(byte_data).decode(coding)
return base64_str
# 加载图片
img1 = Image.open(r'E:\Python\lixin_project\OpenAPI接口测试\test_img\70-1.jpg')
# 图片转base64
img1_base64 = PIL_base64(img1)
# 验证码识别接口
url = "http://bq1gpmr8.xiaomy.net/openapi/verify_code_identify/"
data = {
# 用户的key
"key":"0AAahdF39yYIX2Qy1iAE",
# 验证码类型
"verify_idf_id":"70",
# 样例图片
"img_base64":img1_base64,
}
header = {"Content-Type": "application/json"}
# 发送请求调用接口
response = requests.post(url=url, json=data, headers=header)
# 获取响应数据,识别结果
print(response.text)
print("耗时:", datetime.datetime.now() - t1)
三、实战样例代码
这个是使用selenium进行实际滑动的代码。识别的轨迹和真实滑动的轨迹还是有一定差异。
1、缩放问题
原图宽度是275px,页面图片宽290px,所以缩放系数=290/275=1.05
2、偏移问题
识别的轨迹点是根据图片的轨迹位置来的,但是实际滑动,我发现在y坐标方向和x坐标方向有一定偏移。x方向需要+5px,y方向上需要+85px,才能和验证的轨迹对应上。目前不知道是不是所有用户环境都是偏移这值,所有需要大家自行测试。
3、实战滑动代码
__author__ = "dengxinyan"
import re
import time
import json
import random
import requests
import urllib
import random
from io import BytesIO
from PIL import Image, ImageDraw
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver import ChromeOptions
from selenium.webdriver import FirefoxOptions
from selenium.webdriver.common.action_chains import ActionChains
from Common3.Common3 import *
# key
key = 'efZ7oiIRZN1aOCqfynJt'
# 是否无头模式
headless = False
# 随机生成手机号
def generate_phone_number_v2():
# 各运营商号段
segments = {
'中国移动': ['134', '135', '136', '137', '138', '139', '147', '148', '150', '151',
'152', '157', '158', '159', '172', '178', '182', '183', '184', '187', '188', '198'],
'中国联通': ['130', '131', '132', '145', '146', '155', '156', '166', '171', '175',
'176', '185', '186'],
'中国电信': ['133', '149', '153', '173', '174', '177', '180', '181', '189', '191', '199']
}
# 随机选择一个运营商
operator = random.choice(list(segments.keys()))
# 随机选择该运营商的一个号段
prefix = random.choice(segments[operator])
# 生成剩余数字
suffix = ''.join([str(random.randint(0, 9)) for _ in range(11 - len(prefix))])
return prefix + suffix
# 识别代码
def shibie_70(img1_base64):
# 验证码识别接口
url = "http://bq1gpmr8.xiaomy.net/openapi/verify_code_identify/"
data = {
# 用户的key
"key": key,
# 验证码类型
"verify_idf_id": "70",
# 样例图片
"img_base64": img1_base64,
}
header = {"Content-Type": "application/json"}
# 发送请求调用接口
response = requests.post(url=url, json=data, headers=header)
# 获取响应数据,识别结果
print('得塔云返回结果:', response.text)
return response.json()['data']['res_str']
options = FirefoxOptions()
if headless:
options.add_argument('--headless')
else:
options.add_argument('--window-size=100,100')
options.add_argument('--disable-blink-features=AutomationControlled')
# 禁用webdriver属性(有效)
options.set_preference("dom.webdriver.enabled", False)
options.set_preference("marionette.enabled", False)
options.set_preference('useAutomationExtension', False) #关闭自动化提示
# 禁用自动控制提示
options.set_preference("browser.tabs.remote.autostart", False)
options.set_preference("browser.tabs.remote.autostart.2", False)
# 模拟具有插件的浏览器环境(有效)
options.set_preference("plugin.state.flash", 2) # 设置Flash插件状态为启用
options.set_preference("plugin.state.java", 2) # 设置Java插件状态为启用
# 模拟具有特定的WebGL Vendor值的浏览器环境(WebGL Vendor:可以使用任何一家公认的WebGL Vendor名称,例如"Google Inc."、"Mozilla"、"Apple Inc."等。)
# 好像没有效果
options.set_preference("webgl.vendor", "Mozilla") # 将"VendorName"替换为所需的WebGL Vendor值
# 模拟具有特定的权限设置的浏览器环境
options.set_preference("permissions.default.microphone", 1) # 设置麦克风权限为允许
options.set_preference("permissions.default.camera", 1) # 设置摄像头权限为允许
options.set_preference("permissions.default.geo", 1) # 设置地理位置权限为允许
driver = webdriver.Firefox(executable_path=r'webdriver\geckodriver.exe', options=options)
# 加载防检测js
with open('webdriver\stealth.min.js') as f:
js = f.read()
driver.execute_script(js) # 执行JavaScript代码
# 伪装浏览器
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => false,});")
navigator_webdriver = driver.execute_script("return navigator.webdriver")
driver.execute_script("Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5],});")
plugins_length = driver.execute_script("return navigator.plugins.length")
# 发送请求
driver.get('https://plogin.m.jd.com/mreg/index?appid=300&returnurl=https%3A%2F%2Fmy.m.jd.com%2F&account=')
# 等待【用户同意】元素出现
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//button[@class="protocol-btn agreement"]'))
# 找到【用户同意】元素
tag1 = driver.find_element_by_xpath('//button[@class="protocol-btn agreement"]')
# 点击【用户同意】
tag1.click()
# 等待【用户名】元素出现
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//input[@class="acc-input mobile J_ping"]'))
# 找到【用户名】元素
tag1 = driver.find_element_by_xpath('//input[@class="acc-input mobile J_ping"]')
# 输入用户名(随机生成手机号)
tag1.send_keys(generate_phone_number_v2())
# 等待【登录】元素出现
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//a[@class="btn active"]'))
# 找到【登录】元素
tag1 = driver.find_element_by_xpath('//a[@class="btn active"]')
# 点击【登录】
tag1.click()
for i in range(10):
# 第一次不需要刷新
if i != 0:
# 点击刷新
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//span[@class="jcap_refresh"]'))
tag1 = driver.find_element_by_xpath('//span[@class="jcap_refresh"]')
tag1.click()
time.sleep(3)
# 判断是否出现轨迹验证码
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//img[@id="cpc_img"]'))
tag1 = driver.find_element_by_xpath('//img[@id="cpc_img"]')
# 获取大图
img1_base64 = tag1.get_attribute('src')
# base64编码转PIL
img = base64_PIL(img1_base64)
# 等待【滑动区】元素出现
WebDriverWait(driver, 20).until(lambda x: x.find_element_by_xpath('//canvas[@id="trackLine"]'))
# 找到【滑动区】元素
canvas = driver.find_element_by_xpath('//canvas[@id="trackLine"]')
point_list = eval(shibie_70(img1_base64))
print('识别的轨迹:', point_list)
# 显示轨迹部分
draw = ImageDraw.Draw(img)
kpt_size = 4
for i, point in enumerate(point_list):
# 计算点的边界框(确保是正方形)
half_size = kpt_size // 2
x1 = point[0] - half_size
y1 = point[1] - half_size
x2 = point[0] + half_size
y2 = point[1] + half_size
# 绘制实心圆形点
draw.ellipse([x1, y1, x2, y2], fill=(0, 0, 255), outline=(0, 0, 255))
img.show()
time.sleep(5)
# 处理图片缩放问题 原图宽275px,页面图片宽290px,缩放系数=290/275=1.05,还要处理偏移,x+5,y+85
point_list = [(int(x[0] * 1.05 + 5), int(x[1] * 1.05 + 85)) for x in point_list]
actions = ActionChains(driver)
# 移动到起始点并按下鼠标左键
actions.move_to_element_with_offset(canvas, point_list[0][0], point_list[0][1])
actions.click_and_hold()
actions.pause(1)
# 添加所有中间点的移动动作
for point in point_list[1:-1]: # 跳过第一个和最后一个点
print('aaaaa:', point)
actions.move_to_element_with_offset(canvas, point[0], point[1])
# 可以添加微小延迟使动作更自然
actions.pause(0.1)
# 移动到最后一个点
actions.move_to_element_with_offset(canvas, point_list[-1][0], point_list[-1][1])
# 暂停1秒
actions.pause(1)
# 释放鼠标
actions.release()
# 执行所有动作
actions.perform()
实际的滑动成功率可能需要大家研究研究。可能会涉及到很多因素影响成功率,比如滑动的速度,鼠标的按下、抬起的速度都可能会识别成机器人导致不过的情况。
想了解更多验证码识别请访问:得塔云