Слишком длинный xpath с запросом / оценкой DOMXpath ничего не возвращает - PullRequest
0 голосов
/ 04 ноября 2010

Я использую PHP для извлечения контента по заданному URL и XPATH.Я использую DOMDocument / DOMXPath (с запросом или оценкой).

Для небольшого xpath я получаю правильный результат, но для более длинного xpath он не работает.(И этот xpath кажется хорошим (я получил их с помощью Xpather (плагин firefox) и перепроверил их с YQL).

Есть ли у вас какие-либо советы по этой любопытной проблеме?кода:

$doc = new DOMDocument();
$myXMLString = file_get_contents('http://stackoverflow.com/questions/4097230/too-long-xpath-with-domxpath-query-evaluate-return-nothing');
@$doc->loadHTML($myXMLString); //@ to suppress warnings 
                               //(good for not ending markup)
$xpath = new DOMXPath($doc);

$fullPath ="/html/body/small/path"; //it works
//$fullPath = "/html/body/full/path/with/lot/of/markup";//does not works
$entries = $xpath->query($fullPath);
//or ->evalutate($fullPath) (same behaviour)
//$entries return DOMNodeList (empty for a long path query, 
//                             correct for a small path query)

Я тестирую с ограничением атрибута, но, похоже, он не меняется (с небольшим xpath работает, с более длинным не работает больше)

Пример: для этого токастраница:

$fullPath = "/html
              /body
               /div[4]
                /div[@id='content']
                 /div[@id='question-header']
                  /h1
                   /a";//works (retrieve the question title)
$fullPath = "/html
              /body
               /div[4]
                /div[@id='content']
                 /div[@id='mainbar']
                  /div[@id='question']
                   /table
                    /tbody
                     /tr[2]
                      /td[2]
                       /div[@id='comments-4097230']
                        /table
                         /tbody
                          /tr[@id='comment-4408626']
                           /td[2]
                            /div
                             /a"; //does'nt work 
                                  //(should retrieve 'gaby' from comment)

Редактировать:

Я тестирую с помощью SimpleXML lib, и у меня точно такое же поведение (хороший результат для небольшого запроса, ничего для длинного запроса).


Редактировать 2:

Я также сократил самый длинный xpath, удалив первый элемент, и он работает. Кстати, я действительно не понимаю, почему полный правильный xpath не работает.

1 Ответ

3 голосов
/ 04 ноября 2010

Давайте пройдем этот шаг за шагом:

Шаг 1: репликация ошибки.

Убедившись, что XPath действительно не вернет результат, я написалнебольшой скрипт, чтобы увидеть, насколько глубоко XPath зайдет до того, как сломается

foreach (explode('/', $fullPath) as $segment) {
    $xpath .= trim($segment);
    echo '-------------------------------------------', PHP_EOL,
         'Trying: ', $xpath, PHP_EOL,
         '-------------------------------------------', PHP_EOL;
    echo $xp->evaluate("string($xpath)"), PHP_EOL;
    $xpath .= '/';
}

Последнее, что он вернет, это результат

/html/body/div[4]/div[@id='content']/div[@id='mainbar']/div[@id='question']/table

Шаг 2: проверка разметки

Итак, я проверил разметку, возвращаемую DOMDocument::saveHTML(), чтобы посмотреть, как она выглядит, и не было <tbody> (переформатировано для удобства чтения) :

<div id="question">
    <div class="everyonelovesstackoverflow" id="adzerk1"></div>
        <table>
            <tr><td class="votecell">

Затем я проверил эту страницу, чтобы узнать, выбрасывает ли она DOM или ее действительно не существует.Там не былоПо-видимому, Firebug вставляет его, что объясняет, почему вы получили результат с XPather (но не почему вы получили его с YQL):

Screenshot showing page source and apparently bugged Firebug view

Шаг 3: проверка корректности изаключение

Я удалил <tbody> из XPath и перезапустил скрипт.Нет проблем.Возвращает "Габи".

Хотя я сначала заподозрил ошибку в Firebug, Алехандро заметил, что это может произойти и в DeveloperTools IE.Затем я подозревал, что это будет добавлено JavaScript, но не смог проверить это.После еще одного исследования Алехандро указал мне на Почему Firebug добавляет <tbody> к <table>? - это на самом деле ни Firebug, ни JavaScript, но сами браузеры.

Так что, чтобы изменить мойвывод:

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


Некоторые дополнительные sidenotes

Если вам не нужно изменять разметку перед подачейэто для DOM, вам не нужно использовать file_get_contents() для загрузки содержимого.Вы можете использовать DOM loadHTMLFile():

$dom->loadHTMLFile('http://www.example.com/foo.htm');

Кроме того, правильный способ подавления ошибок - указать libxml использовать его внутренний обработчик ошибок.Но вместо обработки ошибок вы просто очищаете их.Это повлияет только на ошибки, относящиеся к libxml, например, ошибки синтаксического анализа (в отличие от всех ошибок PHP):

libxml_use_internal_errors(TRUE);
libxml_clear_errors();

Наконец, запросы xPath могут выполняться в отношении узла контекста.Таким образом, хотя длинный XPath эффективен с точки зрения времени поиска, вы можете просто использовать getElementById(), чтобы получить самый глубокий из известных узлов, а затем использовать XPath против него.

Другими словами:

libxml_use_internal_errors(TRUE);
$dom = new DOMDocument;
$dom->loadHTMLFile('http://www.example.com/foo.htm');
libxml_clear_errors();
echo $xp->evaluate(
    'string(td[2]/div/a)', 
    $dom->getElementById('comment-4408626'));

также вернет "Gaby".

...