Уведомление об использовании файлов cookie

Этот сайт использует cookie. Файлы cookie запоминают вас, поэтому мы можем предоставить вам персонализированные услуги. Подробнее.

Looking for international version of our service? Go to 2captcha.com

Решение hCaptcha с обновлениями в методе Grid

Мы рады сообщить о последних обновлениях в методе Grid для решения captcha с помощью нашего сервиса. В этой статье мы подробно рассмотрим, как использовать обновленный API для решения hCaptcha. Этот метод решения hCaptcha актуален, когда не работает решение токеном.

Изменения в методе Grid были нужны для дифференцирования подходов к решению капч сеткой, а использование "компьютерного зрения" в сочетании с традиционным распознаванием работниками позволяет распознавать капчи быстрее и с более высокой точностью.

Что такое hcaptcha?

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

Какие изменения были в API?

Теперь метод Grid принимает дополнительный параметр imgType, который используется для направления запроса в соответствующий поток распознавания в соответствии с типом капчи. При решении типа hCaptcha необходимо присвоить параметру imgType, значение hcaptcha.

Как распознать капчу с помощью Grid-метода?

Процесс распознавания условно делится на 4 шага:

  1. Клик на чекбокс капчи
  2. Извлечение необходимые данные капчи
  3. Взаимодействие с API
  4. Отмечание нужных квадратов сетки

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

В этой статье мы сосредоточимся на втором и третьем шагах.

Сбор данных

Задание hCaptcha состоит из:

  • девяти изображений
  • текста задачи
  • изображение задания (опционально)

Получить все элементы изображений можно с помощью селектора 'div.image:not([role="img"])' внутри фрейма капчи. Последние 9 элементов — это плашки, которые мы затем объединим в одно изображение с сеткой 3x3. Первое изображение, если оно присутствует, — это изображение задачи.

Вот что из этого получится.

9 изображений, склеенных вместе

Картинка задания

Для получения текста задания воспользуемся селектором 'h2.prompt-text', чтобы получить элемент h2 и элемент innerText. Убедитесь в том, что язык вашего браузера English, так как мы предполагаем, что текст задания предоставлен на английском языке.

В нашем случае текст выглядит следующим образом:

Click on the images that best match the theme of the sample image.

Ниже приведена функция на JavaScript, которая поможет извлечь данные корректно. Вы можете использовать этот код в iframe капчи после того как нажмёте на чекбокс капчи. Когда задание капчи станет видимым, вызовите функцию getCaptchaData. Функция возвращает Promise, который будет являться объектом, содержащим следующие свойства:

  • type - тип задания API: GridTask или CoordinatesTask. В статье мы рассматриваем случай GridTask
  • body - склеенные вместе плашки или весь набор задания для типа CoordinatesTask в формате строки base64
  • comment - текст задания
  • imgInstructions - изображение задания в виде строки base64 (опционально)

Пример использования функции:

try {
    let data = getCaptchaData()
    console.log(JSON.stringify(data))
} catch (e) {
    console.error(e)
}
Код функции
class Tile {
    constructor(el) {
        this.url = el.style['backgroundImage'].slice(5, -2)
        this.width = el.clientWidth
        this.height = el.clientHeight
        this.img = new Image(this.width, this.height)
        this.img.setAttribute('crossorigin', 'anonymous')
    }
    load(ctx, index = 0, vOffset = 0) {
        return new Promise((resolve) => {
            let pos = [
                { x: 0, y: 0 }, { x: ctx.canvas.width / 3, y: 0 }, { x: ctx.canvas.width / 3 * 2, y: 0 },
                { x: 0, y: ctx.canvas.height / 3 }, { x: ctx.canvas.width / 3, y: ctx.canvas.height / 3 }, { x: ctx.canvas.width / 3 * 2, y: ctx.canvas.height / 3 },
                { x: 0, y: ctx.canvas.height / 3 * 2 }, { x: ctx.canvas.width / 3, y: ctx.canvas.height / 3 * 2 }, { x: ctx.canvas.width / 3 * 2, y: ctx.canvas.height / 3 * 2 }
            ]
            this.img.addEventListener('load', () => {
                ctx.drawImage(this.img, pos[index].x, vOffset + pos[index].y)
                resolve(this.img)
            })
            this.img.src = this.url
        })

    }
}

const getMaxTileSize = (nodes) => {
    return nodes.reduce((m, c) => {
        return {
            width: !m.width || c.clientWidth > m.width ? c.clientWidth : m.width,
            height: !m.height || c.clientHeight > m.height ? c.clientHeight : m.height
        }
    })
}

const getCaptchaData = () => {
    return new Promise((resolve, reject) => {
        let canvas, taskCanvas, result = {}
        const imgNodes = Array.from(document.querySelectorAll('div.image:not([role="img"])'))
        const comment = document.querySelector('h2.prompt-text').innerText

        if (imgNodes.length < 9) {
            const srcCanvas = document.querySelector('canvas')
            let tmpcanvas = document.createElement('canvas')

            tmpcanvas.width = srcCanvas.clientWidth
            tmpcanvas.height = srcCanvas.clientHeight
            let tmpctx = tmpcanvas.getContext('2d')

            tmpctx.drawImage(srcCanvas, 0, 0, srcCanvas.width, srcCanvas.height, 0, 0, tmpcanvas.width, tmpcanvas.height)
            tmpctx.font = "18px sans-serif";
            tmpctx.fillText(comment, 4, 20, tmpcanvas.width - 8);
            let promises = []

            imgNodes.forEach((imgNode, i) => {
                const tile = new Tile(imgNode, tmpctx)
                promises.push(tile.load(tmpctx, i, imgNode.clientHeight / 4))
            })

            Promise.all(promises).then(() => {
                result = {
                    comment,
                    body: tmpcanvas.toDataURL().replace(/^data:image\/?[A-z]*;base64,/, ''),
                    type: 'CoordinatesTask'
                }
                resolve(result)
            })
        } else if (imgNodes.length >= 9) {
            result.type = 'GridTask'
            result.comment = comment
            const maxTileSize = getMaxTileSize(imgNodes)
            const tileNodes = imgNodes.filter(n => (n.clientWidth >= maxTileSize.width && n.clientHeight >= maxTileSize.height))
            canvas = document.createElement('canvas')
            canvas.width = maxTileSize.width * 3
            canvas.height = maxTileSize.height * 3
            let ctx = canvas.getContext('2d')

            let promises = []

            tileNodes.forEach((tileNode, i) => {
                const tile = new Tile(tileNode, ctx)
                promises.push(tile.load(ctx, i))
            })

            Promise.all(promises).then(() => {
                result.body = canvas.toDataURL().replace(/^data:image\/?[A-z]*;base64,/,'')
            })

            if (imgNodes.length > 9) {
                const maxTileSize = getMaxTileSize(imgNodes)
                const [taskNode] = imgNodes.filter(n => (n.clientWidth < maxTileSize.width && n.clientHeight < maxTileSize.height))
                taskCanvas = document.createElement('canvas')
                taskCanvas.width = taskNode.clientWidth
                taskCanvas.height = taskNode.clientHeight
                let taskCtx = taskCanvas.getContext('2d')
                const taskTile = new Tile(taskNode, taskCtx, 0)
                taskTile.load(taskCtx).then(() => {
                    result.imgInstructions = taskCanvas.toDataURL().replace(/^data:image\/?[A-z]*;base64,/, '')
                })
            }
            resolve(result)
        } else {
            reject(`Unknown captcha type. Image nodes count: ${imgNodes.length}`)
        }
    })
}

Пример результата

{
    "type": "GridTask",
    "comment": "Click on the images that best match the theme of the sample image.",
    "imgInstructions": "iVBORw0KGgoAAA...",
    "body": "iVBORw0KGgoAAAA..."
}

Взаимодействие с API

Когда собраны все необходимые данные, отправляется запрос к API для решения капчи. Используются данные, полученные на предыдущем этапе, добавляется параметр imgType со значением hcaptcha, а также указываетсяколичество столбцов и строк, в нашем случае оба имеют значение 3.

Метод: POST
API endpoint: https://api.rucaptcha.com/createTask

Параметры

Параметр Тип Обязательный Описание
type String Да Тип задачи, в нашем случае GridTask
imgType String Да В нашем случае hcaptcha
body String Да Склеенные в одно изображение плашки в формате base64
comment String Да Текст задания на английском языке
rows Number Да Количество строк, в нашем случае 3
columns Number Да Количество столбцов, в нашем случае 3
imgInstructions String Нет Изображение задания в формате base64

Пример запроса

{
    "clientKey":"YOUR_API_KEY",
    "task": {
        "type": "GridTask",
        "imgType": "hcaptcha",
        "body": "iVBORw0KGgoAAAA...",
        "comment": "Click on the images that best match the theme of the sample image.",
        "rows": 3,
        "columns": 3,
        "imgInstructions": "iVBORw0KGgoAAA..."
    }
}

Результат выполнения запроса

{
    "errorId": 0,
    "status": "ready",
    "solution": {
        "click": [
            3,
            4,
            7
        ]
    },
    "cost": "0.0012",
    "ip": "1.2.3.4",
    "createTime": 1692863536,
    "endTime": 1692863556,
    "solveCount": 1
}

Используйте метод click вашего фреймворка автоматизации браузера для клика по соответствующим элементам. Используйте селектор для элементов 'div.task', только помните, что массив элементов картинок нумеруется с 0, а в ответе нашего API картинки нумеруются с 1 по 9. В нашем примере мы кликаем по ним с помощью чистого JavaScript:

document.querySelectorAll('div.task')[3-1].click()
document.querySelectorAll('div.task')[4-1].click()
document.querySelectorAll('div.task')[7-1].click()

Также вы можете использовать наши библиотеки для быстрой реализации метода в вашем решении.

Примеры кода в наших библиотеках

Ruby
result = client.grid({
  method: 'base64',
  key: 'your_api_key',
  recaptcha: 1,
  json: 1,
  recaptchacols: 3,
  recaptcharows: 3,
  img_type: 'hcaptcha',
  textinstructions: 'lease click on all entities similar to the following silhouette',
  imginstructions: Base64.encode64(File.read('path/to/hint.jpg')),
  body: Base64.encode64(File.read('path/to/captcha.jpg')),
  previous_id: 0
})
Python
result = solver.grid( method='base64',
                     body = 'base64',
                     key='your_api_key',
                     recaptcha=1,
                     json=1,
                     recaptchacols=3,
                     recaptcharows=3,
                     img_type='hcaptcha',
                     textinstructions='Please click on all entities similar to the following silhouette',
                     imginstructions=base64.b64encode(open('path/to/hint.jpg', 'rb').read()).decode('utf-8'),
                     previous_id=0)

</details>