Как сделать простой искатель на PHP?
У меня есть веб-страница с кучей ссылок. Я хочу написать скрипт, который будет сбрасывать все данные, содержащиеся в этих ссылках, в локальный файл.
кто-нибудь делал это с PHP? Общие рекомендации и готы были бы достаточны в качестве ответа.
15 ответов:
Meh. Не надо парсить HTML с помощью регулярных выражений.
вот версия DOM, вдохновленная тату:
<?php function crawl_page($url, $depth = 5) { static $seen = array(); if (isset($seen[$url]) || $depth === 0) { return; } $seen[$url] = true; $dom = new DOMDocument('1.0'); @$dom->loadHTMLFile($url); $anchors = $dom->getElementsByTagName('a'); foreach ($anchors as $element) { $href = $element->getAttribute('href'); if (0 !== strpos($href, 'http')) { $path = '/' . ltrim($href, '/'); if (extension_loaded('http')) { $href = http_build_url($url, array('path' => $path)); } else { $parts = parse_url($url); $href = $parts['scheme'] . '://'; if (isset($parts['user']) && isset($parts['pass'])) { $href .= $parts['user'] . ':' . $parts['pass'] . '@'; } $href .= $parts['host']; if (isset($parts['port'])) { $href .= ':' . $parts['port']; } $href .= dirname($parts['path'], 1).$path; } } crawl_page($href, $depth - 1); } echo "URL:",$url,PHP_EOL,"CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL; } crawl_page("http://hobodave.com", 2);
Edit: я исправил некоторые ошибки из версии Tatu (теперь работает с относительными URL-адресами).
Edit: я добавил новый бит функциональности, который не позволяет ему дважды следовать одному и тому же URL.
Edit: Эхо выход на STDOUT теперь, так что вы можете перенаправить его на любой файл вы хочу!--4-->
Edit: Исправлена ошибка, указанная Джорджем в его ответе. Относительные URL-адреса больше не будут добавляться в конец пути url-адреса, а перезаписывать его. Спасибо Джорджу за это. Обратите внимание, что ответ Георгия не несет ответственности за любые: протокол HTTPS, пользователь, пройдите или порт. Если у вас есть http PECL расширение загружается это довольно просто сделать с помощью http_build_url. В противном случае, я должен вручную склеить с помощью parse_url. Спасибо снова Джордж.
здесь моя реализация основана на приведенном выше примере/ответе.
- это класс
- использует Curl
- поддержка HTTP авторизации
- пропустить Url, не принадлежащий к базовому домену
- возвращает код ответа заголовка Http для каждой страницы
- время возврата для каждой страницы
КЛАСС ОБХОДА:
class crawler { protected $_url; protected $_depth; protected $_host; protected $_useHttpAuth = false; protected $_user; protected $_pass; protected $_seen = array(); protected $_filter = array(); public function __construct($url, $depth = 5) { $this->_url = $url; $this->_depth = $depth; $parse = parse_url($url); $this->_host = $parse['host']; } protected function _processAnchors($content, $url, $depth) { $dom = new DOMDocument('1.0'); @$dom->loadHTML($content); $anchors = $dom->getElementsByTagName('a'); foreach ($anchors as $element) { $href = $element->getAttribute('href'); if (0 !== strpos($href, 'http')) { $path = '/' . ltrim($href, '/'); if (extension_loaded('http')) { $href = http_build_url($url, array('path' => $path)); } else { $parts = parse_url($url); $href = $parts['scheme'] . '://'; if (isset($parts['user']) && isset($parts['pass'])) { $href .= $parts['user'] . ':' . $parts['pass'] . '@'; } $href .= $parts['host']; if (isset($parts['port'])) { $href .= ':' . $parts['port']; } $href .= $path; } } // Crawl only link that belongs to the start domain $this->crawl_page($href, $depth - 1); } } protected function _getContent($url) { $handle = curl_init($url); if ($this->_useHttpAuth) { curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); curl_setopt($handle, CURLOPT_USERPWD, $this->_user . ":" . $this->_pass); } // follows 302 redirect, creates problem wiht authentication // curl_setopt($handle, CURLOPT_FOLLOWLOCATION, TRUE); // return the content curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE); /* Get the HTML or whatever is linked in $url. */ $response = curl_exec($handle); // response total time $time = curl_getinfo($handle, CURLINFO_TOTAL_TIME); /* Check for 404 (file not found). */ $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE); curl_close($handle); return array($response, $httpCode, $time); } protected function _printResult($url, $depth, $httpcode, $time) { ob_end_flush(); $currentDepth = $this->_depth - $depth; $count = count($this->_seen); echo "N::$count,CODE::$httpcode,TIME::$time,DEPTH::$currentDepth URL::$url <br>"; ob_start(); flush(); } protected function isValid($url, $depth) { if (strpos($url, $this->_host) === false || $depth === 0 || isset($this->_seen[$url]) ) { return false; } foreach ($this->_filter as $excludePath) { if (strpos($url, $excludePath) !== false) { return false; } } return true; } public function crawl_page($url, $depth) { if (!$this->isValid($url, $depth)) { return; } // add to the seen URL $this->_seen[$url] = true; // get Content and Return Code list($content, $httpcode, $time) = $this->_getContent($url); // print Result for current Page $this->_printResult($url, $depth, $httpcode, $time); // process subPages $this->_processAnchors($content, $url, $depth); } public function setHttpAuth($user, $pass) { $this->_useHttpAuth = true; $this->_user = $user; $this->_pass = $pass; } public function addFilterPath($path) { $this->_filter[] = $path; } public function run() { $this->crawl_page($this->_url, $this->_depth); } }
использование:
// USAGE $startURL = 'http://YOUR_URL/'; $depth = 6; $username = 'YOURUSER'; $password = 'YOURPASS'; $crawler = new crawler($startURL, $depth); $crawler->setHttpAuth($username, $password); // Exclude path with the following structure to be processed $crawler->addFilterPath('customer/account/login/referer'); $crawler->run();
проверить PHP на гусеничном ходу
http://sourceforge.net/projects/php-crawler/
посмотрим, поможет ли это.
в самом простом виде:
function crawl_page($url, $depth = 5) { if($depth > 0) { $html = file_get_contents($url); preg_match_all('~<a.*?href="(.*?)".*?>~', $html, $matches); foreach($matches[1] as $newurl) { crawl_page($newurl, $depth - 1); } file_put_contents('results.txt', $newurl."\n\n".$html."\n\n", FILE_APPEND); } } crawl_page('http://www.domain.com/index.php', 5);
эта функция будет получать содержимое со страницы, а затем сканировать все найденные ссылки и сохранять содержимое в 'результаты.txt'. Функции принимают второй параметр, глубина, который определяет, как долго должны следовать ссылки. Пройти 1 Если вы хотите парсить только ссылки с данной страницы.
С небольшими изменениями в hobodave это код, вот codesnippet вы можете использовать для обхода страниц. Для этого необходимо включить расширение curl на вашем сервере.
<?php //set_time_limit (0); function crawl_page($url, $depth = 5){ $seen = array(); if(($depth == 0) or (in_array($url, $seen))){ return; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); $result = curl_exec ($ch); curl_close ($ch); if( $result ){ $stripped_file = strip_tags($result, "<a>"); preg_match_all("/<a[\s]+[^>]*?href[\s]?=[\s\"\']+"."(.*?)[\"\']+.*?>"."([^<]+|.*?)?<\/a>/", $stripped_file, $matches, PREG_SET_ORDER ); foreach($matches as $match){ $href = $match[1]; if (0 !== strpos($href, 'http')) { $path = '/' . ltrim($href, '/'); if (extension_loaded('http')) { $href = http_build_url($href , array('path' => $path)); } else { $parts = parse_url($href); $href = $parts['scheme'] . '://'; if (isset($parts['user']) && isset($parts['pass'])) { $href .= $parts['user'] . ':' . $parts['pass'] . '@'; } $href .= $parts['host']; if (isset($parts['port'])) { $href .= ':' . $parts['port']; } $href .= $path; } } crawl_page($href, $depth - 1); } } echo "Crawled {$href}"; } crawl_page("http://www.sitename.com/",3); ?>
Я объяснил этот учебник в этом гусеничный скрипт учебник
зачем использовать PHP для этого, когда вы можете использовать wget, например,
wget -r -l 1 http://www.example.com
Как разобрать содержимое, см. лучшие методы для разбора HTML и использовать функцию поиска для примеры. Как разобрать HTML был дан ответ несколько раз раньше.
Hobodave вы были очень близки. Единственное, что я изменил, находится в операторе if, который проверяет, начинается ли атрибут href найденного тега привязки с 'http'. Вместо того, чтобы просто добавить переменную $url, которая будет содержать страницу, которая была передана, вы должны сначала удалить ее до хоста, что можно сделать с помощью функции PHP parse_url.
<?php function crawl_page($url, $depth = 5) { static $seen = array(); if (isset($seen[$url]) || $depth === 0) { return; } $seen[$url] = true; $dom = new DOMDocument('1.0'); @$dom->loadHTMLFile($url); $anchors = $dom->getElementsByTagName('a'); foreach ($anchors as $element) { $href = $element->getAttribute('href'); if (0 !== strpos($href, 'http')) { /* this is where I changed hobodave's code */ $host = "http://".parse_url($url,PHP_URL_HOST); $href = $host. '/' . ltrim($href, '/'); } crawl_page($href, $depth - 1); } echo "New Page:<br /> "; echo "URL:",$url,PHP_EOL,"<br />","CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL," <br /><br />"; } crawl_page("http://hobodave.com/", 5); ?>
Как уже упоминалось, есть гусеничные фреймворки, готовые к настройке, но если то, что вы делаете, так же просто, как вы упомянули, Вы можете сделать это с нуля довольно легко.
выскабливание ссылок:http://www.phpro.org/examples/Get-Links-With-DOM.html
сброс результатов в файл:http://www.tizag.com/phpT/filewrite.php
вопрос в том, как получить исходный код ajax-вызовов? это не сканируется, например, как сканировать изображения по такой ссылке? http://www.tiendeo.nl/Catalogi/amsterdam/16558&subori=web_sliders&buscar=Boni&sw=1366
вы можете попробовать это это может помочь вам
$search_string = 'american golf News: Fowler beats stellar field in Abu Dhabi'; $html = file_get_contents(url of the site); $dom = new DOMDocument; $titalDom = new DOMDocument; $tmpTitalDom = new DOMDocument; libxml_use_internal_errors(true); @$dom->loadHTML($html); libxml_use_internal_errors(false); $xpath = new DOMXPath($dom); $videos = $xpath->query('//div[@class="primary-content"]'); foreach ($videos as $key => $video) { $newdomaindom = new DOMDocument; $newnode = $newdomaindom->importNode($video, true); $newdomaindom->appendChild($newnode); @$titalDom->loadHTML($newdomaindom->saveHTML()); $xpath1 = new DOMXPath($titalDom); $titles = $xpath1->query('//div[@class="listingcontainer"]/div[@class="list"]'); if(strcmp(preg_replace('!\s+!',' ', $titles->item(0)->nodeValue),$search_string)){ $tmpNode = $tmpTitalDom->importNode($video, true); $tmpTitalDom->appendChild($tmpNode); break; } } echo $tmpTitalDom->saveHTML();
спасибо @hobodave.
однако я нашел две слабые стороны в вашем коде. Ваш синтаксический анализ исходного url-адреса для получения сегмента "хост" останавливается на первом слэше. Это предполагает, что все относительные ссылки начинаются в корневом каталоге. Это только иногда.
original url : http://example.com/game/index.html href in <a> tag: highscore.html author's intent: http://example.com/game/highscore.html <-200-> crawler result : http://example.com/highscore.html <-404->
исправьте это, сломав на последнем слэше не первый
второй неродственного ошибка, заключается в том, что
$depth
на самом деле не отслеживает глубину рекурсии, он отслеживает широта первого уровня рекурсии.если бы я считал, что эта страница активно используется, я мог бы отладить эту вторую проблему, но я подозреваю, что текст, который я пишу сейчас, никогда не будет прочитан кем-либо, человеком или роботом, поскольку этой проблеме шесть лет, и у меня даже нет достаточной репутации, чтобы уведомить +hobodave непосредственно об этих дефектах, комментируя его код. В любом случае спасибо hobodave.
я использовал код @hobodave, с этой маленькой настройкой, чтобы предотвратить повторный обход всех вариантов фрагментов одного и того же URL:
<?php function crawl_page($url, $depth = 5) { $parts = parse_url($url); if(array_key_exists('fragment', $parts)){ unset($parts['fragment']); $url = http_build_url($parts); } static $seen = array(); ...
тогда вы также можете опустить
$parts = parse_url($url);
строка внутри цикла for.
Я придумал следующий код паука. Я адаптировал его немного из следующего: PHP - есть ли безопасный способ выполнить глубокую рекурсию? это кажется довольно быстрым....
<?php function spider( $base_url , $search_urls=array() ) { $queue[] = $base_url; $done = array(); $found_urls = array(); while($queue) { $link = array_shift($queue); if(!is_array($link)) { $done[] = $link; foreach( $search_urls as $s) { if (strstr( $link , $s )) { $found_urls[] = $link; } } if( empty($search_urls)) { $found_urls[] = $link; } if(!empty($link )) { echo 'LINK:::'.$link; $content = file_get_contents( $link ); //echo 'P:::'.$content; preg_match_all('~<a.*?href="(.*?)".*?>~', $content, $sublink); if (!in_array($sublink , $done) && !in_array($sublink , $queue) ) { $queue[] = $sublink; } } } else { $result=array(); $return = array(); // flatten multi dimensional array of URLs to one dimensional. while(count($link)) { $value = array_shift($link); if(is_array($value)) foreach($value as $sub) $link[] = $sub; else $return[] = $value; } // now loop over one dimensional array. foreach($return as $link) { // echo 'L::'.$link; // url may be in form <a href.. so extract what's in the href bit. preg_match_all('/<a[^>]+href=([\'"])(?<href>.+?)[^>]*>/i', $link, $result); if ( isset( $result['href'][0] )) { $link = $result['href'][0]; } // add the new URL to the queue. if( (!strstr( $link , "http")) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue)) ) { $queue[]=$base_url.$link; } else { if ( (strstr( $link , $base_url )) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue)) ) { $queue[] = $link; } } } } } return $found_urls; } $base_url = 'https://www.houseofcheese.co.uk/'; $search_urls = array( $base_url.'acatalog/' ); $done = spider( $base_url , $search_urls ); // // RESULT // // echo '<br /><br />'; echo 'RESULT:::'; foreach( $done as $r ) { echo 'URL:::'.$r.'<br />'; }
стоит помнить, что при обходе внешних ссылок (я ценю, что OP относится к собственной странице пользователей) вы должны знать о роботах.формат txt. Я нашел следующее, что, надеюсь, поможет http://www.the-art-of-web.com/php/parse-robots/.
Я создал небольшой класс, чтобы захватить данные из предоставленного url, а затем извлечь html-элементы по вашему выбору. Класс использует CURL и DOMDocument.
php класс:
class crawler { public static $timeout = 2; public static $agent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'; public static function http_request($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERAGENT, self::$agent); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$timeout); curl_setopt($ch, CURLOPT_TIMEOUT, self::$timeout); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); return $response; } public static function strip_whitespace($data) { $data = preg_replace('/\s+/', ' ', $data); return trim($data); } public static function extract_elements($tag, $data) { $response = array(); $dom = new DOMDocument; @$dom->loadHTML($data); foreach ( $dom->getElementsByTagName($tag) as $index => $element ) { $response[$index]['text'] = self::strip_whitespace($element->nodeValue); foreach ( $element->attributes as $attribute ) { $response[$index]['attributes'][strtolower($attribute->nodeName)] = self::strip_whitespace($attribute->nodeValue); } } return $response; } }
пример использования:
$data = crawler::http_request('https://stackoverflow.com/questions/2313107/how-do-i-make-a-simple-crawler-in-php'); $links = crawler::extract_elements('a', $data); if ( count($links) > 0 ) { file_put_contents('links.json', json_encode($links, JSON_PRETTY_PRINT)); }
пример ответа:
[ { "text": "Stack Overflow", "attributes": { "href": "https:\/\/stackoverflow.com", "class": "-logo js-gps-track", "data-gps-track": "top_nav.click({is_current:false, location:2, destination:8})" } }, { "text": "Questions", "attributes": { "id": "nav-questions", "href": "\/questions", "class": "-link js-gps-track", "data-gps-track": "top_nav.click({is_current:true, location:2, destination:1})" } }, { "text": "Developer Jobs", "attributes": { "id": "nav-jobs", "href": "\/jobs?med=site-ui&ref=jobs-tab", "class": "-link js-gps-track", "data-gps-track": "top_nav.click({is_current:false, location:2, destination:6})" } } ]