Генератор sitemap`ов для произвольного сайта.
Version 2.3.0
PHP 7.4+ (php-cli, php-pdo), MySQL
git clone https://github.com/KarelWintersky/kwTools.SitemapGenerator.git
make build_local
sudo mv $(PWD)/production/sitemapgenerator /usr/local/bin/sitemapgenerator
sudo chmod +x /usr/local/bin/sitemapgenerator
sitemapgenerator --help
DEB-пакеты публикуются в релизах:
https://github.com/KarelWintersky/kwTools.SitemapGenerator/releases
sudo dpkg -i <file>.deb
Теперь мы можем запустить скрипт из консоли, передав аргументом путь к файлу конфигурации:
/usr/local/bin/sitemapgenerator --config /path/fo/my_sitemap.ini
--config
- обязательная опция, требует указания конфига. Без неё выводится справка.--help
- необязательная опция, выводит справку.--verbose
- необязательная опция, перекрывает опцию конфигурацииlogging
из глобальной секции и выводит прогресс создания sitemaps.
В папке /docs/example/ есть пример необходимого набора файлов:
config.sitemap+db.example.ini
- пример конфигурационного файлаdata.countries.txt
- текстовый файл со списком страниц для стран мираdata.staticpages.txt
- текстовый файл со списком статических страниц
Все настройки доступа к БД и инструкции по построению сайтмэпов задаются в ini-файлах. Предполагается, что конфиг-файлы недоступны посторонним, но я все равно рекомендую создать для генерации сайтмэпа отдельного пользователя исключительно с правом SELECT и указывать его credentials в файле конфигурации:
Команды для MySQL:
CREATE USER 'sitemapcreator'@'localhost' IDENTIFIED BY 'sitemappassword';
GRANT SELECT ON database.* TO `sitemapcreator`@`localhost`;
FLUSH PRIVILEGES;
Файл конфигурации состоит из как минимум трёх секций:
- секция глобальных настроек
- секция настроек подключения к БД
- одна или несколько секций, описывающих, как строить сайтмап для страниц определенной категории
Опишу эти секции последовательно:
Это обязательная секция, без неё работа невозможна.
; версия 2.3.0
[___GLOBAL_SETTINGS___]
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; URL сайта (домен, включая финальный слэш)
sitehref = 'http://www.example.com/'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; URL (включая домен и финальный слеш) к промежуточным файлам сайтмапов
sitemaps_href = 'http://www.example.com/sitemaps/'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; Директория, куда записываются файлы сайтмапов (разумеется, мы должны иметь права на запись в этот каталог)
sitemaps_storage = '/var/www/example.com/sitemaps/'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; путь и имя файла к основному (индексному) файлу сайтмэпа. Если указать только имя - файл сохранится в текущий каталог.
; разумеется, скрипт должен иметь права на задпись этого файла по указанному пути
sitemaps_mainindex = 'sitemap.xml'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; максимальное количество URL в файле sitemap, по умолчанию 50000
limit_urls = 50000
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; максимальный размер промежуточного файла sitemap без сжатия, по умолчанию 50000000 байт
; на самом деле по стандарту максимум 50 мегабайт, это немного больше 50 млн. байт, но из-за особенностей реализации
; рекомендуется указывать именно такое значение
limit_bytes = 50000000
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; использовать ли gzip для сжатия, по умолчанию TRUE
; это глобальная перекрываемая настройка, её можно переопределить в секции
use_gzip = 1
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; выводить ли информацию о генерации файлов карт? По умолчанию: выводить.
; перекрывается ключом `--verbose`
logging = 1
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; формат представления даты. Допустимые значения:
; iso8601 - дата в формате W3C Datetime (2004-12-23T18:00:15+00:00)
; * YMD - дата в формате Y-m-d (2004-12-23), этот формат используется по умолчанию (если опция не задана или содержит иное значение)
date_format_type = 'iso8601'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; суффикс для секции с настройками подключения
; Пустое значение возможно только в том случае, если ВСЕ секции берут данные из файлов. Но все равно будет выведено предупреждение.
db_section_suffix = 'DATABASE'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ, ТРИ ВАРИАНТА ВОЗМОЖНЫХ ЗНАЧЕНИЙ]
; Указывает, как создавать путь к корню сайта в sitemap
; 0 или не задано: описание локейшена к корню сайта не создается. В этом случае рекомендуется создать секцию с описанием статичных ссылок на пути (см. ниже, тип секции `file`), а в файле указать "/" как путь к корню
; 1 (число) - создается файл сайтмэпа root.xml.gz, который будет содержать единственный локейшен к корню сайта
; foobar (строка) - воспринимается как имя файла сайтмэпа, который будет содержать единственный локейшен к корню сайта
include_root_page = 1
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; Может быть (пере)определена в секции.
; Задает паузу в мс между секциями. Если (пере)определено в секции - означает паузу, которая будет сделана после обработки данной секции.
; В противном случае такая пауза будет сделана после обработки каждой сессии. Если включен режим verbose - будет сообщение о паузе.
; По умолчанию 0
; sleep_between_sections = 0
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; Может быть (пере)определена в секции.
; Пауза в мс между запросами к БД на выборку чанков (цепочек данных). Если переопределено в секции - задает паузу между чанками для секции.
; Если включен режим verbose - будет сообщение о паузе.
; По умолчанию 0
; sleep_between_chunks = 0
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ, ГЛОБАЛЬНАЯ]
; Локаль сообщений скрипта, актуально только для режима `--verbose`
; По умолчанию 'en', возможен вариант 'ru'
; В версии 2.3.0 не поддерживается.
; locale = 'en'
Имя этой секции: ___GLOBAL_SETTINGS:<db_section_suffix>___
. Эту секцию можно опустить, но ТОЛЬКО в том случае
если все остальные секции берут данные из файлов.
[___GLOBAL_SETTINGS:DATABASE___]
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; драйвер, поддерживаются значения 'mysql', 'pgsql', 'sqlite'
; для sqlite обязателен параметр `hostname = /path/to/database.sqlite`
driver = 'mysql'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; hostname для подключения (обычно localhost)
hostname = ''
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; имя пользователя для подключения к БД (помните, я советовал создать отдельного пользователя?)
username = ''
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; пароль для подключения к БД
password = ''
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; имя БД с нужными данными
database = ''
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; порт (для MySQL по умолчанию 3306), указывать обязательно
port = 3306
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; charset (необязательно, по умолчанию utf8)
charset = 'utf8'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; character set collate (необязательно, по умолчанию utf8_unicode_ci)
charset_collate = 'utf8_unicode_ci'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; Выполнить SQL-запрос после соединения с БД.
; Не реализовано в 2.3.0 и ниже
; after_connection_command = ''
При использовании драйвера sqlite
обязателен единственный параметр hostname = /path/to/database.sqlite
.
Данные для построения сайтмапа могут браться из трёх источников:
- БД
- text file
- CSV-file (в разработке)
В зависимости от источника данных описание секции будет различным:
; название секции
[price]
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; источник данных - sql, file, root
source = 'sql'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; запрос к БД для получения количества элементов, для которых строим sitemap
sql_count_request = 'SELECT COUNT(id) AS cnt FROM price'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; поле в результатах запроса, содержащее нужное количество
sql_count_value = 'cnt'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; запрос к БД для получения всех нужных нам элементов (LIMIT ... OFFSET ... не указывать!)
sql_data_request = 'SELECT id, lastmod FROM price'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; имя поля в результатах запроса, содержащее айди страницы
sql_data_id = 'id'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; имя поля в результатах запроса, содержащее дату последней модификации
; страницы. Используется для атрибута lastmod в sitemap-ссылке.
; ВАЖНО: если этого поля в таблице нет в принципе - следует использовать значение 'NOW()', то есть текущий момент времени (таймштамп).
sql_data_lastmod = 'lastmod'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; URL до страницы от корня сайта (исключая домен)
; используется как маска для sprintf
url_location = 'price/%s.html'
; [РЕКОМЕНДУЕМАЯ ОПЦИЯ]
; приоритет страниц в секции, по умолчанию 0.5
url_priority = '0.5'
; [РЕКОМЕНДУЕМАЯ ОПЦИЯ]
; вероятная частота обновления страниц в этой секции
; допустимые значения: always, hourly, daily, weekly, monthly, yearly, never
; значение по умолчанию: never
url_changefreq = 'daily'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; корень имени файла, содержащего ссылки для страниц данной категории
; файлы будут иметь вид price-1, price-2 etc
; опцию можно опустить или оставить пустой - тогда имя файла будет таким же, как имя секции
radical = 'price'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; использовать ли gzip-сжатие для файлов sitemap для данной секции. Перекрывает глобальное значение use_gzip
use_gzip = 0
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; Директория, куда записываются файлы сайтмэпов этой секции. Перекрывает ___GLOBAL_SETTINGS___/sitemaps_storage
sitemaps_storage = ''
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; URL сайта (домен, включая финальный слэш) для этой секции. Перекрывает ___GLOBAL_SETTINGS___/site_href
site_href = ''
запрос может быть и другим, к примеру
SELECT CONCAT('/page/xxx/', id) AS it, FORMAT_DATE(mask, lastdate) AS pld FROM price
тогда имена полей с данными должны быть it
и pld
соотв.
Хотя задать условия выборки можно в опциях
sql_count_request = 'SELECT COUNT(id) AS cnt FROM price WHERE price.actual = 1'
sql_data_request = 'SELECT id, lastmod FROM price WHERE price.actual = 1'
...эффективнее будет использование представления (VIEW), к примеру:
CREATE VIEW actual_price
AS SELECT id, lastmod FROM price WHERE price.actual = 1;
Соответственно опции будут такими:
sql_count_request = 'SELECT COUNT(id) AS cnt FROM actual_price'
sql_data_request = 'SELECT id, lastmod FROM actual_price'
sql_data_request = 'SELECT id, lastmod FROM price ORDER BY lastmod DESC'
Это допустимая строчка. Но лучше тоже через VIEW.
; страны-регионы
[countries]
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; источник данных
source = 'file'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; путь к файлу с данными (хранимыми построчно)
; ВАЖНО: символ '$' означает, что файл ищется в том же каталоге, в котором лежит и файл конфигурации.
; В противном случае мы обязаны указать абсолютный путь к файлу (впрочем, можно сказать $/static/countries.txt)
filename = '$/countries.txt'
; [ОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; URL до страницы от корня сайта (исключая домен)
; используется маска для sprintf
url_location = 'countries/%s/'
; [РЕКОМЕНДУЕМАЯ ОПЦИЯ]
; приоритет страниц в секции, по умолчанию 0.5
url_priority = '0.5'
; [РЕКОМЕНДУЕМАЯ ОПЦИЯ]
; вероятная частота обновления страниц в этой секции
; допустимые значения: always, hourly, daily, weekly, monthly, yearly, never
; значение по умолчанию: never
url_changefreq = 'daily'
; единственное допустимое значение. Означает, что для lastmod ссылки берется текущий таймштамп
lastmod = 'NOW()'
; [НЕОБЯЗАТЕЛЬНАЯ ОПЦИЯ]
; корень имени файла, содержащего ссылки для страниц данной категории
; файлы будут иметь вид countries-1, countries-2 etc (см. separator в секции $GLOBAL_SETTINGS$)
radical = 'countries'
Пустая строчка в текстовом файле-источнике означает, очевидно, пустую строку, хотя так делать не рекомендуется.
Если в статическом файле нужно указать путь к корню сайта - следует добавить строчку
/
Смотри пример в файле конфигурации /docs/example/data.staticpages.txt
, секция [static]
Можно в отладочных целях запретить обработку какой-то секции. Для этого надо указать в теле секции опцию:
enabled = 0
Любое другое значение или отсутствие этой строчки означает необходимость обработки секции.
На моём домашнем сервере (Gentoo/4.14.15 на GA-N3150N-D3V, CPU Celeron™ N3150 (1.6 GHz), PHP 7.1.13, HDD WD Blue 5400rpm in RAID1) для тестовой БД с ~800 тыс. записей файлы сайтмэпа создаются около 45 секунд.
При этом пиковое потребление памяти составляет ~80 Мб.
На реальном проекте (400 тысяч статей/фоторепортажей) генерация занимает 10-11 секунд, пиковое потребление памяти 35-40 Мб.
- Спецификация Sitemap
- библиотека XMLWriter (на Gentoo требуется компиляция с флагом
+xmlwriter
- W3C Datetime Specification
Требуется PHP 7.4 и выше.
MIT