某大型视频平台推出女团选秀,需要给偶像投票。官方为了防止机器人在投票页面设置了滑块验证。
破之。
因为是初学/自学(随便玩玩),代码规范也不太懂,给大家提供一个思路,抛砖引玉。
----------------------------
需求概述:
这个滑块验证如果连续多次失败,就进入无限滑块模式。
和一般的滑块一样,匀速滑动会被判定为机器人。
用到的python库
PIL
pyautogui
numpy
colormath(https://python-colormath.readthedocs.io/en/latest/)
思路
第一步:
滑动一下滑块,大概50左右pix,
通过对比两次图片的区别,识别出是哪种形状的拼块
多次试验后,发现一共5种小拼块。都做好1:1模板图以备使用。
第二步
根据不同的小拼块选择不同的位置,选择一条横线,作为比较对象,对,就是一条线。只用这条线的右半段,因为水印只出现在画面的右半侧
最好能保证这条线的连续性,不要断开(方便写代码)
一条线放大后的样子:
第三步
这个就不太好解释了
1 找到这个图中颜色最接近 0xCCCCCC 这个颜色(为什么是这个颜色呢,我统计了一下几十张图的水印位置,最后算出个平均值,就是这个颜色,很神奇不知道为什么全是C,估计和开发人员选色有关系)
2 查看选好的这条线的各个点与 0xCCCCCC颜色相近程度的值的变化,应该能找到一对儿,这个值变化最大的点也就对应了灰色水印的左右边界,假如是(x1,x2),判断这个长度跟第一步中模板图片中相应位置的线段长度是否一致(相接近)
3 查看1中找到点的坐标,是否在这个(x1,x2)线段之间,如果是就假设找对了,如果不在范围内,就刷新重新找。
有了这个判断程序就能很大概率知道自己找的位置对不对。
第四步
移动滑块,要变速移动,而且要故意多滑动一小段距离,再往回滑动,模拟手残操作,怎么手残怎么设置
------------------------------
实测很有效,一次也没有被发现是机器人。没有连续多次找错位置(一般程序会如上文中的3中那个判断方法,自动发现找错位置,会自动刷新图片,多次刷新图片这个操作不会被记录为机器人),也没有因为滑动的鼠标操作太死板而暴露。
因为程序只判断一条线的颜色情况,所以还有很大的提高空间,但是这个选秀活动很快就结束了,也就没有再继续搞。目前的准确率已经够高了,90+%
-------------------------------------------
上代码
为了提高准确性,有很多细节。尽量注释吧;
初始化
global flash_count, pic_in_start_x, pic_in_start_y, pic_start_x, pic_start_yis_exit_slider()#load_complete()#tobe modifyif debug_flag == False:time.sleep(0.5)image_screen = ImageGrab.grab()else:image_screen = Image.open(path + "151.png")pic_start_x, pic_start_y = find_pic_start_x_y(image_screen)pic_in_start_x = pic_start_x + white_x#inside picpic_in_start_y = pic_start_y + white_y#inside pic
is_exit_slider()#判断屏幕是否需要滑动的滑块验证
#exist slider 1490,577 FAF7F4
def is_exit_slider():slider_flag = Trueflag_point = (1490, 577)while slider_flag == True:image_temp = ImageGrab.grab((flag_point[0], flag_point[1], flag_point[0] + 1, flag_point[1] + 1))get_color = image_temp.getpixel((0,0))if get_color == (244,247,250):slider_flag = Falsereturn Trueelse:time.sleep(1.5)print "..."
为了找出两图区别,通过两次截屏操作,配合滑块移动,
###########screen print pictureh_slider_x = pic_start_x + 34h_slider_y = pic_start_y + 257time.sleep(0.4)pyautogui.moveTo(h_slider_x, h_slider_y)time.sleep(0.1)pyautogui.mouseDown(h_slider_x, h_slider_y, button='left')#time.sleep(0.3)remnant_x = 5remnant_y = 2###########get small_img1if debug_flag == False:small_img1 = ImageGrab.grab((pic_start_x + white_x, pic_start_y + white_y, pic_start_x + white_x + small_pic_width, pic_start_y + white_y + pic_height))else:image1 = Image.open(path + "151.png")#tempsmall_img1 = image1.crop((pic_start_x + white_x, pic_start_y + white_y, pic_start_x + white_x + small_pic_width, pic_start_y + white_y + pic_height)) #pic_start_x,pic_start_y = find_pic_start_x_y(image2)#to be del#small_img1.show()pyautogui.moveRel(small_pic_width, 0,duration=0.5)###########get small_img2if debug_flag == False:small_img2 = ImageGrab.grab((pic_start_x + white_x, pic_start_y + white_y, pic_start_x + white_x + small_pic_width, pic_start_y + white_y + pic_height))else:image2 = Image.open(path + "152.png")#tempsmall_img2 = image2.crop((pic_start_x + white_x, pic_start_y + white_y, pic_start_x + white_x + small_pic_width, pic_start_y + white_y + pic_height))#small_img2.show()
差分图片,将差分结果图片二值化,和之前做好的5个模板图片比较,找到最相近的,确定水印的形状
###########tobe 1/0value picturepic_diff = compare_images(small_img1,small_img2)pic_diff_L = pic_diff.convert('L')threshold_K = 4 # 2 - 5table_one_zero = []for i in range(256):if i < threshold_K:table_one_zero.append(0)else:table_one_zero.append(1)pic_diff_one_zero = pic_diff_L.point(table_one_zero, '1')############find small slider pictureslider_x1,slider_y1,slider_x2,slider_y2 = pic_diff_one_zero.getbbox()slider_m_x = (slider_x1 + slider_x2) / 2 + pic_in_start_xslider_m_y = (slider_y1 + slider_y2) / 2 + pic_in_start_y#print slider_m_x, slider_m_y###########judge slider_picture_number[0,4]slider_pic_no = Slider_Image_Judge(pic_diff_one_zero.crop((slider_x1,slider_y1,slider_x2,slider_y2)))
根据水印形状,选择合适的线的位置
###########get right lineif debug_flag == False:right_line = ImageGrab.grab((pic_in_start_x + pic_width / 2, slider_m_y + CONST_PIC_LIST[slider_pic_no][1], pic_in_start_x + pic_width, slider_m_y + CONST_PIC_LIST[slider_pic_no][1] + 1))else:right_line = image1.crop((pic_in_start_x + pic_width / 2, slider_m_y + CONST_PIC_LIST[slider_pic_no][1], pic_in_start_x + pic_width, slider_m_y + CONST_PIC_LIST[slider_pic_no][1] + 1)) #right_line.show()point_middle_x = get_print_middle(right_line,lab_o,CONST_PIC_LIST[slider_pic_no][2])
point_middle_x 这个就是目标点,也就是要滑块要滑向的位置,终于可以滑了
##########smooth right lineif point_middle_x == False:point_middle_x = get_print_middle(right_line,lab_o,CONST_PIC_LIST[slider_pic_no][2], True)
根据point_middle_x手残式滑动滑块
if point_middle_x != False:slider_distance = point_middle_x - (slider_x1 + slider_x2) / 2 - CONST_PIC_LIST[slider_pic_no][0] + pic_width / 2 + 1 #from slider to slider print#print slider_distance###########slider!!!!pyautogui.moveRel(slider_distance + remnant_x - small_pic_width, remnant_y, duration=0.5)pyautogui.moveRel(0 - remnant_x, remnant_y,duration=0.5)pyautogui.mouseUp(h_slider_x + slider_distance,h_slider_y - remnant_y,button='left')###########judge success?? with picture color.....success_res = slider_success()if success_res == True: flash_count = 0return Trueelse:print "slider failed F5"flash_pic() #exsit or not?time.sleep(1)flash_count += 1if flash_count <= 2 :auto_slider_pic()else:flash_count = 0return Falseelse:print "F5"flash_pic()auto_slider_pic()
还有很多函数,源代码贴在后面吧,
简单说说主要的判断函数:
LabColor 这函数是colormath库的,delta_e_cie2000()这个方法能实现颜色之间差别量化,很好用。
先把rgb颜色转化成Lab,然后用delta_e_cie2000将两个颜色的区别量化
我这里用0xCCCCCC找到这条线的最接近颜色后,又用这个图的最接近颜色代替0xCCCCCC重新计算一次,提高准确度。
计算出每个点的平均值delta_e_list_avg,以这个点为基准,将线段二值化。
对delta_e_list_avg这个平均值的不同倍数为基准,多次计算,直到复合上文中第3点的判断条件。没有符合的就刷新图片(算不出来就不算了)
将二值化的数据 去掉孤立的点,使之更加“平滑”,防止因为图像中个别点颜色较深影响判断。(其实就是原来清晰的图片被后台添加了噪点,噪线,这个操作其实就是笨方法去噪)
#find white print x_middle point
#delta_e_K = 1#0.85 # my K wait to modify
lab_o = LabColor(80.8857694878, -1.83487616655, -1.23182079098,2,"d65")#gray
def get_print_middle(image_input, lab_o, expected_length, delta_e_K = 1, smooth_flag = False):middle_point = 0delta_e_list = list() img_rgb = image_input.load()image_input_width = image_input.size[0]delta_e_list = create_delta(img_rgb, image_input_width, lab_o)# find nearest colormin_delta_e_index = delta_e_list.index(min(delta_e_list))print "min_color:", min_delta_e_index#create delta listimg_rgb_point = img_rgb[min_delta_e_index, 0]rgb = sRGBColor(img_rgb_point[0] / 255.0,img_rgb_point[1] / 255.0,img_rgb_point[2] / 255.0)lab_o = convert_color(rgb, LabColor)delta_e_list = create_delta(img_rgb, image_input_width, lab_o)delta_e_list_avg = sum(delta_e_list) / len(delta_e_list)# to be 1,0auto_k_list = [0, -0.05, -0.1, 0.05, -0.15, -0.2, 0.1, 0.15]for k in range(0, len(auto_k_list)):auto_k = auto_k_list[k]delta_e_flag_list = list()for i in range(0, len(delta_e_list)):if delta_e_list[i] <= (delta_e_K + auto_k) * delta_e_list_avg :delta_e_flag_list.append(1)else:delta_e_flag_list.append(0)# del lonely 1or0 ,to be smooth (incompleted)if smooth_flag == True:print "delta_e_smooth start"delta_e_flag_list = delta_e_smooth(delta_e_flag_list)# get result (start ,end, length)res_lines_list = list()res_lines_list = find_lines(delta_e_flag_list)# if no lines change Kfit_line = find_fit_line(res_lines_list, expected_length)if fit_line == False:continueelse:line_start, line_end, line_length = fit_lineif min_delta_e_index >= line_start and min_delta_e_index <= line_end :print line_start, line_end, line_length,"K:",(delta_e_K + auto_k)middle_point = (line_start + line_end) / 2return middle_pointelse:print "match failed"return False
--------------------------------------
有大神提出可以用截获响应包方法获取水印的形状等操作,一个是需要分析网页代码有点不太会,另外这个实装平台是安卓使用TCGame.exe同步到PC上的,所以很难把数据包传给python中。才了这个笨方法。
源码:https://download.csdn.net/download/lst666/12635818