Основи XPath

8 хв. читання

XPath — це потужна мова, яка часто використовується при парсингу веб-сайтів. Вона дозволяє звертатися до вузлів (node, ноди) чи вираховувати значення з XML та HTML. Схожі функції використовують CSS селектори, але XPath дозволяє вам робити набагато більше.

З XPath ви можете парсити дані на основі текстових елементів, і не тільки в структурі сторінки. Тому коли вам потрібно спарсити досить "кривий" сайт, XPath може зберегти багато вашого часу.

Цей туторіал ознайомить вас з основами XPath, а потім ви зможете просуватися до більш просунутих функцій.

Для своїх експериментів з XPath ви можете використовувати цей сервіс.

Основи основ

Припустимо, у нас є такий документ:


  
    <title>My page</title>
  
  
    <h2>Welcome to my <a href="#">page</a></h2>
    <p>This is the first paragraph.</p>
    
  

Xpath представляє будь який XML/HTML документ як дерево елементів (вузлів). Корінна нода (root) не є частиною елементу, але вважається батьківським вузлом початкового елементу в документі (для HTML це — ``). Приблизно так це виглядає:

Основи XPath

Як бачите, в дереві XPath є декілька типів для вузлів.

  • Елемент: представляє HTML-елемент, наприклад, тег h2.

  • Атрибут: представляє атрибут елементу, наприклад, атрибут href в тегу <a href=""http://www.example.com"">example</a>.

  • Коментар: представляє коментар в документі (``).

  • Текстовий вузол: представляє текст всередині елементу, наприклад, example в <p>example</p>.

Важливо усвідомлювати різницю між цими вузлами. А тепер давайте зануримося в XPath.

Ось так за допомогою XPath виділяється елемент:

/html/head/title

Такі шляхи називаються location path. Вони дозволяють вказувати шлях відносно контекстного вузла (в даному випадку root). Цей шлях складається з трьох частин, розділених слешами. Воно означає "починаючи з елементу html, шукай всередині елемент head, а всередині нього title". Контекстний вузол змінюється кожен крок, так він буде дорівнювати head на останньому кроці.

Зазвичай ми не знаємо (або нам просто лінь) точний шлях вузол-в-вузол, ми можемо використати пошук по всьому документу:

//title

Цей вираз означає "проглянь все дерево від початку (//) і знайди елемент title".

Взагалі, вирази, що ми бачили вище - це скорочений синтаксис XPath. Повна версія минулого виразу буде виглядати так:

/descendant-or-self::node()/child::title

Тобто // це аналог descendant-or-self, що означає поточний вузол, або будь-який рівнями нижче. Ця частина вираження називається віссю (axis) і визначає набір вузлів, з який буде виконуватися вибірка (рівнями нижче, вище або на тому ж рівні).

Наступна частина вираження — node(), яка називається тестом вузла (node test), яка зберігає вираження, за яким вирішується слід вибирати поточний вузол чи ні. В даному випадку виділяються вузли всіх типів. Потім йде інша вісь — child, яка означає "пройди по дочірніх вузлах, відносно поточного", а тест вузла в даному випадку title.

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

Ви можете виділяти вузли як по імені, так і по типу.

Ось декілька прикладів:

  • /html — вибирає всі вузли з іменем html відносно корінного елементу.

  • /html/head — вибирає вузли названі head в вузлі html.

  • //title — вибирає всі вузли title в документі.

  • //h2/a — вибирає всі вузли a в документі, що вкладені в вузол h2.

А ось деякі приклади виділення по типу:

  • //comment() — виділяє лише вузли коментарів.

  • //node() — виділяє всі вузли в дереві.

  • //text() — виділяє лише текстові вузли.

  • //* — виділяє всі вузли, за виключенням коментарів та текстових вузлів.

І, звісно, ми можемо комбінувати ці способи:

//p/text()

Це вираження виділяє текстові вузли всередині всіх елементів p. В HTML, який ми показували вище, цей вираз виділить "This is the first paragraph.".

А тепер давайте розглянемо як ми можемо фільтрувати результати. Нехай, в нас є такий документ:


  
    <ul>
      <li>Quote 1</li>
      <li>Quote 2 with <a href="...">link</a></li>
      <li>Quote 3 with <a href="...">another link</a></li>
      <li><h2>Quote 4 title</h2> ...</li>
    </ul>
  

Виділити перший пункт списку ми можемо ось так:

//li[position() = 1]

