Работа с трафиком в CDP. Решаем ReCaptcha через ZennoBrowser с помощью CapMonster

Porosenok

Client
Регистрация
26.09.2010
Сообщения
1 291
Благодарностей
119
Баллы
63

Здравствуйте, уважаемые форумчане. В этой статье мы продолжаем наше знакомство с CDP. В прошлой мы познакомились с базовыми принципами работы и использовали домен Network для прослушивания сообщений передаваемых по протоколу Websocket. В этой статье я хочу развить эту тему и показать как про помощи CapMonster Cloud и ZennoBrowser решать ReCaptcha ловя обычные HTTP-запросы с картинками и заданиями. Ну что ж, погнали.

Часть 1. Подготовка.
За основу берем проект с прошлой статьи. Но так как нам предстоит кликать по картинкам рекапчи, добавим в класс Helpers несколько вспомогательных методов из которых затем соберем метод для клика:
Во-первых метод для поиска элемента по xpath:
C#:
public static async Task<string> PerformSearchAsync(CDPClient cdpClient, string query, string sessionId = null)
{
    try
    {
        var response = await cdpClient.SendCommandAsync("DOM.performSearch", new { query }, sessionId: sessionId);
        var result = response["result"];
        if (result != null)
        {
            string searchId = result.Value<string>("searchId");
            if (string.IsNullOrEmpty(searchId))
            {
                throw new Exception("searchId null");
            }
            int resultCount = result.Value<int>("resultCount");
            if (resultCount == 0)
            {
                throw new Exception("resultCount = 0");
            }
            return searchId;

        }
        else
        {
            throw new Exception("PerformSearchAsync result null");
        }

    }
    catch (Exception ex)
    {
        throw new Exception($"Произошла ошибка: {ex.Message}");
    }

}
Суть такая: мы отправляем команду DOM.performSearch вместе с нашим xpath (или css селектором), в результате получаем id поиска (searchId) по которому запрашиваем результат через следующий метод:

C#:
public  static async Task<int> GetSearchResultsAsync(CDPClient cdpClient, string searchId, int matchNum = 0, string sessionId = null)
{
    try
    {
        var response = await cdpClient.SendCommandAsync("DOM.getSearchResults", new { searchId, fromIndex = matchNum, toIndex = matchNum + 1 }, sessionId: sessionId);
        var result = response["result"] as JObject;
        if (result != null)
        {
            var nodeIdsArray = result["nodeIds"] as JArray;

            if (nodeIdsArray != null && nodeIdsArray.Count > 0)
            {
                int firstNodeId = nodeIdsArray[0].Value<int>();

                if (firstNodeId == 0)
                {
                    throw new Exception("GetSearchResultsAsync firstNodeId = 0, result = " + response.ToString() + " searchId = " + searchId);
                }
                return firstNodeId;
            }
            else
            {
                throw new Exception("GetSearchResultsAsync нет массив nodeIds пуст");
            }
        }
        else
        {
            throw new Exception("GetSearchResultsAsync нет поля result");
        }
    }
    catch (Exception ex)
    {
        throw new Exception($"Произошла ошибка {ex.Message}");
    }
}
В результате если элемент есть на странице получаем nodeId, по которому через метод DOM.getBoxModel запрашиваем координаты нашего элемента:

C#:
public  static async Task<string> GetBoxModelAsync(CDPClient cdpClient, int nodeId, string sessionId = null)
{
    try
    {
        var response = await cdpClient.SendCommandAsync("DOM.getBoxModel", new { nodeId }, sessionId: sessionId);
        var result = response["result"]["model"];
        if (result != null)
        {
            var contentArray = result["content"] as JArray;

            if (contentArray != null)
            {
                string modelContent = contentArray.ToString();
                if (string.IsNullOrEmpty(modelContent))
                {
                    throw new Exception("modelContent null");
                }
                return modelContent;
            }
            else
            {
                throw new Exception("GetBoxModelAsync массив null");
            }
        }
        else
        {
            throw new Exception("GetBoxModelAsync result null");
        }
    }
    catch (Exception ex)
    {
        throw new Exception($"Произошла ошибка GetBoxModelAsync: {ex.Message}");
    }
}
В итоге соберем эти 3 метода вместе в методе ClickAsync, который будет находить координаты элемента и кликать в случайное место (клик происходит при помощи CDP-метода Input.dispatchMouseEvent, сначала вызываем его для нажатия мыши, затем для отпускания) :

