Назойливая игра - разметка данных для google. Если, занимаетесь сбором доступной информации с ресурсов, не принадлежащим вам, и не сумели реализовать решение для преодоления этой преграды, советы от начинающего разработчика вам помогут. Опишу один из способов, основанный на детекторе объектов, хорошо справляется с типом 4x4, хуже с 3x3. Использую архитектуру YOLO, "золотая середина" точности/производительности, подход одинаков для всех детекторов. В коммерческом продукте стоит использовать "ансамбль" нейронных сетей, к детектору добавить классификацию каждой ячейки, это повысит общую точность с приемлемой производительностью. Также эту задачу можно решить, использую обучение с подкреплением A2C/DQN или любую современную архитектуру, трансформеры, генеративно-состязательные сети.

Примерный алгоритм

  1. Поиск позиции кнопки

  2. Имитация движения мыши к координатам кнопки "Я не робот"

  3. Имитация нажатия кнопки "Я не робот"

  4. Получение картинки ( с сервера google, посылается целой )

  5. Получение класса искомого объекта ( из html )

  6. Получение координат ячеек ( из html )

  7. Решение

  8. Активация ячеек исходя из решения

  9. Нажатие готово/далее/подтвердить

  10. Обработать

Если reCaptcha продолжается, повторить с 4 по 10.

Полезные инструменты в Python

  • pynput

  • selenium

  • numpy

  • tensorflow

  • opencv

  • scipy

  • beautiful soup, можно обойтись одним selenium

Код найден в просторах моей нейронной сети, значит что то видел в интернет сети, живу не в вакууме

from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from pynput.mouse import Button, Controller
import scipy.interpolate as si
import numpy as np
import cv2
import time
import random
import os
import io
import base64

# использование B-сплайна для имитации человеческих движений мыши
def human_like_mouse_move(action, start_element):
    points = [[6, 2], [3, 2],[0, 0], [0, 2]];
    points = np.array(points)
    x = points[:,0]
    y = points[:,1]
    t = range(len(points))
    ipl_t = np.linspace(0.0, len(points) - 1, 100)
    x_tup = si.splrep(t, x, k=1)
    y_tup = si.splrep(t, y, k=1)
    x_list = list(x_tup)
    xl = x.tolist()
    x_list[1] = xl + [0.0, 0.0, 0.0, 0.0]
    y_list = list(y_tup)
    yl = y.tolist()
    y_list[1] = yl + [0.0, 0.0, 0.0, 0.0]
    x_i = si.splev(ipl_t, x_list)
    y_i = si.splev(ipl_t, y_list)
    startElement = start_element
    action.move_to_element(startElement);
    action.perform();
    c = 5
    i = 0
    for mouse_x, mouse_y in zip(x_i, y_i):
        action.move_by_offset(mouse_x,mouse_y);
        action.perform();
        print("Move mouse to, %s ,%s" % (mouse_x, mouse_y))   
        i += 1    
        if i == c:
            break;

# "конфигурирование" selenium
def my_proxy(PROXY_HOST,PROXY_PORT):
    fp = webdriver.FirefoxProfile()
    fp.set_preference("network.proxy.type", 1)
    fp.set_preference("network.proxy.socks",PROXY_HOST)
    fp.set_preference("network.proxy.socks_port",int(PROXY_PORT))
    fp.update_preferences()
    options = Options()
    options.headless = True # отключаем визуализацию происходящего
    return webdriver.Firefox(executable_path="geckodriver/geckodriver", options=options, firefox_profile=fp)

# с tor прокси, reCaptcha будет вечной, 
# хорошо для экспериментов
proxy = my_proxy("127.0.0.1", 9050)
proxy.get("https://www.google.com/search?q=apple")

После запроса, по понятным многим причинам, появляется reCaptcha.

# reCaptcha отображается в iframe's 
# переключение iframe №1
proxy.switch_to.frame(proxy.find_elements_by_tag_name("iframe")[0]) 
# находим кнопку "Я не робот"
check_box = WebDriverWait(proxy, 10).until(EC.element_to_be_clickable((By.ID ,"recaptcha-anchor")))
time.sleep(2)
# имитируем нажатие кнопки "Я не робот
action =  ActionChains(proxy);
human_like_mouse_move(action, check_box)
check_box.click()
time.sleep(2)

Получаем информацию для дальнейшей обработки: ссылка на изображение, класс поиска, тип reCaptcha.

# переключение iframe №2
proxy.switch_to.default_content()
iframes = proxy.find_elements_by_tag_name("iframe")
proxy.switch_to.frame(iframes[2])
html = proxy.page_source
# получаем ссылку изображения, тип reCaptcha
try:
      img_rc = proxy.find_elements_by_xpath('//img[@class="rc-image-tile-33"]')[0]
      t_type = 3
except IndexError:
      img_rc = proxy.find_elements_by_xpath('//img[@class="rc-image-tile-44"]')[0]
      t_type = 4 
# получаем искомый класс
try:
      required_class = proxy.find_elements_by_xpath('//div[@class="rc-imageselect-desc-no-canonical"]/strong')[0].text
except IndexError:
      required_class = proxy.find_elements_by_xpath('//div[@class="rc-imageselect-desc"]/strong')[0].text
time.sleep(2)

