写在前面
最近笔者在抓取大众点评数据时,遇到了各种各样的验证码,如:滑块,滑块拼图,图片,图例验证码,不知道还有没有其他的验证码(心累)。但总体美团的验证码识别起来时还是比较简单的,没有那种花里胡哨的验证,就是服务器检验比较严格。
数据特点
某团的点选和其他点选差不多,按顺序点击。同时背景图的图例和图例图的图例方向和大小是一样的。
获取背景图和图例图
这里获取背景图有两种方式:
- 通过api 获取背景和图例。
- 通过自动化工具获取背景和图例。
方式1 获取的背景图会发现图片经过切割的,并不是完整的图片。
因此,我们选择方式2,通过selemuim 获取背景和图例,同时这里的背景和图例都是canvas 绘制直接找图片url 肯定不行,我们可以用 js 获取图片的 base64,再将 base 64 转为图片。
r = brower.execute_script('return document.getElementsByClassName("img-answer")["0"].src') # 图例图 base64
k = brower.execute_script('return document.getElementsByClassName("yoda-inference-question")[0].toDataURL()') # 背景图 base64
测试图片
识别思路
一个验证码是由两张图组成的,一个的背景图,一个是图标图。 首先在识别之前,看看要解决哪些问题。
- 从图标图中按顺序(按顺序点击的依据)抠出4个图标并处理图例图片
- 将扣出来的图例图和背景图中的图例配对
按顺序抠出图例
这里我们使用OpenCV模块扣取图例,因为这里的图例比较简单背景也没有特别的颜色,所以我们简单处理一下就行
image = cv2.imread(fg) # fg 是图例图
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 灰度图
_, threshold_img = cv2.threshold(gray_img, 0, 255, cv2.THRESH_OTSU) # 二值化
kernel = np.ones([3, 3], np.uint8)
dialte_img = cv2.dilate(threshold_img, kernel, 2) # 图例膨胀
dst = cv2.bitwise_not(dialte_img) # 像素取反
结果就是这样
有了上面的结果后,按顺序抠图就简单了。从左到右遍历下康康纵向的像素和是不是0就ok了
roi_image = []
i = 0
while i < image.shape[1]:
if(np.sum(dialte_img[:,i]) > 0):
start_col = i
while np.sum(dialte_img[:,i]) > 0:
i += 1
end_col = i
# 抠图
roi_image.append(image[:,start_col:end_col])
else:
i += 1
扣图效果
优化: 扣出来的图例左右已经没有多余的背景,但是上下还有一定的的多余的背景,测试之后发现会影响后面匹配所有我们这里直接处理。
img = cv2.imread(tuli) # tuli: 已经扣出来的图例
rows, cols, channel = img.shape
min_x = rows
min_y = cols
max_x = 0
max_y = 0
count = 0
for x in range(1, rows):
for y in range(1, cols):
if list(set(img[x, y]))[0] == 0:
if not max_x:
max_x = count
count = 0
break
else:
count += 1
else:
min_x = min_x - count
img1 = img[max_x:min_x, max_y:min_y]
最终效果
缩小图例和背景
因为背景图和图例图实际大小并不是与浏览器的大小一致,因此我们还需要对图片进行缩小,方便后续匹配。
# 背景图缩小
bg = cv2.imread(bg)
y, x = bg.shape[0:2]
new_bg = cv2.resize(bg, (int(x - 80), int(y - 54)))
# 图例缩小
fg = cv2.imread(fg)
_y, _x = fg.shape[0:2]
new_fg = cv2.resize(fg, (int(_x / 2), int(_y / 2)))
定位背景图图例
因为这里图例比较简单,同时没有什么旋转,缩放等花里胡哨的操作,我们可以直接使用OpenCV中的模板匹配来定位图例。
result = cv2.matchTemplate(bg, tuli, cv2.TM_CCOEFF_NORMED) # 模板匹配
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
br = (max_loc[0] + tw, max_loc[1] + th)
# 画框框
cv2.rectangle(bg, max_loc, br, (0, 0, 255), cv2.WINDOW_NORMAL)
cv2.imshow(f'tuli.png', bg)
cv2.waitKey(0)
识别结果
最后经过测试,正确率大概80、90% 的样子,还是挺高的。
优化点
1. 我没考虑那种隔得很开的图标,比如下图中的点赞标识本来是一个图例,它分成了部分扣取。
参考文章
前面提到过,美团验证码识别比较简单,但是后端验证很严格(心累),后续再分析图例验证的算法。