Python爬虫实战:Selenium结合OpenCV破解知乎滑动验证码

周予安
周予安
发布时间:2026-10-14| 3人浏览过
原创

本文详解如何利用Python的Selenium与OpenCV库实现知乎滑动验证码的自动识别与登录。内容涵盖通过高斯模糊和Canny算法提取滑块位移、模拟人类运动轨迹以规避风控、以及使用Chrome远程调试模式隐藏Selenium特征,提供完整的代码实现与注意事项。

滑动验证距离

首先需要获取验证码的背景图和滑块图。利用OpenCV库,对两张图片分别进行高斯模糊去噪和Canny边缘检测处理。随后使用matchTemplate方法进行图像匹配,从而计算出滑块的滑动距离。需要注意的是,知乎验证码在计算出的原始距离基础上,还需额外向右偏移10像素。


def get_distance(self, bg_img_path='./bg.png', slider_img_path='./slider.png'):
        """获取滑块移动距离"""

        # 背景图片处理
        bg_img = cv.imread(bg_img_path, 0)  # 读入灰度图片
        bg_img = cv.GaussianBlur(bg_img, (3, 3), 0)  # 高斯模糊去噪
        bg_img = cv.Canny(bg_img, 50, 150)  # Canny算法进行边缘检测
        # 滑块做同样处理
        slider_img = cv.imread(slider_img_path, 0)
        slider_img = cv.GaussianBlur(slider_img, (3, 3), 0)
        slider_img = cv.Canny(slider_img, 50, 150)
        # 寻找最佳匹配
        res = cv.matchTemplate(bg_img, slider_img, cv.TM_CCOEFF_NORMED)
        # 最小值,最大值,并得到最小值, 最大值的索引
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
        # 例如:(-0.05772797390818596, 0.30968162417411804, (0, 0), (196, 1))
        top_left = max_loc[0]  # 横坐标
        return top_left

滑块运动轨迹

为了模拟真实人类的操作行为,在滑块移动到缺口位置后,不应立即停止,而是继续向后滑动一小段距离,然后再回退至准确位置。这种加减速及回退的动作能有效降低被网站风控系统识别的概率。


def get_tracks(self, distance):
        '''滑动轨迹 '''

        tracks = []
        v = 0
        t = 0.2  # 单位时间
        current = 0  # 滑块当前位移
        distance += 10  # 多移动10px,然后回退
        while current < distance:
            if current < distance * 5 / 8:
                a = random.randint(1, 3)
            else:
                a = -random.randint(2, 4)
            v0 = v  # 初速度
            track = v0 * t + 0.5 * a * (t ** 2)  # 单位时间(0.2s)的滑动距离
            tracks.append(round(track))  # 加入轨迹
            current += round(track)
            v = v0 + a * t
        #回退到大致位置
        for i in range(5):
            tracks.append(-random.randint(1, 3))
        return tracks

鼠标滑动操作

利用Selenium提供的鼠标动作链(ActionChains),按照预先计算好的滑动轨迹执行具体的鼠标滑动操作,包括点击、拖拽和释放等动作。


def mouse_move(self,slide,tracks):
        '''鼠标滑动'''

        #鼠标点击滑块并按照不放
        ActionChains(self.driver).click_and_hold(slide).perform()
        #按照轨迹进行滑动,
        for track in tracks:
            ActionChains(self.driver).move_by_offset(track, 0).perform() 
        ActionChains(self.driver).release(slide).perform()

规避知乎Selenium检测

在使用Selenium自动化爬取知乎时,可能会遇到错误代码10001:请求异常请升级客户端后重新尝试。该错误产生的原因是知乎网站能够检测到Selenium自动化测试脚本的特征。

为规避检测,可以采用Chrome浏览器的远程调试模式,结合Selenium来遥控操作Chrome浏览器进行数据抓取。这种方式可以隐藏Selenium的自动化特征,从而绕过网站的检测机制。

添加环境变量

需要将chrome.exe所在的目录添加到系统的环境变量中,例如C:\Program Files\Google\Chrome\Application。配置完成后,即可直接在命令行中输入chrome.exe来启动浏览器。

打开cmd窗口,执行启动命令。


chrome.exe --remote-debugging-port=9222 --user-data-dir="E:\eliwang\selenium_data"

启动时需确保指定的端口未被占用,并通过user-data-dir参数指明配置文件的存储路径,该路径可根据需要自定义。

执行命令后,浏览器将启动并打开一个新的标签页,此时浏览器处于远程调试状态。

Selenium接管浏览器的主要代码实现如下。


options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")

关闭浏览器窗口

1、使用浏览器对象的close()方法即可关闭当前窗口,quit()方法在此场景下不适用。

2、整个过程涉及手动打开浏览器实例,随后由程序接管并手动关闭。

完整的登录代码实现如下。


# coding:utf-8

import cv2 as cv
import time
import random
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait as WAIT
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from urllib.request import urlretrieve


