Генератор карты сайта на PHP (скрипт)

Обновлено: 2 июля 2014

Обновил скрипт с учётом пожеланий и найденных ошибок. Спасибо всем кто написал отзыв!

Общие сведения о карте сайта - sitemap.xml

Генерация файла sitemap.xml - подразумевает сбор всех ссылок расположенных на страницах вашего сайта, для последующего предоставления поисковикам. Сам файл имеет xml формат (состоит из xml тегов и использует utf-8 кодировку), создан он для предоставления информации о ваших страницах поисковым системам (Google.com, Yandex.ru, Bing.com, Mail.ru, Nigma.ru, Meta.ua, Baidu.com, Turtle.ru и другим). Полезен файл тем, что помогает определять местонахождение страниц вашего сайта и дополнительную информацию, такую как последнее обновление, приоритет (важность) страниц относительно других и частоту обновления той или иной страницы. Это помогает более рационально индексировать ваш сайт поисковой системе, однако задание страниц в файле sitemap.xml не гарантирует индексацию, а лишь даёт дополнительное и полезное для всех представление о сайте для поискового робота.

Формат файла может быть и не xml, а txt и быть как единым файлом - так и разделённым на несколько частей (при количестве ссылок свыше 50000). Подробнее о спецификации и пример содержания - смотрите в другой статье, об использовании карты сайта.

карта сайта

Полезность файла sitemap.xml

В последнее время, можно встретить сообщения о том, что sitemap.xml бесполезен и отчасти - это так. Поисковые роботы и без того найдут все страницы Вашего сайта, однако исходя из текста на "http://support.google.com/webmasters/bin/answer.py?hl=ru&answer=182072" о GoogleBot, "Процедура сканирования начинается с получения списка URL веб-страниц, который создается на основе результатов предыдущих сеансов сканирования. Его дополняют данные из файлов Sitemap, предоставленных веб-мастером." - можно предположить, что наличие этого файла и его своевременное обновление, способствует более быстрой индексации сайта. Приоритеты, частота обновления и информация о последнем изменении страницы - думаю не будут лишними и так уж бесполезными. Также хочется отметить, что поисковик Bing.com, при добавлении нового сайта просит добавить ссылку на файл sitemap.xml - что думаю сделано не просто так и имеет смысл.


Аннотация скрипту - генератору sitemap.xml

Хочется отметить, что если у Вас готовая CMS (Wordpress, Joomla, Modx, Eleanor, Bitrix и другие) - то дальше Вам читать нету смысла, ибо для большинства CMS уже созданы моды/плагины/расширения для генерации карты сайта. Полезен же скрипт будет тем, кто использует самописные сайты или не может найти плагин для своей CMS. Лично я, пользуюсь собственнонаписанной CMS, поэтому создание этого скрипта было необходимостью. Конечно, существуют готовые решения для генерации, онлайн сервисы генерации и сложные скрипты, которые я какое-то время адаптировал под себя, но обилие лишних настроек и непонятного (местами лишнего) кода - стало для меня весьма надоедливо. Поэтому был написан этот простой и лёгкий скрипт, выполняющий основные действия для генерации sitemap.xml, с минимальными и понятными настройками.

Хочется сразу сообщить, что скрипт имеет недостатки - приоритеты и частота обновления проставляются одинаково, т.к. иной вариант подразумевает вычисление этих данных при помощи определённых записей из вашей базы данных. По той же причине в скрипте не проставляется дата последнего обновления страницы. Но не имея этих преимуществ - скрипт легко внедрится в любой сайт в качестве отдельного генератора или легко встроится, т.к. имеет весьма мало требований к настройке.

Как можно увидеть ниже - скрипт очень прост и использует базовые методы. В скрипте используется лишь одна самописная функция - "sitemap_geturls", работающая в цикле для каждой страницы сайта и вызываемая сама по себе для каждой последующей страницы сайта (кроме первого запуска). Цель функции - собирать работающие ссылки находящиеся на хосте сайта в переменную "urls" (переменная не копируется в функцию, копируется ссылка на неё, для экономии памяти) и проверить каждую ссылку на работоспособность и на содержание ещё не собранных ссылок. В комментариях к коду - смотрите подробности работы той или иной части скрипта.


