3 место Навигация бота на web-страницах

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
Приветствую всех! :-)

В этой статье мы рассмотрим основные принципы, типовые задачи, полезные сниппеты по теме навигации и взаимодействия с web-страницами.

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

Исходя из этого статья разделена на 2 части: первая для новичков, вторая для всех остальных.

Поэтому кто не новичок – можно сразу переходить в конец этого поста (там есть кое-что для приверженцев кубиков), или к началу следующего. :-)


Основы навигации и взаимодействия с web-страницами

Начнём с того, как вообще строится взаимодействие бота со страницами, открытыми в браузере ZennoPoster, как оно устроено.

Когда мы переходим на какую-либо страницу в браузере (или, когда наш бот активирует экшен «Табы --> Переход на страницу») – браузер получает исходный код этой странички от сервера, на основе которого выстраивает так называемую DOM-модель (Document Object Model, объектная модель документа/страницы), а далее и сам визуальный вид страницы, который мы привыкли видеть.

DOM-модель – именно та часть, которая нам понадобится в построении взаимодействия нашего бота и браузера. Для просмотра DOM в ProjectMaker предназначено специальное окно под названием «Дерево элементов».

1.1.png


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

На скриншоте мы видим выделенный элемент под именем «form» (имя элемента, кстати, равно html-тегу этого элемента). Визуально на странице этот элемент-форма состоит из поля ввода и кнопки «Найти», которые в свою очередь тоже являются элементами (элемент с тегом «input» и элемент с тегом «button»). То есть, элементы могут быть вложенные друг в друга.

Если развернуть все элементы в дереве (кнопка «+» слева от названия элемента) – то, как правило, окажется, что на страницы сотни, а то и тысячи различных элементов.

Как можно использовать дерево элементов?

В окне дерева элементов можно быстро найти нужный элемент на странице, для того чтобы дальше что-либо с ним сделать.

Например, нам нужно найти поле ввода, чтобы вписать в него какое-либо слово. Полями ввода являются элементы с тегом «input» - благодаря этому знанию можем быстро найти этот элемент в дереве. Однако, так как в дереве обычно сотни и тысячи элементов, проще исходить из обратного – найти элемент на странице визуально, а потом автоматически перейти к нему в дереве.

Сделать это можно, кликнув правой кнопкой мыши по элементу в браузере, выбрать из контекстного меню пункт «Исследовать элемент» – сразу после этого в дереве автоматически выберется этот же элемент. Примечание: визуально на странице элементы дерева часто «накладываются» друг на друга, поэтому данное действие может выбрать не тот элемент. В этом случае просто нужно поискать нужный элемент среди соседей выделенного, он обычно находится рядом.

1.2.png


У каждого элемента есть набор параметров (атрибутов), которые отличают его ото всех других элементов на странице. Эти атрибуты можно посмотреть в окне под названием «Свойства элемента».

1.3.png


В этом окне отображаются атрибуты того элемента, который выделен в данный момент в дереве элементов. У каждого атрибута есть название (например, «id») и значение (например, «text»).

На скриншоте выделено поле ввода с тегом «input». Если мы внимательно просмотрим дерево элементов, то найдём ещё несколько таких полей с тегом «input». Однако, многие атрибуты у них разные. Именно по атрибутам наш бот сможет находить нужные элементы. Например, для ввода имени при регистрации найдёт именно то поле, которое предназначено для имени, а не для фамилии или логина, и не поле-обманку. Для этого в шаблоне нужно будет указать, что надо найти элемент с определенным тегом («input») и уникальной парой атрибут-значение (в примере выше подойдёт атрибут «id» со значением «text»). Как именно это можно сделать – разберём чуть позже.


Для чего нам могут понадобиться элементы?

Практически любое взаимодействия бота со страницей – это взаимодействие с её элементами.
С элементами можно сделать следующие действия.

1. Установить значение атрибута.
Пример использования – когда нужно заполнить какое-нибудь поле ввода. Чаще всего в этом случае нужно установить заданный текст в значение атрибута «value».

2. Получить значение атрибута.
Пример использования – когда нужно спарсить какой-либо текст с элемента. Чаще всего нужный текст можно получить из атрибутов «InnerHtml» и «InnerText».

3. Вызвать событие.
Пример использования – клик по элементу, эмуляция наведения мыши на элемент.

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

Для этих целей в ProjectMaker существует инструмент под названием «Конструктор действий». Его можно вызвать для конкретного элемента, щелкнув по нему правой клавишей мыши в Дереве элементов или окне браузера и выбрав пункт «В конструктор действий».

1.4.png


Панель «Конструктора» состоит из двух рабочих областей: «Поиск элемента» и «Выбор действия».

«Поиск элемента»

В первой области находятся настройки поиска элемента, с которым необходимо взаимодействовать боту. При вызове «Конструктора» все поля и выпадающие списки автоматически заполняются параметрами и атрибутами того элемента, через который произведен вызов.

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

№ документа и № формы. Документов и форм на странице может быть несколько, и по умолчанию ZennoPoster ставит те номера форм, в которых находится целевой элемент. Однако, в большинстве случаев это нежелательный вариант. Лучше, чтобы при каждом запуске бот искал элемент на всех документах и формах страницы, так как со стороны сайта однажды может поменяться их порядок, и привязка к номерам полетит.

Например, владелец сайта добавит новый блок с рекламой в виде отдельного документа под номером 2. Все последующие номера документов страницы сместятся на 1. Если, скажем, наш элемент был на 3-м документе, то после добавления блока он будет на 4-м. При этом экшен поиска перестанет находить нужный элемент, потому что будет искать его только в 3-м документе.

Чтобы поиск происходил во всех документах и формах, в выпадающих списках нужно выбрать значение «-1». Чтобы не делать это каждый раз, рекомендуется поставить следующие галочки в настройках ProjectMaker или ZennoPoster.

1.5.png


Имя и значение атрибута. В идеале эта пара должна быть уникальная для всей страницы, чтобы наш бот однозначно мог найти нужный элемент, не спутав его с другим. Чтобы проверить уникальность, жмём справа кнопку «Поиск», под которой отобразятся номера элементов, которые были найдены по текущим настройкам поиска. Если нашёлся всего 1 элемент под номером «0» - значит всё хорошо, атрибут и значения были подобраны верно. Если нашлось несколько – желательно попробовать другие пары «атрибут-значение», чтобы находился только 1 наш элемент.

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

1.6.png


Например, для этого поля ввода мы можем вручную вписать в «Конструкторе» Имя элемента «aria-label», а в значение «Запрос», и мы так же найдём только наш один элемент.

1.7.png


Ещё один важный момент, связанный с именем и значением атрибута – необходимо следить, чтобы значение было «надёжное» с точки зрения возможных изменений на сайте, как в верстке, так и вообще. Если значение выглядит как автоматически сгенерированное или динамически меняющееся, то лучше сразу выбрать другую пару «атрибут-значение».

1.8.png


Иначе уже при следующем запуске бота элемент может не найтись.

Обычно чисто по виду несложно догадаться о том, генерируемое или динамически меняется то или иное свойство, или нет. Однако, если возникнут сомнения - автоматически генерируемые значения можно проверить так – нужно обновить страницу в браузере ProjectMaker и попытаться найти тот же элемент по той же паре «атрибут-значение». Если элемент нашёлся – скорее всего значение статично.

Более тонкую настройку проверки значения атрибута можно настроить с помощью выпадающего списка «Тип поиска». По умолчанию выставлен тип «text» – т.е. значение атрибута содержит текст из поля «Значение». По аналогии, тип «notext» – значение атрибута содержит текст из поля «Значение», тип «regexp» – значение атрибута соответствует регулярному выражению из поля «Значение»

«Выбор действия»

В этой области можно выбрать и протестировать действие с выбранным элементом. Есть 3 типа действия, которые соответствуют упоминаемым нами ранее:

1) Set – установить значение атрибута;

2) Get – получить значение атрибута;

3) Rise – вызвать событие.

Для первых двух действий выбираем атрибут, для «Set» дополнительно вписываем нужный текст в поле «Значение» – всё довольно просто.

Для действия «Rise» нужно выбрать JS-событие, которое будет применено к текущему элементу. Примеры популярных событий: «click» – эмуляция клика по элементу, «onmouseover» – эмуляция наведения мыши на элемент. Для большинства сайтов подобные действия в виде вызова событий выполняются нормально, однако нередко наиболее «вкусные» сайты для ботоводов имеют либо особую структуру, либо повышенную защиту. Поэтому «Rise» будет работать не всегда, нужна более высокий уровень эмуляции. Но, об этом в следующих главах.

Далее, по кнопке «Тестировать» можно сразу протестировать выбранное действие на странице, по кнопке «Добавить в проект» в шаблоне автоматически создастся экшен, который будет искать элемент и делать с ним действие, в точности по выставленным настройкам из «Конструктора».

Полезные сниппеты

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

В окончании данной главе рассмотрим предпочтительные способы для поиска элементов, а именно XPath и CSS-селекторы.

Поиск через CSS-селекторы предпочтителен для тех, кто новичок в ZennoPoster, но не новичок в web-разработке, таким люди обычно хорошо умеют ими пользоваться, для них и предпочтителен этой вариант.

Для полных же новичков я бы порекомендовал потихоньку начать осваивать XPath – язык запросов к элементам страницы, очень гибкий и мощный. В одном из прошлых конкурсов статей была хорошая статья по этому инструменту. Так же в гугле/ютубе можно найти плейлист с уроками по XPath от sibbora (заодно рекомендую обратить на внимание на полный его курс по ZennoPoster+C#).

К сожалению, пользоваться XPath и CSS-селекторами на момент написания статьи можно только из C#-кода, что является серьёзной проблемой для новичков. Чтобы компенсировать этот недостаток, я создал несколько сниппетов, принцип построения которых – максимальное удобство, насколько это в данном случае возможно.

Чтобы у вас не было необходимости копаться в C# коде, все настройки сниппетов вынесены в переменные проекта со специальными префиксами (т.е. перед использованием желательно создать у себя в шаблоне эти переменные).

1.9.png


Достаточно заполнить значения переменных и выполнить нужный сниппет.



Таскать из проекта в проект такие сниппеты неудобно, поэтому к сообщению прикрепляю архив с папкой «Навигация по страницам», который нужно закинуть в подобную папку на компьютере:



После этого можно в любом проекте ProjectMaker создать экшен C#-кода и вставить туда любой сниппет в пару кликов:




Для тех, кому интересен код сниппетов, выкладываю тут пример первого.

C#:
// НАСТРОЙКИ СНИППЕТА

// По умолчанию все значения для сниппета берутся из переменных
// проекта с префиксами "find_element_", "set_value_", "get_value_", "rise_"

// Если заполнить поля ниже - значения переменных будут игнорироваться

//Один или несколько тегов (через ';')
string tag = "";
//Название атрибута
string attributeName = "";
//Значение атрибута
string attributeValue = "";
//Тип поиска значения (text, notext, regexp)
string searchKind = "";
//Номер совпадения
int number = 0;

//Название атрибута для установки
string setAttrName = "";
//Значение атрибута для установки
string setAttrValue = "";


#region Капот

//Получаем значения из переменных
if (string.IsNullOrWhiteSpace(tag) && project.Variables.Keys.Contains("find_element_tag"))
{
    tag = project.Variables["find_element_tag"].Value;
}
if (string.IsNullOrWhiteSpace(attributeName) && project.Variables.Keys.Contains("find_element_attr_name"))
{
    attributeName = project.Variables["find_element_attr_name"].Value;
}
if (string.IsNullOrWhiteSpace(attributeValue) && project.Variables.Keys.Contains("find_element_attr_value"))
{
    attributeValue = project.Variables["find_element_attr_value"].Value;
}
if (string.IsNullOrWhiteSpace(searchKind) && project.Variables.Keys.Contains("find_element_search_kind"))
{
    searchKind = project.Variables["find_element_search_kind"].Value;
}
if (searchKind!="text" && searchKind!="notext" && searchKind!="regexp")
{
    searchKind = "text";
}
if (project.Variables.Keys.Contains("find_element_number"))
{
    int.TryParse(project.Variables["find_element_number"].Value, out number);
}

if (string.IsNullOrWhiteSpace(setAttrName) && project.Variables.Keys.Contains("set_value_attr_name"))
{
    setAttrName = project.Variables["set_value_attr_name"].Value.ToLower();
}
if (string.IsNullOrWhiteSpace(setAttrValue) && project.Variables.Keys.Contains("set_value_attr_value"))
{
    setAttrValue = project.Variables["set_value_attr_value"].Value;
}

//Ищем элемент
var tab = instance.ActiveTab;
var el = tab.FindElementByAttribute(tag, attributeName, attributeValue, searchKind, number);
if (el.IsNull || el.IsVoid)
{
    throw new Exception("Элемент по заданным атрибутам не найден, действие не выполнено!");
}
//Устанавливаем значение атрибута
if (setAttrName=="value")
{
    el.SetValue(setAttrValue, instance.EmulationLevel);
}
else
{
    el.SetAttribute(setAttrName, setAttrValue);
}

#endregion
 
Тема статьи
Другое
Номер конкурса статей
Девятый конкурс статей

Вложения

Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте нужную программу. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
Дополнительные средства навигации, разбор некоторых задач


Расширенная эмуляция мыши

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

Главным образом это относится к выполнению действий на странице. Основной альтернативой вызова событий «Rise» в данный момент является расширенная эмуляция мыши: http://zennolab.com/wiki/ru:virtual_mouse

Подробно она рассматривалась в этой теме. Там же можно найти шаблон-пример, в котором демонстрируется использование всех методов виртуальной мыши.

Так же на форуме можно найти пользовательскую альтернативу продвинутой мыши в виде подключаемой dll-библиотеки.

Так как опять же расширенная эмуляция мыши в текущий момент доступна только в C#-коде, по этой теме я так же подготовил набор сниппетов по FullEmulationMouse, доступных в прикрепленном архиве «Эмуляция мыши». Помещаем их в папку «Snippets» по тому же принципу, как написано в предыдущей главе. Далее можем вставлять в C#-сниппет любого своего проекта:

2.1.png


Они построены по-другому принципу, нежели сниппеты из предыдущей главы, поэтому новичкам перед использованием нужно будет немного почерпнуть информации из вики и форума насчёт использования экшенов C#-кода.


Теперь предлагаю разобрать пару ситуаций-задачек по навигации.


Фейковые элементы

Делая ботов под ZennoPoster, рано или поздно натыкаешься на проблему элементов-дубликатов, которые 1 в 1 нужные нам элементы, с одинаковыми атрибутами и их значениями. Особенно это актуально, если наша задача заполнить какую-нибудь форму, у них часто стоит защита от ботов, построенная на этом принципе.

2.2.png


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

Последний способ, пожалуй, самый действенный и универсальный. Если высота (атрибут «height») и ширина (атрибут «width») элемента больше нуля – это означает, что элемент визуально виден на странице, он показывается пользователю. А это, в свою очередь, практически всегда означает, что данный элемент «настоящий», с им можно взаимодействовать.

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

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

Для примера возьмём кусок кода из первого сниппета предыдущей главы:

C#:
var el = tab.FindElementByAttribute(tag, attributeName, attributeValue, searchKind, number);
if (el.IsNull || el.IsVoid)
{
    throw new Exception("Элемент по заданным атрибутам не найден, действие не выполнено!");
}
Он отвечает за поиск элемента по атрибутам, но найдёт любой подходящий элемент, не только видимый на странице. Чтобы найти именно видимый, нужно немного переделать сниппет. Заодно предусмотрим ещё один случай. Бывает, что элементы имеют высоту и ширину, но расположены они за пределами страницы, и по факту тоже не видны пользователю. В этом случае нужна дополнительная проверка того, что значения атрибутов «topInBrowser» и «leftInBrowser» не являются отрицательными.

C#:
//Ищем коллекцию элементов по заданным атрибутам
var col = tab.FindElementsByAttribute(tag, attributeName, attributeValue, searchKind);
//Создаём результирующий элемент
var elResult = tab.FindElementByXPath("//sngvuyrbuy",0);
//Если в коллекции есть элементы
if (col.Count>0)
{
    //В цикле проверяем каждый элемент коллекции
    foreach (var el in col.Elements)
    {
        //Получаем значения topInBrowser и leftInBrowser
        int topInBrowser = int.Parse(el.GetAttribute("topInBrowser"));
        int leftInBrowser = int.Parse(el.GetAttribute("leftInBrowser"));
        //Если элемент видимый
        if (el.Height>0 && el.Width>0 && topInBrowser>=0 && leftInBrowser>=0)
        {
            //Присваиваем результирующему элементу текущий
            elResult = el;
            //Выходим из цикла проверки
            break;
        }
    }
}//Проверяем, нашли ли нужный элемент
if (elResult.IsNull || elResult.IsVoid)
{
    throw new Exception("Видимый элемент по заданным атрибутам не найден, действие не выполнено!");
}
В данном случае код будет искать первый видимый элемент на странице, удовлетворяющий поиску по атрибутам.

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

C#:
// НАСТРОЙКИ СНИППЕТА

// По умолчанию все значения для сниппета берутся из переменных
// проекта с префиксами "find_element_", "set_value_", "get_value_", "rise_"

// Если заполнить поля ниже - значения переменных будут игнорироваться

//Один или несколько тегов (через ';')
string tag = "";
//Название атрибута
string attributeName = "";
//Значение атрибута
string attributeValue = "";
//Тип поиска значения (text, notext, regexp)
string searchKind = "";
//Номер совпадения
int number = 0;

//Название атрибута для установки
string setAttrName = "";
//Значение атрибута для установки
string setAttrValue = "";


#region Капот

//Получаем значения из переменных
if (string.IsNullOrWhiteSpace(tag) && project.Variables.Keys.Contains("find_element_tag"))
{
    tag = project.Variables["find_element_tag"].Value;
}
if (string.IsNullOrWhiteSpace(attributeName) && project.Variables.Keys.Contains("find_element_attr_name"))
{
    attributeName = project.Variables["find_element_attr_name"].Value;
}
if (string.IsNullOrWhiteSpace(attributeValue) && project.Variables.Keys.Contains("find_element_attr_value"))
{
    attributeValue = project.Variables["find_element_attr_value"].Value;
}
if (string.IsNullOrWhiteSpace(searchKind) && project.Variables.Keys.Contains("find_element_search_kind"))
{
    searchKind = project.Variables["find_element_search_kind"].Value;
}
if (searchKind!="text" && searchKind!="notext" && searchKind!="regexp")
{
    searchKind = "text";
}
if (project.Variables.Keys.Contains("find_element_number"))
{
    int.TryParse(project.Variables["find_element_number"].Value, out number);
}

if (string.IsNullOrWhiteSpace(setAttrName) && project.Variables.Keys.Contains("set_value_attr_name"))
{
    setAttrName = project.Variables["set_value_attr_name"].Value.ToLower();
}
if (string.IsNullOrWhiteSpace(setAttrValue) && project.Variables.Keys.Contains("set_value_attr_value"))
{
    setAttrValue = project.Variables["set_value_attr_value"].Value;
}

var tab = instance.ActiveTab;
//Ищем коллекцию элементов по заданным атрибутам
var col = tab.FindElementsByAttribute(tag, attributeName, attributeValue, searchKind);
//Создаём результирующий элемент
var elResult = tab.FindElementByXPath("//sngvuyrbuy",0);
//Если в коллекции есть элементы
if (col.Count>0)
{
    //В цикле проверяем каждый элемент коллекции
    foreach (var el in col.Elements)
    {
        //Получаем значения topInBrowser и leftInBrowser
        int topInBrowser = int.Parse(el.GetAttribute("topInBrowser"));
        int leftInBrowser = int.Parse(el.GetAttribute("leftInBrowser"));
        //Если элемент видимый
        if (el.Height>0 && el.Width>0 && topInBrowser>=0 && leftInBrowser>=0)
        {
            //Присваиваем результирующему элементу текущий
            elResult = el;
            //Выходим из цикла проверки
            break;
        }
    }
}
//Проверяем, нашли ли нужный элемент
if (elResult.IsNull || elResult.IsVoid)
{
    throw new Exception("Видимый элемент по заданным атрибутам не найден, действие не выполнено!");
}

//Устанавливаем значение атрибута
if (setAttrName=="value")
{
    elResult.SetValue(setAttrValue, instance.EmulationLevel);
}
else
{
    elResult.SetAttribute(setAttrName, setAttrValue);
}

#endregion
По такому же принципу можно (и рекомендуется) переделать все остальные заготовки сниппетов, как из первой главы по навигации, так из этой, по эмуляции мыши. Пусть это будет домашним заданием для заинтересовавшихся. J


Нестандартные выпадающие списки

Классические выпадающие списки на страницах состоят из элемента с тегом «select» и вложенных элементов с тегом «option». Почти всегда нужные пункты из них легко выбираются с помощью сниппетов этой темы.

Однако, лично я в последнее такие встречаю крайне редко, в основном у крупных ресурсов стоят другие выпадающие списки. Например, стоит одиночный «div», при клике мыши на который скрипты показывают на странице блок с вариантами выбора, состоящих из других «div». Нередко на таких списках вызов событий «RiseEvent» не работает, и приходится использовать либо эмуляцию мыши, либо клавиатуры (стрелки «вверх» и «вниз» для перебора вариантов, «Enter» для выбора). Предлагаю рассмотреть примерный алгоритм выбора вариантов в таких списках.

Для примера возьмём, скажем, выбор контрольного вопроса в форме регистрации яндекса. Допустим, нам нужно выбрать вариант «Ваша любимая компьютерная игра».

Кнопка списка

2.3.png


Варианты списка

2.4.png



Первая задача – подвести мышку и кликнуть по списку. Ищем элемент списка и кликаем.

C#:
var tab = instance.ActiveTab;
string text = "Ваша любимая компьютерная игра";

//Находим элемент выпадающего списка
var elDropDown = tab.FindElementByXPath("//button[contains(@class,'control-questions')]", 0);

//Ведём мышь к списку
tab.FullEmulationMouseMoveToHtmlElement(elDropDown);
Thread.Sleep(300);
//Кликаем по списку
tab.FullEmulationMouseClick("left", "click");
Thread.Sleep(400);
Обратите внимание на небольшие паузы. При работе с такими списками с эмуляцией мыши желательно их ставить, без задержек клики иногда не срабатывают, мышка иногда идет не туда, куда надо. Связано это в основном с тем, что скрипты ещё не успели поменять положение элементов, а мышь что-то делает (например, пошла к старым координатам).
UPD: Как заметил Zymlex в комментариях, паузы - не самый рациональный подход. В идеале вместо пауз делать проверку элемента, а точнее цикл с проверкой (пока элемент не готов - делаем микропаузы в цикле, как только становится готов - прерываем цикл).

Теперь ищем коллекцию элементов-вариантов, находим нужный, кликаем по нему. Ищем коллекцию именно сейчас, когда они появились на странице перед пользователем (в идеале сделать проверку на то, что они действительно появились – как это сделать, мы рассматривали в предыдущем примере).

C#:
//Ищем элементы
var colOptions = tab.FindElementsByXPath("//div/span[@class='menu__text']");
//В цикле ищем элементы
foreach (var elOption in colOptions)
{
    //Если нашли нужный вариант
    if (elOption.InnerText==text)
    {
        //Ведём мышь
        tab.FullEmulationMouseMoveToHtmlElement(elOption);
        Thread.Sleep(300);
        //Кликаем
        tab.FullEmulationMouseClick("left", "click");
        Thread.Sleep(400);
        break;
    }
}

Заготовка для универсальной гулялки по web-страницам

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

Логика работы сниппета:

1 ) Переходим на стартовую страницу;
2 ) Ищем видимые элементы с текстом;
3 ) Если страниц нет – переходим к пункту №10;
4 ) Берём первый элемент для чтения;
5 ) С определенной вероятностью пропускаем его и переходим к следующему;
6 ) Иначе, если текст короткий, пропускаем и переходим к следующему;
7 ) Иначе эмулируем чтение текста на элементе;
8 ) С небольшой вероятностью переходим по ссылке в текущем тексте, возвращаемся к пункту №2;
9 ) Повторяем пункты №4-8 для всех элементов страницы, предназначенных для чтения;
10 ) Переходим по случайной отображаемой ссылке на странице, возвращаемся к пункту №2;
11 ) Выполняем пункты №2-10 пока не просмотрим нужное количество страниц.

C#:
//Адрес стартовой страницы
string url = "https://ru.wordpress.org/";
//Количество страниц для чтения
int pageCount = 3;
//XPath элементов для эмуляции чтения
string xpath = "//h1[string-length(text())>10]|//h2[string-length(text())>10]|//h3[string-length(text())>10]|//h4[string-length(text())>10]|//p[string-length(text())>10]";
//Переменная для определения вероятности
int chance = 0;
//Шанс пропуска блоков, в %
int chanceSkip = 70;
//Переменная для определения перехода в абзаце
bool isLinkClicked = false;

var rnd = Global.Classes.rnd;
var tab = instance.ActiveTab;

//Переходим на заданную страницу
tab.Navigate(url);
if (tab.IsBusy)    tab.WaitDownloading();
Thread.Sleep(1700);

//Цикл чтения страниц (1 итерация - 1 страница)
for (int p=0; p<pageCount; p++)
{
    //Ищем элементы странички для эмуляции чтения
    var colTexts = tab.FindElementsByXPath(xpath);
    //Вычленяем только видимые на странице элементы
    var colResult = tab.FindElementsByXPath("//sngvuyrbuy");
    if (colTexts.Count>0)
    {
        foreach (var el in colTexts.Elements)
        {
            int topInBrowser = int.Parse(el.GetAttribute("topInBrowser"));
            int leftInBrowser = int.Parse(el.GetAttribute("leftInBrowser"));
            //Если элемент видимый
            if (el.Height>0 && el.Width>0 && topInBrowser>=0 && leftInBrowser>=0)
            {
                colResult.Add(el);
            }
        }
    }
    colTexts = colResult;

    //Если коллекция пуста, выводи соответствующее сообщение в лог
    if (colTexts.Count<1)
    {
        project.SendWarningToLog("Подходящие тексты для чтения не найдены! Страница: '"+tab.URL+"'", true);
    }

    int num = 1;
    //В цикле проходим элементы коллекции
    foreach (var elText in colTexts.Elements)
    {
        // Определяем, пропускать ли данный блок
        chance = rnd.Next(1, 101);
        //На основе выпавшего значения решаем, пропускать ли блок
        if (chance <= chanceSkip)
        {
            //С вероятностью chanceSkip пропускаем
            project.SendInfoToLog("Пропускаем чтение элемента №" + num + ", тег: "+elText.TagName);
            num++;
            Thread.Sleep(rnd.Next(3,9)*100);
            continue;
        }

        // Пропускаем слишком короткие блоки с определенной вероятностью
        if (elText.InnerText.Length < 100)
        {
            //Если длина текста блока меньше 100 символов
            chance = rnd.Next(1, 101);    //Случайно берём значение от 1 до 100
            if (chance <= chanceSkip)
            {
                //С вероятностью chanceSkip не читаем его
                project.SendInfoToLog("Пропускаем чтение элемента №" + num + " из-за малой длины его текста, тег: "+elText.TagName);
                num++;
                continue;
            }
        }

        //Записываем в лог номер текущего элемента и название его тега
        project.SendInfoToLog("Читаем элемент №" + num + ", тег: "+elText.TagName);

        //Эмулируем чтение элемента
        tab.FullEmulationMouseMoveAboveHtmlElement(elText, 30);

        //С определенной вероятностью переходим по случайной ссылке в абзаце
        var colLinks = elText.FindChildrenByXPath(".//a");
        //Если в коллекции есть элементы (ссылки)
        if (colLinks.Count > 0)
        {
            chance = rnd.Next(1, 101);
            if (chance <= 3)
            {
                //С вероятностью 3% переходим по случайной ссылке из абзаца
                var elLink = colLinks.Elements[rnd.Next(colLinks.Count)];
                tab.FullEmulationMouseMoveToHtmlElement(elLink);
                Thread.Sleep(rnd.Next(4,8)*100);
                tab.FullEmulationMouseClick("left", "click");
                if (tab.IsBusy)    tab.WaitDownloading();
                Thread.Sleep(rnd.Next(5,25)*100);
                //Сообщаем коду ниже что перешли по ссылке
                isLinkClicked = true;
                //Выходим из цикла, переходим к следующей страничке
                break;
            }
        }

        //Случайная пауза между чтением блоков
        chance = rnd.Next(0, 2);
        if (chance==0)
        {
            Thread.Sleep(rnd.Next(5,50)*100);
        }

        num++;
    }

    //Если при чтении не переходили на другую страницу
    if (isLinkClicked == false)
    {
        //Находим все элементы, содержащие ссылки
        var colLinks = tab.FindElementsByXPath("//a");
        //Вычленяем только видимые на странице ссылки
        colResult = tab.FindElementsByXPath("//sngvuyrbuy");
        if (colLinks.Count>0)
        {
            foreach (var el in colLinks.Elements)
            {
                int topInBrowser = int.Parse(el.GetAttribute("topInBrowser"));
                int leftInBrowser = int.Parse(el.GetAttribute("leftInBrowser"));
                if (el.Height>0 && el.Width>0 && topInBrowser>=0 && leftInBrowser>=0)
                {
                    colResult.Add(el);
                }
            }
        }
        colLinks = colResult;
        //Проверка наличия ссылок
        if (colLinks.Count<1)
        {
            project.SendWarningToLog("Видимых ссылок на странице не найдено! "+tab.URL, true);
            project.SendInfoToLog("Завершили гуляние по страницам. Количество прочитанных страниц: "+(p+1), true);
            return "ok";
        }
        //Ведём мышку к случайному элементу со ссылкой
        var elLink = colLinks.Elements[rnd.Next(colLinks.Elements.Length)];
        tab.FullEmulationMouseMoveToHtmlElement(elLink);
        Thread.Sleep(rnd.Next(4,8)*100);
        tab.FullEmulationMouseClick("left", "click");
        if (tab.IsBusy)    tab.WaitDownloading();
        Thread.Sleep(rnd.Next(5,25)*100);
    }
    project.SendInfoToLog("Завершили чтение "+ (p+1) +"-й страницы.", true);
}

