Подтвердить что ты не робот

Почему мой запрос XPath (очистка HTML-таблиц) работает только в Firebug, но не в приложении, которое я разрабатываю?

Это предназначено для предоставления канонического Q & A всем тем похожим (но слишком конкретным вопросам, чтобы быть близким целевым кандидатом), появляющимся один или два раза в неделю.

Я разрабатываю приложение, которое должно анализировать веб-сайт со своими таблицами. Поскольку получение выражения XPath для очистки веб-страниц является скучным и подверженным ошибкам, я хотел бы использовать эту функцию XPath extractor Firebug (или аналогичные инструменты в других браузерах) для этого.

Пример ввода выглядит следующим образом:

<!-- snip -->
<table id="example">
  <tr>
    <th>Example Cell</th>
    <th>Another one</th>
  </tr>
  <tr>
    <td>foobar</td>
    <td>42</td>
  </tr>
</table>
<!-- snip -->

Я хочу извлечь первую ячейку данных ( "foobar" ). Firebug предлагает выражение XPath

//table[@id="example"]/tbody/tr[2]/td[1]

который отлично работает в любых плагинах-установщиках XPath, но не в моем собственном приложении (результаты не найдены). Если я отключу запрос до //table[@id], он снова будет работать.

Что пойдет не так?

4b9b3361

Ответ 1

Проблема: DOM Требуется <tbody/> Теги

Firebug, инструмент разработчика Chrome, функции XPath в JavaScript и другие работают с DOM, а не с базовым исходным кодом HTML.

DOM для HTML требует, чтобы все таблицы строк, не содержащиеся в заголовке таблицы нижнего колонтитула (<thead/>, <tfoot/>), были включены в теги body тела <tbody/>. Таким образом, браузеры добавляют этот тег, если он отсутствует при разборе (X) HTML. Например, Документация Microsoft DOM говорит

Элемент tbody отображается для всех таблиц, даже если таблица явно не определяет элемент tbody.

В другом ответе на stackoverflow содержится подробное объяснение .

С другой стороны, HTML не обязательно требует, чтобы тег использовался:

Начальный тег tbody всегда требуется, за исключением случаев, когда таблица содержит только одно тело таблицы, а также не имеет разделов таблицы или стопы.

Большинство процессоров XPath работают с необработанным XML

Исключая JavaScript, большинство процессоров XPath работают с необработанным XML, а не с DOM, поэтому не добавляют теги <tbody/>. Кроме того, библиотеки парсера HTML, такие как и выводить только XHTML, а не "DOM-HTML".

Это распространенная проблема, размещенная в Stackoverflow для PHP, Ruby, Python, Java, С#, Google Docs (электронных таблиц) и многих других. Селен работает внутри браузера и работает на DOM - так что это не затрагивается!

Воспроизведение проблемы

Сравните источник, показанный Firebug (или инструментами Chrome Dev) с тем, который вы получаете, щелкнув правой кнопкой мыши и выбрав "Показывать источник страницы" (или что бы он ни называл в ваших браузерах) - или используя curl http://your.example.org на командная строка. Latter, вероятно, не содержит никаких элементов <tbody/> (они редко используются), Firebug всегда будет показывать их.


Решение 1: Удалить /tbody Шаг оси

Проверьте, действительно ли таблица, на которую вы застряли, не содержит элемент <tbody/> (см. последний абзац). Если это так, у вас, вероятно, есть еще одна проблема.

Теперь удалите шаг оси /tbody, поэтому ваш запрос будет выглядеть как

//table[@id="example"]/tr[2]/td[1]

Решение 2: Пропустить <tbody/> Теги

Это довольно грязное решение и вероятность неудачи для вложенных таблиц (может переходить во внутренние таблицы). Я бы рекомендовал это в очень редких случаях.

Заменить шаг оси /tbody на шаг потомка:

//table[@id="example"]//tr[2]/td[1]

Решение 3: Разрешить ввод с и без <tbody/> Тэги

Если вы заранее не уверены в своей таблице или используете запрос как в "источнике HTML", так и в контексте DOM; и не хотите/не можете использовать хак из решения 2, предоставить альтернативный запрос (для XPath 1.0) или использовать "необязательный" шаг оси (XPath 2.0 и выше).

  • XPath 1.0:
    //table[@id="example"]/tr[2]/td[1] | //table[@id="example"]/tbody/tr[2]/td[1]
  • XPath 2.0: //table[@id="example"]/(tbody, .)/tr[2]/td[1]

Ответ 2

Натолкнулась на ту же проблему. Я почти написал рекурсивный funtion, чтобы проверять каждый тег тэга, если он существует, и пересекать его таким образом, тогда я вспомнил, что знаю регулярное выражение.:)

Перед разбором, получите html как строку. Вставьте теги <tbody> и </tbody> с регулярным выражением, затем загрузите их обратно в свой объект DOMDocument.

Йенс Эрат дает хорошее объяснение, но здесь

Решение 4. Убедитесь, что источник HTML всегда имеет теги <tbody> с регулярным выражением

JavaScript
    var html = '<html><table><tr><td>foo</td><td>bar</td></tr></table></html>';
    html.replace(/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/g,"$1<tbody>").replace(/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/g,"$1</tbody>$4");

PHP
    $html = $dom->saveHTML();
    $html = preg_replace(array('/(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/','/(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/'),array('$1<tbody>','$1</tbody>$4'),$html);
    $dom->loadHTML($html);

Просто регулярное выражение:

matches `<table>` tag with whatever else junk inside the tag and between this and the next tag if the next tag is NOT `<tbody>` also with stuff inside the tag

    /(<table([^>]+)?>([^<>]+)?)(?!<tbody([^>]+)?>)/

replace with

    $1<tbody>

the $1 referencing the captured `<table>` tag with contents.
Do the same for the closing tag like this:

    /(<(?!(\/tbody))([^>]+)?>)(<\/table([^>]+)?>)/

replace with

    $1</tbody>$4

Таким образом, dom всегда будет иметь теги <tbody>.