cover_image

简析自动化爬虫的验证码突破

Iron黄小铁 银河产品技术团队
2024年11月27日 03:40
图片



让新中产的美好生活有更多的选择


微信号:银河技术团队

官网:www.galaxy-immi.com



一、简介:

网站的验证码通常用于验证用户身份或者防止恶意爬取数据,对于自动化爬虫来说,验证码往往会造成不少麻烦。本文我将以基于python-playwright对知乎平台的数据爬取为例来讲解关于验证码的识别问题,并提供相应的代码示例。

这里简单介绍一下playwright是什么,它是微软在 2020 年初开源的新一代自动化测试工具,可以驱动浏览器进行各种自动化操作。其具有以下特点

  • 支持当前所有的主流浏览器,包括 chrome、edge、firefox、safari;

  • 支持浏览器有头和无头模式;

  • 安装和配置过程简单,会自动安装对应的浏览器和驱动,不需要额外配置 WebDriver 等

使用浏览器的自动化操作还爬取数据更加简单且稳定性和适应性更强,playwright的安装和基本操作这里不做赘述,可以通过官方api文档了解

二、简单验证码

普通验证码如常见的数字和英文字母可以通过OCR扫描就可以突破

图片


在提取并保存验证码的图片后,我们可以使用pytesseract和opencv-python对图片进行预处理和识别。

1、安装相关库


pip install pytesseract opencv-python
2、编写代码进行图片识别

可以使用playwright读取img标签的src属性对图片进行读取和保存

img = browser_page.query_selector('img.captcha-img')img_url = img.get_attribute('src')
然后进行图片的识别

def verify_captcha(image_path): # image_path 为图片保存路径 image = cv2.imread(image_path) # 转换为灰度图像 gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 将图片进行高斯模糊 blurred_image = cv2.GaussianBlur(gray_image, (5, 5), 0) # 使用二值化提高对比度 _, binary_image = cv2.threshold(blurred_image, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) # 使用PyTesseract进行OCR recognized_text = pytesseract.image_to_string(binary_image, lang='eng') print(recognized_text)


三、复杂验证码

除了上述所说的简单的文字和字母的验证码,现在也出现了越来越多的复杂的验证码,这里简单介绍两种验证码,也是知乎平台常见的两种验证码。

1、普通滑块验证码


图片


对于这种验证码,首先我们要计算出滑块与卡位的距离,同样可以用到opencv-python库

def img_distance(x, y):    # x,y 为滑块图片和源图片的文件内容    # 将图片数据转换为numpy数组并解码rgb格式    img_x = np.frombuffer(x, np.uint8)    img = cv2.imdecode(img_x, cv2.COLOR_RGB2BGR)    # 另一个图片做系统操作    img_y = np.frombuffer(y, np.uint8)    template = cv2.imdecode(img_y , cv2.COLOR_RGB2BGR)  # 滑动滑块图片计算两个图片间的距离    res = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)    value = cv2.minMaxLoc(res)[2][0]    distance = value    return distance


接着我们需要模拟人的行为操作鼠标完成滑动,如果我们一步到位直接让滑块划到对应位置必验证失败。因为平台会做行为检测,太机械的行为无法通过验证。

def sliding_deal(page):     # x,y为我们在页面上读到的图片链接并请求到的图片文件内容    distance = img_distance(x, y)  # 获取到验证码dom的边界坐标   box = page.locator(".yidun_control").bounding_box()  # 鼠标移到滑块处并按下  page.locator(".yidun_slider__icon").hover()    page.mouse.down()  # 分解步数,我们将滑动行为分成多次进行以模拟人手动拖动行为    steps = 30      for step in range(1, steps + 1):        progress = step / steps        next_step = distance * progress    # 随机取数,让每一步的距离不均等        mov_x = box['x'] + next_step + random.uniform(-2, 2)        print(next_step)        mov_y = box['y'] + random.uniform(-2, 2)    # 移动鼠标拖动滑块移动        page.mouse.move(mov_x, mov_y)    # 每完成一小步停顿0.02-0.1秒        time.sleep(random.uniform(0.02, 0.1))  #鼠标抬起完成滑动进行验证    page.mouse.up()