Вираз в квадратних дужках називається предикатом і він фільтрує вузли, що повертаються з виразу //li. В даному випадку він перевіряє позицію кожного вузла, використовуючи функцію position(), яка повертає позицію поточного вузла в результаті (наборі вузлів). Зауважте, що нумерація починається з 1. Інакше цей вираз можна записати так:

//li[1]

Обидва вирази повернуть це:

<li class="quote">Quote 1</li>

Ось декілька прикладів предикатів:

  • //li[position()%2=0] — виділяє елементи li на парних позиціях.

  • //li[a] — виділяє елементи li, в яких є елемент a.

  • //li[a or h2] — виділяє елементи li, в яких є елемент a або h2.

  • //li[ a [ text() = "link" ] ] — виділяє елементи li, в яких є елемент a з текстом "link". Також можна записати як //li[ a/text()="link" ].

  • //li[last()] — виділяє останній елемент li в документі.

Підводячи підсумок, шлях складається з кроків, розділених слешами, кожен крок містить в собі вісь, тест вузла та предикат. Ось приклад виразу з двох кроків, кожен з яких має свою вісь, тест вузла та предикат.

//li[ 4 ]/h2[ text() = "Quote 4 title" ]

А ось аналог без скорочень:

/descendant-or-self::node()
    /child::li[ position() = 4 ]
        /child::h2[ text() = "Quote 4 title" ]

Також ми можемо об'єднати два вирази в один за допомогою оператора |. Наприклад, ми можемо виділити всі елементи a та h2 в документі.

//a | //h2

Тепер розглянемо наступний документ:


  
    <ul>
      <li id="begin"><a href="https://scrapy.org">Scrapy</a></li>
      <li><a href="https://scrapinghub.com">Scrapinghub</a></li>
      <li><a href="https://blog.scrapinghub.com">Scrapinghub Blog</a></li>
      <li id="end"><a href="http://quotes.toscrape.com">Quotes To Scrape</a></li>
    </ul>
  

Уявимо, що нам потрібно отримати всі посилання на HTTPS URL'и. Ми можемо зробити це перевіркою атрибуту href:

//a[starts-with(@href, "https")]

Цей вираз спочатку виділяє всі посилання на сторінці, а потім перевіряє чи починається атрибут href з https. Доступ до атрибуту здійснюється за допомогою синтаксису @attributename.

І ще декілька прикладів.

  • //a[@href="https://scrapy.org"] — виділяє елементи a, що ведуть до https://scrapy.org.

  • //a/@href — виділяє адреси, на які ведуть посилання на сторінці.

  • //li[@id] — виділяє лише ті елементи li, для яких задано id.

Більше про вісь

До цього ми бачили два вида осей:

  • descendant-or-self

  • child

Але їх набагато більше. Розглянемо такий документ


  
    <p>Intro paragraph</p>
    <h1>Title #1</h1>
    <p>A random paragraph #1</p>
    <h1>Title #2</h1>
    <p>A random paragraph #2</p>
    <p>Another one #2</p>
    A single paragraph, with no markup
    <div><p>Footer text</p></div>
  

Тепер ми хочемо видобути лише перший параграф після кожного заголовку. Щоб зробити це, ми можемо використати вісь following-sibling, яка виділяє всі елементи на тому ж рівні після поточного елементу.

//h1/following-sibling::p[1]

В даному прикладі контекстною нодою, до якої застосовувалося following-sibling була h1.

А якщо ми хочемо виділити текст перед футером? Ми можемо використати preceding-sibling:

//div[@id='footer']/preceding-sibling::text()[1]

В даному випадку ми виділяємо першу текстову ноду перед футером ("A single paragraph, with no markup").

XPath також дозволяє нам виділяти елементи, базуючись на їх текстовому контенті. Ми можемо використати цю фічу разом з віссю parent, щоб отримати батьківський вузол елементу 'p' з текстом "Footer text".

//p[ text()="Footer text" ]/..

В результаті ми отримаємо елемент "

Footer text

". Як бачите, .. використовується як скорочення для осі parent.

Альтернативою вислову вище може бути таке вираження:

//*[p/text()="Footer text"]

Воно виділяє всі елементи в які вкладені елементи p з текстом "Footer text".

Ви можете знайти всю специфікацію тут.

Література

Якщо ви хочете дізнатися більше, то ось невеличкий список ресурсів:

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 3.9K
Приєднався: 11 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація