Я пытаюсь поцарапать сайт используя метод запроса DOMXPath. Я успешно удалил с этой страницы 20 URL-адресов каждого якоря новостей.
$url = "http://www.sandiego6.com/about-us/meet-our-team";
$xPath = "//p[@class='bio']/a/@href";
$html = new DOMDocument();
@$html->loadHtmlFile($url);
$xpath = new DOMXPath( $html );
$nodelist = $xpath->query($xPath);
$profileurl = array();
foreach ($nodelist as $n){
$value = $n->nodeValue;
$profileurl[] = $value;
}
Я использовал полученный массив в качестве URL-адреса для очистки данных с каждой из био-страниц News Anchor.
$imgurl = array();
for($z=0;$z<$elementCount;$z++){
$html = new DOMDocument();
@$html->loadHtmlFile($profileurl[$z]);
$xpath = new DOMXPath($html);
$nodelist = $xpath->query("//img[@class='photo fn']/@src");
foreach($nodelist as $n){
$value = $n->nodeValue;
$imgurl[] = $value;
}
}
На каждой странице профиля News Anchor есть 6 xPath, которые нужно очистить (один из них — массив $ imgurl). Затем я отправляю эти очищенные данные в MySQL.
Пока все отлично работает — Кроме когда я пытаюсь получить URL Twitter из каждого профиля, потому что этот элемент не найден на каждой странице профиля привязки новостей. В результате MySQL получает 5 столбцов с 20 полными строками и 1 столбец (твиттерл) с 18 строками данных. Эти 18 строк неправильно выровнены с другими данными, потому что если xPath не существует, он, похоже, пропускается.
Как мне объяснить отсутствие xPaths? В поисках ответа я нашел чье-то утверждение, которое гласило: «nodeValue никогда не может быть нулевым, потому что без значения узел не будет существовать». Учитывая это, если нет nodeValue, как я могу программно распознать, когда эти xPath не существуют, и заполнить эту итерацию каким-либо другим значением по умолчанию, прежде чем он перейдет к следующей итерации?
Вот запрос для URL-адресов Twitter:
$twitterurl = array();
for($z=0;$z<$elementCount;$z++){
$html = new DOMDocument();
@$html->loadHtmlFile($profileurl[$z]);
$xpath = new DOMXPath($html);
$nodelist = $xpath->query("//*[@id='bio']/div[2]/p[3]/a/@href");
foreach($nodelist as $n){
$value = $n->nodeValue;
$twitterurl[] = $value;
}
}
Так как узел Twitter появляется ноль или один раз, измените foreach на
$twitterurl [] = $nodelist->length ? $nodelist->item(0)->nodeValue : NULL;
Это будет держать содержимое в синхронизации. Однако вам придется принять меры для обработки значений NULL в запросе, который вы используете для вставки их в базу данных.
Я думаю, что у вас есть несколько проблем с обработкой данных, и вы постараетесь изложить их в моем ответе в надежде, что он всегда прояснит ваш главный вопрос:
Я нашел чье-то утверждение, которое гласило: «nodeValue никогда не может быть нулевым, потому что без значения узел не будет существовать». Учитывая это, если нет nodeValue, как я могу программно распознать, когда эти xPath не существуют, и заполнить эту итерацию каким-либо другим значением по умолчанию, прежде чем он перейдет к следующей итерации?
Прежде всего, сбор URL-адресов каждой страницы профиля (детализации) — хорошая идея. Вы даже можете извлечь из этого больше пользы, поместив это в общий контекст вашей работы по очистке:
* profile pages
`- profile page
+- name
+- role
+- img
+- email
+- facebook
`- twitter
Это структура, которая у вас есть с данными, которые вы хотели бы получить. Вам уже удалось получить URL всех страниц профиля:
$url = "http://www.sandiego6.com/about-us/meet-our-team";
$xPath = "//p[@class='bio']/a/@href";
$html = new DOMDocument();
@$html->loadHtmlFile($url);
$xpath = new DOMXPath($html);
$nodelist = $xpath->query($xPath);
$profileurl = array();
foreach ($nodelist as $n) {
$value = $n->nodeValue;
$profileurl[] = $value;
}
Поскольку вы знаете, что следующие шаги будут состоять в том, чтобы загрузить и запросить более 20 страниц профиля, одно из самых первых действий, которые вы можете сделать, — это извлечь часть кода, которая создает DOMXPath из URL в свою функцию. Это также позволит вам легче обрабатывать ошибки:
/**
* @param string $url
*
* @throws RuntimeException
* @return DOMXPath
*/
function xpath_from_url($url)
{
$html = new DOMDocument();
$saved = libxml_use_internal_errors(true);
$result = $html->loadHtmlFile($url);
libxml_use_internal_errors($saved);
if (!$result) {
throw new RuntimeException(sprintf('Failed to load HTML from "%s"', $url));
}
$xpath = new DOMXPath($html);
return $xpath;
}
Это превращает основную обработку в более сжатую форму, чем только путем извлечения (перемещения) кода в xpath_from_url
функция:
$xpath = xpath_from_url($url);
$nodelist = $xpath->query($xPath);
$profileurl = array();
foreach ($nodelist as $n) {
$value = $n->nodeValue;
$profileurl[] = $value;
}
Но это также позволяет вам внести еще одно изменение в код: теперь вы можете обрабатывать URL-адреса непосредственно в структуре вашей основной процедуры извлечения:
$url = "http://www.sandiego6.com/about-us/meet-our-team";
$xpath = xpath_from_url($url);
$profileUrls = $xpath->query("//p[@class='bio']/a/@href");
foreach ($profileUrls as $profileUrl) {
$profile = xpath_from_url($profileUrl->nodeValue);
// ... extract the six (inkl. optional) values from a profile
}
Как видите, этот код пропускает создание массива URL-адресов профиля, поскольку коллекция всех URL-адресов профиля уже передана первой операцией xpath.
Теперь отсутствует часть, позволяющая извлечь до шести полей со страницы сведений. С этим новым способом итерации по URL-адресам профиля довольно легко управлять — просто создайте одно выражение xpath для каждого поля и извлекайте данные. Если вы используете DOMXPath::evaluate
вместо DOMXPath::query
тогда вы можете получить строковые значения напрямую. Строковое значение несуществующего узла, является пустой строкой. Это на самом деле не тестирование, если узел существует или нет, если вам нужно NULL
вместо «» (пустая строка) это нужно делать по-другому (я тоже могу это показать, но сейчас дело не в этом). В следующем примере извлекаются имя и роль якоря:
foreach ($profileUrls as $i => $profileUrl) {
$profile = xpath_from_url($profileUrl->nodeValue);
printf(
"#%02d: %s (%s)\n", $i + 1,
$profile->evaluate('normalize-space(//h1[@class="entry-title"])'),
$profile->evaluate('normalize-space(//h2[@class="fn"])')
);
// ... extract the other four (inkl. optional) values from a profile
}
Я выбираю непосредственный вывод значений (и не заботюсь о добавлении их в массив или аналогичную структуру), чтобы было легко следить за тем, что происходит:
#01: Marc Bailey (Morning Anchor)
#02: Heather Myers (Morning Anchor)
#03: Jim Patton (10pm Anchor)
#04: Neda Iranpour (10 p.m. Anchor / Reporter)
...
Получение информации об электронной почте, Facebook и Twitter работает одинаково:
foreach ($profileUrls as $i => $profileUrl) {
$profile = xpath_from_url($profileUrl->nodeValue);
printf(
"#%02d: %s (%s)\n", $i + 1,
$profile->evaluate('normalize-space(//h1[@class="entry-title"])'),
$profile->evaluate('normalize-space(//h2[@class="fn"])')
);
printf(
" email...: %s\n",
$profile->evaluate('substring-after(//*[@class="bio-email"]/a/@href, ":")')
);
printf(
" facebook: %s\n",
$profile->evaluate('string(//*[@class="bio-facebook url"]/a/@href)')
);
printf(
" twitter.: %s\n",
$profile->evaluate('string(//*[@class="bio-twitter url"]/a/@href)')
);
}
Теперь он уже выводит данные по мере необходимости (я пропустил изображения, потому что они не могут быть хорошо отображены в текстовом режиме:
#01: Marc Bailey (Morning Anchor)
email...: [email protected]
facebook: https://www.facebook.com/marc.baileySD6
twitter.: http://www.twitter.com/MarcBaileySD6
#02: Heather Myers (Morning Anchor)
email...: [email protected]
facebook: https://www.facebook.com/heather.myersSD6
twitter.: http://www.twitter.com/HeatherMyersSD6
#03: Jim Patton (10pm Anchor)
email...: [email protected]
facebook: https://www.facebook.com/Jim.PattonSD6
twitter.: http://www.twitter.com/JimPattonSD6
#04: Neda Iranpour (10 p.m. Anchor / Reporter)
email...: [email protected]
facebook: https://www.facebook.com/lightenupwithneda
twitter.: http://www.twitter.com/@LightenUpWNeda
...
Так что теперь эти маленькие строки кода с одним foreach
Циклы уже достаточно хорошо представляют исходную структуру, обозначенную:
* profile pages
`- profile page
+- name
+- role
+- img
+- email
+- facebook
`- twitter
Все, что вам нужно сделать, это просто следовать этой общей структуре того, как данные доступны в вашем коде. Затем в конце, когда вы видите, что все данные могут быть получены по желанию, вы выполняете операцию сохранения в базе данных: одна вставка на профиль. это одна строка на профиль. вам не нужно хранить все данные, вы можете просто вставить (возможно, с некоторой проверкой, если они уже существуют) данные для каждой строки.
Надеюсь, это поможет.
Приложение: Код полностью
<?php
/**
* Scraping detail pages based on index page
*/
/**
* @param string $url
*
* @throws RuntimeException
* @return DOMXPath
*/
function xpath_from_url($url)
{
$html = new DOMDocument();
$saved = libxml_use_internal_errors(true);
$result = $html->loadHtmlFile($url);
libxml_use_internal_errors($saved);
if (!$result) {
throw new RuntimeException(sprintf('Failed to load HTML from "%s"', $url));
}
$xpath = new DOMXPath($html);
return $xpath;
}
$url = "http://www.sandiego6.com/about-us/meet-our-team";
$xpath = xpath_from_url($url);
$profileUrls = $xpath->query("//p[@class='bio']/a/@href");
foreach ($profileUrls as $i => $profileUrl) {
$profile = xpath_from_url($profileUrl->nodeValue);
printf(
"#%02d: %s (%s)\n", $i + 1, $profile->evaluate('normalize-space(//h1[@class="entry-title"])'),
$profile->evaluate('normalize-space(//h2[@class="fn"])')
);
printf(" email...: %s\n", $profile->evaluate('substring-after(//*[@class="bio-email"]/a/@href, ":")'));
printf(" facebook: %s\n", $profile->evaluate('string(//*[@class="bio-facebook url"]/a/@href)'));
printf(" twitter.: %s\n", $profile->evaluate('string(//*[@class="bio-twitter url"]/a/@href)'));
}