爬虫 - 点击图片验证码 (按词找图)

前言:

  • 在我们使用爬虫登录账号中常常会遇到各种验证码,如:图片验证码、滑块验证、计算验证码......
  • 其中图片验证码又可分为识别图片内容和按词找图 (12306) 等类别。识别图片内容的验证码可以用云打码、Tesseract、第三方 (腾讯、阿里、百度) 文字识别接口等方法识别,然而对于给定一组词语,然后根据给定的词语点击相应图片的验证码又该如何应对呢 (如:12306 登录验证)?

12306 模拟登陆

这里采用 Selenium + 超级鹰的方式进行模拟登陆

超级鹰

  • 注册:普通用户

  • 登录:普通用户

    • 题分查询:如果题分没有可以进行充值 (如:1 元 = 1000 题分)
    • 生成一个软件 ID (ID 后续会使用)
    • 下载示例代码:[开发文档] --> [Python] --> [点击这里下载]
  • 价格体系 (9004)

    • 标准价格:1 元 = 1000 题分,根据 VIP 级别和单次充值金额,有不同的赠送,低至五折
    • 可变位长类型验证码,仅按实际长度计分,描述的题分仅为上限题分
    • 坐标类返回值 x,y 更多坐标以 | 分隔,原图左上角 0,0 以像率 px 为单位,x 是横轴,y 是纵轴
      9101 坐标选一,返回格式:x,y 15
      9102 点击两个相同的字,返回:x1,y1|x2,y2 22
      9202 点击两个相同的动物或物品,返回:x1,y1|x2,y2 40
      9103 坐标多选,返回 3 个坐标,如:x1,y1|x2,y2|x3,y3 20
      9004 坐标多选,返回 1~4 个坐标,如:x1,y1|x2,y2|x3,y3 25
      9104 坐标选四,返回格式:x1,y1|x2,y2|x3,y3|x4,y4 30
      9005 坐标多选,返回 3~5 个坐标,如:x1,y1|x2,y2|x3,y3 30
      9008 坐标多选,返回 5~8 个坐标,如:x1,y1|x2,y2|x3,y3|x4,y4|x5,y5 40
  • API 接口文档 (Python)

    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    #!/usr/bin/env python
    # coding:utf-8

    import requests
    from hashlib import md5


    class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
    self.username = username
    password = password.encode('utf8')
    self.password = md5(password).hexdigest()
    self.soft_id = soft_id
    self.base_params = {
    'user': self.username,
    'pass2': self.password,
    'softid': self.soft_id,
    }
    self.headers = {
    'Connection': 'Keep-Alive',
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
    }

    def PostPic(self, im, codetype):
    """
    im: 图片字节
    codetype: 题目类型 参考 http://www.chaojiying.com/price.html
    """
    params = {
    'codetype': codetype,
    }
    params.update(self.base_params)
    files = {'userfile': ('ccc.jpg', im)}
    r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
    headers=self.headers)
    return r.json()

    def ReportError(self, im_id):
    """
    im_id:报错题目的图片ID
    """
    params = {
    'id': im_id,
    }
    params.update(self.base_params)
    r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
    return r.json()


    if __name__ == '__main__':
    # 用户中心>>软件ID 生成一个替换 96001
    chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名的密码', '96001')
    # 本地图片文件路径 来替换 a.jpg 有时WIN系统须要//
    im = open('test.png', 'rb').read()
    # 1902 验证码类型 官方网站>>价格体系 3.4+版 print 后要加()
    print(chaojiying.PostPic(im, 9004)['pic_str'])

    '''
    运行结果示例:
    {'err_no': 0, 'err_str': 'OK', 'pic_id': '8134314075817400001', 'pic_str': '56,183|86,94', 'md5': '7ee081fbec04bfeda6e3e91aa747cf7d'}
    '''