Скачиваем изображение с сервера, не самым "элегантным способом". Встраиваем код javascript, что бы выполнить XMLHttpRequest, полученный ответ отобразить в canvas, для дальнейшей передачи в python строкой base64. После получения base64, преобразовываем в numpy array. Альтернативное получение изображения, описано в статье https://habr.com/ru/post/449236/

answ = proxy.execute_script(''' 
                var img = new Image();
                var cnv = document.createElement('canvas');
                cnv.id = 'tutorial';
                    img.onload = function(){
                      cnv.height = img.height;
                      cnv.width = img.width;
                      console.log(cnv.width, cnv.height, img.width, img.height);
                      cnv.getContext('2d').drawImage(img, 0, 0);
                    }
                        var request = new XMLHttpRequest();
                        request.open('GET', arguments[0].src);
                        request.responseType = 'blob';
                        request.onload = function() {
                            var reader = new FileReader();
                            reader.readAsDataURL(request.response);
                            reader.onload =  function(e){
                                img.src = e.target.result;
                            };
                        };
                        request.send();
                var child = document.body.appendChild(cnv);
                ''', img_rc)
time.sleep(4)
answ = proxy.execute_script(''' 
                cnv = document.getElementById('tutorial');
                return cnv.toDataURL('image/jpeg').substring(22);
                ''')

nparr = np.asarray(bytearray(io.BytesIO(base64.b64decode(answ)).read()), dtype=np.uint8)
img_np = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

Получены все нужные компоненты, для ответа на вопрос: "в каком квадрате находиться искомый объект". На этом ресурсе не целесообразно в очередной раз описывать архитектуру YOLO детектора, поэтому демонстрирую фрагменты кода нужные этой статье.

def draw_boxes(image, boxes, scores, labels, classes, detection_size, search_class):
    """
    :param boxes, shape of  [num, 4]
    :param scores, shape of [num, ]
    :param labels, shape of [num, ]
    :param image,
    :param classes, the return list from the function `read_coco_names`
    """
    new = np.ones(shape=image.shape, dtype=np.float32)
    ans = []
    if boxes is None: 
        return ans, image, new
    for i in range(len(labels)): # for each bounding box, do:
        bbox, score, label = boxes[i], scores[i], classes[labels[i]]
        bbox_text = "%s %.2f" %(label, score)
        # convert_to_original_size
        detection_size, original_size = np.array(detection_size), np.array(image.shape[1])
        ratio = float(original_size) / float(detection_size)
        bbox = list((bbox.reshape(2,2) * ratio).reshape(-1))
        coord = [abs(int(x)) for x in bbox]
        # встраиваем наш плохой код
        o0 = coord[0]
        o1 = coord[1]
        o2 = coord[2]
        o3 = coord[3]
        # создаем маску искомых объектов
        if search_class == label.split('\n')[0]:
             new[o1:o3, o0:o2, :] = 2
             ans.append(classes[labels[i]])
    return ans, image, new
  
# основная функция для получения ответа
def imcr(i, col, activation_threshold = 1):
    answ = []
    im_w, im_h, im_c = i.shape
    w, h = im_w//col, im_h//col
    sZero = i[0:w, 0:h,:].size
    num = 0
    for wi in range(0, col):
		    for hi in range(0, col):
                        num += 1
                        P_R = (np.sum(i[wi*w:(wi+1)*w, hi*h:(hi+1)*h, :]) / sZero) * 100
                        P_R = P_R - 100
                        if activation_threshold < int(P_R):
                              answ.append(num)
                        else:
                              pass
    return answ
  
def ocr(img_np, required_class, t_type):
    # img_np -> YOLO -> B,C 
    # выполняем функцию не максимального подавления
    boxes, scores, labels = cpu_nms(B, C, len(classes), max_boxes=1000, score_thresh=0.4, iou_thresh=0.5)
    # встроил свой плохой код в чужой код визуализации данных
    result, img, z_image = draw_boxes(img_np, boxes, scores, labels, classes, yolo_image_shape, required_class)
    # наша основная функция
    answ = imcr(np.array(z_image), t_type)  

Наша основная функция, imcr(image_array, type_captcha, activation_threshold) - получая маску искомого объект, функция проверяет процент заполнения в каждой ячейке по заданному условию

  • параметры:
    image_array - маска объекта
    type_captcha - тип картинки, пример 4 (4x4)
    activation_threshold - заданное условие активации, по умолчанию 1%

  • возвращает:
    номера искомых ячеек в списке

Получаем ответ, имитируем нажатие нужной ячейки, подтверждаем.

answ_ocr = ocr(img_np, required_class, t_type)
ids = proxy.find_elements_by_xpath('//td[@class="rc-imageselect-tile"]')
for i in answ_ocr:
      ids[i].click() 
# поиск кнопки подтверждения  
confirm_btn = WebDriverWait(proxy, 4).until(EC.element_to_be_clickable((By.XPATH ,'//button[@id="recaptcha-verify-button"]'))) #Нахожу кнопку
action =  ActionChains(proxy);
human_like_mouse_move(action, confirm_btn) # двигать курсор к кнопке  
confirm_btn.click() # нажать

Это не детальное руководство, а советы с фрагментами кода. Если применять предложенный подход, с другими алгоритмами, точность 93% и выше. Используя детектор, ваша главная задача повысить точность детектора (YOLO, RCNN, SSD). Также, одно из условий успешного обхода google captcha, использование "чистых proxy". У меня не особо получается преобразовывать свои мысли в текст, надеюсь попытка успешна, и моя статья появиться на этом ресурсе.