У меня есть веб-страница с кучей ссылок. Я хочу написать сценарий, который сбрасывал бы все данные, содержащиеся в этих ссылках, в локальный файл.
Кто-нибудь делал это с PHP? В качестве ответа достаточно общих рекомендаций и ошибок.
У меня есть веб-страница с кучей ссылок. Я хочу написать сценарий, который сбрасывал бы все данные, содержащиеся в этих ссылках, в локальный файл.
Кто-нибудь делал это с PHP? В качестве ответа достаточно общих рекомендаций и ошибок.
Мех. Не анализируйте 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);
Изменить: я исправил некоторые ошибки в версии Тату (теперь работает с относительными URL-адресами).
Изменить: я добавил новую функциональность, которая не позволяет ему дважды переходить по одному и тому же URL-адресу.
Изменить: теперь вывод выводится в STDOUT, чтобы вы могли перенаправить его в любой файл, который хотите.
Изменить: исправлена ошибка, указанная Джорджем в его ответе. Относительные URL-адреса больше не будут добавляться в конец пути URL-адреса, а будут перезаписывать его. Спасибо Джорджу за это. Обратите внимание, что ответ Джорджа не учитывает ни https, ни пользователя, ни пароль, ни порт. Если у вас загружено расширение http://us3.php.net/manual/en/book.http.php PECL, это довольно просто сделать с помощью http://us3.php.net/manual/en/function.http-build-url.php. В противном случае мне придется вручную склеивать с помощью parse_url. Еще раз спасибо, Джордж.
Вот моя реализация на основе приведенного выше примера / ответа.
КЛАСС CRAWL:
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 Crawler
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);
Эта функция получит содержимое страницы, затем просканирует все найденные ссылки и сохранит содержимое в «results.txt». Функции принимают второй параметр, глубину, который определяет, как долго следует переходить по ссылкам. Передайте 1, если хотите анализировать только ссылки с данной страницы.
Зачем использовать PHP для этого, если вы можете использовать wget, например
wget -r -l 1 http://www.example.com
Чтобы узнать, как проанализировать содержимое, см. Лучшие методы для синтаксического анализа HTML и используйте функцию поиска для примеров. О том, как разбирать HTML, уже несколько раз рассказывали.
С некоторыми небольшими изменениями в коде hobodave, вот код-сниппет, который можно использовать для сканирования страниц. Для этого необходимо, чтобы на вашем сервере было включено расширение 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);
?>
Я объяснил это руководство в этом руководстве по скрипту сканера
Хободаве, ты был очень близок. Единственное, что я изменил, - это оператор 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 < / а>
Я использовал код @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.
Вы можете попробовать это, это может вам помочь
$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 напрямую об этих дефектах, добавив комментарий к его коду. Все равно спасибо хободаве.
Я придумал следующий код паука. Я немного адаптировал его из следующего: 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>.+?)\1[^>]*>/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 относится к собственной странице пользователя), вы должны знать о файле robots.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})"
}
}
]
Это старый вопрос. С тех пор произошло много хорошего. Вот мои два цента по этой теме:
Чтобы точно отслеживать посещаемые страницы, вам необходимо сначала нормализовать URI. Алгоритм нормализации включает несколько шагов:
GET http://www.example.com/query?id=111&cat=222
GET http://www.example.com/query?cat=222&id=111
Преобразуйте пустой путь. Пример: http://example.org → http://example.org/
Используйте процентное кодирование с заглавной буквы. Все буквы в тройке процентного кодирования (например, "% 3A") нечувствительны к регистру. Пример: http://example.org/a%c2%B1b → http://example.org/a%C2%B1b
Удалите ненужные точечные сегменты. Пример: http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
Возможно какие-то другие правила нормализации
Атрибут href
имеет не только тег <a>
, но и тег <area>
https://html.com/tags/area/. Если вы не хотите ничего пропустить, вам также нужно очистить тег <area>
.
Отслеживайте прогресс сканирования. Если сайт небольшой, это не проблема. И наоборот, это может быть очень неприятно, если вы просканируете половину сайта, и это не удастся. Рассмотрите возможность использования базы данных или файловой системы для хранения прогресса.
Будьте добры к владельцам сайтов. Если вы когда-нибудь собираетесь использовать свой сканер за пределами своего веб-сайта, вам придется использовать задержки. Без задержек скрипт работает слишком быстро и может значительно замедлить работу некоторых небольших сайтов. С точки зрения системных администраторов это похоже на DoS-атаку. Статическая задержка между запросами поможет.
Если вы не хотите этого делать, попробуйте Crawlzone и поделитесь своим мнением. Также ознакомьтесь со статьей, которую я написал некоторое время назад https://www.codementor.io/zstate/this-is-how-i-crawl-n98s6myxm