python lxml различный результат на windows и linux


Linux

>>> from lxml import etree
>>> html='''<td><a href=''>a1</a></td>
... <td><a href=''>a2</a></td>
... '''
>>> p=etree.HTML(html)
>>> a=p.xpath("//a[1]")
>>> for i in a:
...    print i.text
... 
a1
a2

Окна.

>>> html='''<td><a href=''>a1</a></td>
... <td><a href=''>a2</a></td>
... '''
>>> from lxml import etree
>>> p=etree.HTML(html)
>>> a=p.xpath("//a[1]")
>>> for i in a:
...    print i.text
...
a1
>>> b=p.xpath("//a[2]")
>>> for i in b:
...    print i.text
...
a2

В Windows, я могу легко использовать a[1] и a[2], чтобы получить эти два значения. Но в Linux xpath //a[1] объединяет эти два текста ссылок.

Это делает программу не столь совместимой в этих ОС. Мне приходится модифицировать код на разных ОС. Это ошибка модуля lxml ? Есть какое-нибудь решение для этого ?

1 5

1 ответ:

Я могу подтвердить тот же результат на Linux, что и вы. Он возвращает список из двух элементов вместо одного элемента.

Чего требует xpath //a[1]

Он запрашивает любой элемент a, который является первым в его контексте.

Как у вас есть a элемент, встроенный в td, td есть контекст для расчета позиции и есть два вхождения такой ситуации.

Изменение xpath на "(//a)[1]" решает проблему.

Цитирование от MSDN по операторам и специальным символам

Операторы шаблона фильтра ([]) имеют более высокий приоритет, чем операторы пути (/ и //). Например, выражение / / comment () [3] выбирает все комментарии с индексом, равным 3 относительно родительского комментария в любом месте документа. Это отличается от выражения (//comment ()) [3], которое выбирает третий комментарий из множества всех комментариев относительно родителя. Первое выражение может возвращать больше чем один комментарий, в то время как последний может вернуть только один комментарий.

Понизить сломанную версию Windows lxml 3.3.5

Xpath //a[1], возвращающий только один элемент предоставленного документа, просто неверен и должен быть сообщен авторам lxml.

Состояние lxml на разных платфомах и ОС:

  • Win: lxml 2.3.0-OK
  • Win: lxml 3.3.5-BUG
  • Lin: lxml 3.3.5-OK
  • Lin: lxml 2.3.0-OK

Чтобы принять решение portable, вам потребуется lxml==2.3.0, так как эта версия ведет себя на Windows, а также на Linux правильно (возможно, есть другая версия, хорошо работающая на обеих платформах, я больше не тестировал).

Бонус-тестовый набор

Предполагая, что вы установили nose

$ pip install nose

Вы можете использовать следующие test_xpath.py:

from lxml import etree
import nose

print "=================================="
print "lxml version: ", etree.__version__
print "=================================="

def test_html():
    html_str = """
    <td><a href=''>a1</a></td>
    <td><a href=''>a2</a></td>
    """
    doc = etree.HTML(html_str.strip())
    elms = doc.xpath("//a[1]")
    assert len(elms) == 2, """xpath `//a[1]` shall return 2 elements"""
    assert all(elm.tag == "a" for elm in elms), "all returned elements shall be `a`"
    assert elms[0].text == "a1"
    assert elms[1].text == "a2"

def test_xml():
    xml_str = """
    <root>
        <td><a href=''>a1</a></td>
        <td><a href=''>a2</a></td>
    </root>
    """
    doc = etree.fromstring(xml_str.strip())
    elms = doc.xpath("//a[1]")
    assert len(elms) == 2, """xpath `//a[1]` shall return 2 elements"""
    assert all(elm.tag == "a" for elm in elms), "all returned elements shall be `a`"
    assert elms[0].text == "a1"
    assert elms[1].text == "a2"

nose.main()

И быстро выполните тест:

$ python test_xpath.py  -v
==================================
lxml version:  2.3.0
==================================
test_xpath.test_html ... ok
test_xpath.test_xml ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.002s

OK