Как автоматически решать слайдер капчу

Обход слайдер капчи с использованием JS и  Puppeteer

Слайдер капча блокирует трафик от автоматических ботов, что приводит к проблемам доступностью сайтов и создает трудности при тестировании проектов.

Статья описывает процесс взаимодействия с API сервиса для реализации решения по автоматическому обходу слайдеров.

Обход слайдер капчи

Разработали пример. Пример кода создан для того, чтобы показать, как можно использовать API для обхода разных пользовательских капч в формате слайдера.

2Captcha - это сервис для автоматического обхода любых слайдер капч.

Решение

Чтобы решить обойти слайдер, нужно рассчитать путь, по которому мы должны перетащить слайдер. В большинстве случаев нужно всего две точки: начало и конец, причем точка начала обычно статична, так что мы можем найти ее всего один раз. Вторую точку могут найти работники, мы можем показать им изображение и дать инструкции, описывающие, какую именно точку им нужно указать, они нажмут на эту точку, и API вернет координаты этой точки. Нужный метод API описан в документации, называется Coordinates.

Подход

Для взаимодействия с капчей мы должны использовать браузер и фреймворк, позволяющий управлять браузером. В этом примере мы будем использовать Puppeteer в качестве фреймворка. Также нужен @2captcha/captcha-solver для взаимодействия с API сервиса.

Подготовка

Установка:

yarn add puppeteer @2captcha/captcha-solver

Установите ключ API:

export APIKEY=your_api_key_here

Пример кода

Поскольку в коде мы используем операторы ES6 import, давайте добавим следующее свойство в файл package.json:

"type": "module"

Создайте файл с именем index.js и приступите к добавлению кода:

Прежде всего, импортируем зависимости

import puppeteer from 'puppeteer'
import { Solver } from '@2captcha/captcha-solver'
import { readFile } from 'node:fs/promises'

Затем давайте создадим новый экземпляр Solver с нашим ключом API

const solver = new Solver(process.env.APIKEY)

Нам понадобится сгенерировать несколько случайных чисел, поэтому добавим простую однострочную строку, которая выполнит эту задачу

const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

Остальной код будет завернут в самоисполняющуюся async-функцию, так как гораздо удобнее вызывать все Promise-based методы Puppeeter с помощью async/await.

(async () => {
 // the rest of the code
})();

Запустим браузер, откроем открытую вкладку и откроем демонстрационную страницу captcha. Мы также определим переменную success, которая будет хранить состояние процесса обхода капчи.

const browser = await puppeteer.launch({
    devtools: true,
    slowMo: 11
})
const [page] = await browser.pages()

await page.goto('https://www.jqueryscript.net/demo/image-puzzle-slider-captcha/')

let success = false

Никогда нет 100% гарантии, что мы обойдем капчу с первой попытки, поэтому давайте запустим цикл. Мы выйдем из цикла, когда капча будет успешно разгадана.

Демо-страница показывает модальное окно согласия на использование cookies для некоторых стран, поэтому давайте откажемся от всех cookies.

Также загрузим инструкции, которые показаны работникам.

while (!success) {
    try {
        const consentButton = await page.waitForSelector('body > div.fc-consent-root > div.fc-dialog-container > div.fc-dialog.fc-choice-dialog > div.fc-footer-buttons-container > div.fc-footer-buttons > button.fc-button.fc-cta-do-not-consent.fc-secondary-button', { timeout: 3000 })
        if (consentButton) consentButton.click()
    } catch (e) { }

    const instruction = await readFile('./imginstructions.png', { encoding: 'base64' })

Далее нужно захватить изображение капчи и отправить его в API с помощью метода Coordinates. Есть вероятность, что изображение не загрузится, поэтому мы проверяем длину возвращаемого URL-адреса данных.

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

Результат содержит массив координат точек. В нашем случае должна быть только одна точка. Мы используем ее координату x как расстояние между левой границей изображения и центром целевого фрагмента головоломки.

const img = await page.evaluate(() => document.querySelector('canvas').toDataURL())
if (img.length < 2000) return

try {
    const res = await solver.coordinates({
        body: img,
        textinstructions: 'Puzzle center | Центр пазла',
        imginstructions: instruction
    })
    const offset = res.data[0].x       

Затем мы получим элемент слайдера, его координаты и размеры. Мы будем использовать его центр в качестве отправной точки для перетаскивания.

const slider = await page.$('div.slider')

const bb = await slider.boundingBox()

const init = {
    x: bb.x + bb.width / 2,
    y: bb.y + bb.height / 2
}

Затем вычисляем координаты конечной точки. В нашем случае ширина квадратной части головоломки составляет 40px, поэтому нам нужно вычесть половину этой ширины, так как мы ожидаем получить центр головоломки. Мы также используем полученную координату y, чтобы избежать перемещения указателя только по горизонтали, так как мы знаем, что капча отслеживает путь.

const target = {
    x: bb.x + bb.width / 2 + parseFloat(offset) - 20,
    y: res.data[0].y
}

Опционально можно нарисовать небольшую рамку на изображении, чтобы увидеть точное место, на которое нажал работник.

await page.evaluate((coord) => {
    console.log(coord)
    const canvas = document.querySelector('#captcha > canvas')
    let ctx = canvas.getContext('2d')
    ctx.globalAlpha = 1
    ctx.fillStyle = 'red'
    ctx.fillRect(coord.x, coord.y, 3, 3)
}, {
    x: parseInt(res.data[0].x),
    y: parseInt(res.data[0].y)
})

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

await page.mouse.move(init.x, init.y)
await page.mouse.down()
await page.mouse.move(target.x, target.y, {
    steps: randomInt(50, 100)
})
await page.mouse.up()

Наконец, мы пытаемся понять, удалось ли нам обойти капчу. В нашем случае после решения мы перенаправляемся на другую страницу, поэтому ждем навигации. В случае успешного решения мы выходим из цикла, устанавливая переменную success в true, сообщаем о правильном ответе в API, делаем скриншот и закрываем страницу и браузер. В случае ошибки (отсутствие навигации в течение 5 секунд) мы сообщаем о неправильном ответе и делаем еще одну попытку решить капчу.

try {
    await page.waitForNavigation({ timeout: 5000 })
    success = true
    await solver.goodReport(res.id)
    await page.screenshot({
        path: 'screenshot.png'
    })
    await new Promise(ok => setTimeout(() => ok(), 5000))
    await page.close()
    await browser.close()
} catch (e) {
    await solver.badReport(res.id)
}

Как вы могли заметить, код, начинающийся с взаимодействия с API, завернут в блок try/catch, поэтому нам нужно закрыть этот блок с помощью catch, а также закрыть наш цикл здесь.

    } catch (e) {
        console.log(`Failed to solve the captcha: ${e.err}`)
    }
}

Using this demo

Вы можете просто клонировать репозиторий, установить зависимости и запустить:

git clone git@github.com:2captcha/custom-slider-demo.git
yarn #or npm i
yarn start #or npm start

Это образовательный контент, не забывайте об этике при использовании сервиса.

Полезные материалы

Отвечаем на вопросы

Если после прочтения документации у вас остались вопросы, будем рады ответить и помочь:

Можно задать вопрос любым способом: