Я пишу общий проводник HTML, который может выполнять список операций, таких как посещение страницы, поиск таблицы, поиск строк, сохранение данных и т. Д. Он использует Goutte / Guzzle внутри и, следовательно, может использовать селекторы CSS и XPath. У меня есть интересная проблема, на которой я застрял, относительно выбора нового набора результатов относительно существующего набора результатов.
Рассмотрим этот демонстрационный HTML-код:
<h2>Burrowing</h2>
<ul>
<li>
<a href="/jobs/junior-mole">Junior Mole</a>
</li>
<li>
<a href="/jobs/head-of-badger-partnerships">Head of Badger Partnerships</a>
</li>
<li>
<a href="/jobs/trainee-worm">Trainee Worm</a>
</li>
</ul>
<h2>Tree Surgery</h2>
<ul>
<li>
<a href="/jobs/senior-woodpecker">Senior Woodpecker</a>
</li>
<li>
<a href="/jobs/owl-supervisor">Owl Supervisor</a>
</li>
</ul>
<h2>Grass maintenance</h2>
<ul>
<li>
<a href="/jobs/trainee-sheep">Trainee sheep</a>
</li>
<li>
<a href="/jobs/sheep-shearer">Sheep shearer</a>
</li>
</ul>
<h2>Aerial supervision</h2>
<ul>
<li>
<a href="/jobs/head-magpie-ops">Head of Magpie Operations</a>
</li>
</ul>
Я запускаю этот запрос CSS, чтобы получить роли в ссылках (это правильно возвращает восемь элементов):
ul li a
Для каждого из них я хотел бы получить категорию, которая в каждом случае представляет собой <h2>
, непосредственно предшествующую <ul>
. Теперь я мог сделать это с помощью абсолютного селектора CSS следующим образом:
h2
Однако это дает четыре результата, поэтому я не знаю, какая категория (h2) соответствует какой работе (ссылка). Мне нужно получить восемь результатов: три лота из первой категории, два из второй, два из третьей и один из четвертой, чтобы каждая категория соответствовала каждой роли.
Я задавался вопросом, нужен ли мне для этого родительский селектор, поэтому я переключился с CSS на XPath и сначала попробовал это, что дает каждому h2 сразу следующий элемент списка:
//h2[(following-sibling::ul)[1]/li/a]
Это обнаруживает, что h2s имеет указанную родительскую структуру, но снова возвращает четыре результата - ничего хорошего.
Следующая попытка:
//ul/li[../preceding-sibling::h2[1]]
Это дает правильное количество результатов (на основе получения элемента списка с непосредственно предшествующим заголовком), но получает текст ссылки, а не текст категории.
Я думал о выполнении цикла - я знаю, что у меня есть восемь результатов, поэтому я мог бы это сделать (X - это вводимая переменная, циклическая от 1 до 8). Это работает, но я считаю добавление ручного цикла здесь довольно неэлегантным - я стараюсь, чтобы мои правила были как можно более общими:
//li[X]/../preceding-sibling::h2[1]
Есть ли операция XPath, которая может вернуть требуемые результаты? Во избежание сомнений я ищу следующее (или просто текстовые элементы подойдут):
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Tree Surgery</h2>
<h2>Tree Surgery</h2>
<h2>Grass maintenance</h2>
<h2>Grass maintenance</h2>
<h2>Aerial supervision</h2>
CSS тоже подойдет, но я предполагаю, что это невозможно, потому что CSS не имеет родительского оператора (в любом случае Goutte просто преобразует селекторы CSS в селекторы XPath).
Поскольку я использую PHP (5.5), я считаю, что должен придерживаться XPath 1.0.
2 ответа
Нет, не существует единственного выражения XPath 1.0, которое возвращало бы то, что вам нужно. Во-первых, потому что XPath 1.0 не позволяет перебирать промежуточные результаты, а во-вторых, потому что последовательность элементов определена как набор узлов - в котором не может быть дубликатов.
Я вижу два возможных решения вашей проблемы. Либо напишите код PHP,
- сначала извлекает все соответствующие узлы
a
, например с выражением типа//a
- применяет второе выражение XPath к каждому из них по очереди:
preceding::h2[1]
Вам придется написать этот PHP-код самостоятельно, учитывая мои слабые навыки в нем. Но я могу предложить альтернативу: вы также можете использовать преобразование XSLT 1.0, есть процессоры XSLT 1.0 в PHP.
< Сильный > Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
<xsl:template match="/">
<xsl:for-each select="//a">
<xsl:copy-of select="preceding::h2[1]"/>
</xsl:for-each>
</xsl:template>
</xsl:transform>
Применительно к вашему вводу (после добавления корневого элемента) результат будет
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Burrowing</h2>
<h2>Tree Surgery</h2>
<h2>Tree Surgery</h2>
<h2>Grass maintenance</h2>
<h2>Grass maintenance</h2>
<h2>Aerial supervision</h2>
Попробуйте в Интернете здесь. Кстати, если вам интересно, как это сделать с XPath 2.0 с помощью for
, как вы упомянули в комментарии, см. вместо этой версии :
for $a in //a return $a/preceding::h2[1]
Поэтому я не уверен, как вы пытаетесь это использовать, но я бы попробовал что-то вроде:
$links = $cralwer->filter('ul li a');
foreach ($links as $link) {
// do stuff with the link
// ...
// get the H2
$header = $link->parents()->filter('ul[../preceding-sibling::h2]');
// do stuff with the header
}
Нет, не существует единственного выражения XPath 1.0, которое возвращало бы то, что вам нужно. Во-первых, потому что XPath 1.0 не позволяет перебирать промежуточные результаты, а во-вторых, потому что последовательность элементов определена как набор узлов - в котором не может быть дубликатов.
Конечно, вы также можете использовать Symfony\Component\DomCrawler::each
и делать это внутри замыкания вместо выполнения foreach ...
Похожие вопросы
Новые вопросы
php
PHP — это открытый, мультипарадигмальный, динамически типизированный и интерпретируемый язык сценариев, изначально разработанный для веб-разработки на стороне сервера. Используйте этот тег для вопросов о программировании на языке PHP.