这里我们设置的步数越多滑块滑动得越慢,太快或者太慢都不合适,这里需要自己根据实际情况调整

2、图形验证码


当我们做好滑块验证码验证之后,突然发现知乎的验证码已经变成了图形验证码。。。

图片

对于这种验证码,我们也可以通过类似滑块验证码匹配图形相似度的方式去匹配和计算相对位置,但是你会发现知乎的这种验证码,它的源图片和下面的图标图片是一整张的。

图片

那么我们在进行图片识别之前还得先进行切图的操作,这时候我们其实可以换另一种更简便的方式来做,那就是接入第三方打码平台,即专门处理各类验证码的平台,这种平台一般支持各种不同类型的验证码。

图片

使用平台的好处是既不需要我们进行繁杂的验证的过程开发和验证,又让我们可以更快速的对平台验证变更做出反应。下面以知乎的图像验证码为例。


def img_verify(page): #从页面获取图片并进行base64 img = page.query_selector('img.yidun_bg-img') img_url = img.get_attribute('src') response = requests.get(img_url) image_type = response.headers['Content-Type'] image_content = response.content base64_encoded = b64encode(image_content).decode('utf-8') data_base64 = f'data:{image_type};base64,{base64_encoded}' # 通过请求打码平台接口得到对应坐标 url = config_base["DETAYUN_URL"] key = config_base["DETAYUN_KEY"] json_data = { "key": key, "verify_idf_id": 54, "img1": data_base64, "img2": "" } """ 在响应体可以取到对应的坐标 "code": 200, "msg": "成功", "data": { "res_str": "[(82, 89), (267, 64), (259, 116)]" } """ response = requests.post(url, json=json_data)
取到坐标之后,我们再控制鼠标选中对应坐标的位置即可。这里跟滑块验证码一样需要注意行为检测的问题。例如直接使用playwright的page.click(x,y)传入坐标也可以选中图形,但是验证必失败,因为click没有运动轨迹无法通过行为检测。

我们必须模拟人移动鼠标一个个点击图标的行为来通过行为检测才能验证通过


def click_img(): # 定位到图形边框坐标 div = page.query_selector('div.yidun_bgimg') box = div.bounding_box() s_x, s_y = box['x'], box['y'] # 分步模拟鼠标人为移动,步数越多移动越慢 step = 20 tag = 0 for bit_x, bit_y in coordinates: # 每点完一个位置模拟反应时间 time.sleep(random.uniform(0.5, 1)) # 计算出每一小步需要移动的下一个坐标的位置 if tag > 0: bit_x, bit_y = bit_x - coordinates[tag - 1][0], bit_y - coordinates[tag - 1][1] x, y = s_x + coordinates[tag - 1][0], s_y + coordinates[tag - 1][1] else: x, y = s_x, s_y m_x, m_y = bit_x / step, bit_y / step for i in range(step): # 控制鼠标移动到对应坐标 page.mouse.move(x + m_x * (i + 1), y + m_y * (i + 1)) # 每一小步后短暂停顿模拟人为拖动 time.sleep(random.uniform(0.05, 0.5)) # 控制鼠标按下抬起模拟鼠标点击 page.mouse.down() page.mouse.up() tag = tag + 1 exit_count = exit_count + 1 page.wait_for_timeout(2000)
最后我们的行为轨迹会如下图,同样步数越多操作越慢,一般20-30步为宜

图片


写在最后:

除了我上述所说的验证码,不同平台还有其他五花八门的各种验证码。所以使用打码平台可以更快的支持验证码的识别,我们只需要负责通过行为检测就可以了。

当然使用打码平台也存在风险,毕竟依赖于第三方,如果他们出现异常的话我们的程序也将无法正常执行,建议备选几家平台做风险切换。


图片



让新中产的美好生活有更多的选择


微信号:银河技术团队

官网:www.galaxy-immi.com




继续滑动看下一个
银河产品技术团队
向上滑动看下一个