project.SendInfoToLog("Завершили гуляние по страницам.", true);
Вы можете тестировать данный сниппет на любых страницах, по мере надобности расширять его. В нём не хватает ряда нужных в плане универсальности вещей, но и в таком виде он неплохо гуляет по многим страницам интернета.

На этом всё, благодарю за внимание. :-)
 

Вложения

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

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 352
Благодарностей
3 279
Баллы
113
  • Спасибо
Реакции: LaGir

one

Client
Регистрация
22.09.2015
Сообщения
6 790
Благодарностей
1 264
Баллы
113

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
От большинства задержек можно избавиться, просто заменив их на проверку элемента.
Абсолютно верно, в данном контексте целесообразнее добавлять небольшой цикл проверки с минизадержками, дополню чутка пример в статье.
Видимо, не очень внимательно читали :-) Пример рассчитан как раз на то, чтобы не использовать вызов событий, так как не всегда в таких ситуациях они работают как надо.
 

Moadip

Client
Регистрация
26.09.2015
Сообщения
509
Благодарностей
823
Баллы
93
Отличная статья. Для новичков самое то.
Умение работать с DOM моделью это самый первый навык который надо оттачивать когда начинаешь работать с зенкой.
Без четкого понимания как добраться до нужного элемента, вся остальная работа просто встанет.
А это понимание основывается на различных "фишках" при поиске(к чему привязываться, что смотреть), и приходит только в процессе практики. :D
Xpath тоже musthave, хотя бы азы. Т.к. без него в некоторых случаях до нужного элемента будет очень трудно добраться.
 

Juniorcpa

Client
Регистрация
27.05.2014
Сообщения
2 031
Благодарностей
1 285
Баллы
113
Пытался когда-то произвести эмуляцию мышкой, но если элемент не в зоне видимости инстанса, а мышка двигается к нему, то мышка зависает. Есть решение? :-)
 

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 352
Благодарностей
3 279
Баллы
113
Пытался когда-то произвести эмуляцию мышкой, но если элемент не в зоне видимости инстанса, а мышка двигается к нему, то мышка зависает. Есть решение? :-)
Второй пост статьи содержит пример проверки.
В основном надо проверять координаты, размер и атрибут hidden.
 
  • Спасибо
Реакции: LaGir

vladinvest

Client
Регистрация
29.05.2016
Сообщения
51
Благодарностей
6
Баллы
8
Как реализовать клик по кнопке, прикрытой js? Пробовал и через атрибуты, и через XPath сделать, только с третьего раза прошибается. Js отключать нельзя, проект сразу отвалится.
 

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
Пытался когда-то произвести эмуляцию мышкой, но если элемент не в зоне видимости инстанса, а мышка двигается к нему, то мышка зависает. Есть решение? :-)
Перед движением, помимо описанного в статье, можно проверять, находится ли целевой элемент в пределах окна инстанса.
Начальные координаты инстанса (0; 0), значит атрибуты элементы "leftInBrowser" и "topInBrowser" не должны быть меньше 0.
Конечные координаты инстанса (правая и нижняя границы) обычно равны конечным координатам главного документа на вкладке (т.е. по сути его высоте и ширине). Значит, координаты элемента не должны превышать этих значений.
Соответственно, можем сделать такую проверку:
C#:
var tab = instance.ActiveTab;

//Находим элемент
var el = tab.FindElementByXPath("//h1", 0);

//Высота и ширина главного документа страницы
//Они, как правило - нижняя и правая конечные точки инстанса, что нам и нужно
int borderRight= tab.MainDocument.Width;
int borderBottom = tab.MainDocument.Height;

//Координаты элемента в браузере
int elX = int.Parse(el.GetAttribute("leftInBrowser"));
int elY = int.Parse(el.GetAttribute("topInBrowser"));

//Проверка нахождения элемента в пределах окна инстанса
if (elX < 0 || elY < 0 || elX > borderRight || elY > borderBottom)
{
    throw new Exception("Элемент находится за пределами окна инстанса!");
}


Как реализовать клик по кнопке, прикрытой js? Пробовал и через атрибуты, и через XPath сделать, только с третьего раза прошибается. Js отключать нельзя, проект сразу отвалится.
Пока не совсем понятно, что за проблема, что подразумевается под "js, который прикрывает кнопку". В идеале реальный пример/страничку где такое происходит, или хотя более детальное описание.
 

Viking01

Client
Регистрация
19.08.2017
Сообщения
228
Благодарностей
151
Баллы
43
огонь статья) особенно полезна в плане эмуляции) проголосую однозначно)
 
  • Спасибо
Реакции: LaGir

bizzon

Client
Регистрация
08.09.2015
Сообщения
1 084
Благодарностей
126
Баллы
63

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
В видео есть сниппет "Количество элементов по XPath". Можно где-то найти такой?
Он виден на видео полностью, можно просто переписать. Вот код:
C#:
//Получаем путь XPath
string xpath = project.Variables["find_element_xpath"].Value;
//Ищем количество элементов по пути
int count = instance.ActiveTab.FindElementsByXPath(xpath).Count;
//Выводим количество в лог и в возвращаемую переменную
return "Найдено "+count+" элементов по пути '"+xpath+"'";

Если XPath не знаком (или иные трудности с ним), вот сниппет для поиска по атрибутам:
C#:
// Получаем тег, название и значение атрибута, тип поиска из переменных проекта
//Один или несколько тегов (через ';')
string tag = project.Variables["find_element_tag"].Value;
//Название атрибута
string attributeName = project.Variables["find_element_attr_name"].Value;
//Значение атрибута
string attributeValue = project.Variables["find_element_attr_value"].Value;
//Тип поиска значения (text, notext, regexp)
string searchKind = project.Variables["find_element_search_kind"].Value;

//Ищем количество элементов
int count = instance.ActiveTab.FindElementsByAttribute(tag, attributeName, attributeValue, searchKind).Count;
//Выводим количество в лог и в возвращаемую переменную
return "Найдено "+count+" элементов";
 

AcidX

Client
Регистрация
19.08.2012
Сообщения
8
Благодарностей
3
Баллы
3
Извините, может быть нубский вопрос, но все таки, если я правильно понял, то методы навигации - это методы таба. А как добраться до вложенных элементов в другой элемент, т.е. не от таба, а от конкретного элемента в цикле например?
 

doc

Client
Регистрация
30.03.2012
Сообщения
8 605
Благодарностей
4 595
Баллы
113
Извините, может быть нубский вопрос, но все таки, если я правильно понял, то методы навигации - это методы таба. А как добраться до вложенных элементов в другой элемент, т.е. не от таба, а от конкретного элемента в цикле например?
у элементов в коде есть методы FindChild и FindChildren, похожие на FindElement и FindElements у таба
 
  • Спасибо
Реакции: LaGir и AcidX

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
Перед движением, помимо описанного в статье, можно проверять, находится ли целевой элемент в пределах окна инстанса.
Начальные координаты инстанса (0; 0), значит атрибуты элементы "leftInBrowser" и "topInBrowser" не должны быть меньше 0.
Конечные координаты инстанса (правая и нижняя границы) обычно равны конечным координатам главного документа на вкладке (т.е. по сути его высоте и ширине). Значит, координаты элемента не должны превышать этих значений.
Соответственно, можем сделать такую проверку:
В последних версиях ZennoPoster следующий код уже не работает:
C#:
//Координаты элемента в браузере
int elX = int.Parse(el.GetAttribute("leftInBrowser"));
int elY = int.Parse(el.GetAttribute("topInBrowser"));
Нужно использовать взамен подобный:
C#:
//Координаты элемента в браузере
int elX = el.DisplacementInBrowser.X;
int elY = el.DisplacementInBrowser.Y;
Также, при наличии проверки на нахождение элемента в пределах окна инстанса периодически встречаю странные глюки, которых без неё не бывает. Пока не разобрался почему так происходит, поэтому пока не рекомендовал бы добавлять эту проверку. Хотя, проблемы вроде только при отладке в PM, но надо потестить получше.
 

Tp0yaH

Client
Регистрация
26.09.2017
Сообщения
1
Благодарностей
0
Баллы
1
Для полных же новичков я бы порекомендовал потихоньку начать осваивать XPath
К сожалению, пользоваться XPath и CSS-селекторами на момент написания статьи можно только из C#-кода, что является серьёзной проблемой для новичков.
Почему бы, не рекомендовать, сразу осваивать JS? Более универсальное решение, менее громоздкое, не будет являться серьезной проблемой для новичков, даст понимания DOM-а...
 

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 352
Благодарностей
3 279
Баллы
113
не будет являться серьезной проблемой для новичков
Будет, взаимодействие всё равно идёт с использованием C#, да и логику предпочтительней писать на нём, как и весь шаб.
Но JS тоже нужен, некоторые моменты в C# работают не так как бы хотелось или отсутствуют, большинству же хватает кубиков, иногда сниппетов.
 
  • Спасибо
Реакции: LaGir

Nord

Client
Регистрация
22.03.2012
Сообщения
2 358
Благодарностей
1 419
Баллы
113

LaGir

