XPath универсальный C# шаг работы с элементами

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
Данный код позволяет очень удобно работать с xpath из C# шага, задача кода - замена стандартных "кубиков" на более продвинутый метод поиска элементов - xpath, при этом в удобной обёртке.

Технология xpath очень гибкая, в частности одно из популярных применений (которые сложно сделать без неё стандартными средствами ZP) - поиск элементов методом "найти элемент, а в нём другой элемент, а в нём третий..." то есть вложенный поиск когда невозможно найти уникальный признак по которому можно сразу идентифицировать элемент.

Также можно например найти соседний элемент (который имеет уникальный признак), и от него "оттолкнуться" чтобы найти нужный нам элемент (который не имеет уникального признак поиска). В общем простор для поиска элементов просто широчайший.

Как работает:
- Вставляется функция в блок "общий код", это и есть обработчик
- Из C# шага вызывается код "клиента" который выполняет что вам надо (кликнуть, взять что-то, установить значение, в общем классический get|set|rise)

В чём основное удобство, вот так выглядит код в C# шаге:
C#:
string xpath_exp = "//select[@id='lang-chooser']/option[@selected='selected'][contains(.,'United States')]";
string action_ev = "rise|focus";
string set_action ="";

return CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);
Входные параметры:
xpath_exp - выражение xpath для нахождения элемента
action_ev - что делаем с элементом, возможные варианты:

аргумент 1:
- get - взять значение
- set - установить значение
- rise - выполнить JS event

аргумент 2: то что делаем с элементом, например комплексные варианты с примерами:
get|width - взять ширину найденого элемента
set|value - установить значение в элемент, например в текстовое поле нужный вам текст
rise|click - клик по элементу

В общем здесь всё то же самое что и в конструкторе действий.

set_action - используется лишь в случаях когда делается set (например set|value или там set|style), то есть установка значения, в этом случае пишется указанный текст, то есть то что будет прописано в значении.

Также реализованы следующие фишки:
rise|scroll - промотает до нужного элемента
set|selecteditems - выбор выпадающего меню, при этом поддерживает Regex:ваш_текст

Прочая информация:
- Код работает начиная с версии ZP 5.8.0.0
- Используется тип эмуляции заданный в проекте (в настройках, либо заданный вами в шаге)
- Сейчас пока что нет возможности распознавать капчу таким образом (не сделан get|captcha)
- Если элемент не будет найден, то этот шаг выйдет с ошибкой
- Для составления xpath выражений удобно использовать расширение браузера FireFox под названием Firepath (ставится как дополнение к дополнению Firebug)
- Код написал darkdiver по моей просьбе, за что ему низкий поклон и большая благодарность
- Уроки по xpath можно найти здесь http://zvon.org/xxl/XPathTutorial/Output_rus/example1.html

Пример использования кода во вложениях.
 

Вложения

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

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

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

JackSQ

Client
Регистрация
03.03.2015
Сообщения
32
Благодарностей
8
Баллы
8
Спасибо. так, глядишь до XPath скоро доберемся в стандартных экшенах
 
  • Спасибо
Реакции: phoenixs и bezvozni

LexxWork

Client
Регистрация
31.10.2013
Сообщения
1 194
Благодарностей
759
Баллы
113
какие события еще поддерживаются и можно ли передавать координаты события если они должны быть?
cуществует ли FindElementAndExecuteAction для более одного элемента?
Как быть с регулярным выражением?
Какие исключения вызывает этот метод если обьект не найден?
Какой практический смысл в этом методе если есть куча других?
 
Последнее редактирование:

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
какие события еще поддерживаются
Любые как и в конструкторе действий, set|get - value, innertext, outerhtml и.т.д., rise - focus, click, blur, onchange - и.т.д.
можно ли передавать координаты события если они должны быть?
Имеется ввиду например работа с drag & drop? Если да, то сейчас не поддерживается
cуществует ли FindElementAndExecuteAction для более одного элемента?
В C# не разбираюсь, на первый взгляд это просто название функции самописной, которая вызывается из общего кода, соответственно туда можно что-то своё добавить. Прояснить может darkdiver.
Как быть с регулярным выражением?
В данной реализации они не предусмотрены, поэтому придётся писать замену на xpath. Честно говоря с тех пор как начал использовать xpath - не возникало ни разу необходимости возвращаться на регулярки, они просто стали не нужны абсолютно в этом классе задач. Может быть Mozilla выкатит когда-нибудь версию xpath 2.0 в которой поддерживаются регулярки.
Какие исключения вызывает этот метод если обьект не найден?
Под объёктом имеется ввиду элемент? Если да, то в зависимости от причины почему не найден сработают исключения:
- вкладка не найдена
- элемент не найден
Какой практический смысл в этом методе если есть куча других?
Если кратко то стояла задача использовать мощь xpath и простоту кубиков (стандартных шагов поиска элементов), соответственно смысл именно в этом - просто и удобно использовать гибкий инструмент в (почти) привычном виде.
 
  • Спасибо
Реакции: budora

bigcajones

Client
Регистрация
09.02.2011
Сообщения
1 215
Благодарностей
672
Баллы
113
and where is the library for CommonCode?
 

bigcajones

Client
Регистрация
09.02.2011
Сообщения
1 215
Благодарностей
672
Баллы
113
When I try your template it tells me 'no reference to CommonCode'. Maybe because I'm using 5.7.1 version?
 
  • Спасибо
Реакции: CSS

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
  • Спасибо
Реакции: bigcajones

olymp

Client
Регистрация
08.04.2015
Сообщения
13
Благодарностей
11
Баллы
3
Хотел бы немного дополнить как можно собирать все элементы со страницы т.к. в оригинальном примере вы можете получить только 1-й элемент на странице, к примеру вам необходимо собрать все ссылки со страницы используя XPath, а не только первую, так вот для начала вам необходимо добавить в общий код следующую функцию:
C#:
public static HtmlElement[] FindElementsByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           var hes = tab.FindElementsByXPath(xpath);
           if (hes.IsVoid) throw new Exception("элементы не найдены");
            return hes.Elements;
        }
Теперь используя вот такую конструкцию в коде, вы можете подсчитать общее кол-во элементов на странице по критериям поиска:
C#:
var linksCollection = CommonCode.FindElementsByXpath(instance, xpath_exp);
return linksCollection.Length;
А вот теперь и пример как можно собрать все элементы со страницы и записать их в список, в моем случае это были ссылки:
C#:
var links = project.Lists["Links"];
var xpath_exp = ".//*[@id='123']/a";
var action_ev = "get|href";
var set_action ="";
var linksCollectionLength = CommonCode.FindElementsByXpath(instance, xpath_exp);
for (int i = 0; i < linksCollectionLength.Length; i++)
{
var linksCollection = CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);
links.Add(linksCollection);
}
Хочу выразить благодарность darkdiver - т.к. именно он помог мне разобраться как это все осуществить! Советую использовать данную возможность поиска т.к. она реально облегчает этот процесс, а в некоторых случаях без неё просто не реально добраться до нужного элемента!
 
Последнее редактирование модератором:

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
olymp, благодарю за код!

Ранее разговаривал с Дайвером, говорит слишком высокий overhead при таком подходе, поэтому лично я отказался на тот момент. Метод хорош сам по себе, только его бы упростить путём выноса в общий код этих циклов, оставив в C# шаге "заказчика" только интерфейс запроса (как сейчас сделано: запрос в шаге, а обработчик в общем коде). Придумать только нужно некий абстрактный интерфейс вроде "save|список_куда_сохраняем" и объединить его с существующим в единое целое.

Думаю кому-то понадобится это дело (как вам понадобилось по циклу перебирать), возможно даже и я это буду - тот доработает и заделится решением это красиво и удобно реализующим. Так и сдвинется дело вперёд.
 

daymos

Client
Регистрация
11.11.2009
Сообщения
791
Благодарностей
227
Баллы
43
Почему xpath неидет в стандартном наборе зенки?
 
  • Спасибо
Реакции: bezvozni

AZANIR

Client
Регистрация
09.06.2014
Сообщения
403
Благодарностей
185
Баллы
43
С тех пор как познакомился с xpath понял что регулярками хуже тянуть то, что необходимо, но и они не лишены необходимости в использовании, поэтому плюсую за xpath в самой зенке вообще вещь просто офигенная.
 

