/kwTools.SitemapGenerator

Генератор сайтмэпа (sitemap) для сайта на основе конфигурационного файла.

Primary LanguagePHPMIT LicenseMIT

Что это?

Генератор sitemap`ов для произвольного сайта.

Version 2.3.0

Установка

Пререквезиты

PHP 7.0+ (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-пакета

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 = ''

Важный момент №1 - Необычные запросы

запрос может быть и другим, к примеру

SELECT CONCAT('/page/xxx/', id) AS it, FORMAT_DATE(mask, lastdate) AS pld FROM price

тогда имена полей с данными должны быть it и pld соотв.

Важный момент №2 - Условные операторы в запросе

Хотя задать условия выборки можно в опциях

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'
Важный момент №3 - sort order
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 тысяч статей/фоторепортажей) генерация занимает 14 секунд, пиковое потребление памяти 77 Мб.

Ссылки

Совместимость

Требуется PHP 7.0 и выше.

Лицензия

MIT