Поради щодо використання XPath

3 хв. читання
01 листопада 2021

XPath для програміста, що працює з парсерами, це один з must-have. Він дозволяє більш гнучко (в порівнянні з CSS селекторами) описувати локації всередині HTML/XML документа. Якщо ви ще не знайомі з ним, то ось гарний туторіал.

А в цій статті ми поговоримо про деякі помилки, які допускають користувачі, що вже знайомі з XPath. Для демонстрації прикладів ми будемо використовувати Scrapy Selector API

Не використовуйте contains(.//text(), "search text"), використовуйте contains(., "search text")

А тепер розкажу чому: .//text() повертає набір текстових елементів, node-set. А коли node-set перетворюється в текстовий рядок (що й відбувається при передаванні його в функції, що працює з рядками), результат повернеться лише для першого елементу.

>>> from scrapy import Selector
>>> sel = Selector(text='<a href="#">Click here to go to the <strong>Next Page</strong></a>')
>>> xp = lambda x: sel.xpath(x).extract() # let's type this only once
>>> xp('//web.archive.org/web/20230402095329/https://a//text()') # take a peek at the node-set
[u'Click here to go to the ', u'Next Page']
>>> xp('string(//a//text())') # convert it to a string
[u'Click here to go to the ']

А коли в стрічку конвертується одна нода, то повертається і її текст, і її вкладених тегів.

>>> xp('//a[1]') # selects the first a node
[u'<a href="#">Click here to go to the <strong>Next Page</strong></a>']
>>> xp('string(//a[1])') # converts it to string
[u'Click here to go to the Next Page']

Підсумуємо:

# Добре
>>> xp("//a[contains(., 'Next Page')]")
[u'<a href="#">Click here to go to the <strong>Next Page</strong></a>']

# Погано
>>> xp("//a[contains(.//text(), 'Next Page')]")
[]

# Добре
>>> xp("substring-after(//a, 'Next ')")
[u'Page']

# Погано
>>> xp("substring-after(//a//text(), 'Next ')")
[u'']

Детальніше тут.

Розумійте різницю між //node[1] та (//node)[1]

//node[1] вибирає кожну першу ноду серед вкладених в батьківську.

(//node)[1] шукає всі ноди в документі, а потім повертає першу.

>>> from scrapy import Selector
>>> sel=Selector(text="""
....: <ul class="list">
....: <li>1</li>
....: <li>2</li>
....: <li>3</li>
....: </ul>
....: <ul class="list">
....: <li>4</li>
....: <li>5</li>
....: <li>6</li>
....: </ul>""")
>>> xp = lambda x: sel.xpath(x).extract()
>>> xp("//li[1]") 
[u'<li>1</li>', u'<li>4</li>']
>>> xp("(//li)[1]") 
[u'<li>1</li>']
>>> xp("//ul/li[1]")
[u'<li>1</li>', u'<li>4</li>']
>>> xp("(//ul/li)[1]")
[u'<li>1</li>']

Також

//a[starts-with(@href, '#')][1] отримує набір локальних посилань-якорів, що вкладені безпосередньо в батьківську ноду і повертає кожну першу.

(//a[starts-with(@href, '#')])[1] отримує набір всіх нодів, що вкладені в батьківську (на всіх рівнях) і повертає першу.

Будьте уважні при пошуку за класом

Найкращий спосіб знайти якийсь елемент за класом, використовуючи XPath, це:

*[contains(concat(' ', normalize-space(@class), ' '), ' someclass ')]

Багатослівно. Але давайте розглянемо на прикладах:

>>> sel = Selector(text='<p class="content-author">Someone</p><p class="content text-wrap">Some content</p>')
>>> xp = lambda x: sel.xpath(x).extract()

# Не працює, бо в елемента більше одного класу
>>> xp("//*[@class='content']")
[]

# Отримуємо не те, що нам потрібно
>>> xp("//*[contains(@class,'content')]")
[u'<p class=""content-author"">Someone</p>']

# А ось так нормально
>>> xp("//*[contains(concat(' ', normalize-space(@class), ' '), ' content ')]")
[u'<p class="content text-wrap">Some content</p>']

Також, для пошуку за класом не гріх використати й CSS селектори.

>>> sel.css(".content").extract()
[u'<p class="content text-wrap">Some content</p>']
>>> sel.css('.content').xpath('@class').extract()
[u'content text-wrap']

Більше доків по Scrapy селекторах тут.

Розберіться зі всіма роздільниками

Якщо ви тільки поверхнево ознайомились з XPath, то я рекомендую перш за все гарно ознайомитися з роздільниками, щоб не писати власні велосипеди. Гарний туторіал.

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

Сніппет для отримання текстового контенту

Невеличкий сніпет, що отримує текст сторінки, ігноруючи <script> та <style>, а також ноди, що містять в собі лише пробіли:

1//*[not(self::script or self::style)]/text()[normalize-space(.)]

Джерело

А в вас є ще якісь хитрощі при роботі з XPath? Пишіть в коментарі.

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

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

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

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