如何使用 JS 和 Puppeteer 绕过“滑块验证码”

同一话题的下一故事 - 如何使用 JS 对 GeeTest“滑块验证码”进行解算

对于网站所有者而言,垃圾信息一直是一个大问题。另一方面,验证码也会让人头大,因为其会对用户影响造成负面影响。

看到验证码我就烦。相信大家都一样。验证用户身份的方法有很多种,但每一种方法都不是太行。

近年来,机器人的“智商”也在提高,网站所有者很难确保网站内容不会受到机器人的侵扰。只要有足够的时间和资源,我们几乎可以绕过一切验证码。我们可以使用 Puppeteer 插件来解算 reCAPTCHA。我们可以寻求提供验证码解算服务的公司的帮助。如:2Captcha。您可在这里了解如何使用 Puppeteer 和 2Captcha 服务。

目前还有一些网站采用了“滑动验证”的验证码。那么,为什么会有人会选择如此简单的验证方式呢?

其背后的原因是:

  • 大部分机器人不会执行 JS,此方式可阻止此类机器人
  • 滑动验证是一种对于用户比较友好的方式
  • 对于移动端用户而言,滑动也是非常自然的操作方式

所以对于真人用户,滑动验证码算是比较优秀和简单的验证方式了。但在更加智能的机器人看来,对这种方式进行解算也不是什么难事。

下面我们就来试着绕过一些滑动验证码。

滑动提交

“滑动提交”表单 jQuery 插件。这是可用于预防表单垃圾信息的一种验证方式。

首先我们现填写输入字段。为了滑动滑块,我们需要:

  • 将鼠标放在滑块中心
  • 按下鼠标
  • 移动鼠标
  • 松开鼠标

const puppeteer = require('puppeteer')

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  })
  const page = await browser.newPage()

  await page.goto('http://knthn.com/slidetosubmit/')
  await page.type('input[name="name"]', 'Puppeteer Bot')
  await page.type('input[name="email"]', 'js@automation.com')

  let sliderElement = await page.$('.slide-submit')
  let slider = await sliderElement.boundingBox()

  let sliderHandle = await page.$('.slide-submit-thumb')
  let handle = await sliderHandle.boundingBox()

  await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
  await page.mouse.down()
  await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 10 })
  await page.mouse.up()

  // success!

  await browser.close()
}

run()

大功告成。简简单单。

Dipbit 注册滑块

Dipbit 是一家数字货币交易所的网站。其登录和注册页面上都使用了“滑动验证”元素。

Dipbit 在其验证方式中采用了更机智的方法,所以这里我们需要添加一段代码来隐藏 Puppeteer 的执行工作。

const puppeteer = require('puppeteer')

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  })
  const page = await browser.newPage()

  await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false
    })
  })

  await page.goto('https://www.dippit.com/auth/Login')
  await page.type('#email', 'js@automation.com')
  await page.type('#password', 'password123')

  let sliderElement = await page.$('.slidetounlock')
  let slider = await sliderElement.boundingBox()

  let sliderHandle = await page.$('.nc_iconfont.btn_slide')
  let handle = await sliderHandle.boundingBox()

  await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
  await page.mouse.down()
  await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 50 })
  await page.mouse.up()

  // success!

  await browser.close()
}

run()

淘宝

淘宝是阿里巴巴旗下的网上购物网站。其采用了与注册 Dipbit 时类似的滑块验证方式。唯一的区别是其注册表单被放在了 iframe 内部。对 Puppeteer 而言,这并不是什么问题。

const puppeteer = require('puppeteer')

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  })
  const page = await browser.newPage()

  await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false
    })
  })

  await page.goto('https://world.taobao.com/markets/all/sea/register')

  let frame = page.frames()[1]
  await frame.waitForSelector('.nc_iconfont.btn_slide')

  const sliderElement = await frame.$('.slidetounlock')
  const slider = await sliderElement.boundingBox()

  const sliderHandle = await frame.$('.nc_iconfont.btn_slide')
  const handle = await sliderHandle.boundingBox()

  await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
  await page.mouse.down()
  await page.mouse.move(handle.x + slider.width, handle.y + handle.height / 2, { steps: 50 })
  await page.mouse.up()

  // success!

  await browser.close()
}