C#:
public static async Task ClickAsync(CDPClient cdpClient, string xpath, string firstPageSessionId)
{
    for (int i = 0; i < 5; i++)
    {
        try
        {
            //Подгружаем DOM-модель
            await cdpClient.SendCommandAsync("DOM.getDocument", sessionId: firstPageSessionId);
            Random rand = new Random();
            //Делаем поиск элемента по Xpath
            string searchId = await PerformSearchAsync(cdpClient, xpath, sessionId: firstPageSessionId);
            await Task.Delay(150);
            //Запрашиваем результаты поиска
            int nodeId = await GetSearchResultsAsync(cdpClient, searchId, 0, sessionId: firstPageSessionId);
            await Task.Delay(150);
            //Получаем box model найденного элемента
            string modelContent = await GetBoxModelAsync(cdpClient, nodeId, sessionId: firstPageSessionId);

            //Получаем координаты из box model
            string input = modelContent.Trim('[', ']', ' ');
            string[] stringArray = input.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            double[] numbers = stringArray
                .Select(s => s.Trim())
                .Select(s => double.Parse(s, CultureInfo.InvariantCulture))
                .ToArray();
            int x = rand.Next(Convert.ToInt16(numbers[0]), Convert.ToInt16(numbers[2]));
            int y = rand.Next(Convert.ToInt16(numbers[1]), Convert.ToInt16(numbers[5]));

            //Делаем сам клик
            await cdpClient.SendCommandAsync("Input.dispatchMouseEvent", new { type = "mousePressed", x, y, button = "left", clickCount = 1 }, sessionId: firstPageSessionId);
            await Task.Delay(100); //Задержка между нажатием и отпусканием кнопки
            await cdpClient.SendCommandAsync("Input.dispatchMouseEvent", new { type = "mouseReleased", x, y, button = "left", clickCount = 1 }, sessionId: firstPageSessionId);
            return;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        await Task.Delay(2000);
    }
    throw new Exception($"Не получилось кликнуть на {xpath}");
}
Так же в некоторых моментах нам будет необходимо получать HTML содержимое некоторых элементов. Для этого есть CDP-команда DOM.getOuterHTML, в которую передается node id элемента, и возвращается outerHTML. Сделаем для этого C# метод:
C#:
        public static async Task<string> GetOuterHTMLAsync(CDPClient cdpClient, int nodeId, string sessionId)
        {
            try
            {
                var response = await cdpClient.SendCommandAsync("DOM.getOuterHTML", new { nodeId }, sessionId: sessionId);
                var result = response["result"];
                string outerHTML = result.Value<string>("outerHTML");
                if (string.IsNullOrEmpty(outerHTML))
                {
                    throw new Exception("outerHTML null");
                }
                return outerHTML;
            }
            catch (Exception ex)
            {
                throw new Exception($"GetOuterHTMLAsync Произошла ошибка: {ex.Message}");
            }
        }
На основе этого метода сделаем два вспомогательных TryGetHtmlAsync - который будет проверять наличие элемента на странице и GetHtmlAsync который будет по xpath или селектору искать ноду, и возвращать ее outerHTML. Тут эти методы приводить не буду чтобы не перегружать статью кодом, в проекте они будут в классе Helpers.

Часть 2. Рекапча.
Теперь переходим к самому интересному. Для начала немного поговорим о том как устроена сама рекапча. Если она сразу отображается при открытии страницы, то это означает что при загрузке страницы создаются несколько фреймов, в которых она и находится. В CDP и страница и фрейм (и не только они) это target, т.е. потенциальная цель для того чтобы подключиться, получить sessionId и взаимодействовать (как мы делали со страницей в прошлой статье). Так как фреймов несколько, напишем небольшой метод, который будет получать все таргеты через Target.getTargets и по нужным критериям (тип и url) возвращать нужный нам:
C#:
public  static async Task<string> GetTargetId(CDPClient cdpClient, string url = null, string type = "page")
{
    var targets = await cdpClient.SendCommandAsync("Target.getTargets");

    string targetId = (string)targets["result"]["targetInfos"]
        .Children()
        .FirstOrDefault(t => url == null ? ((string)t["url"])?.Contains("chrome://newtab/") == true : ((string)t["url"])?.Contains(url) == true && (string)t["type"] == type)?
        ["targetId"];

    if (!String.IsNullOrEmpty(targetId))
    {
        return targetId;
    }
    throw new Exception($"targetId для {url} не найден");
}
Как говорилось выше рекапча использует два фрейма на странице - в первом (его url содержит recaptcha/api2/anchor) прогружаются сами картинки на странице, куда мы будем кликать, а через второй (recaptcha/api2/bframe) проходят запросы с картинками и заданиями.
Получим таргеты и сессии к этим фреймам:
C#:
    string frameTarget = await GetTargetId(cdpClient, "recaptcha/api2/anchor", "iframe");
    command = await cdpClient.SendCommandAsync("Target.attachToTarget", new
    {
        targetId = frameTarget,
        flatten = true
    });

    string framePageSessionId = command["result"]["sessionId"].Value<string>();

    string secondFrameTarget = await GetTargetId(cdpClient, "recaptcha/api2/bframe", "iframe");
    command = await cdpClient.SendCommandAsync("Target.attachToTarget", new
    {
        targetId = secondFrameTarget,
        flatten = true
    });

    string secondFramePageSessionId = command["result"]["sessionId"].Value<string>();
Теперь нам нужен метод, похожий на метод MonitorWS из прошлой статьи, который будет слушать соответствующие события из домена Network. В контексте CDP при отправке запроса в браузере происходят следующие события:

Network.requestWillBeSent (браузер собирается отправить запрос) -> (опционально, если запрос реально идет по сети, а не просто возвращается ответ из кеша) Network.requestWillBeSentExtraInfo (дополнительные данные запроса, например заголовки с сетевого уровня) -> Network.responseReceived (получен ответ) -> (опционально) Network.responseReceivedExtraInfo (данные сетевого уровня ответа) -> Network.dataReceived (получены данные/часть данных, может вызываться несколько раз) -> Network.loadingFinished (загрузка данных успешно завершена, либо loadingFailed если неуспешно)

После того как запрос успешно завершен для получения его данных необходимо вызвать CDP-метод Network.getResponseBody. На C# это будет выглядеть примерно так:
C#:
public static async Task<string> GetResponseBodyAsync(CDPClient cdpClient, string requestId, string sessionId)
{
    try
    {
        string body = null;
        for (int i = 0; i < 15; i++)
        {
            var response = await cdpClient.SendCommandAsync("Network.getResponseBody", new { requestId }, sessionId: sessionId);
            var error = response["error"];
            if (error != null)
            {
                string errorMessage = error["message"]?.Value<string>();

                if (errorMessage == "No resource with given identifier found")
                {
                    throw new Exception($"Resource {requestId} error - No resource with given identifier found");
                }

                await Task.Delay(1000);
                continue;
            }
            var result = response["result"];
            if (result == null)
            {
                await Task.Delay(1000);
                continue;
            }
            body = result.Value<string>("body");
            if (!string.IsNullOrEmpty(body))
            {
                break;
            }
            await Task.Delay(1000);
        }

        if (string.IsNullOrEmpty(body))
        {
            throw new Exception("body null");
        }

        return body;
    }
    catch (Exception ex)
    {
        throw new Exception($"Произошла ошибка: {ex.Message}");
    }
}
Метод делает несколько попыток запросить через Network.getResponseBody тело ответа, если ошибка критичная, то возвращает исключение.

А теперь сам метод который будет ловить содержимое ответов на запросы по определенным урлам (специально для рекапчи сделан именно не один урл, а список урлов, т.к. задания приходят с разных урлов). Помимо прочего метод принимает два параметра: disableNetwork и durableMessages. Первый нужен чтобы домен Network не отключался после работы, т.к. если ставим сразу несколько заданий на ловлю содержимого разных запросов отключение Network может мешать. А опция durableMessages нужна для того чтобы точно сохранился ответ на запрос потому что иногда бывает такое что браузер очищает эти данные. Так же есть таймаут, чтобы не ожидать бесконечно (на случай если проблемы с коннектом).
C#:
public static async Task<string> CaptureFromUrl(CDPClient cdpClient, string sessionId, List<string> targets, int timeout = 30000, bool disableNetwork = true, bool durableMessages = false)
{
    var base64Tcs = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
    string targetRequestId = null;

    string targetsLog = string.Join(", ", targets);
    Console.WriteLine($"Запустил CaptureFromUrl для списка: {targetsLog} с таймаутом {timeout}мс");

    Action<JObject> requestWillBeSentHandler = null;
    Action<JObject> responseHandler = null;
    Action<JObject> loadingFinishedHandler = null;
    Action<JObject> loadingFailedHandler = null;

    void Cleanup()
    {
        if (responseHandler != null) cdpClient.UnsubscribeEvent("Network.responseReceived", responseHandler);
        if (loadingFinishedHandler != null) cdpClient.UnsubscribeEvent("Network.loadingFinished", loadingFinishedHandler);
        if (loadingFailedHandler != null) cdpClient.UnsubscribeEvent("Network.loadingFailed", loadingFailedHandler);
    }


    responseHandler = (parameters) =>
    {
        try
        {
            string url = parameters["response"]?["url"]?.Value<string>();
            string currentRequestId = parameters["requestId"]?.Value<string>();

            if (url != null)
            {

                bool isMatch = targets.Any(t => url.Contains(t));
                if (isMatch)
                {
                    if (targetRequestId == null)
                    {
                        //Нашли нужный requestId
                        targetRequestId = currentRequestId;
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Ошибка в обработчике responseReceived: {ex.Message}");
        }
    };

    loadingFinishedHandler = async (parameters) =>
    {
        try
        {
            string requestId = parameters["requestId"]?.Value<string>();

            bool isMatch = false;
            //Проверяем загруженный запрос, если это наш targetRequestId который мы поймали в обработчике responseHandler значит успех
            if (targetRequestId != null && requestId == targetRequestId)
            {
                isMatch = true;
            }

            if (isMatch)
            {
                //Запрашиваем тело найденого запроса, возвращаем результат и отписываемся
                string base64 = await GetResponseBodyAsync(cdpClient, targetRequestId, sessionId);
                if (!string.IsNullOrEmpty(base64))
                {
                    base64Tcs.TrySetResult(base64);
                    Cleanup();
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Ошибка в обработчике loadingFinished: {ex.Message}");
            base64Tcs.TrySetException(ex);
            Cleanup();
        }
    };

    loadingFailedHandler = (parameters) =>
    {
        try
        {
            string requestId = parameters["requestId"]?.Value<string>();
            bool isMatch = false;
  

            if (targetRequestId != null && requestId == targetRequestId)
            {
                isMatch = true;
            }

            //Если наш запрос недогрузился возвращаем исключение
            if (isMatch)
            {
                base64Tcs.TrySetException(new Exception($"Loading failed for request {requestId}"));
                Cleanup();
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Ошибка в обработчике loadingFailed: {ex.Message}");
        }
    };

    cdpClient.SubscribeEvent("Network.responseReceived", responseHandler);
    cdpClient.SubscribeEvent("Network.loadingFinished", loadingFinishedHandler);
    cdpClient.SubscribeEvent("Network.loadingFailed", loadingFailedHandler);

    if (durableMessages)
    {
        await cdpClient.SendCommandAsync("Network.configureDurableMessages", new
        {
            maxTotalBufferSize = 100 * 1024 * 1024,
            maxResourceBufferSize = 50 * 1024 * 1024
        }, sessionId: sessionId);
    }

    await cdpClient.SendCommandAsync("Network.enable", sessionId: sessionId);

    string result = null;
    try
    {
        var completedTask = await Task.WhenAny(base64Tcs.Task, Task.Delay(timeout));
        if (completedTask == base64Tcs.Task)
        {

            result = await base64Tcs.Task;
        }
        else
        {
            Cleanup();
            string errorMsg = $"CaptureFromUrl timed out ({timeout}ms) for list: {targetsLog}";
            throw new TimeoutException(errorMsg);
        }
    }
    finally
    {
        if (disableNetwork)
        {
            try
            {
                await cdpClient.SendCommandAsync("Network.disable", sessionId: sessionId);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error disabling network: {ex.Message}");
            }
        }
        Cleanup();
        Console.WriteLine($"{DateTime.Now} - Закончил CaptureFromUrl для {targetsLog}, result = {(result != null && result.Length < 300 ? result : result?.Substring(0, 300))}");
    }
    return result;
}
Все готово, теперь переходим к самому интересному - классу Recaptcha который и будет непосредственно решать рекапчу. Он состоит из 2 методов - solve, который ловит задания и картинки и решает их и метода HandleDynamicGrid для решения динамических картинок 1х1.

Сначала метод solve. Он подключается к фреймам рекапчи, кликает на квадратик, ловит из трафика задания (вида /m/0pg52, CapMonster как раз их понимает), кладет их в очередь, ловит base64 картинку. Отправляет это все на CapMonster, затем получает решение и кликает но нужным элементам. Если вдруг нет картинок куда кликнуть, то перезагружает капчу (и так же ловит в трафике задания и картинку). Если вдруг попадается динамическая капча то вызывает метод HandleDynamicGrid. Когда новых заданий нет, капча считается решенной и метод завершается.
C#:
 public static async Task solve(CDPClient cdpClient, string firstPageSessionId)
{
     string frameTarget = await Helpers.GetTargetId(cdpClient, "recaptcha/api2/anchor", "iframe");
     var command = await cdpClient.SendCommandAsync("Target.attachToTarget", new
     {
         targetId = frameTarget,
         flatten = true
     });

     string framePageSessionId = command["result"]["sessionId"].Value<string>();

     string secondFrameTarget = await Helpers.GetTargetId(cdpClient, "recaptcha/api2/bframe", "iframe");
     command = await cdpClient.SendCommandAsync("Target.attachToTarget", new
     {
         targetId = secondFrameTarget,
         flatten = true
     });

     string secondFramePageSessionId = command["result"]["sessionId"].Value<string>();
     Console.WriteLine($"framePageSessionId = {framePageSessionId}, secondFramePageSessionId = {secondFramePageSessionId}");

     //Создаем задание для ловли картинок
     var captureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "payload?p=" }, disableNetwork: false, durableMessages: true);

     //И для ловли типа задания
     var taskCaptureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "recaptcha/api2/reload", "recaptcha/api2/userverify" }, disableNetwork: false, durableMessages: true);

     await Helpers.ClickAsync(cdpClient, ".//div[@class='recaptcha-checkbox-borderAnimation']", framePageSessionId);

    //Очередь заданий
     Queue<string> taskKeyQueue = new();
     for (int i = 0; i < 15; i++)
     {
         Console.WriteLine($"Iteration {i}");
         bool isDynamic = false;
         string grid = String.Empty;
         string task = String.Empty;

         //Если очередь заданий пуста то ждем ответа на запрос в котором приходят задания
         if (taskKeyQueue.Count == 0)
         {
             string fullResponse = await taskCaptureTask;

             //Если нет слова pmeta то капча решена
             if (!fullResponse.Contains("pmeta"))
             {
                 break;
             }

             //Парсим сами задания и кладем в очередь
             var matches = Regex.Matches(fullResponse, @"(?<=\["")/m/0.*?(?="")");
             foreach (Match m in matches)
             {
                 if (!String.IsNullOrEmpty(m.Value))
                 {
                     Console.WriteLine($"Add to queue {m.Value}");
                     taskKeyQueue.Enqueue(m.Value);
                 }
              
             }

             //Если есть слово dynamic значит картинки капчи динамически обновляются
             if (fullResponse.Contains("dynamic"))
             {
                 isDynamic = true;
             }
         }

         // Берём следующее задание из очереди
         if (taskKeyQueue.Count > 0)
         {
             task = taskKeyQueue.Dequeue();
         }

         string base64 = await captureTask;
         //На всякий случай проверяем на пустоту
         if (String.IsNullOrEmpty(base64))
         {
             throw new Exception("Не подгрузилась картинка рекапчи");
         }

         //Определяем размер сетки
         string gridHTML = await Helpers.GetHtmlAsync(cdpClient, ".//img[contains(@class, 'rc-image-tile')]", secondFramePageSessionId);
         grid = Regex.Match(gridHTML, @"(?<=rc-image-tile-).*?(?="")").Value;

         if (String.IsNullOrEmpty(task) || String.IsNullOrEmpty(grid))
         {
             throw new Exception("Не смог выпарсить задание или grid");
         }

         //Отправляем задание на Capmonster
         int RCId = await cmCloud.sendRC(new List<string> { base64 }, task, grid);
         //очищаем переменную base64 перед получением новой картинки
         base64 = null;
         List<bool> result = await cmCloud.get(RCId);

         // Функция для перезагрузки капчи при ошибке
         async Task Reload()
         {
             captureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "payload?p=" }, disableNetwork: false);
             taskKeyQueue.Clear();
             taskCaptureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "recaptcha/api2/reload", "recaptcha/api2/userverify" }, disableNetwork: false);
             await Helpers.ClickAsync(cdpClient, ".//button[@id='recaptcha-reload-button']", secondFramePageSessionId);
         }

         //Если среди не нашлось правильных ответов то перезагружаем капчу и пробуем снова
         if (result == null)
         {
             await Reload();
             continue;
         }

         //Словарь для маппинга динамических картинок 1х1
         Dictionary<string, int> newBaseDict = new();
         List<string> base64List = new();
  
         //Кликаем на нужные картинки
         for (int y = 0; y < result.Count; y++)
         {
             if (result[y])
             {
                 if (grid == "44")
                 {
                     await Helpers.ClickAsync(cdpClient, $".//td[@id='{y}']", secondFramePageSessionId);
                 }
                 else
                 {
                     //Если сетка 3x3 значит картинки могут быть динамическими, т.е. подгружаться новые после нажатия
                     var captureTask1 = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "payload?p=" }, timeout: 10000);
                     await Helpers.ClickAsync(cdpClient, $".//td[@id='{y}']", secondFramePageSessionId);
                     if (isDynamic)
                     {
                         //Ловим новые картинки 1x1
                         string base641 = await captureTask1;
                         Console.WriteLine($"Поймал картинку 1*1 номер по порядку {y}");
                         base64List.Add(base641);

                         //Когда мы отправим капмонстру картинку 1x1 нам просто придет true или false, чтобы понять на какой элемент по порядку кликать добавляем в словарь base64 картинки как ключ и порядковый номер в сетке
                         newBaseDict[base641] = y;
                     }

                 }
             }
             await Task.Delay(Random.Shared.Next(150, 500));
         }

         if (base64List.Count > 0)
         {
             bool dynamicSuccess = await HandleDynamicGrid(cdpClient, secondFramePageSessionId, task, base64List, newBaseDict);
             if (!dynamicSuccess)
             {
                 Console.WriteLine($"dynamicSuccess not success");
                 await Reload();
                 continue;
             }
         }

         captureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "payload?p=" }, disableNetwork: false, durableMessages: true);
         taskCaptureTask = Helpers.CaptureFromUrl(cdpClient, secondFramePageSessionId, new List<string> { "recaptcha/api2/reload", "recaptcha/api2/userverify" }, disableNetwork: false, durableMessages: true);

         await Helpers.ClickAsync(cdpClient, ".//button[@id='recaptcha-verify-button']", secondFramePageSessionId);

         //Проверяем, если есть сообщение о том что динамическая капча не решена то обновляем и пробуем заново
         if (await Helpers.TryGetHtmlAsync(cdpClient, @".//div[(@class='rc-imageselect-error-select-more' or @class='rc-imageselect-error-dynamic-more') and not(contains(@style,'display:none'))]", secondFramePageSessionId, timeout: 2))
         {
             await Helpers.ClickAsync(cdpClient, ".//button[@id='recaptcha-reload-button']", secondFramePageSessionId);
         }
     }
}
А вот сам метод HandleDynamicGrid. Он кликает на одиночные картинки и ловит новые, затем уже если нужно кликает по ним и так до бесконечности, пока есть на что кликать
C#:
private static async Task<bool> HandleDynamicGrid(CDPClient cdpClient, string sessionId, string task, List<string> base64List, Dictionary<string, int> baseDict)
{
    List<string> newBase64List = new();
    Dictionary<string, int> newBaseDict = new();
    var captureTask = Helpers.CaptureFromUrl(cdpClient, sessionId, new List<string> { "payload?p=" }, timeout: 15000);
    Console.WriteLine("start HandleDynamicGrid");
    for (int i = 0; i < base64List.Count; i++)
    {
        var base64 = base64List[i];
        try
        {
            int RCId = await cmCloud.sendRC(new List<string> { base64 }, task, "1x1");
            List<bool> result = await cmCloud.get(RCId);

            if (result == null)
            {
                return false;
            }

            if (result != null && result.Count > 0 && result[0])
            {
                if (baseDict.TryGetValue(base64, out int originalIndex))
                {
                    //Пауза для того чтобы одиночная картинка успела отобразиться. После клика она отображается не сразу и если клик произойдет слишком быстро пока она не отобразилась то он улетит в пустую
                    await Task.Delay(2000);
                    Console.WriteLine($"{DateTime.Now} - Кликаю по {originalIndex}");
                    await Helpers.ClickAsync(cdpClient, $".//td[@id='{originalIndex}']", sessionId);
                    string newBase64 = await captureTask;
                    Console.WriteLine($"Поймал еще картинку 1*1 номер по порядку {originalIndex}{Environment.NewLine}");
                    if (!string.IsNullOrEmpty(newBase64))
                    {
                        newBase64List.Add(newBase64);
                        newBaseDict[newBase64] = originalIndex;
                    }

                    // Перезапускаем ловлю картинки для новой итерации
                    if (i < base64List.Count - 1)
                    {
                        captureTask = Helpers.CaptureFromUrl(cdpClient, sessionId, new List<string> { "payload?p=" });
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"HandleDynamicGrid произошла ошибка {ex.Message}");
            return false;
        }
    }

    if (newBase64List.Count > 0)
    {
        //Рекурсия
        return await HandleDynamicGrid(cdpClient, sessionId, task, newBase64List, newBaseDict);
    }
    return true;
}
У нас получилось! Всем спасибо за внимание!

141025
 

Вложения

Последнее редактирование:

Akriver9

Новичок
Регистрация
28.06.2019
Сообщения
1
Благодарностей
1
Баллы
3
Спасибо за статью :bf:
 
  • Спасибо
Реакции: Porosenok

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)