Найти в Дзене
2Captcha

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

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

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

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()

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

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

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

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()

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

-3

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)

-4

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

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 – это то, что нам нужно»

-5

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

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 в конце концов сообразят, как сделать эту капчу-слайдер сложнее, или выбросят эту жалкую капчу-слайдер, потому что она вообще не защищает.