Потомок xpath и потомок или я работают совершенно по-разному

Я пытаюсь найти все секунды tds среди потомков div с указанным id, т.е. 22 и 222. Первое решение, которое приходит мне в голову, было:

//div[@id='indicator']//td[2]

но он выбирает только первую ячейку таблицы, т. е. 22, но не одновременно 22 и 222. Затем я заменил // на /descendant-or-self::node()/ и получил тот же результат (очевидно). Но когда я удалил «-или-я», выражение xpath начало работать, как и ожидалось.

 test1 = test_tree.xpath(u"//div[@id='indicator']/descendant-or-self::node()/td[2]")
 print len(test1) #prints 1 (first one: 22)

 test1 = test_tree.xpath(u"//div[@id='indicator']/descendant::node()/td[2]")
 print len(test1) #prints 2 (22 and 222)

Вот тестовый HTML

<html>
    <body>
        <div id='indicator'>
            <table>
               <tbody>
                    <tr>
                        <th>1</th>
                        <th>2</th>
                        <th>3</th>
                    </tr>
                    <tr>
                        <td>11</td>
                        <td>22</td>
                        <td>33</td>
                    </tr>
                    <tr>
                        <td>111</td>
                        <td>222</td>
                        <td>333</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

Мне интересно, почему оба выражения не работают одинаково, поскольку все td являются потомками элемента div, независимо от того, включен ли div или нет.


