[EVO] Установка заголовка Last-Modified и 304 Not Modified для поисковых систем (Update от 15.11.2014)

Зачем нужен Last-Modified

HTTP заголовок Last-Modified сообщает клиенту время последнего изменения страницы (объекта). Если клиент (браузер, поисковый робот) получил заголовок Last-Modified, то при следующем обращении к адресу, при условии, что страница (объект) есть в локальном кеше, он добавит вопрос If-Modified-Since (не изменилась ли страница после даты, полученной в Last-Modified). В свою очередь сервер, получив запрос If-Modified-Since должен сверить полученную временную метку с временем последнего изменения страницы и, если страница не изменялась ответить 304 Not Modified.
Если страница не изменилась, то сервер прекратит передачу данных после отправки заголовков с кодом 304 Not Modified, тело страницы, изображения и другие объекты передаваться не будут.
Для поисковых же систем это полезно тем что за один и тот же промежуток времени робот успеет проиндексировать больше новых страниц на вашем сайте, потому что не будет загружать страницы которые не поменялись.


В итоге при правильном заголовке мы получаем несколько плюсов:
— поисковики быстрее индексируют сайт, потому что обходят только новые страницы и изменённые, не тратя ограниченное время на старые страницы (особенно заметно на больших сайтах)
— Сайт появляется в выдаче поисковиков при фильтре по дате (Например: удовлетворяющий запрос за час/сутки/неделю/год)
— Экономия трафика как сервера так и у клиента, а как следствие снижение нагрузки на сервер, и визуальное ускорение сайта у клиента.

В Modx-evo такого функционала до сих пор не реализовано, и на это есть свои причины. Самая главная причина:
Очень сложно реализовать универсальную схему работы с динамическими сайтами, будь то авторизация или добавление товара в корзину с пересчётом цены или динамические блоки и так далее. Потому что придётся тратить большие ресурсы на актуализацию заголовка для каждого клиента и каждого его действия, и эти затраты просто напросто не оправдаются.

После долгих раздумий я понял, а зачем это нашему посетителю? Единственный плюс для посетителя это экономия трафика (и то, если он дважды просматривает страницу), но этого можно добиться и клиентским кэшированием и другими способами. Last-Modified это всё же два плюса для лучшей индексации и один для снижения нагрузки на сервер.

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

Первый плагин
Плагин проверяет зашедшего на сайт по юсерагенту, и если это поисковой робот то ставит заголовки.

Название плагина: LastModified (но в обще то нет разницы)
Системные события: OnWebPagePrerender

function DetectSearchEngine($USER_AGENT)
{
    $engines = array(
    array('Aport', 'Aport'),
    array('Google', 'Google'),
    array('msnbot', 'MSN'),
    array('Rambler', 'Rambler'),
    array('Yahoo', 'Yahoo'),
    array('Yandex', 'Yandex'),
    array('Aport', 'Aport robot'),
    array('Google', 'Google'),
    array('msnbot', 'MSN'),
    array('Rambler', 'Rambler'),
    array('Yahoo', 'Yahoo'),
    array('AbachoBOT', 'AbachoBOT'),
    array('accoona', 'Accoona'),
    array('AcoiRobot', 'AcoiRobot'),
    array('ASPSeek', 'ASPSeek'),
    array('CrocCrawler', 'CrocCrawler'),
    array('Dumbot', 'Dumbot'),
    array('FAST-WebCrawler', 'FAST-WebCrawler'),
    array('GeonaBot', 'GeonaBot'),
    array('Gigabot', 'Gigabot'),
    array('Lycos', 'Lycos spider'),
    array('MSRBOT', 'MSRBOT'),
    array('Scooter', 'Altavista robot'),
    array('AltaVista', 'Altavista robot'),
    array('WebAlta', 'WebAlta'),
    array('IDBot', 'ID-Search Bot'),
    array('eStyle', 'eStyle Bot'),
    array('Mail.Ru', 'Mail.Ru Bot'),
    array('Scrubby', 'Scrubby robot'),
    array('Yandex', 'Yandex')
    );
   
    foreach ($engines as $engine)
    {
        if (stristr($USER_AGENT, $engine[0]))
        {
            return($engine[1]);
        }
    }
    return (false);
} 

