验证码反爬

主要类型

  • 数字字母混合型 CNN
  • 滑块验证码 opencv 懂的话比第一种更容易破解
  • 中文标记文字位置 难
  • 12306

常用手段

  • ocr
  • 打码平台
  • 深度学习CNN opencv

opencv 破解腾讯滑块验证码

使用Chrome + selenium

frame 切换

1
2
3
4
5
6
7
8
9
10
11
wait.until(EC.presence_of_element_located((By.ID, "tcaptcha_iframe")))
driver.switch_to.frame('tcaptcha_iframe')

# 刷新验证码
wait.until(EC.presence_of_element_located((By.CLASS_NAME, "tc-action-icon"))).click()

# 解决验证码
get_captcha(page)

# 返回到默认default_content
driver.switch_to.default_content()

验证码结构

验证码由两部分组成block(滑块),bg(背景)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_captcha(page):
    time.sleep(1)
    
    # save block,bg img
    bg = wait.until(EC.presence_of_element_located((By.ID, 'slideBg')))
    down_img(bg.get_attribute('src'), IMG_PATH.format(page))

    block = wait.until(EC.presence_of_element_located((By.ID, 'slideBlock')))
    slider_name = '{}_temple'.format(page)
    down_img(block.get_attribute('src'), IMG_PATH.format(slider_name))
    
    # get gap 左上角x坐标
    gap_top_left = get_gap(page, bg.size, block.size)
    
    # block 与 bg 的起始距离
    xoffset = abs(bg.location['x'] - block.location['x']) + 1
    
    # 拖滑块到gap
    drag_slider(gap_top_left, xoffset, block.size)

识别gap

download的img和页面显示的img size不同, 必须resize,不然寻找的坐标不准,方便后面拖动滑块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def get_img(page, bg_size, block_size, is_debug):
    """get resize img"""
    bg = cvutil.load_img(IMG_PATH.format(page))
    bg = cvutil.resize(bg, width=bg_size['width'], height=bg_size['height'])

    block_name = '{}_temple'.format(page)
    tem = cvutil.load_img(IMG_PATH.format(block_name))
    tem = cvutil.resize(tem, width=block_size['width'], height=block_size['height'])

    if is_debug:
        cvutil.plt_color_img(bg)
        cvutil.plt_color_img(tem)
    return bg, tem


def search_tem(bg, tem, is_debug):
    """
    opencv 模板匹配
    
    :param bg:
    :param tem:
    :param is_debug:
    :return:
    """
    bg_gray = cvutil.gray(bg)
    tem = cvutil.gray(tem)
    cvutil.plt_gray_img(tem)
    w, h = tem.shape[1::-1]
    tem = np.abs(255 - tem)
    if is_debug:
        cvutil.plt_gray_img(tem)

    res = cv.matchTemplate(bg_gray, tem, cv.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)

    top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)

    if is_debug:
        cv.rectangle(bg, top_left, bottom_right, 255, 2)
        cvutil.plt_color_img(bg)

    return top_left[0]


def get_gap(page, bg_size, block_size, is_debug=False):
    print(page, bg_size, block_size)
    bg, tem = get_img(page, bg_size, block_size, is_debug)
    return search_tem(bg, tem, is_debug)


get_gap(773, {'height': 195, 'width': 341}, {'height': 68, 'width': 68})

滑块拖动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def drag_slider(gap_x, xoffset, block_size):
    slider = wait.until(EC.presence_of_element_located((By.ID, 'tcaptcha_drag_thumb')))
    w = slider.size['width']
    distance = gap_x - xoffset

    ActionChains(driver).click_and_hold(slider).perform()

    total_x = 0
    while True:

        # x = random.randint(xoffset + block_size['width'], gap_x + block_size['width'])
        x = random.randint(10, 25)
        y = random.randint(-1, 1)
        total_x += x
        if total_x > distance:
            x = x - (total_x - distance)
            total_x = distance

        # print('{}/{} [{}]'.format(x, total_x, distance))

        action = ActionChains(driver)
        # action.move_by_offset(xoffset=x, yoffset=y).perform()
        action.move_to_element_with_offset(slider, xoffset=x + w / 2, yoffset=y).perform()
        time.sleep(random.uniform(0.1, 1))

        if total_x == distance:
            break

    # print('stop {}/{} [{}]'.format(x, total_x, distance))
    ActionChains(driver).release(slider).perform()

效果

成功率还是挺高的