person Anton Kolokolcev    schedule 29.07.2015    source источник
comment
все три xpath на тестере xpath дают 2 элемента на выходе   -  person splash58    schedule 29.07.2015
comment
Все возвращают 2 элемента: i.imgur.com/32WRNHs.png   -  person falsetru    schedule 29.07.2015
comment
ржу не могу. вот мой вывод. точно такой же код, но разные результаты imgur.com/fZCL6nH   -  person Anton Kolokolcev    schedule 29.07.2015
comment
Еще одно замечание: Selenium IDE также выделяет только первый td[2], в то время как расширение FireFinder для Firebug показывает оба :(   -  person Anton Kolokolcev    schedule 29.07.2015
comment
Я воспроизвел ваш пример на локальном сервере с HTML-страницей, содержащей ваш пример HTML. Я использую scrapy, поэтому мой селектор — это селектор LXML Xpath. Я использовал это значение xpath .//div[@id='indicator']//tr/td[2], и оно дает правильные результаты [u'<td>22</td>', u'<td>222</td>']   -  person William Kinaan    schedule 29.07.2015
comment
@WilliamKinaan Да. Добавление родителя tr также работает в моем случае, но мне просто интересно, почему это не работает просто как //td[2]   -  person Anton Kolokolcev    schedule 29.07.2015


Ответы (3)


Я думаю, вы нашли ошибку в вашем процессоре XPath.

person Michael Kay    schedule 29.07.2015
comment
Я разместил отчет об ошибке в системе отслеживания ошибок lxml lib. Вероятно, связано с совместимостью Python + lxml + Windows. - person Anton Kolokolcev; 29.07.2015

Я думаю, что нашел причину этой проблемы:

http://www.w3.org/TR/xpath20/#id-errors-and-opt

«В некоторых случаях процессор может определить результат выражения без доступа ко всем данным, которые подразумеваются семантикой формального выражения. Например, формальное описание выражений фильтра предполагает, что $s[1] следует оценивать, исследуя все элементы в последовательность $s и выбор всех тех, которые удовлетворяют предикату position()=1. На практике многие реализации распознают, что они могут вычислить это выражение, взяв первый элемент в последовательности и затем выйдя».

Так что средства нет. Это зависит от реализации процессора xpath, однако я до сих пор не понимаю, почему //div[@id='indicator']/descendant-or-self::node()/td[2] и //div[@id='indicator']/descendant::node()/td[2] дают разные результаты.

person Anton Kolokolcev    schedule 30.07.2015

Я разработал веб-страницу, содержащую HTML, который вы указали в своем вопросе.

Когда вы используете этот xpath:

.//div[@id='indicator']//tr/td[2]

Он работает так, как ожидалось, и результат:

[u'<td>22</td>', u'<td>222</td>']

Однако в соответствии с вашим комментарием, вы спрашивали, когда .//td[2] не работает. Причина в том, что .//td дает вам список всех td(s) в вашей модели DOM. Добавление индекса, такого как [2], приведет к появлению второго td в этом списке.

Подводя итог: вот результаты применения .//td и .//td[2] соответственно:

введите здесь описание изображения

и если вы хотите взять текст внутри этих tds, вы должны добавить /text() следующим образом:

введите здесь описание изображения

Обновлять:

ОП сказал:

So why then //div[@id='indicator']/descendant::node()/td[2] produces ['22', '222']? According to your comment: "Adding an index such as [2] will result in the second td in that list" it should populate only ['22'].

Я попытаюсь объяснить, что здесь происходит:

  1. descendant:node() не равно //
  2. равно // это: descendant-or-self::node()
  3. Это объясняется в спецификации W3C:

введите здесь описание изображения

Надеюсь, этот код поможет вам: введите здесь описание изображения

person William Kinaan    schedule 29.07.2015
comment
Так почему же тогда //div[@id='indicator']/descendant::node()/td[2] выдает ['22', '222']? Согласно вашему комментарию: добавление индекса, такого как [2], приведет к тому, что второй td в этом списке должен заполнить только ['22']. - person Anton Kolokolcev; 30.07.2015
comment
В ПОРЯДКЕ. Попробую еще раз объяснить: test = test_tree.xpath(u"//div[@id='indicators_minimize']/descendant-or-self::node()")[0] print etree.tostring(test, encoding='cp866', pretty_print=True) Результат: <?xml version='1.0' encoding='cp866'?> <div id="indicators_minimize"> <table> <tbody> <tr> <th>1</th> <th>2</th> <th>3</th> </tr> <tr> <td>11</td> <td>22</td> <td>33</td> </tr> <tr> <td>111</td> <td>222</td> <td>333</td> </tr> </tbody> </table> </div> - person Anton Kolokolcev; 30.07.2015
comment
Тогда: test = test_tree.xpath(u"//div[@id='indicators_minimize']/descendant::node()")[0] print etree.tostring(test, encoding='cp866', pretty_print=True) Результат: <?xml version='1.0' encoding='cp866'?> <table> <tbody> <tr> <th>1</th> <th>2</th> <th>3</th> </tr> <tr> <td>11</td> <td>22</td> <td>33</td> </tr> <tr> <td>111</td> <td>222</td> <td>333</td> </tr> </tbody> </table> - person Anton Kolokolcev; 30.07.2015
comment
Единственное отличие состоит в том, что первый xml-код инкапсулируется в тег div (из-за оператора -or-self). Но нас не волнует родитель div или любой другой уровень глубины родителя, потому что мы ищем потомков. После вычисления выражения нам все равно, где искать tds: в потомках div или в потомках таблицы (и те, и другие содержат нужные tds). Однако результаты различаются. По крайней мере на моей машине. Взгляните на первые два ответа здесь: все три выражения xpath возвращают одинаковые значения. - person Anton Kolokolcev; 30.07.2015
comment
@AntonKolokolcev еще раз, какой у тебя вопрос? Вы уже задали два разных вопроса, и я ответил на них оба. Пожалуйста, будьте конкретны, я пытаюсь помочь здесь. - person William Kinaan; 30.07.2015
comment
мой вопрос: почему операторы //div[@id='indicator']/descendant-or-self::node()/td[2] и //div[@id='indicator']/descendant::node()/td[2] дают разные результаты? Просто для справки: ось потомков содержит потомков узла контекста; потомок является потомком или потомком ребенка и так далее. Ось «потомок или я» содержит узел контекста и потомков узла контекста. - person Anton Kolokolcev; 30.07.2015
comment
они дают разные результаты, поскольку являются разными выражениями. Среднее из них (как я упоминал ранее) написано здесь. Ещё раз: ПРИМЕЧАНИЕ. Путь расположения //para[1] не означает то же самое, что и путь расположения /descendant::para[1]. Последний выбирает первый элемент-потомок para; первый выбирает все потомки пара-элементов, которые являются первыми пара-потомками своих родителей. Вы получили эту записку? - person William Kinaan; 30.07.2015
comment
Да, я получил эту записку. Еще 3 комментария: 1) Сначала вы сказали, что причина в том, что .//td дает вам список всех td (ов) в вашей DOM. Добавление индекса, такого как [2], приведет ко второму td в этом списке и в вашем последнем комментарии: первый (//para[1]) выбирает все потомки элементов para, которые являются первыми дочерними элементами para их родителей. что полностью противоречит вашему первому комментарию. 2) Обратитесь к изображению из второго ответа: i.imgur.com/32WRNHs.png а затем к моему изображению: m.imgur.com/fZCL6nH. Полностью идентичный код дает разные результаты на разных машинах. - person Anton Kolokolcev; 30.07.2015
comment
3) //para[1] не означает то же самое, что и /descendant::para[1], потому что //para[1] означает /descendant-or-self::node()/para[1] - person Anton Kolokolcev; 30.07.2015
comment
1) оба описания, которые я дал, одинаковы, они не противоположны (постарайтесь больше думать об этом). 2) второе изображение правильное. Первый нет. Однако вам нужно будет проверить, какую версию XPath использует парсер. Даже если это может быть ошибка, важно, чтобы у вас была информация для правильного понимания двух выражений XPath. 3) для вашего третьего пункта я уже указал это в своем ответе. Удачи в вашем проекте - person William Kinaan; 30.07.2015