Client
Регистрация
01.10.2015
Сообщения
211
Благодарностей
849
Баллы
93
@Nord
А что вот это за абракадабра "sngvuyrbuy"?
Просто набор случайных символов. В этой строчке нужно объявить/получить пустую коллекцию элементов, это можно сделать как раз через поиск несуществующих элементов (по крайней мере я других рабочих вариантов не нашёл).


@Tp0yaH
Почему бы, не рекомендовать, сразу осваивать JS? Более универсальное решение, менее громоздкое, не будет являться серьезной проблемой для новичков, даст понимания DOM-а...
1) То, что вы процитировали, реально освоить за 1-2 вечера. За сколько осваивается полноценный язык программирования, пусть это и JavaScript? Соответственно, вы уверены, что такое сравнение корректно в контексте полезности для новичков?
2) Если и давать рекомендацию по изучению полноценного языка программирования чисто для ZP, то уж лучше C#, возможностей в этом контексте гораздо больше именно у него. Ну и необходимые основы JS новичками и так сами по себе изучаются в процессе освоения PM и ZP. Большее от JS в пределах ZP обычно не нужно, за исключением некоторых специфических задач.
3) Если говорить об универсальном решении при наличии большого количества времени (как для изучения JS), то, имхо, тут стоит изучать понемножку, в комплексе всё то, что часто используется в Zenno и с Zenno - C#, JS, SQL, HTTP, PHP, RegExp, XPath, ... Концентрируясь на чём-то одном, далеко на платформе Zenno не уедешь.
 

g1sm0

Client
Регистрация
25.09.2015
Сообщения
2
Благодарностей
0
Баллы
1
№ документа и № формы. Документов и форм на странице может быть несколько
А что именно подразумевается под номерами форм и документов? Ведь мы работаем со страницей - разве 1 страница(то, что находится между <body></body>) не есть 1 документ?
 

doc

Client
Регистрация
30.03.2012
Сообщения
8 605
Благодарностей
4 595
Баллы
113
А что именно подразумевается под номерами форм и документов? Ведь мы работаем со страницей - разве 1 страница(то, что находится между <body></body>) не есть 1 документ?
боди может быть не один, а форма тем более
 
  • Спасибо
Реакции: g1sm0

Armagidec

Client
Регистрация
27.08.2014
Сообщения
96
Благодарностей
16
Баллы
8
Подскажите как вставить переменную в код - //Номер совпадения

Вот так не работает - int num = project.Variables["schetchik"].Value;

/ Поиск элемента по тегу
//Тег элемента
string tag = "h2";
//Номер совпадения
int num = 0;
 

Armagidec

Client
Регистрация
27.08.2014
Сообщения
96
Благодарностей
16
Баллы
8
Код:
// Поиск элемента по тегу
//Тег элемента
string tag = "h2";
//Номер совпадения
int num = int.Parse(project.Variables["number"].Value);

//Поиск элемента
var el = instance.ActiveTab.FindElementByTag(tag, num);
//Движение к элементу
instance.ActiveTab.FullEmulationMouseMoveToHtmlElement(el);
Разобрался...

Вот так работает
Код:
//Номер совпадения
int num = int.Parse(project.Variables["number"].Value);
 

Veterinar

Client
Регистрация
01.07.2016
Сообщения
194
Благодарностей
22
Баллы
18
Пока не совсем понятно, что за проблема, что подразумевается под "js, который прикрывает кнопку". В идеале реальный пример/страничку где такое происходит, или хотя более детальное описание.
Он имеет ввиду, элемент спрятанный за JS popup, я с такой фигней в Одноклассниках сталкивался, когда после действия OnMouseOver выскакивают дополнительные элементы с которыми можно взаимодействовать, сам эту проблему и решил только действием OnMouseOver. А как сделать чтобы без лишних действий так и не разобрался.
 

ZhbanOFF

Client
Регистрация
23.02.2018
Сообщения
2
Благодарностей
1
Баллы
3
Люди добрые, подскажите как сделать что бы мышька зажала элимент и передвинула его в нужное место?! нашёл только как сделать чтобы мышка пошла на элимент и нажала, а чтобы перетащила не нашёл...
 

Вложения

  • 51,9 КБ Просмотры: 719
  • Спасибо
Реакции: Hazar

Hazar

Client
Регистрация
05.12.2018
Сообщения
107
Благодарностей
19
Баллы
18
@Nord

Просто набор случайных символов. В этой строчке нужно объявить/получить пустую коллекцию элементов, это можно сделать как раз через поиск несуществующих элементов (по крайней мере я других рабочих вариантов не нашёл).
Я посмотрел ваше видео по снипетам по XPath. Теперь скажите почему проще не воспользоваться конструктором действий? Он как мне кажется выполняет все те-же действия...
 
Последнее редактирование:

Hazar

Client
Регистрация
05.12.2018
Сообщения
107
Благодарностей
19
Баллы
18
Люди добрые, подскажите как сделать что бы мышька зажала элимент и передвинула его в нужное место?! нашёл только как сделать чтобы мышка пошла на элимент и нажала, а чтобы перетащила не нашёл...
Читайте выше, про эмуляцию, там есть ответ, он даже шаблон приложил. Раздел в шаблоне Drag and Drop
 

sw_sw

Client
Регистрация
24.02.2017
Сообщения
691
Благодарностей
277
Баллы
63
Добрый день!
Подскажите плс, как в 7 зенке сниппеты вставить?
папки нет нужной в дире проги.
 

Dimson

Client
Регистрация
02.05.2014
Сообщения
11
Благодарностей
2
Баллы
3
Добрый день!
Подскажите плс, как в 7 зенке сниппеты вставить?
папки нет нужной в дире проги.
Может есть в 7 версии как и в 5
Безымянный.jpg
 
  • Спасибо
Реакции: sw_sw

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