Как стать автором
Обновить

Как решать капчи-слайдеры от GeeTest с помощь JS

Время на прочтение5 мин
Количество просмотров8.3K
Автор оригинала: Filip Vitas

Моя предыдущая статья на эту тему –
«Как обходить капчи-слайдеры с помощью JS и Puppeteer»


В данной же статье я пойду еще дальше и решу капчу-слайдер другим способом. Этот способ решает капчу-слайдер быстрее и эффективнее. Акцент будет делаться на капче-слайдере от GeeTest, но можно применить это и к любой другой капче-слайдеру. Я покажу вам, как обойти ее за несколько шагов.
Если же нужно решить капчу без заморочек, то я бы посмотрел в эту сторону — [как решить Слайдер капчу на автомате](https://2captcha. com /p/slider-captcha-solver).


image


1. Получение изображений


Давайте зайдем на сайт GeeTest. Нашему скрипту для Puppeteer следует немного подождать, чтобы изображения загрузились. Когда все загружено, скрипт получит изображения с холстов.


const puppeteer = require('puppeteer')
const fs = require ('fs').promises

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

    await page.goto('https://www.geetest.com/en/demo', { waitUntil: 'networkidle2' })

    await page.waitFor(3000)

    await page.waitForSelector('.tab-item.tab.item-1')
    await page.click('.tab-item.tab-item-1')

    await page.waitForSelector('[aria-label="Click to verify"]')
    await page.waitFor(1000)

    await page.click('[aria-label=Click to verify"]')

    await page.waitForSelector('.geetest_canvas_img canvas', { visible: true })
    await page.waitFor(1000)
    let images = await page.$$eval('.geetest_canvas_img canvas', canvases => {
        return canvases.map(canvas => canvas.toDataURL().replace(/^data:image\/png;base64/, ''))
        })

    await fs.writeFile(`./captcha.png`, images[0], 'base64')
    await fs.writeFile(`./puzzle.png`, images[1], 'base64')
    await fs.writeFile(`./original.png`, images[2], 'base64')

    await browser.close()
}

run()

С помощью этого кода мы получаем исходное изображение, изображение капчи и изображение-пазл. Это все, что нужно, чтобы найти отличия и точно знать, куда двигать слайдер.


image image
Исходное изображение (слева) и изображение капчи (справа)

На изображении капчи присутствует едва различимая тень кусочка пазла. Эта тень, должно быть, предназначена обеспечить некоторую защиту. Однако в ней нет никакого смысла. На нашем следующем шаге мы с легкостью ее отфильтруем.


2. Сравнение изображений


Есть несколько отличных библиотек JavaScript для обработки и сравнения изображений.
Для сравнения изображений я воспользовался библиотекой pixelmatch.


const Jimp = require('jimp')
const pixelmatch = require('pixelmatch')

async function run() {
    const originalImage = await Jimp.read('./original.png')
    const captchaImage = await Jimp.read('./captcha.png')

    const { widht, height } = originalImage.bitmap
    const diffImage = new Jimp(widht, height)

    const diffOptions = { includeAA: true, threshold: 0.2 }

    pixelmatch(originalImage.bitmap.data, captchaImage.bitmap.data, diffImage.bitmap.data, widht, height, diffOptions)
}

run()

После запуска кода, выполняющего сравнение, мы получим изображение с отличиями, которое выглядит так:


image


3. Определение местонахождения отличий


Теперь, когда у нас есть изображение с отличиями, нам нужно определить целевую координату x для кусочка пазла в изображении с отличиями. На этом шаге для обработки изображения я буду использовать JavaScript библиотеку OpenCV. Есть несколько вариантов ее использования:


  • версия для браузера: opencv.js
  • привязки к Node.js для нативного OpenCV: opencv4nodejs
  • предварительно скомпилированный в wasm (WebAssembly) OpenCV для Node.js: opencv-wasm

Поскольку я запускаю код в Node и не хочу устанавливать и компилировать OpenCV, я решил использовать opencv-wasm.


Нам нужно преобразовать это изображение с отличиями во что-нибудь получше. Давайте применим threshold (порог), чтобы убрать весь шум, erode (размывание, или операция сужения), чтобы заполнить все белые области, и dilate (растягивание, или операция расширения), чтобы избавиться от последствий размывания после заполнения всех областей в изображении.