run()

拼图式的滑动验证码

我也遇到过一种需要进行“滑动验证”的 Vue 组件。对于真人用户来说,这种方式可能非常简单,但对于机器人而言其难度就上去了。

这种验证方式会首先获取图像,然后创建 2 个画布与 1 个滑块。同时采用拼图的方式来对最初的图像进行渲染。用户将需要通过移动滑块来填充缺失的那一块拼,并在拼图位置正确时放开鼠标,此时即可完成验证。

这种验证码会采用随机的拼图位置来迷惑机器人。

这里我其实并不想采取什么特别黑科技的方法(比如:机器学习或者 OCR),所以我选择了一点一点移动滑块,然后将结果图像与初始图像进行对比。

图像对比方面,我使用的是 rembrandt.js 库。移动鼠标,并在找到了结果图像与初始图像差异最轻微的拼图位置时松开鼠标。

const puppeteer = require('puppeteer')
const Rembrandt = require('rembrandt')

async function run() {
  const browser = await puppeteer.launch({
    headless: false,
    defaultViewport: { width: 1366, height: 768 }
  })
  const page = await browser.newPage()

  let originalImage = ""

  await page.setRequestInterception(true)
  page.on('request', request => request.continue())
  page.on('response', async response => {
    if (response.request().resourceType() === 'image') {
      originalImage = await response.buffer().catch(() => {})
    }
  })

  await page.goto('https://monoplasty.github.io/vue-monoplasty-slide-verify/')

  const sliderElement = await page.$('.slide-verify-slider')
  const slider = await sliderElement.boundingBox()

  const sliderHandle = await page.$('.slide-verify-slider-mask-item')
  const handle = await sliderHandle.boundingBox()

  let currentPosition = 0
  let bestSlider = {
    position: 0,
    difference: 100
  }

  await page.mouse.move(handle.x + handle.width / 2, handle.y + handle.height / 2)
  await page.mouse.down()

  while (currentPosition < slider.width - handle.width / 2) {
    await page.mouse.move(
      handle.x + currentPosition,
      handle.y + handle.height / 2 + Math.random() * 10 - 5
    )

    let sliderContainer = await page.$('.slide-verify')
    let sliderImage = await sliderContainer.screenshot()

    const rembrandt = new Rembrandt({
      imageA: originalImage,
      imageB: sliderImage,
      thresholdType: Rembrandt.THRESHOLD_PERCENT
    })

    let result = await rembrandt.compare()
    let difference = result.percentageDifference * 100

    if (difference < bestSlider.difference) {
      bestSlider.difference = difference
      bestSlider.position = currentPosition
    }

    currentPosition += 5
  }

  await page.mouse.move(handle.x + bestSlider.position, handle.y + handle.height / 2, { steps: 10 })
  await page.mouse.up()

  // success!

  await browser.close()
}

run()

还有一个值得一提的地方。这里我对滑块在 Y 轴上的移动方式进行了随机化,用于模拟真实用户的鼠标移动😎

await page.mouse.move(
  handle.x + currentPosition,
  handle.y + handle.height / 2 + Math.random() * 10 - 5
)

所有代码示例都可在 github repo 中查看,欢迎随意复制粘贴。

结论

验证码总是让人两难:网站是应该提供更好的体验和更简单验证方式,还是积极保护自身免受机器人侵扰而降低用户体验?
网站和机器人之间的战争永不停歇。无论网站采用何种验证方式,总是会有人能想出绕过验证码的方式——这只是时间问题而已。

在此声明,本文中的内容只是出于交流目的,请各位以负责任的方式使用 Puppeteer。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值