//определяем поисковой ли бот пришёл к нам
$detect = DetectSearchEngine($_SERVER['HTTP_USER_AGENT']);
//если бот то
if ($detect)
{
  //Получаем время изменения ресурса из таблички site_content
  $last_modified_time = $modx->documentObject['editedon'];
	
		//Если поисковик спрашивает у сервера: у меня есть эта страница в кэше с определённого времени, изменилась ли она?
		//смотрим на время, сверяем:
		if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $last_modified_time){
        	//Если НЕ изменилась то отправляем 301 ответ
        	header('HTTP/1.1 304 Not Modified');
			//И убиваем выполнение дальнейших скриптов, то есть сервер кроме ответа заголовка не шлёт саму страничку
        	die;
					}	
	
	//устанавливаем поисковику Last-Modified если он первый раз на странице либо если страница обновлена.
	header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_modified_time).' GMT');	
	
}
else
{
	//тут можно выполнять код для обычного посетителя
}


Второй плагин
Плагин занимается тем что при создании нового документа или при изменении документа обновляет даты редактирования и у родительских документов, это надо для например новостных лент или каталога товаров.
Так же в конфигурации плагина можно проставить id тех ресурсов (через запятую) которые будут принудительно обновлять свою дату хоть они и не родители вновь созданного документа. Ну например туда надо включить id главной страницы, потому что обычно на главной делается блок кратких записей (новости, статьи, новинки товаров).

Название плагина: Updateeditedon (но в обще то нет разницы)
Конфигурация плагина: &updateid=Update id;string;1,2
Системные события: OnDocDuplicate, OnDocFormSave

$e =& $modx->Event;
//проверяем события
if (($e->name == 'OnDocFormSave') or ($e->name == 'OnDocDuplicate')) {
	if (!isset ($updateid)) { $updateid = '1'; }
	//получаем id ресурса который обновили или создали      
	$id = ($_POST['id'])? $_POST['id'] : $e->params['id'];
	//получаем всех родителей от нашего документа
	$getParentIds = $modx->getParentIds($id);
	//Добавляем id страниц которым принудительно надо сменить дату обновления
	$getParentIds = array_merge( $getParentIds, (explode(',',$updateid)) );
	//Убираем из массива дублирующиеся id
	$getParentIds = array_unique($getParentIds);
	
	//перебираем родителей    
	foreach ($getParentIds as $getParentId)
	{	
		//получаем полное имя таблицы
		$table = $modx->getFullTableName( 'site_content' );  
		//записываем новые данные
		$fields = array('editedon'  => time() );
		//делаем update строки в таблице
		$result = $modx->db->update( $fields, $table, 'id = "' . $getParentId . '"' );
    }
	
}


Результат продемонстрирован на скриншотах (Такой результат будет виден поисковым системам если на странице не было изменений с последнего посещения роботом)
До плагина:


После включения плагина:


Думаю кому ни будь будет полезно.

P.S. Искал названия ботов по интернету, если есть какие то изменения и дополнения прошу написать

25 комментариев

avatar
Давно задумывался сделать по уму но возникает куча моментов
1 на чем залип это при включении Last-Modified начинает бажить корзина SHK
тоесть все что не кешированное будет работать не коректно :(
тоесть данные решение уже не подходит к примеру для интернет магазина притом даже на просто страничке новости :(

В остальном написать даже сложную логику с проверкой менялись ли дочерние документы не составить труда
avatar
Да, всё верно, динамические сайты отказались нормально работать.
Обновил статью, исправил недостатки.
avatar
отличная идея, загрузка статических сайтов сократилась на 10% :)
avatar
Теперь можно использовать и для динамических сайтов, но польза будет только для SEO. То есть клиент не почувствует ускорения.
avatar
часто при допилах приходится править css файл, и для отслеживания изменений обновлять страничку в браузере. Вопрос — можно ли включить также проверку каких либо файлов на наличие изменений?
avatar
.htaccess


<FilesMatch "style\.css$">
  ExpiresActive On
  ExpiresDefault A1
  Header append Cache-Control must-revalidate
</FilesMatch>
avatar
Да, через .htaccess Запретить кэширование браузером файла стилей как уже подсказали
avatar
поставил плагин №2 на сайт. При проверке на сайте last-modified.com/ru/last-modified-if-modified-since-php.html, результат отрицательный (Last-Modified не найден!). Как исправить?
avatar
Всё верно, сервис покажет что заголовки не найдены. так как плагин работает ТОЛЬКО с поисковыми роботами, а когда вы проверяете через этот сервис то для плагина это обычный посетитель. Это сделано для возможности работы динамических сайтов, например как у вас.
Только надо что бы в системе стояли оба плагина!
Комментарий отредактирован 2014-11-15 14:44:49 пользователем vladsvd
avatar
Вы можете отключить проверку на роботов, для этого замените в первом плагине строчку
<code>$detect = DetectSearchEngine($_SERVER['HTTP_USER_AGENT']);</code>
на
<code>$detect = '1';</code>
и заново протестируйте через тот сервис.

Только потом верните всё обратно, иначе сайт для клиентов будет работать некорректно.
Комментарий отредактирован 2014-11-15 14:49:29 пользователем vladsvd
avatar
спасибо, установил и второй плагин… В плагин №2 прописал id всех разделов (т.е. и контакты, доставка, оплата и т.д.) — это ничего страшного? Есть ли сервисы, что б узнать корректно ли работают эти плагины, без отключения проверки на роботов?
avatar
Страницы типо контакты и оплаты не надо прописывать, если вы на них что ни будь поменяете, то дата последнего редактирования в них и так отобразиться. Заголовок ставиться сам во все страницы.
А принудительно надо ставить айдишники таких страниц как допустим у вас detiruekb.ru/katalog/novinki.html потому что на ней постоянно добавляются товары, но она не родитель для этих товаров. То есть вы внесёте новую коляску в категорию коляски и даты редактирования сами обновяться во всей этой цепочке Каталог детских товаров » ДЕТСКИЕ ТОВАРЫ ДЛЯ ОТДЫХА » ЗИМНИЕ ТОВАРЫ » Санки-коляска Кенгуру-7-Р, но не обновятся в разделе «новинки», для этого этот параметр я и ввёл, таких страниц у вас всего нечего.

По поводу сервисов, я не знаю сервисов которые выдают себя за поисковых роботов в юсерагенте
avatar
можно установить User-Agent Switcher for Chrome, ввести данные любого поискового бота. тем самым подменив их. и загрузить сайт. на каждой страничке в панели разработчика во вкладке network появиться что то типо Last-Modified:Fri, 14 Nov 2014 18:43:33 GMT… с вашим временем изменения страницы.
Но мне кажется проще проверку на роботов отключить если охото проверить работает ли, потому что больше там ломаться то не чему.
avatar
кстати у вас всё нормально работает, я проверил
avatar
спасибо!
avatar
Спасибо!
avatar
О только добрался полезная штука но можно написать в 1 плагин что б не разносить на 2 :)
сделаю на досуге и добавлю в Extrass)
avatar
Дим так ты добавил в Extrass?
avatar
Я проверил — нет, нету там плагина.
avatar
Спасибо! Но пишет: 304 Not Modified не найден! MODx 1.2.1

Хотя Last-Modified найден!
avatar
avatar
Я проверял через другой сервис с подстановкой юзерагента яндексбота. В панели вэбмастера Яндекса ответ 200, а не 304.

Может в первой части плагина юзерагент указывать не как просто Yandex, а YandexBot/3.0?

Хотя попробовал. Ответ все равно 200.
avatar
modx.im/blog/fast-solution/2807.html#comment24637
Всё верно, сервис покажет что заголовки не найдены. так как плагин работает ТОЛЬКО с поисковыми роботами, а когда вы проверяете через этот сервис то для плагина это обычный посетитель.
Здесь какой код отдаёт? webmaster.yandex.ru/tools/server-response/
Комментарий отредактирован 2017-12-15 11:58:57 пользователем igvind
avatar
Вы хотите сказать, что я не читал сообщение vladsvd? Detect я ставил в 1. Панель вэбмастера отдает: Статус в поисковой базе:200OK. Поэтому я подумал, что поисковых ботов нужно прописывать так, как они представляются.
avatar
Как прописать конфигурацию второго плагина? Актуален ли сейчас список ботов и вообще эти плагины для Evo 1.4.11?
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.