class Zhihu_login:
    '''知乎模拟登陆'''

    def __init__(self):
        options = webdriver.ChromeOptions()
        #操控chrome浏览器
        options.add_experimental_option("debuggerAddress", "127.0.0.1:9222")
        self.driver = webdriver.Chrome(options=options)
        self.wait = WAIT(self.driver, 5)
        self.url = 'https://www.zhihu.com/'
        self.bg_img_path = './bg.png'
        self.slider_img_path = './slider.png'


    def run(self):
        '''执行入口'''

        self.driver.get(self.url)
        try:
            if WAIT(self.driver,3).until(EC.presence_of_element_located((By.ID,'Popover15-toggle'))):
                print('登陆成功')
                self.save_cookie()
                self.driver.close()
        except:
            # 切换到密码登陆
            self.wait.until(EC.element_to_be_clickable((By.XPATH, '//div[contains(@class,"SignFlow-tabs")]/div[2]'))).click()
            name_input = self.driver.find_element_by_name('username')
            name_input.clear()
            name_input.send_keys('账号')
            pass_input = self.driver.find_element_by_name('password')
            pass_input.clear()
            pass_input.send_keys('密码')
            self.wait.until(EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]'))).click()  # 点击登陆按钮
            time.sleep(1)
            #进行滑动验证,最多尝试5次重新验证
            if self.slide_verify():
                print('登陆成功')
                self.save_cookie()
                self.driver.close()
            else:
                print('第1次登陆失败')
                for i in range(4):
                    print('正在尝试第%d次登陆'%(i+2))
                    if self.slide_verify():
                        print('第%d次登陆成功'%(i+2))
                        self.save_cookie()
                        self.driver.close()
                        return
                    print('第%d次登陆失败' % (i + 2))
                print('登陆失败5次,停止登陆')
                self.driver.close()


    def slide_verify(self):
        '''滑动验证'''

        slider_button = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//div[@class="yidun_slider"]')))
        self.bg_img_url = self.wait.until(EC.presence_of_element_located((By.XPATH, '//img[@class="yidun_bg-img"]'))).get_attribute('src')  # 获取验证码背景图url
        self.slider_img_url = self.wait.until(EC.presence_of_element_located((By.XPATH, '//img[@class="yidun_jigsaw"]'))).get_attribute('src')  # 获取验证码滑块图url
        urlretrieve(self.bg_img_url, self.bg_img_path)
        urlretrieve(self.slider_img_url, self.slider_img_path)
        distance = self.get_distance(self.bg_img_path, self.slider_img_path)
        distance += 10  # 实际移动距离需要向右偏移10px
        tracks = self.get_tracks(distance)
        self.mouse_move(slider_button,tracks)
        try:
            element = self.wait.until(EC.presence_of_element_located((By.ID,'Popover15-toggle')))
        except:
            return False
        else:
            return True

    def save_cookie(self):
        cookie = {}
        for item in self.driver.get_cookies():
            cookie[item['name']] = item['value']
        print(cookie)
        print('成功获取登陆知乎后的cookie信息')


    def mouse_move(self,slide,tracks):
        '''鼠标滑动'''

        #鼠标点击滑块并按照不放
        ActionChains(self.driver).click_and_hold(slide).perform()
        #按照轨迹进行滑动,
        for track in tracks:
            ActionChains(self.driver).move_by_offset(track, 0).perform()
        ActionChains(self.driver).release(slide).perform()


    def get_distance(self, bg_img_path='./bg.png', slider_img_path='./slider.png'):
        """获取滑块移动距离"""

        # 背景图片处理
        bg_img = cv.imread(bg_img_path, 0)  # 读入灰度图片
        bg_img = cv.GaussianBlur(bg_img, (3, 3), 0)  # 高斯模糊去噪
        bg_img = cv.Canny(bg_img, 50, 150)  # Canny算法进行边缘检测
        # 滑块做同样处理
        slider_img = cv.imread(slider_img_path, 0)
        slider_img = cv.GaussianBlur(slider_img, (3, 3), 0)
        slider_img = cv.Canny(slider_img, 50, 150)
        # 寻找最佳匹配
        res = cv.matchTemplate(bg_img, slider_img, cv.TM_CCOEFF_NORMED)
        # 最小值,最大值,并得到最小值, 最大值的索引
        min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)
        # 例如:(-0.05772797390818596, 0.30968162417411804, (0, 0), (196, 1))
        top_left = max_loc[0]  # 横坐标
        return top_left


    def get_tracks(self, distance):
        '''滑动轨迹 '''

        tracks = []
        v = 0
        t = 0.2  # 单位时间
        current = 0  # 滑块当前位移
        distance += 10  # 多移动10px,然后回退
        while current < distance:
            if current < distance * 5 / 8:
                a = random.randint(1, 3)
            else:
                a = -random.randint(2, 4)
            v0 = v  # 初速度
            track = v0 * t + 0.5 * a * (t ** 2)  # 单位时间(0.2s)的滑动距离
            tracks.append(round(track))  # 加入轨迹
            current += round(track)
            v = v0 + a * t
        #回退到大致位置
        for i in range(5):
            tracks.append(-random.randint(1, 3))
        return tracks


if __name__ == '__main__':
    Zhihu_login().run()

到此这篇关于python爬虫之利用selenium+opencv识别滑动验证并模拟登陆知乎的文章就介绍到这了,更多相关selenium+opencv滑动验证内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。