掘金自动登录的实现|Python 主题月

本文正在参加「Python主题月」,详情查看 活动链接

前景回顾

我在 掘金登录引发的思考 一文中分析了掘金登录的一些知识,虽说通过程序组装登录参数后直接调用登录接口是有可行性,但是性价比太低。再者如果登录策略修改,可能到头来是 竹篮打水一场空,白忙活。

所以今天直接换个角度思考问题,借助 Selenium (一个用于Web应用程序测试的工具)完成登录。

技术背景

关于本文用到技术的一些简介,只需要简单了解即可。当然这些技术不了解也没有关系,不妨碍我们往下看。

破解登录

Large GIF (1202x818).gif

我们直接看如何借助 Selenium 完成登录,大致分为以下步骤:

  • 通过模拟浏览器的操作获取滑动验证码
  • 破解滑动验证码
    • a. 计算出滑块需要滑动的距离
    • b. 模拟浏览器拖拽完成滑动验证码识别
  • 模拟浏览器实现登录

1. 环境介绍

  • 硬件 Mac
  • 语言 Python3
  • 编辑器 PyCharm
  • 浏览器 Chrome

2. 获取滑动验证码

通过分析登录过程可以确定滑块验证码由两张图片构成。一张为滑块图片,另一张为背景图。这里只需要通过 Selenium 模拟浏览器的操作完成登录前的页面跳转最终获取验证码图片 URL,此步骤没有太多的难点,代码如下:

from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By


driver = webdriver.Chrome(executable_path="./chromedriver")
driver.get("https://juejin.cn/")

# 代码中的注释为从首页开始到获取URL的每一步骤,可以自己打开浏览器尝试

# 点击登录按钮
login_button = driver.find_element(By.XPATH, '''//button[text()="登录"]''')
ActionChains(driver).move_to_element(login_button).click().perform()

time.sleep(2)

# 选择账号密码登录
other_login_span = driver.find_element(By.XPATH, '''//span[text()="
          其他登录方式
        "]''')
ActionChains(driver).move_to_element(other_login_span).click().perform()
time.sleep(2)

# 填充用户名密码
username_input = driver.find_element(By.XPATH, '//input[@name="loginPhoneOrEmail"]')
password_input = driver.find_element(By.XPATH, '//input[@name="loginPassword"]')
username_input.send_keys("")  # 掘金的账号
password_input.send_keys("")  # 掘金的密码

# 点击登录
login_button = driver.find_element(By.XPATH, '''//button[text()="
        登录
      "]''')
ActionChains(driver).move_to_element(login_button).click().perform()
time.sleep(2)

# 获取滑块验证码图片
verify_image1 = driver.find_element(By.XPATH, '''//img[@id="captcha-verify-image"]/../img[1]''')
verify_image2 = driver.find_element(By.XPATH, '''//img[@id="captcha-verify-image"]/../img[2]''')
verify_image1_src = verify_image1.get_attribute("src")
verify_image2_src = verify_image1.get_attribute("src")
复制代码

3. 破解滑块验证码

下图为对应滑块、背景图以及对应的识别过程

image.png

Jun-29-2021 19-00-35.gif

简单的来说:识别过程就是将滑块对应的图片拖到背景图凹陷的地方。

3.1 定位滑块坐标

这里使用 Python Opencv 的模板匹配。模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。过程大致为在待检测图像上,从左到右,从上向下计算模板图像与重叠子图像的匹配度,匹配程度越大,两者相同的可能性越大。

cv2.matchTemplate 前两个参数分别是待模板图片、背景图片,第三个参数是匹配算法,这里使用是 TM_CCOEFF_NORMED (标准相关性系数匹配)。该函数返回结果是由每个位置的比较结果组合所构成的一个结果集(多维数组)。

例如输入图像(原始图像)尺寸是W H,模板的尺寸是w h,则返回值的大小为(W-w+1)* (H-h+1)。

# 这里需要注意 slider_pic background_pic 是灰度化 二值化后的图片