模拟登陆

  • 流程

    • 使用 Selenium 打开登录页面,选择账号登录
    • 对当前 Selenium 打开的这张页面进行截图
    • 对当前图片局部区域(验证码图片)进行裁剪
      • 好处:将验证码图片和模拟登陆进行一一对应
    • 使用超级鹰识别验证码图片(坐标)
    • 输入账户名和密码并点击登录
    • 使用 ActionChains 点击图片
    • 使用 ActionChains 移动滑块
  • 示例

    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
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    # 使用超级鹰识别12306验证码,然后使用Selenium登录12306
    # 超级鹰:https://www.chaojiying.com/
    # 12306: https://kyfw.12306.cn/otn/resources/login.html

    import time
    import requests
    from hashlib import md5

    import requests
    from PIL import Image
    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from selenium.webdriver.chrome.options import Options

    # 创建一个参数对象
    options = Options()
    options.add_argument('--disable-gpu')
    # 设置界面最大化
    options.add_argument('--start-maximized')
    # 规避对Selenium的检测
    options.add_experimental_option('excludeSwitches', ['enable-automation'])
    # 隐藏window.navigator.webdriver
    options.add_argument('--disable-blink-features=AutomationControlled')


    class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
    self.username = username
    password = password.encode('utf8')
    self.password = md5(password).hexdigest()
    self.soft_id = soft_id
    self.base_params = {
    'user': self.username,
    'pass2': self.password,
    'softid': self.soft_id,
    }
    self.headers = {
    'Connection': 'Keep-Alive',
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
    }

    def PostPic(self, im, codetype):
    """
    im: 图片字节
    codetype: 题目类型 参考 http://www.chaojiying.com/price.html
    """
    params = {
    'codetype': codetype,
    }
    params.update(self.base_params)
    files = {'userfile': ('ccc.jpg', im)}
    r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files,
    headers=self.headers)
    return r.json()

    def ReportError(self, im_id):
    """
    im_id:报错题目的图片ID
    """
    params = {
    'id': im_id,
    }
    params.update(self.base_params)
    r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
    return r.json()


    def main():
    # 创建浏览器对象
    browser = webdriver.Chrome(options=options)
    # 打开12306登录页面
    login_url = 'https://kyfw.12306.cn/otn/resources/login.html'
    browser.get(login_url)
    time.sleep(2)
    # 选择“账号登录”
    browser.find_element_by_xpath('//li[@class="login-hd-account"]/a').click()
    time.sleep(2)
    # 将当前页面进行截图且保存
    browser.save_screenshot('page.png')
    # 图片元素
    code_img_ele = browser.find_element_by_id('J-loginImg')
    # 验证码图片左上角坐标 {'x': 1005, 'y': 291}
    location = code_img_ele.location
    print('location:', location)
    # print(location['x'])
    # print(type(location['x']))
    # print('*' * 60)
    # 验证码图片的长和宽 {'height': 190, 'width': 320}
    size = code_img_ele.size
    print('size:', size)
    # print(size['height'])
    # print(type(size['height']))
    # 确定验证码左上角和右下角坐标(裁剪的区域就确定了)
    rangle = (location['x'], location['y'], location['x'] + size['width'], location['y'] + size['height'])
    print('rangle:', rangle)
    # 裁剪验证码区域
    img = Image.open('page.png')
    code_img_name = 'code.png'
    # crop根据指定区域进行图片裁剪(注意:将Windows下“缩放与布局”调整为100%)
    frame = img.crop(rangle)
    frame.save(code_img_name)
    # 将验证码图片提交给超级鹰进行识别
    chaojiying = Chaojiying_Client('username', 'password', 'id')
    im = open('code.png', 'rb').read()
    # 结果示例:56,183|86,94
    result = chaojiying.PostPic(im, 9004)['pic_str']
    print('result:', result)
    # 处理超级鹰返回来的数据
    # 存储即将被点击的点的坐标 [[x1,y1],[x2,y2]]
    all_list = []
    if '|' in result:
    list_1 = result.split('|')
    count_1 = len(list_1)
    for i in range(count_1):
    xy_list = []
    x = int(list_1[i].split(',')[0])
    y = int(list_1[i].split(',')[1])
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
    else:
    xy_list = []
    x = int(result.split(',')[0])
    y = int(result.split(',')[1])
    xy_list.append(x)
    xy_list.append(y)
    all_list.append(xy_list)
    print('all_list:', all_list)
    # 输入用户账号
    browser.find_element_by_id('J-userName').send_keys('XXX')
    time.sleep(0.5)
    # 输入用户密码
    browser.find_element_by_id('J-password').send_keys('XXX')
    # 遍历列表,使用动作量对每一个列表元素对应的x,y指定的位置进行操作
    for oli in all_list:
    x = oli[0]
    y = oli[1]
    ActionChains(browser).move_to_element_with_offset(code_img_ele, x, y).click().perform()
    time.sleep(0.5)
    time.sleep(3)
    # 点击登录按钮
    browser.find_element_by_id('J-login').click()
    time.sleep(3)
    # 找到滑块
    slider = browser.find_element_by_xpath('//span[@id="nc_1_n1z"]')
    # 找到滑块轨迹
    track = browser.find_element_by_id('nc_1__scale_text')
    time.sleep(0.5)
    # 滑动滑块
    action = ActionChains(browser)
    action.drag_and_drop_by_offset(slider, track.size['width'], -slider.size['height']).perform()
    time.sleep(5)
    browser.quit()


    if __name__ == '__main__':
    main()

  • ActionChains 方法列表

    方法 说明
    click(on_element=None) 单击鼠标左键
    click_and_hold(on_element=None) 点击鼠标左键,不松开
    context_click(on_element=None) 点击鼠标右键
    double_click(on_element=None) 双击鼠标左键
    drag_and_drop(source, target) 拖拽到某个元素然后松开
    drag_and_drop_by_offset(source, xoffset, yoffset) 拖拽到某个坐标然后松开
    key_down(value, element=None) 按下某个键盘上的键
    key_up(value, element=None) 松开某个键
    move_by_offset(xoffset, yoffset) 鼠标从当前位置移动到某个坐标
    move_to_element(to_element) 鼠标移动到某个元素
    move_to_element_with_offset(to_element, xoffset, yoffset) 移动到距某个元素(左上角坐标)多少距离的位置
    perform() 执行链中的所有动作
    release(on_element=None) 在某个元素位置松开鼠标左键
    send_keys(*keys_to_send) 发送某个键到当前焦点的元素
    send_keys_to_element(element, *keys_to_send) 发送某个键到指定元素