PHP код генератора sitemap.xml

// Поможет при длительном выполнении скрипта
set_time_limit(0);
$host='makefuture.net'; // Хост сайта
$scheme='http://'; // http или https?
$urls=array(); // Здесь будут храниться собранные ссылки
$content=NULL; // Рабочая переменная
// Здесь ссылки, которые не должны попасть в sitemap.xml
$nofollow=array('/go.php','/search/','/404/');
// Первой ссылкой будет главная страница сайта, ставим ей 0, т.к. она ещё не проверена
$urls[$scheme.$host]='0';
// Разрешённые расширения файлов, чтобы не вносить в карту сайта ссылки на медиа файлы. Также разрешены страницы без разрешения, у меня таких страниц подавляющее большинство.
$extensions[]='php';$extensions[]='aspx';$extensions[]='htm';$extensions[]='html';$extensions[]='asp';$extensions[]='cgi';$extensions[]='pl';
// Корневая директория сайта, значение можно взять из $_SERVER['DOCUMENT_ROOT'].'/';
$engine_root='/usr/local/www/makefuture.net/docs/';

// Функция для сбора ссылок
function sitemap_geturls($page,&$host,&$scheme,&$nofollow,&$extensions,&$urls)
{
	//Возможно уже проверяли эту страницу
	if($urls[$page]==1){continue;}
	//Получаем содержимое ссылки. если недоступна, то заканчиваем работу функции и удаляем эту страницу из списка
	$content=file_get_contents($page);if(!$content){unset($urls[$page]);return false;}
	//Отмечаем ссылку как проверенную (мы на ней побывали)
	$urls[$page]=1;
	//Проверяем не стоит ли запрещающий индексировать ссылки на этой странице мета-тег с nofollow|noindex|none
	if(preg_match('/<[Mm][Ee][Tt][Aa].*[Nn][Aa][Mm][Ee]=.?("|\'|).*[Rr][Oo][Bb][Oo][Tt][Ss].*?("|\'|).*?[Cc][Oo][Nn][Tt][Ee][Nn][Tt]=.*?("|\'|).*([Nn][Oo][Ff][Oo][Ll][Ll][Oo][Ww]|[Nn][Oo][Ii][Nn][Dd][Ee][Xx]|[Nn][Oo][Nn][Ee]).*?("|\'|).*>/',$content)){$content=NULL;}
    //Собираем все ссылки со страницы во временный массив, с помощью регулярного выражения.
	preg_match_all("/<[Aa][\s]{1}[^>]*[Hh][Rr][Ee][Ff][^=]*=[ '\"\s]*([^ \"'>\s#]+)[^>]*>/",$content,$tmp);$content=NULL;
	//Добавляем в массив links все ссылки не имеющие аттрибут nofollow
	foreach($tmp[0] as $k => $v){if(!preg_match('/<.*[Rr][Ee][Ll]=.?("|\'|).*[Nn][Oo][Ff][Oo][Ll][Ll][Oo][Ww].*?("|\'|).*/',$v)){$links[$k]=$tmp[1][$k];}}
	unset($tmp);
    //Обрабатываем полученные ссылки, отбрасываем "плохие", а потом и с них собираем...
	for ($i = 0; $i < count($links); $i++)
	{
		//Если слишком много ссылок в массиве, то пора прекращать нашу деятельность (читай спецификацию)
		if(count($urls)>49900){return false;}
		//Если не установлена схема и хост ссылки, то подставляем наш хост
		if(!strstr($links[$i],$scheme.$host)){$links[$i]=$scheme.$host.$links[$i];}
		//Убираем якори у ссылок
		$links[$i]=preg_replace("/#.*/X", "",$links[$i]);
		//Узнаём информацию о ссылке
		$urlinfo=@parse_url($links[$i]);if(!isset($urlinfo['path'])){$urlinfo['path']=NULL;}
		//Если хост совсем не наш, ссылка на главную, на почту или мы её уже обрабатывали - то заканчиваем работу с этой ссылкой
		if((isset($urlinfo['host']) AND $urlinfo['host']!=$host) OR $urlinfo['path']=='/' OR isset($urls[$links[$i]]) OR strstr($links[$i],'@')){continue;}
		//Если ссылка в нашем запрещающем списке, то также прекращаем с ней работать
		$nofoll=0;if($nofollow!=NULL){foreach($nofollow as $of){if(strstr($links[$i],$of)){$nofoll=1;break;}}}if($nofoll==1){continue;}
		//Если задано расширение ссылки и оно не разрешёно, то ссылка не проходит
		$ext=end(explode('.',$urlinfo['path']));
		$noext=0;if($ext!='' AND strstr($urlinfo['path'],'.') AND count($extensions)!=0){$noext=1;foreach($extensions as $of){if($ext==$of){$noext=0;continue;}}}if($noext==1){continue;}
		//Заносим ссылку в массив и отмечаем непроверенной (с неё мы ещё не забирали другие ссылки)
		$urls[$links[$i]]=0;
		//Проверяем ссылки с этой страницы
		sitemap_geturls($links[$i],$host,$scheme,$nofollow,$extensions,$urls);
	}
	return true;
}
 