# 读取滑块
slider_pic = cv2.imread("slider_pic")
# 读取背景图
background_pic = cv2.imread("background_pic")
# 比较两张图的重叠区域
result = cv2.matchTemplate(slider_pic, background_pic, cv2.TM_CCOEFF_NORMED)

# 获取图片的缺口位置 top, left 为滑块相对于图片左上角的位置
top, left = np.unravel_index(result.argmax(), result.shape)
复制代码

备注:np.unravel_index(result.argmax(), result.shape)获取多维数组最大值对应坐标的值(对应为被找到图的像素坐标)。因为滑块移动只考虑水平移动,所以这里直接省略 top 即可。

3.2 生成滑动轨迹

众所周知人在拖拽图片时很难做到匀速操作,所以这里需要使用程序模拟人的拖拽。使得图片的运动轨迹更加无序。这里思路以及代码源于 极验滑动验证码识别

因为不能一次性将滑块拖到指定位置,这里需要模拟正常的拖动。所以拖动的逻辑为前段滑块做匀加速运动,后段滑块做匀减速运动,利用物理学的加速度公式即可完成验证。

滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 x 表示,所需时间用 t 表示,它们之间满足如下关系:

x = v0 * t + 0.5 * a * t * t 
v = v0 + a * t
复制代码
# 其中 track 为滑块拖动的轨迹
def get_track(distance):  # distance为传入的总距离
    # 移动轨迹
    track = []
    # 当前位移
    current = 0
    # 减速阈值
    mid = distance * 4 / 5
    # 计算间隔
    t = 0.2
    # 初速度
    v = 1

    while current < distance:
        if current < mid:
            # 加速度
            a = 4
        else:
            # 加速度
            a = -3
        v0 = v
        # 当前速度
        v = v0 + a * t
        # 移动距离
        move = v0 * t + 1 / 2 * a * t * t
        # 当前位移
        current += move
        # 加入轨迹
        track.append(round(move))
    return track
复制代码

4. 页面滑块操作

上一步借助 opencv 完成滑块验证码定位以及轨迹的生成,下面就是在页面上完成对滑块的拖拽。


# 定位到移动按钮
verify_div = self.driver.find_element(By.XPATH, '''//div[@class="sc-kkGfuU bujTgx"]''')

# 按下鼠标左键
ActionChains(self.driver).click_and_hold(verify_div).perform()
time.sleep(0.5)

# 遍历轨迹进行滑动
for t in track:
    time.sleep(0.01)
    ActionChains(self.driver).move_by_offset(xoffset=t, yoffset=0).perform()
# 释放鼠标 完成拖拽
ActionChains(self.driver).release(on_element=verify_div).perform()
复制代码

注意事项: 代码中的 time.sleep() 是为了使页面操作有短暂的停顿,而不是连贯的快速操作。

5. 获取 cookie

在完成滑块验证码的识别后,页面正常登录跳转成功,此时只需要获取到 juejin.cn 对应的 cookie即可。

# 获取当前页面 cookie
driver.get_cookies()

'''
[{'domain': '.juejin.cn',
  'expiry': 1632902943,
  'httpOnly': False,
  'name': 'MONITOR_WEB_ID',
  'path': '/',
  'secure': False,
  'value': 'e7fa2492-...-8ff5e04c6727'},
 # ...
 {'domain': '.juejin.cn',
  'expiry': 1630310943,
  'httpOnly': True,
  'name': 'sessionid_ss',
  'path': '/',
  'sameSite': 'None',
  'secure': True,
  'value': 'bfac25b956...f7be812054f'},
 {'domain': '.juejin.cn',
  'expiry': 1630310943,
  'httpOnly': True,
  'name': 'sessionid',
  'path': '/',
  'secure': False,
  'value': 'bfac25b956b...42f7be812054f'},
 ]
'''
复制代码

后记

需要优化的点

  • 图片多次本地存储、读取耗时,优化为直接在内存中处理图片。
  • 代码在一个文件中杂乱无章,优化为按功能划分不同的对象。
  • 识别有错误的几率,这里需要增加错误重试机制(如下图)。

Large GIF (1202x818).gif

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享