头大系列:以虎嗅网注册页面的滑块验证码为例:
开发环境
- python3.7
- selenium模块
- re模块
- PIL模块
- urllib模块
破解分析
打开浏览器调试工具,点击左上角按钮进行页面元素跟踪,然后将鼠标移动到滑块验证码的图像上,就发现问题了,这也是关键所在:
验证码的图像是由很多个这样的10x58(宽10高58)的小方块拼接而成的。再检查一下元素标签,发现每一个小方块的元素标签里面都有background-image:url以及background-position这两个关键的信息。
既然是url,就不妨打开链接来观察。发现打开的链接是一张图片,但却是一张乱七八糟的图片。
得到这张乱七八糟的图片,第一感觉是重影。试猜想,会不会将每一个小方块的url链接图片都重叠在一起或拼接在一起就能组成我们所看到的验证码图片。
但经打开每一张图片发现,每个小方块后面的url链接的图片是一样的。
这就说明不可能是将图片进行重叠或拼接了。那就有可能是另外一种可能:将这张完整的验证码图片的部分区域切割然后打乱排序,形成一张新的图片,而这张新的图片就是我们现在所看到的乱七八糟的图片。
再观察发现,每个小方块后面还有background-position这个重要的信息。发现里面正是-157px -58px; -145px -58px;这种形式,是不是很像坐标的形式了。而进一步猜想,将我们刚刚的乱七八糟的图按每一个小方块的坐标进行切割,然后再进行排序,是不是就能得到原来完整的验证码图片了。
def go():
...
fulls = driver.find_elements_by_class_name('gt_cut_fullbg_slice')
locationlist = []
for index,full in enumerate(fulls):
fulllocation = {}
cc = full.get_attribute('style')
fullurl = re.findall(r'url\("(.*?)"\);',cc)[0].replace('webp','jpg')
fulljpg = fullurl
fulllocation['x'] = re.findall(r'background-position:(.*?)px .*?px;',cc)[0]
fulllocation['y'] = re.findall(r'background-position:.*?px (.*?)px;',cc)[0]
locationlist.append(fulllocation)
namea = "full78.jpg"
urllib.request.urlretrieve(fulljpg,namea)
getfull(namea,locationlist)
进行实践:
运用PIL模块的切割crop()以及粘贴paste(),重新将图片进行整合。效果神奇出现:
def getfull(namea,locationlist):
old = Image.open(namea)
new = Image.new('RGB',(260,116)) #260,116是完整验证码的大小,10x58的小方块一共有56块。一行26个,即260。上下两行,即116。
shang = []
xia = []
for location in locationlist:
if location['y'] == str(-58):
shang.append(old.crop((abs(int(location['x'])),58,abs(int(location['x']))+10,116)))
if location['y'] == str(0):
xia.append(old.crop((abs(int(location['x'])),0,abs(int(location['x']))+10,58)))
#粘贴
i = 0
for ima in shang:
new.paste(ima,(i,0)) #根据图片左上角的坐标来粘贴
i +=10
i = 0
for imx in xia:
new.paste(imx,(i,58))
i +=10
new.save("yes.jpg")
先不管其他了的。先把两张完整的图片拼接出来再进行下一步吧。
按照相同的方法,找到页面元素中鼠标点击后的出现缺口的验证码图片位置,找到缺口图片乱七八糟的url链接。
通过相同方法拼接:
已经得到两张关键的验证码图片,那接下来就是PIL大显身手的时候了,对比两张图片的像素点,缺口位置明显黑了一块,也就是RGB三色必然与周围的像素RGB三色差别明显。
将两张图片的每个像素点进行遍历对比,RGB三色任意一色差距大于一定的范围即视为是滑块的缺口。
def compare(yes,no):
for xzhou in range(0,260):
for yzhou in range(0,116):
pianyi = offset(yes,no,xzhou,yzhou)
if pianyi == None:
pass
else:
return pianyi
def offset(yes,no,x,y):
pix1 = yes.getpixel((x,y))
pix2 = no.getpixel((x,y))
for rgb in range(0,3):
if pix1[rgb] - pix2[rgb] > 48:
print (x)
return x #这就是偏移量
找到缺口之后返回遍历的像素点的x坐标值(因为滑块只能X轴左右移动,所以y轴可以不需要用到),而x坐标就是滑块需要移动的距离。
def startslide(driver,xoffest):
slideelements = driver.find_element_by_xpath('//*[@id="login-modal"]/div/div/div/div[2]/div/div[2]/div/div[3]/div[2]')
person = ActionChains(driver)
person.click_and_hold(slideelements)
allx = int(xoffest)+16 #乱图与完整的图大小是有差别的,完整图的偏移量较小,我们所滑动的偏移量较大,故这根据情况调试。
person.move_to_element_with_offset(slideelements,allx,0).release().perform()
print ("OK")
结果展示:
完美拼接。但被吃了,也是美中不足:
快速,匀速,准确,所谓的“快,准,稳”,一样不差。
那就认定你是机器人来滑动的吧,把你吃掉!