// (START!) Первоначальный старт функции для проверки главной страницы и последующих
sitemap_geturls($scheme.$host,$host,$scheme,$nofollow,$extensions,$urls);

// Когда все ссылки собраны, то обрабатываем их и записываем в файлы sitemap.xml и sitemap.txt (должны быть права на запись)
$sitemapXML='<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.google.com/schemas/sitemap/0.84"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.google.com/schemas/sitemap/0.84 http://www.google.com/schemas/sitemap/0.84/sitemap.xsd">
<!-- Last update of sitemap '.date("Y-m-d H:i:s+06:00").' -->';
$sitemapTXT=NULL;
 
// Добавляем каждую ссылку
foreach($urls as $k => $v){$sitemapXML.="\r\n<url><loc>{$k}</loc><changefreq>weekly</changefreq><priority>0.5</priority></url>";$sitemapTXT.="\r\n".$k;}
 
//Окончание для файла sitemap.xml
$sitemapXML.="\r\n</urlset>";

//Некоторые символы, а также кириллица - должны быть в правильной кодировке/виде (по спецификации)
$sitemapXML=trim(strtr($sitemapXML,array('%2F'=>'/','%3A'=>':','%3F'=>'?','%3D'=>'=','%26'=>'&','%27'=>"'",'%22'=>'"','%3E'=>'>','%3C'=>'<','%23'=>'#','&'=>'&')));
$sitemapTXT=trim(strtr($sitemapTXT,array('%2F'=>'/','%3A'=>':','%3F'=>'?','%3D'=>'=','%26'=>'&','%27'=>"'",'%22'=>'"','%3E'=>'>','%3C'=>'<','%23'=>'#','&'=>'&')));

//Запись в файл
$fp=fopen($engine_root.'sitemap.txt','w+');if(!fwrite($fp,$sitemapTXT)){echo 'Ошибка записи!';}fclose($fp);
$fp=fopen($engine_root.'sitemap.xml','w+');if(!fwrite($fp,$sitemapXML)){echo 'Ошибка записи!';}fclose($fp);


THe enD

Думаю этот скрипт пригодится всем тем, кто хочет написать свой с нуля или найти простое решение, если вам понравилось - откомментируйте или тыкните статью в какую-либо социальную сеть, будет очень приятно. Однако, используйте его на свой страх и риск. Ответственность - на вас.

Для больших проектов с тысячами ссылок - запускайте скрипт из под крона, т.к. ему может понадобиться длительное время выполнения.

Если нашли какой-либо недочёт, пожалуйста, откомментируйте.

Приятного использования :)

Комментарии и отзывы к материалу

30.04.2013

sergius пишет:
ошибку пишет $sitemapXML=trim(strtr($sitemapXML,array('%2F'=>'/','%3A'=>':','%3F'=>'?','%3D'=>'=','%26'=>'&','%27'=>''','%22'=>'"','%3E'=>'>','%3C'=>'

02.05.2013

Андрей пишет:
Так ошибка синтаксиса у Вас в строке $sitemapXML=trim(strtr.....
Скобку, похоже, требует.

А так, спасибо. Хороший скрипт.

03.05.2013

Андрей пишет:
А можно ли сделать, чтобы скрипт ходил не по всему сайту, а брал ссылки только с одной его страницы, где они идут списком?

07.05.2013

Григорий (MakeFuture) пишет:
Скорее не на скобку, а на 3 апострофа в ряд... Поправил.
Андрей, можно. Нужно задать нужную страницу на старте и убрать обращение функции к самой себе. Правим строку при старте: sitemap_geturls($scheme.$host. тут добавляем путь к странице, например $scheme.host.'/page/mypage.html',$host,$scheme,$nofollow,$extensions,$urls); Затем замените строку: $urls[$link[$i]]=0; на $urls[$link[$i]]=1;continue; - так ссылки будут сразу проверенными и с них не будут собираться другие.

02.06.2013

Сергей пишет:
добрый день
вот такие ссылки обрабатываются
а вот такие нет:


попробовал подправить OR $urlinfo['path']=='/' на OR $urlinfo['path']=='./' OR $urlinfo['path']=='' OR $urlinfo['path']=='/'
не помогло

05.08.2013

андрей пишет:
Скажите можно ведь сюда добавить
Если возможно примерщик покажите.

06.08.2013

Григорий (MakeFuture) пишет:
Что добавить?

03.10.2013

оыщь пишет:
$sitemapXML=NULL;$sitemapTXT=NULL;
обнуляют заголовок хмл =)
за скрипт очень признателен

06.10.2013

Виктор пишет:
Вообще ничего не происходит в результате работы скрипта! Даже ошибок нет!
Как я понял достаточно указать название сайта и корневую папку.
В массиве $urls - пусто;
P.S. Файл со скриптом лежит в корне. Тестировал на локальном сервере, поэтому права на запись не причём.

29.11.2013

Роман пишет:
Как или где указать место, куда скидываются файлы?

29.11.2013

Григорий (MakeFuture) пишет:
9 строка - абсолютный путь до папки хоста
70,71 строки - название файла, в этих строчках можно убрать $engine_root. и к названию файла добавить путь относительно нахождения скрипта генерации.

06.12.2013

Максим пишет:
Ошибочка, может и та, которая была, но почему не поправили в первом посте?

PHP Parse error: syntax error, unexpected T_STRING in /var/www/****/data/www/****/karta.php on line 53

karta.php - сам скрипт

10.06.2014

Rcreate пишет:
Генератор не учитывает rel nofollow для ссылок и мета страницы.

01.07.2014

Григорий (MakeFuture) пишет:
Обновил скрипт с учётом пожеланий и найденных ошибок:
исправлены ошибки указанные в комментариях
теперь учитывает meta и nofollow
ещё несколько небольших поправок

31.07.2014

Сергей пишет:
Автор - молодец, спасибо! :)

06.08.2014

Александр пишет:
Здравствуйте! Подскажите как его запустить по-подробнее, сорри за нубство, буду рад ответу! Спасибо!

08.08.2014

Григорий (MakeFuture) пишет:
Если сам скрипт у вас правильно настроен, то нужно лишь положить его в корневую папку сайта и запустить, например из браузера. http://example.com/generator.php
Когда скрипт выполнится - он запишет в файлы sitemap.txt и sitemap.xml (тоже находящиеся в корне сайта) найденные ссылки.

12.09.2014

Jack пишет:
Есть пара моментов.
Необходимо проверять на пустоту, например попадется пустая ссылка <a href="" class="test">About</a>
тогда ваша регулярка вытащит данные между " class="
рабочий вариант:
строка 28: preg_match_all("/<[Aa][\s]{1}[^>]*[Hh][Rr][Ee][Ff][^=]*=[ '\"\s]([^ \"'>\s#]+)[^>]*>/",$content,$tmp);

Ваш вариант после объединения будет проверять domain.ruabout вместо domain.ru/about
для этого добавим проверку:
строка 38: if(!strstr($links[$i],$scheme.$host)){
// для ссылок без слеша <a href="about">About</a>
// проверяем начало ссылки, если нет слеша и не начинается на http: mailto: и т.д.
if (!preg_match_all('/^(\w+):/', $links[$i]) && $links[$i][0] != '/') {
$links[$i] = '/'.$links[$i];
}
$links[$i]=$scheme.$host.$links[$i];
}

12.09.2014

Юра пишет:
а добавьте на ваш сайт ссылку типа <a href="tel:28092180291">20138901</a> и все пропало....

18.09.2014

Владимир пишет:
Здравствуйте.
Можете пожалуйста подробно описать принципы настройки и установки скрипта.... Для людей не имеющих познаний в PHP.

22.09.2014

Юра пишет:
Parse error: syntax error, unexpected T_STRING in /home/u274787291/public_html/gen.php on line 62
Вот что не так? подскожите!

11.10.2014

Lust пишет:
Спасибо!
Одно но: не работает с кириллическими доменами. Пробовал к $host применить urlencode - не помогает. У Вас нет решения? В идеале должно воспринимать как http://xn--d1abbgf6aiiy.xn--p1ai/ , так и президент.рф .

11.10.2014

Lust пишет:
А ещё плохо, что отсутствует xml-тег - <lastmod> </lastmod>

11.10.2014

Lust пишет:
и последнее, если сайт большой - скрипт отваливается по таймауту (504). Если нет возможности изменить php.ini на хостинге - это печально

14.10.2014

Вячеслав пишет:
У меня скрипт выдает ошибку:
"Warning: file_get_contents(http://***.com): failed to open stream: HTTP request failed! in /home/***/public_html/***.com/cron/sitemap.php on line 23
Ошибка записи!"
файлы создаются но без нужного списка ссылок, помогите пожалуйста, в чем может быть проблема?

16.10.2014

Вячеслав пишет:
Очень хороший (судя по отзывам) и нужный (конкретно мне) скрипт.

Автор, подскажите пожалуйста, где может быть проблема? Скрипт, при выполнении Cron'ом присылает ошибку:
Warning: file_get_contents(http://****.com): failed to open stream: HTTP request failed! in /home/****/public_html/****.com/cron/sitemap.php on line 22
Ошибка записи!
при этом в корне сайта создается 2 файла: sitemap.txt (совсем пустой) и sitemap.xml (со строками переменной $sitemapXML).

27.10.2014

EUGEN пишет:
Спасибо, очень помогли. Но пришлось попотеть, что бы заработало, ибо в Вашем коде сбита логика.
Вот здесь:
//Если не установлена схема и хост ссылки, то подставляем наш хост
if(!strstr($links[$i],$scheme.$host)){$links[$i]=$scheme.$host.$links[$i];}

наш хост добавляется ко всем ссылкам, где данный хост не обнаружен. Т.е. если будет найдена ссылка на сторонний ресурс (http://vk.com, например), мы получим ссылку вида http://mysite.ru/http://vk.com

Следовательно, дальнейшую проверку на хост эта ссылка уже пройдёт:
//Если хост совсем не наш, ссылка на главную, на почту или мы её уже обрабатывали - то заканчиваем работу с этой ссылкой
if((isset($urlinfo['host']) AND $urlinfo['host']!=$host) OR $urlinfo['path']=='/' OR isset($urls[$links[$i]]) OR strstr($links[$i],'@')){continue;}

И она попадёт в sitemap.
Я, в качестве лекарства, поставил вот такой костыль
if(!strstr($links[$i],$scheme.$host))
{
if(isset($urlinfo['host']) AND $urlinfo['host'].'/'!=$host)
$links[$i]=$links[$i];
else
$links[$i]=$scheme.$host.$links[$i];
}

10.11.2014

Виктор пишет:
Спасибо. Зафигачил на свой сайт, Создало замечательный sitemap. Автору Плюс!
Буду пользоваться.

13.11.2014

Faredo пишет:
Большое спасибо! Использую для создания кэша всех страниц на сайте. Не люблю когда пользователи ждут загрузки..

22.11.2014

Саша пишет:
Не работает с кириллическими урлами :(

10.12.2014

Геннадий пишет:
Попробовал работу скрипта. С настройками вопросов не возникло. Применил на сайте, где всего то около 700 страниц. Так вот скрипт после запуска крутится, крутится, а в итоге ошибка 502 Bad Gateway.
А потом решил в переменной $host оставить домен, как у вас - $host='makefuture.net';
И тогда скрипт за пяток секунд составил карту этого домена.

13.02.2015

Audi пишет:
Да, это правильный подход - лучше всё писать под себя, чтобы знать каждую строку кода "в лицо".
Спасибо за скрипт, задача оказалось далеко не тривиальной, но вы с ней справились на отлично :)
У меня были различные идеи - даже подключение скрипта ко всем страницам, который бы прописывал в sitemap текущий адрес, если его там нет. Но это вряд ли хорошая идея. Лучше раз в день пропарсить весь сайт.

И, да, странно, что не добавили определение Last-Modified - эти данные легко получить HTTP-запросом HEAD, для любого URL.

15.02.2015

anonymous пишет:
$extensions[]='txt';
$extensions[]='rar';
$extensions[]='zip';

не обрабатывает такие файлы!

11.03.2015

Андрей пишет:
Очень! Очень-очень не хватает расстановки Priority в зависимости от уровня вложенности страницы...

28.05.2015

Дмитрий пишет:
Возможно я конечно ошибаюсь но continue должен быть обязательно связан с циклом чего у вас я вижу не везде...

14.06.2015

theblackpost пишет:
Очень не хватает в скрипте распределения <priority> по уровню вложенности. Last-Modified добавить вообще фигня:

// Добавляем каждую ссылку
$datetoday = date("Y-m-d");
foreach($urls as $k => $v){
$sitemapXML.="\r\n<url><loc>{$k}</loc><lastmod>$datetoday</lastmod><changefreq>weekly</changefreq><priority>1.00</priority></url>";
$sitemapTXT.="\r\n".$k;
}

14.06.2015

theblackpost пишет:
А, да. Кирилица будет обрабатываться если подключить библиотеку idna (найти в инете не долго):

<?
require_once('idna_convert.class.php');
function converttopuny($host) {
$idn = new idna_convert(array('idn_version'=>2008));
$punycode=isset($_REQUEST['punycode']) ? stripslashes($_REQUEST['punycode']) : '';
$punycode=(stripos($punycode, 'xn--')!==false) ? $idn->decode($punycode) : $idn->encode($punycode);
return $punycode;
}

13.08.2015

Коля пишет:
Установил, работает но не совсем как надо, URL с пробелами не обрабатывает до конца(
Если возможно помогите исправить...

31.08.2015

Антон пишет:
Привет!
Если я правильно понял, то надо скачать библиотеку idna_convert.class.php(в корень сайта), дописать приведенный код"require_once('idna_convert.class.php');......return $punycode;}" в скрипт.
Куда? В начало или после "$host='www.мойсайт.рф"?
Или где-то надо вызвать функцию "converttopuny($host)"?
Спасибо! Классная статья!

18.09.2015

theblackpost пишет:
Антон:

function converttopuny($host) {
$idn = new idna_convert(array('idn_version'=>2008));
$host=isset($host) ? stripslashes($host) : '';
$host=(stripos($host, 'xn--')!==false) ? $idn->decode($host) : $idn->encode($host);
return $host;
}

$host= trim($_GET['site']); // Хост сайта

if (preg_match("/[а-я]/i", $host)) {
require'idna_convert.class.php';
$host = converttopuny($host);
}

24.09.2015

Zullbatol пишет:
Классный скрипт, спасибо! Для неопытных ставьте <?php код?> и поменяйте адрес где будет хранится файл xml, txt

10.10.2015

ВАСЯ пишет:
504 Gateway Time-out
При большом количестве страниц сайта скрипт выдает ошибку. Возможно ли учитывать при поиске уже сохраненые в sitemap.xml страницы

12.12.2015

Рулон Обоев пишет:
Спасибо за скрипт! Отлично работает. Живет теперь на http://konata.ru :-)

17.02.2016

Wind пишет:
Доброго дня!
Подскажите в чем ошибка: на локальном сервере тестирую, выдает вот такое

Warning: file_get_contents(http://lisq/page/tos): failed to open stream: HTTP request failed! HTTP/1.0 404 Not Found in D:\svr\NEWSERV\Open_Server\OpenServer\domains\lisq\gen.php on line 24

Отправить комментарий

Как зовут?

Сообщение