Парсер robots.txt на PHP: как выбирается правило
robots.txt ♡
Одна лишняя строка в robots.txt может закрыть от поисковика весь сайт, и ты не заметишь этого неделями. Сидишь, ждёшь трафик, а поисковик тебе тихо ставит «прочитано» и уходит в игнор. Поэтому на портале есть проверка: она разбирает файл, ловит в нём ошибки и показывает, разрешён ли конкретный адрес для Googlebot и для Яндекса.
Как поисковик выбирает правило
Почти вся путаница вокруг robots.txt держится на двух правилах, о которых все вечно забывают.
Первое: из всех правил, подходящих к адресу, побеждает самое длинное. Не то, что выше в файле, и не то, что написано позже.
Второе: если длина одинаковая, побеждает Allow.
Благодаря этому у закрытой папки может быть открытый файл:
User-agent: *
Disallow: /private/
Allow: /private/press.html
Адрес /private/press.html разрешён, потому что правило Allow длиннее. А /private/other.html закрыт, и никакой мольбой его не открыть.
Как это выглядит в коде
В методе RobotsTxt::check() я перебираю правила выбранной группы и запоминаю победителя:
$winner = null;
foreach ($rules as $rule) {
if (! self::matches($rule['path'], $path)) {
continue;
}
$length = strlen($rule['path']);
if ($winner === null || $length > strlen($winner['path'])
|| ($length === strlen($winner['path']) && $rule['type'] === 'allow')) {
$winner = $rule;
}
}
return ['allowed' => $winner === null || $winner['type'] === 'allow'];
Если ни одно правило не подошло, адрес разрешён: всё, что не запрещено, то разрешено. Философия «я не запрещал, значит, можно».
Звёздочка и знак доллара
Google и Яндекс понимают в путях два спецсимвола: * (любая последовательность знаков) и $ (конец адреса). Чтобы их поддержать, я превращаю шаблон в регулярное выражение:
$anchored = str_ends_with($pattern, '$');
$body = $anchored ? substr($pattern, 0, -1) : $pattern;
$regex = '#^'.str_replace('\*', '.*', preg_quote($body, '#')).($anchored ? '$' : '').'#';
Порядок важен. Сначала весь текст экранируется через preg_quote(), и только потом \* заменяется на .*. Сделаешь наоборот, и точки, скобки и знаки вопроса из путей начнут работать как спецсимволы регулярки. Классический кринж, который потом полдня отлавливаешь.
Какой робот читает какую группу
Файл делится на группы по строкам User-agent, и несколько таких строк подряд относятся к одной группе. Для конкретного робота берётся группа с самым длинным именем, которое входит в его название: например, googlebot сильнее звёздочки. Если своей группы у робота нет, ему достаётся группа со звёздочкой, для всех остальных.
Поэтому проверка на портале сразу выдаёт результат для трёх случаев: для всех роботов, для Googlebot и для Яндекса.
Какие косяки находит парсер
- В строке нет двоеточия, а значит, это вообще не директива.
- Директива User-agent без имени робота.
- Allow или Disallow стоит до первой строки User-agent, и роботы такое правило просто не учтут.
- Путь не начинается с / или *.
- Sitemap указан не полным адресом с https://.
- Неизвестная директива. К ней парсер подбирает похожую по расстоянию Левенштейна: вместо Dissalow вежливо предложит disallow.
Директивы-динозавры
Отдельно парсер ругается на Host и Crawl-delay. Яндекс давно забыл про Host, главное зеркало теперь задают редиректом. Google не учитывает Crawl-delay, а скорость обхода для Яндекса лучше выставлять в Вебмастере. То есть эти строки в файле просто пылятся.
Чего парсер не делает
Он учитывает BOM в начале файла и разные переносы строк. Вопрос «закрыт ли весь сайт» проверяется отдельно: нужен Disallow: / без перекрывающего Allow. Директиву Clean-param он разбирает только по синтаксису, а её действие не моделирует. Боты, кроме звёздочки, Googlebot и Яндекса, отдельно не проверяются.
Парсер показывает, как файл должен работать по правилам. Что робот делает на самом деле, смотри в Вебмастере и Search Console, там последнее слово за ними. Как я готовлю к поиску сам портал, включая его robots.txt, лежит в отдельной статье, а чужой файл я забираю через защищённый клиент.
Комментарии
Буду рада вашим мыслям, идеям и вопросам!
Давайте обсуждать ♡
Делитесь
своими мыслями
— мне очень
это важно ♡