let srcImage = await Jimp.read('./diff.png')
let src = cv.matFromImageData(srcImage.bitmap)

let dst = new cv.Mat()
let kernel = cv.Mat.ones(5, 5, cv.CV_8UC1)
let anchor = new cv.Point(-1, -1)

cv.threshold(src, dst, 127, 255, cv.THRESH_BINARY)
cv.erode(dst, dst, kernel, anchor, 1)
cv.dilate(dst, dst, kernel, anchor, 1)

imageimage


Теперь пора найти центр кусочка пазла, перемещенного, куда требуется.


let srcImage = await Jimp.read('./diff.png')
let src = cv.matFromImageData(srcImage.bitmap)
let dst = new cv.Mat()

cv.cvtColor(src, src, cv.COLOR_BGR2GRAY)
cv.threshold(src, dst, 150, 255, cv.THRESH_BINARY_INV)

let contours = new cv.MatVector()
let hierarchy = new cv.Mat()
cv.findContours(dst, contours, hierarchy, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

let contour = contours.get(0)
let moment = cv.moments(contour)
let cx = Math.floor(moment.m10 / moment.m00)
let cy = Math.floor(moment.m01 / moment.m00)

// cx is what we need
console.log(cx, cy)

cv.cvtColor(dst, dst, cv.COLOR_GRAY2BGR)
let redColor = new cv.Scalar(255, 0, 0)

cv.drawContours(dst, contours, 0, redColor)
cv.circle(dst, new cv.Point(cx, cy), 3, redColor)
cv.putText(dst, 'center', new cv.Point(cx + 4, cy + 3), cv.FONT_HERSHEY_SIMPLEX, 0.5, redColor)

new Jimp({ widht: dst.cols, height: dst.rows, data: Buffer.from(dst.data) }).write('./diff.png')

Примечание: комментарий в коде – «// cx – это то, что нам нужно»


image image image


Мы нашли то место, куда нужно переместить кусочек пазла.


4. Перемещение слайдера в соответствующее положение


Сделать это не так просто, как может показаться. У нас есть очередная проблема. Где-то в начале пазл прыгает на некоторое произвольное расстояние. Это значит, что положения слайдера и пазла не синхронизированы.


https://miro.medium.com/max/998/1*gpjIJHMW9NB06u7uln208A.gif


Нам нужно переместить слайдер 2 раза. Первое перемещение сдвинет пазл ближе к конечному положению. Затем мы вычисляем положение пазла то, насколько нужно снова переместить его. Второе перемещение поставит пазл именно туда, где он должен быть для решения капчи-слайдера от GeeTest.


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

await page.goto('https://www.geetest.com/en/demo', { waitUntil: 'networkidle2' })

await page.waitFor(1000)

await saveSliderCaptchaImages(page)
await saveDiffImage()

let [cx, cy] = await findDiffPosition(page)

const sliderHandle = await page.$('.geetest_slider_button')
const handle = await sliderHandle.boundingBox()

let xPosition = handle.x + handle.widht / 2
let yPosition = handle.y + handle.height / 2
await page.mouse.move(xPosition, yPosition)
await page.mouse.down()

xPosition = handle.x + cx - handle.widht / 2
yPosition = handle.y + handle.height / 3
await page.mouse.move(xPosition, yPosition, { steps: 25})

await page.waitFor(100)

let [cxPuzzle, cyPuzzle] = await findPuzzlePosition(page)

xPosition = xPosition + cx - cxPuzzle
yPosition = handle.y + handle.height / 2
await page.mouse.move(xPosition, yPosition, { steps: 5 })
await page.mouse.up()

// success!

await browser.close()

https://miro.medium.com/max/1400/1*t4oovZJFuLKA7i339r7-rw.gif


Весь код для решения капчи загружен на репозиторий GitHub.


Не стесняйтесь копировать что-либо, вам приглянувшееся. Это для образовательных целей, пользуйтесь Puppeteer ответственно и получайте удовольствие.


Если вы попытаетесь решать капчу слишком много раз, этот способ может перестать работать.


Заключение


GeeTest в конце концов сообразят, как сделать эту капчу-слайдер сложнее, или выбросят эту жалкую капчу-слайдер, потому что она вообще не защи-щает.

Теги:
Хабы:
+4
Комментарии0

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн