4 место [Обзор]ZennoPoster + xPath на примере Яндекс.Маркета

Sz5

Client
Регистрация
10.12.2012
Сообщения
157
Реакции
186
Баллы
43
Большой привет всему Community Zennolab:az:

Сегодня проведем небольшой разбор языка запросов xPath и его применении в ZennoPoster.
Также напишем несколько заготовок-болванок, чтобы наши знания закрепились на практике)

Дисклеймер
В статье используется мат :av:

XPath — это язык запросов к элементам html документа. Чтобы получить интересующие данные, необходимо всего лишь создать запрос, описывающий эти данные. Остальную работу за вас выполнит интерпретатор языка xPath. @AlexeyKuzmin

Инструментарий:
При построении xPath запросов необходимо:
  1. Базовые знания HTML тегов
  2. Базовая логика (дедукция и индукция) :eek:
  3. Знание синтаксиса xPath часто используемые:
    1. ancestor (предок, батя)
    2. descendant (потомок, пезд*к)
    3. starts-with (начинается с)
    4. contains(содержит ключевое слово)
  4. Понять основные принципы построения xPath запросов
Взял для примера популярный сайт Яндекс.Маркет, практическая польза, сайт не самый простой есть, где потренироваться + для интернет магазинов оживить сайт готовыми комментариями от живых людей или наполнить и актуализировать информацию о товаре, никогда не помешает :bq:

Работать будем с разделом мобильных телефонов.
Справедливости ради, хочу заметить, что работать будет с любым разделом, Вам необходимо лишь будет изменить id в url на свои.

1.png


Начнем с Бати.

2.jpg

Данная картинка соответствует тому, что у одного Бати, есть несколько Пезд*ков, которые в свою очередь являются Батями для других, количество элементов и степень их родства ограничивается лишь фантазией человека, который все это дело верстал.

Для того, чтобы наш xPath запрос вернул нужный нам элемент, необходимо будет построить маршрут от Бати к Пезд*ку*

*
Примечание На самом деле, мы можем написать запрос xPath и минуя батю получить результат и это даже будет работать, но это не про нас. Мы любим посложнее.:cd:

Закрепим на практике примечание)

Переходим на страничку Яндекс.Маркета -> Мобильные телефоны

Активируем xPather в появившемся окне набираем наш "первый" xPath запрос -
Код:
Развернуть Свернуть Копировать
//div[@class='snippet-card__view']

3.png

Мы нашли div у которого название класса соответствует snippet-card__view.

Разберем структуру xPath запроса:

  1. // - Две косые черты, просто запоминанием, что все начинается с них
  2. div – мы говорим, что будем искать в html коде все div (это может быть любой html тег)
  3. [Тут будет запрос] – в этих скобках, будет проходить вся работа.
  4. @class = собачка вначале обязательно, искать только те div у которых есть class
  5. 'snippet-card__view' находим только тот class, у которого имя snippet-card__view, не забываем про верхние апострафы ' '
По такому же принципу можно найти snippet-card__info, snippet-card__content

Усложним задачу.

Получим все отзывы. Посмотрим, как лучше составить xPath запрос

Попробуем обратиться к элементу «Отзывов» напрямую, минуя Батю.

Открываем xPather и набираем в поле по аналогии.
Код:
Развернуть Свернуть Копировать
//span[@class='small']

4.png


Хапнули лишнее, ну ничего, попробуем изменить запрос, задействуем ключевое слово descendant.

5.jpg

А вот и Батя:D

6.png



Разберем построение xPath запроса при использовании descendant

Код:
Развернуть Свернуть Копировать
//[запрос к бате]/(косая черта)descendant(ключевое слово)::(два двоеточия)[запрос_к_пезд*ку]
Готовый запрос, исходя из конструкции выше.
Код:
Развернуть Свернуть Копировать
//div[@class='snippet-card__content']/descendant::span[@class='small']
Результат не заставил себя долго ждать, Мы получили все отзывы без лишних результатов.

7.png


Мои поздравления, если вы до сих пор читаете этот бред:do:

Дальше будет интереснее (facepalm)

  • contains(@class, 'имя_класса') – возвращает true, если первая строка содержит вторую, иначе возвращает false.
Применяю когда имена классов являются динамическими или сгенерированными случайным образом.
Примеры этой функции будут в изобилии доступны исходном коде приложенного проекта.
  • starts-with(@class, 'имя_класса') - возвращает true если первая строка начинается со второй, иначе возвращает false.
Данная функция полезна, если, мы знаем, что какой-то из html элементов начинается с определенной ключевой фразы.

Вернемся в Yandex.Market

Продолжим погружение:de:

Нам необходимо определить общий xPath класс Бати.
При изучении исходного кода страницы, мы можем заметить одинаковый тег data-id, к нему мы и привяжемся

8.png


Построим следующий xPath запрос:
Код:
Развернуть Свернуть Копировать
//div[starts-with(@data-id, 'model-')]
Результат


9.png


Примечание
Весь свет на теге div не сошелся, мы можем использовать и другие теги, чтобы построить xPath запрос, старайтесь идти по пути наименьшего сопротивления и задействовать готовые функции, которые доступны в языке xPath, иначе со временем Ваш запрос будет похож на вот такой кусок
Код:
Развернуть Свернуть Копировать
/html/body/div[1]/div[4]/div[2]/div[1]/div[2]/div[1]/div[1]/div[3]/div/div[1]/div/h3/a/span

10.jpg




Теперь начнем применять наши знания по назначению, в рамках ZennoPoster-а:do:

Поставим задачу «Спарсить следующую информацию со странички Яндекс.Маркета» :
  1. Получим название товара
  2. Получим стоимость товара от и до
  3. Получим рейтинг товара
  4. Получим количество отзывов
  5. Получим ID товара, чтобы потом можно было парсить внутренние странички.
  6. Получим комментария к товару
Результат сформируем в строки и выведем их в логи.

Основная наша работа будет проходить на языке программирования C#.
В ZennoPoster
доступны специально написанные классы HtmlElement и HtmlElementCollection

11.png


Описании работы и заготовки-болванки в прикрепленный проекте.

12.png


На момент 15.05.2017 года. xPath запросы актуальны и выполняют своё назначение.

Спасибо за ваше внимание, лучшая благодарность для автора будет ваш голос:ay:
 
Номер конкурса статей
  1. Седьмой конкурс статей
Тема статьи
  1. Другое

Вложения

Последнее редактирование:
Батя в здании 8-)
Расписано круто, автору респект
 
  • Спасибо
Реакции: Sz5
Голосовать буду за Вас :)
 
  • Спасибо
Реакции: leha52rus и Sz5
Прекрасное оформление статьи, ставлю нраица :-)
 
  • Спасибо
Реакции: Sz5
Я уж думал что в Зенно появилась удобная тулза для работы с Xpath.
/html/body/div[1]/div[4]/div[2]/div[1]/div[2]/div[1]/div[1]/div[3]/div/div[1]/div/h3/a/span
Вот так примерно выглядит работа с FB.
 
  • Спасибо
Реакции: iBotovod и Sz5
Хорошая статья
XPath — это язык запросов к элементам html документа
На самом деле XPath - это язык запросов к элементам XML-документа о чем сообщает первая буква X. И это вносит свои коррективы - если HTML невалидный, что чаще всего и бывает (не закрытый тег, нет парного тега, не экранирован спецсимвол и т.п.), то чистый XPath вылетит с ошибкой - поэтому, сначала правится HTML и приводится к нормальному XML-виду (так работает большинство компонентов, например HtmlAgilityPack).

Стандартная панель разработчика в движке Chromium (по F12) тоже умеет искать по XPath и по CssQuery. В чём преимущества компонента XPather?
0d64279939074e769cb449e4d80e300b.png
 
Последнее редактирование модератором:
Возможно я скудоумный, мне регулярками проще.
 
Возможно я скудоумный, мне регулярками проще.
Малейшее изменение страницы (добавление пробела, изменение названия класса) и регулярка отвалится, XPath более надежён в этом смысле.

P.S. Почему не надо парсить HTML регулярками: http://stackoverflow.com/questions/...t-xhtml-self-contained-tags?tab=votes#tab-top :)
 
Однозначно статья имеет место быть. Плюсую и мой голос.
 
  • Спасибо
Реакции: Sz5
  • starts-with(starts-with(@class, 'имя_класса')) - возвращает true если первая строка начинается со второй, иначе возвращает false.
Тут скорее всего опечатка, два раза starts-with


  • // - Две косые черты, просто запоминанием, что все начинается с них
Думаю, стоит ещё рассказать о том, что если мы ищем по xpath внутри другого HtmlElement элемента, то нужно использовать:
Код:
Развернуть Свернуть Копировать
*//

Пример (найти первую форму и в ней кнопку сабмит):
Код:
Развернуть Свернуть Копировать
HtmlElement he_form = instance.ActiveTab.FindElementByXPath(".//form", 0);
HtmlElement he_submit = he_form.FindChildByXPath("*//input[@type='submit']", 0);


Одна картинка после длиннющего плохого xpath - битая.
ЗЫ: картинки нужно залить на сервера зеннолаба, кста.


PS: статья очень понравилась, буду голосовать за неё) Стиль оформления и текст улыбнули) Понимаю, что некоторые вещи могут быть неприемлимы, но тут они крайне уместны. Особенно с поиском через Батю - прям гениальное сравнение!
 
  • Спасибо
Реакции: AndrewSuul, Ground и Sz5
Стандартная панель разработчика в движке Chromium (по F12) тоже умеет искать по XPath и по CssQuery. В чём преимущества компонента XPather?
А ещё в Chrome можно искать через консоль JS используя:
Код:
Развернуть Свернуть Копировать
$x("тут_xpath")
Тоже очень удобно, я юзаю именно так :)
 
  • Спасибо
Реакции: kfil и Sz5
Я бы добавил поиск по Xpath без браузера при работе на post get запросах, в разы ускоряет. Но и так отлично, когда разбирался с ним - потратил много времени на поиск информации. На форуме 5 полезных постов.
 
  • Спасибо
Реакции: Sz5
Screenshot_4.png
статья заебз 8-) Понятно что искать по постоянным элементам , и которые не меняются - это вы для ознакомления привели так сказать с темой. И что вы и Xpath можете много больше.. а можно попросить разобрать другой пример? дапустим https://plus.google.com/collection/cU7oBB?hl=ru получить описание к картинкам и сами картинки ?
 
  • Спасибо
Реакции: Sz5
а можно попросить разобрать другой пример? дапустим https://plus.google.com/collection/cU7oBB?hl=ru получить описание к картинкам и сами картинки ?
Не знаю как в гуглоплюсе генерируются пути к картинкам, но скорее всего лучше отталкиваться от такой логики: ищем img, где в содержимом src (через contains) есть "googleusercontent.com/proxy/".
А описания соответственно через "соседние" к этому элементы получать.

Да, бывает что встречаются сайты, где классы у тегов генерируются автоматически или меняются при малейшем изменении верстки (например, из-за использования минификаторов css), но почти всегда можно оттолкнуться от каких-то других элементов. Например, даже использовать "text()" для получения содержимого (то что внутри тега) и сравнивать этот текст.

Но бывают ситуации, что и такие способы не спасают. Поэтому для каждого подхода должны быть свои инструменты и важно понимать и знать где и как их применять.
Лепить везде регулярки - бред. Лепить везде xpath - бред. Комбинировать эти способы - гораздо лучше.
 
Последнее редактирование:
  • Спасибо
Реакции: Sz5
а можно попросить разобрать другой пример? дапустим https://plus.google.com/collection/cU7oBB?hl=ru получить описание к картинкам и сами картинки ?
Картинки:
Код:
Развернуть Свернуть Копировать
//img[contains(@src, 'googleusercontent') and @alt != '' and @width > 100]

Описание:
Код:
Развернуть Свернуть Копировать
//div[@dir='ltr']
 
  • Спасибо
Реакции: The_vAe и lzlmrf
ищем img, где в содержимом src (через contains) есть "googleusercontent.com/proxy/".
не пойдет - это только на загруженых с сайтов(с сылками).
А описания соответственно через "соседние" к этому элементы получать.
звучит красиво - но можно пример ? и желательно от ТС .
 
@Geograph спасибо за отзыв, специально убрал упоминания про xml, дабы не смущать не подготовленного зрителя)
xPather использую исключительно исходя из эстетических моментов :bm:
@seomiks оно действительно проще, но кода всегда получается больше) попозже попробую провести несколько сравнений количества кода при использовании xPath и простых регулярок)
@lzlmrf Почему бы и нет. Держите готовый проект, увы без красочного описания, снабдил код небольшими комментариями.
@stanar да есть даже готовая библиотека HtmlAgilityPack или AngleSharp в последнем вроде есть возможность использовать css селекторы
@Lord_Alfred не смог найти, где картинка битая (
P.S. Залив картинок это можно только посредством прикрепления атачей?

Лепить везде регулярки - бред. Лепить везде xpath - бред. Комбинировать эти способы - гораздо лучше.
Самый верный подход :ay:
 

Вложения

Последнее редактирование:
Есть предположения из неподтвержденных источников, что в htmlagilitypack идет утечка памяти, но обоснований этому у меня нет, как и опровержений.
 
Есть предположения из неподтвержденных источников, что в htmlagilitypack идет утечка памяти, но обоснований этому у меня нет, как и опровержений.
Библиотека давно лишилась поддержки автора( использую ее для парсинга нескольких ресурсов, утечек не замечал, все работает стабильно, правда гоняю все это в один поток.
 
тоже очень понравилась статья,закреплю в закладках, буду обращаться по случаю как к мини мануалу)
ТС где вы были на прошлом конкурсе статей) зимой как раз плотно изучал xpath в том числе и по нескольким постам на форуме!
мой голос за статью полюбому)
 
  • Спасибо
Реакции: Sz5
Спасибо за статью. Я занимаюсь по большей части парсингом. Поэтому мне особо актуально. Я вообще регулярками обходился. Но за альтернативное решение - огромное спасибо. Голосовать буду, скорее всего, за Вас. Удачи.
 
  • Спасибо
Реакции: Sz5
Активируем xPather в появившемся окне набираем наш "первый" xPath запрос -
вы прям сразу набираете запрос..а почему именно его? как пришли к такому решению? Плагин только показывает то что набрали - поиска или выделения блока нет (или я не нашел)
 
вы прям сразу набираете запрос..а почему именно его? как пришли к такому решению? Плагин только показывает то что набрали - поиска или выделения блока нет (или я не нашел)
1. Перед началом работы пробегаюсь по всем блокам сайта инспектором(CTRL+SHIFT+I) , главная задача найти за что зацепиться, это может быть какое-то ключевое слово или какой-то определенный тег, которые есть только у этого элемента.
Наша задача отделить "зерна от плевел".
2. При принятии решения надо руководствоваться целями, если это парсинг, то основная цель это привести к коллекции (HtmlElementCollection) чтобы можно было в цикле все удобно перебрать, если это кнопка или поле на странице, то главный принцип это простота запроса, нет нужды с самого начала лепить огород из запросов (батя->пизд*к) если можно напрямую построить запрос привязавшись к какому-то ключевому слову или тегу.
3. Если xPath запрос написан правильно, то элемент подсветится в браузере, как на скрине
4e548393ca.png


@lzlmrf Не спешите при построении запроса, а также будьте очень внимательны при составлении запроса,(если допускаете ошибку или забыли запятую, то запрос не будет работать), первое время сам пропускал ' апострофы, забывал писать @, ставить скобки, это нормальное явление, начинайте с простого и не пытаться сходу строить сложный запрос, который задействует весь набор запросов xPath. Велика вероятность, что вы допустите ошибку именно в синтаксисе, а не в логике запроса.
P.S. Главное это практика, со временем, xPath запрос у Вас будет собираться еще на этапе просмотра исходного кода.

 
  • Спасибо
Реакции: zennoX, lzlmrf и Sanekk
Кстате, вот ещё приведу запросы которые я ещё использую:

not - не содержит

примеры:
//a[not (@href)] - поиск всех элементов с тегом "а", у которых нет атрибута "href"
//label[not(@for='login')] - поиск всех элементов с тегом "label", у которых нет атрибута "for" со значением "login"

text() - InnerText
примеры
//span[text()='лалала'] - поиск всех элементов с тегом "span", у которых есть текст "лалала"
//a[contains(text(),"Удаленные")] - поиск всех элементов с тегом "a", которые содержат текст "Удаленные"

and - дополнительное условие
пример:
//*[@id='owner_photo_edit' and not(@style='display: none;')]
 
Спасибо за статью. Возникло пару вопросов по шаблону (http://joxi.ru/xAeBY5kt3zN8ry)
Главный вопрос как сохранить все элементы в переменую/список/таблицу?
Если на странице 100 найденных элементов - блоков(и каждый разбивается как у вас на "имя товара, описание, количество отзывов и т.д."), как сделать так чтобы эти элементы ложилось куда то(список, таблица, переменая).
 
  • Спасибо
Реакции: Sz5
@Tnyrtin
5bb15ac7bf.png


  • Создаем один список и одну табличку, переименовываем их в info
  • Добавляем их в проект на свои места.

Код:
Развернуть Свернуть Копировать
IZennoList infoList = project.Lists["info"];
IZennoTable infoTable = project.Tables["info"];
  • Формируем строку для таблички, разделяем ее табуляциями, чтобы каждый новый элемент лег в отдельный столбик + удаляем ключевики в цене "от" и "до"
Код:
Развернуть Свернуть Копировать
string itemInfoTable = string.Format("https://market.yandex.ru/product/{0} \t {1} \t {2} \t {3} \t {4} \t {5}", idItem, nameItem.InnerText, rateItem.InnerText, reviewsItem.InnerText, priceFromItem.InnerText.Replace("от",""), priceToItem.InnerText.Replace("до",""));
  • Добавляем в табличку и в список
Код:
Развернуть Свернуть Копировать
//Отправляем в список.
infoList.Add(itemInfo);
infoTable.AddRow(itemInfoTable);

Результат
9dd00d01b9.png


Прикреплю готовый пример к этому посту.

Остальное, можно сделать по аналогии :do:
 

Вложения

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

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