zennoX

Client
Регистрация
05.04.2014
Сообщения
466
Благодарностей
124
Баллы
43
Я и мои коллеги по ZP очень ждем xpath в стандартном функционале (в кубиках, конструкторе действий)
 

AZANIR

Client
Регистрация
09.06.2014
Сообщения
403
Благодарностей
185
Баллы
43
Да важный момент для тек кто не так сильно знаком с СИ
добавте директивы юзинг и во вкладке (общий код )
надеюсь директивы вы умеете добавлять.
после // Insert your code here
вставте этот код

Код:
        public static HtmlElement FindByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           HtmlElement he = tab.FindElementByXPath(xpath, 0);
           if (he.IsVoid || he.IsNull) throw new Exception("элемент не найден");
            return he;
        }
    
        private static void WaitDownloading(Tab tab)
        {
            if (tab.IsBusy) tab.WaitDownloading();
        }
    
        public static string FindElementAndExecuteAction(Instance instance, string xpath, string action, string attrValue="")
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
        
            var he = CommonCode.FindByXpath(instance, xpath);
            if (!action.Contains("|")) throw new ArgumentException ("неверный формат action");
            var tmp = action.ToLower().Split(new [] {'|'}, 2);
            var mainAction = tmp[0];
            var subAction = tmp[1];
        
            switch (mainAction)
            {
                case "get":
                    switch(subAction)
                    {
                        case "value":
                            string attributeValue = he.GetValue();
                            if (string.IsNullOrEmpty(attributeValue))
                                throw new Exception("Значение пустое");
                            return attributeValue;
                        case "selecteditems":
                            string selectedItems = he.GetSelectedItems();
                            if (string.IsNullOrEmpty(selectedItems))
                                throw new Exception("Значение selectedItems пустое");
                            return selectedItems;
                        default:
                            string attributeText = he.GetAttribute(subAction);
                            if (string.IsNullOrEmpty(attributeText))
                                throw new Exception(string.Format("Атрибут {0} пустой", subAction));
                            return attributeText;
                    }
                case "rise":
                    switch(subAction)
                    {
                        case "scroll":
                            he.ScrollIntoView();
                            break;
                        default:
                            he.RiseEvent(subAction, instance.EmulationLevel);
                            break;
                    }
                    WaitDownloading(tab);
                    return "ok";
                case "set":
                    switch(subAction)
                    {
                        case "value":
                            he.SetValue(attrValue, instance.EmulationLevel, he.TagName=="select");
                            break;
                        case "selecteditems":
                            const string selecteditems_regexp = "Regex:";
                            if (attrValue.StartsWith(selecteditems_regexp))
                            {
                                attrValue = attrValue.Substring(selecteditems_regexp.Length, attrValue.Length - selecteditems_regexp.Length);
                                var regex = new Regex(attrValue);
                                var items = he.GetAttribute("items").Split(new [] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                                var matchedItems = string.Join(";", items.Where(item => regex.Match(item).Success));
                                he.SetAttribute("selecteditems", matchedItems); 
                            }
                            else
                            {
                                he.SetSelectedItems(attrValue);
                            }
                            break;
                        default:
                            he.SetAttribute(subAction, attrValue);
                            break;
                    }
                    WaitDownloading(tab);
                    return "ok";
                default:
                    throw new Exception ("неизвестное действие");
            }
            return "ok";
        }
 

trapni

Client
Регистрация
16.10.2013
Сообщения
288
Благодарностей
22
Баллы
18
Подскажите, а как сделать клик по ссылке на сайте со скроллом вниз\вверх? Ссылки находятся в списке, потом ее нужно найти на странице и если есть кликнуть.
И можно ли реализовать как-то скролл на паузе?
 
Последнее редактирование:

mig-z

Client
Регистрация
05.12.2014
Сообщения
213
Благодарностей
51
Баллы
28
Ребят, помогите пожалуйста. Я новичок в C# но очень хочется иметь возможность использовать xpath для сбора элементов в список. Вроде делал все как сказал olymp Но у меня ошибки при компиляции кода проекта. Что то я не так сделал(

Проект приложил.
 

Вложения

mig-z

Client
Регистрация
05.12.2014
Сообщения
213
Благодарностей
51
Баллы
28
Ребят, кто тоже плохо понимает что куда вставлять. Держите последний демо проект по xpath из него просто копируйте OwnCodeUsings http://screencast.com/t/EySbqxpRdg3J

Проект приложил.
 

Вложения

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

mig-z

Client
Регистрация
05.12.2014
Сообщения
213
Благодарностей
51
Баллы
28
В список не собирает. Берет только первое значение но добавляет его в список столько раз, сколько встречается элементов в списке. Имя всегда одинаковое. Потестил на меню на тестовой странице.
Хотел бы немного дополнить как можно собирать все элементы со страницы т.к. в оригинальном примере вы можете получить только 1-й элемент на странице, к примеру вам необходимо собрать все ссылки со страницы используя XPath, а не только первую, так вот для начала вам необходимо добавить в общий код следующую функцию:
C#:
public static HtmlElement[] FindElementsByXpath(Instance instance, string xpath)
        {
            Tab tab = instance.ActiveTab;
           if ((tab.IsVoid) || (tab.IsNull)) throw new Exception("вкладка не найдена");
           if (tab.IsBusy) tab.WaitDownloading();
      
           // find html element by tag
           var hes = tab.FindElementsByXPath(xpath);
           if (hes.IsVoid) throw new Exception("элементы не найдены");
            return hes.Elements;
        }
Теперь используя вот такую конструкцию в коде, вы можете подсчитать общее кол-во элементов на странице по критериям поиска:
C#:
var linksCollection = CommonCode.FindElementsByXpath(instance, xpath_exp);
return linksCollection.Length;
А вот теперь и пример как можно собрать все элементы со страницы и записать их в список, в моем случае это были ссылки:
C#:
var links = project.Lists["Links"];
var xpath_exp = ".//*[@id='123']/a";
var action_ev = "get|href";
var set_action ="";
var linksCollectionLength = CommonCode.FindElementsByXpath(instance, xpath_exp);
for (int i = 0; i < linksCollectionLength.Length; i++)
{
var linksCollection = CommonCode.FindElementAndExecuteAction(instance, xpath_exp, action_ev, set_action);
links.Add(linksCollection);
}
Хочу выразить благодарность darkdiver - т.к. именно он помог мне разобраться как это все осуществить! Советую использовать данную возможность поиска т.к. она реально облегчает этот процесс, а в некоторых случаях без неё просто не реально добраться до нужного элемента!

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

Вложения

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
@mig-z, воспользуйтесь этим снипетом:
C#:
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;

// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//a");

//вытаскиваем атрибут href из каждого элемента
var data = heCol.AttributesToString("href").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
Позже объединю это дело с кодом из первого поста.

Снипет ищет все совпадения по XPath (все теги <a>), вытаскивает у каждого совпадения свой аттрибут ("href"), и сохраняет их все в список ("Список 1").
 

AZANIR

Client
Регистрация
09.06.2014
Сообщения
403
Благодарностей
185
Баллы
43
как я понимаю XPath работает только с табом то есть с реальной страницей , нельзя его использовать в гет запросах(
 

Лев

Client
Регистрация
09.12.2014
Сообщения
290
Благодарностей
264
Баллы
63
Ты можешь полученный html из get установить в активную вкладку, и дальше работать как обычно
Код:
var x = project.Variables["res"].Value; // переменная x с html кодом после гет запроса
instance.ActiveTab.MainDocument.Body.SetAttribute("innerHtml", x);
Или можно сторонние библиотеки подключить, вот наверно хорошая штука =)
http://habrahabr.ru/post/112325/
 
  • Спасибо
Реакции: pg2016 и aluminoter

mig-z

Client
Регистрация
05.12.2014
Сообщения
213
Благодарностей
51
Баллы
28
@mig-z, воспользуйтесь этим снипетом:
C#:
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;

// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//a");

//вытаскиваем атрибут href из каждого элемента
var data = heCol.AttributesToString("href").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
Позже объединю это дело с кодом из первого поста.

Снипет ищет все совпадения по XPath (все теги <a>), вытаскивает у каждого совпадения свой аттрибут ("href"), и сохраняет их все в список ("Список 1").

Спасибо!

Доработал ваш пример, для тех кто хочет вытаскивать значения innertext. На примере тестовой страницы уроков зенно.
Код:
Tab tab = instance.MainTab;
if (tab.IsBusy) tab.WaitDownloading();
// get document
Document doc = tab.MainDocument;
// find element by attribute
HtmlElementCollection heCol = doc.FindElementsByXPath(@"//div[@id='nav']/ul/li/a");
//вытаскиваем атрибут innertext из каждого элемента
var data = heCol.AttributesToString("innertext").Split(new string[] {Environment.NewLine},0).ToList();

//закидываем всё в список
project.Lists["Список 1"].AddRange(data);
project.SendInfoToLog("добавлено ["+data.Count+"] элементов");
 
  • Спасибо
Реакции: zennoX

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 179
Благодарностей
3 235
Баллы
113
Имеем на странице 100 однотипных DIV с . Внутри каждого ещё несколько DIV с разной степенью вложенности. В одном DIV нужная мне ссылка, в другом DIV нужный мне текст, третий DIV может быть, а может и нет и внутри него картинки, которые также могут быть, а может и нет. Все DIV можно идентифицировать по статичным outerhtml.
Мне нужно составить таблицу с 4 столбцами: порядковый номер DIV (от 1 до 100), ссылка, текст, список урлов картинок.
Поможет ли мне в этом xpath или есть способ попроще?
Если поможет, то как его правильно организовать?
 

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 179
Благодарностей
3 235
Баллы
113
Почему-то get|innerhtml возвращает постоянно пустоту. Хотя там есть контент.
Такое ощущение что GetAttribute не умеет всех атрибутов возвращать.
С src для img тоже самое - пустой возвращает.
 
Последнее редактирование:

rostonix

Administrator
Команда форума
Регистрация
23.12.2011
Сообщения
29 080
Благодарностей
5 625
Баллы
113
Почему-то get|innerhtml возвращает постоянно пустоту. Хотя там есть контент.
Такое ощущение что GetAttribute не умеет всех атрибутов возвращать.
С src для img тоже самое - пустой возвращает.
умеет. где-то ошибаетесь с идентификацией элемента
 

Astraport

Client
Регистрация
01.05.2015
Сообщения
4 179
Благодарностей
3 235
Баллы
113
Было бы очень круто, если бы в след. версиях Зенно появилась фича по упрощению поиска и конструированию структуры Xpath. По типу как по правому клику "Исследовать элемент", а ниже "Получить путь к элементу в xpath".
Ну и набор заготовок строк для разных целей, а их там полсотни.
 

rostonix

Administrator
Команда форума
Регистрация
23.12.2011
Сообщения
29 080
Благодарностей
5 625
Баллы
113
Было бы очень круто, если бы в след. версиях Зенно появилась фича по упрощению поиска и конструированию структуры Xpath. По типу как по правому клику "Исследовать элемент", а ниже "Получить путь к элементу в xpath".
Ну и набор заготовок строк для разных целей, а их там полсотни.
планируем
 

IgorSush

Client
Регистрация
11.02.2016
Сообщения
308
Благодарностей
105
Баллы
43
Наверняка простой вопросец, но бьюсь уже час:
Небходимо в xpath_exp использовать переменную из проекта, например вот здесь:

string xpath_exp = "//*[@id='chat_99350594']/div[2]/div[2]/div/div/textarea";

вместо "chat_99350594" нужно использовать переменную "test_var".

в редактор кода она вставляется(по правому клику) как project.Variables["test_var"].Value, но код не работает.
Эти кавычки меня доведут
 

CSS

Client
Регистрация
22.05.2010
Сообщения
1 328
Благодарностей
645
Баллы
113
C#:
string xpath_exp = "//*[@id='"+project.Variables["test_var"].Value+"_99350594']/div[2]/div[2]/div/div/textarea";
 
  • Спасибо
Реакции: ttimbaland1983 и IgorSush

IgorSush

Client
Регистрация
11.02.2016
Сообщения
308
Благодарностей
105
Баллы
43
Спасибо!:-)
 
Последнее редактирование:

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