Перейти к содержимому
COS WP Woo
Назад к блогу
Расширение функционала27 мин чтения

Медиабиблиотека на 40 тысяч файлов: папки, чистка и alt-тексты

Хостинг прислал письмо — диск заполнен на 98 процентов. Разбираясь, я обнаружил то, что вижу в девяти магазинах из десяти — плоскую свалку из сорока тысяч файлов, тройной запас ненужных миниатюр и пустые alt-тексты у трёх четвертей фотографий. Рассказываю, как навести в этом порядок, не снеся то, что живёт в блоках и мета-полях.

# Медиабиблиотека на 40 тысяч файлов: папки, чистка и alt-тексты

Письмо от хостинга пришло в понедельник в семь утра: «Использовано 98% дискового пространства». Я привык к таким письмам — обычно это база данных разрослась или логи не чистились полгода. Зашёл по SSH, посмотрел `du -sh` по папкам, и увидел то, что видел уже не в первый раз: `wp-content/uploads` весила сорок один гигабайт. На сайте с полутора тысячами товаров. Ни видео, ни каких-то тяжёлых архивов — просто изображения. Я запустил `find . -type f | wc -l` в этой папке и получил число, от которого стало не по себе: 187 тысяч файлов. На полторы тысячи товаров.

Дальше было интересно копать. Я взял случайный товар, открыл его карточку в медиабиблиотеке и увидел ровно одну картинку в интерфейсе — а на диске под неё лежало восемь файлов разного размера. Взял второй товар — та же история, только уже десять файлов, потому что кто-то когда-то загружал фото в очень большом разрешении, и WordPress старательно нарезал его на все нужные ему миниатюры. Взял третий — и обнаружил три версии одной и той же фотографии, загруженные с разницей в год, потому что менеджер не помнил, что фото уже есть, и просто заливал заново с флешки фотографа. Умножьте это на полторы тысячи товаров, добавьте фотографии для блога, баннеры, иконки, которые правил дизайнер пять итераций назад и не удалил старые версии — и вот вам сорок один гигабайт и сто восемьдесят семь тысяч файлов.

Я рассказываю эту историю не потому, что она уникальна. Я рассказываю её потому, что она типична до скуки. За те годы, что я занимаюсь развитием интернет-магазинов на WooCommerce, я не помню ни одного проекта старше двух лет, где медиабиблиотека не превратилась бы в такую свалку. И дело не в лени владельцев или криворукости менеджеров контента — дело в том, что WordPress спроектирован для медиабиблиотеки так, будто на сайте будет полсотни картинок для десятка статей. Он прекрасно с этим справляется. А когда картинок сорок тысяч, и с ними работают одновременно фотограф, менеджер по контенту, дизайнер и импортёр из 1С — система, рассчитанная на полсотни файлов, начинает трещать по швам.

Свалка, в которой ничего не найти

Откройте стандартную медиабиблиотеку WordPress на любом магазине, который работает больше года. Вы увидите сетку миниатюр, отсортированную по дате загрузки, и строку поиска, которая ищет только по названию файла и по alt-тексту, если он вообще заполнен. Всё. Ни папок, ни тегов, ни возможности сгруппировать файлы по товару, по разделу каталога, по автору загрузки, по кампании, для которой делался баннер. Это библиотека без стеллажей — просто гора книг, сваленных в одну комнату в том порядке, в котором их туда занесли.

Пока магазин маленький, это работает. У вас триста товаров, триста фотографий, вы более-менее помните, что где лежит, и поиск по названию файла быстро находит нужное. Но проходит год, полтора, два. Каталог вырос до пяти тысяч позиций. Кто-то запустил блог и накидал туда двести иллюстраций. Дизайнер сделал три ребрендинга баннеров для главной страницы, и от каждого остались файлы. Менеджер по контенту, который вёлся полгода назад, использовал свою систему именования файлов — «photo1.jpg», «photo2.jpg» — а нынешний называет их по артикулу товара. В итоге у вас сорок тысяч файлов, и найти конкретную фотографию можно только если вы точно помните её имя. А если не помните — открываете сетку миниатюр и листаете. Я засекал время на одном таком магазине: чтобы найти фото конкретного товара, менеджеру требовалось в среднем полторы минуты пролистывания. На двадцать таких поисков в день — это полчаса чистого времени, потраченного на то, чтобы найти файл, который уже есть на сервере.

И вот тут возникает вопрос, который я задаю себе каждый раз, разбираясь с очередной такой свалкой: а почему, собственно, ядро WordPress за двадцать с лишним лет своего существования так и не обзавелось нормальной организацией файлов? Ответ, мне кажется, тот же самый, что и в истории с поиском по каталогу — WordPress строился как движок для блогов, а блогу папки в медиабиблиотеке действительно не очень нужны. Одна статья — три-пять иллюстраций, вставленных прямо в текст. Проблема начинается там, где WordPress используют не для блога, а для полноценного интернет-магазина с тысячами SKU, каждый из которых тянет за собой набор фотографий, документов, сертификатов. Здесь плоская структура ломается сама по себе, просто под собственным весом.

Проблема усугубляется тем, что медиабиблиотеку почти никогда не ведёт один человек. За два-три года жизни магазина через неё проходит фотограф, который снимал первую партию товаров, дизайнер, который делал баннеры для запуска, менеджер по контенту номер один, менеджер по контенту номер два, который пришёл ему на смену и завёл собственную систему именования файлов, разработчик, который заливал технические иконки для вёрстки, и импортёр из 1С, который тянет фотографии по расписанию без какого-либо участия человека вообще. У каждого из них было своё представление о том, как называть файлы и куда их класть — точнее, представления о «куда класть» не было вовсе, потому что класть было физически некуда. В итоге через пару лет никто в компании, включая владельца, не может с уверенностью сказать, есть ли у конкретного товара актуальное фото на сервере или его придётся заказывать заново.

Я специально считал стоимость такого хаоса не в гигабайтах, а в рублях и часах. Гигабайты — это копейки, современный SSD на хостинге стоит недорого, даже сорок один гигабайт лишнего груза обходится в сумму, сравнимую со стоимостью пары чашек кофе в месяц. А вот полчаса рабочего времени менеджера каждый день, потраченные на поиск файлов, которые давно потерялись в свалке — это уже совсем другие деньги на годовом горизонте, и это ещё не считая упущенных возможностей: пока менеджер листает медиатеку в поисках нужного фото, он не отвечает на заявку клиента и не готовит карточку нового товара. Диск дешёвый. Время дорогое. И именно вторая цифра обычно убеждает владельца бизнеса, что порядок в медиатеке — это не эстетическая прихоть, а вполне измеримая экономия.

Есть, конечно, плагины, которые решают эту задачу — самый известный из них называется Real Media Library, и он неплохо справляется с базовой организацией. Но у стороннего плагина есть системная проблема: он не знает о вашем каталоге товаров, о ваших заказах, о вашей 1С-синхронизации. Он умеет раскладывать файлы по папкам, и всё. Мы столкнулись с этим на нескольких проектах, где RML был установлен, и там же лежал наш плагин — и всякий раз это выглядело как два разных мозга, которые не разговаривают друг с другом: один знает про папки, другой — про товары, а связать «эта папка = эта категория каталога» руками приходится владельцу. Мы решили, что раз уж мы и так владеем всей структурой магазина — товарами, категориями, атрибутами, документами — логичнее встроить организацию медиафайлов туда же, а не заставлять владельца магазина держать в голове два независимых мысленных дерева.

Папки, которых в WordPress не предусмотрели

Мы сделали это максимально просто с архитектурной точки зрения — завели отдельную таксономию `wpaic_media_folder` прямо на типе записи `attachment`. Звучит как техническая деталь, но смысл в том, что мы не изобретали параллельную систему хранения файлов и не City нужно было переносить сами файлы на диске — мы просто дали вложениям ярлычок, к какой папке они относятся, точно так же, как товар получает ярлычок категории. Это значит, что папки в нашей медиатеке — это не файловая структура на диске, а логическая группировка поверх существующих файлов. Ничего физически не переезжает, ничего не может «потеряться» при переносе, а сам механизм ровно тот же, что WordPress использует для категорий и меток — только применённый к вложениям.

Дерево папок можно строить произвольной вложенности: «Каталог» → «Моторные масла» → «Синтетика», и внутри последней папки лежат фотографии конкретно этой товарной линейки. Каждая папка может получить свой цвет — мелочь, но когда у вас пятнадцать папок верхнего уровня, цветовая маркировка резко ускоряет визуальную навигацию, потому что человеческий глаз ищет цветовое пятно быстрее, чем читает текст. Папки можно переименовывать, перемещать между собой — при этом мы аккуратно проверяем, чтобы папка случайно не была перемещена сама в себя же, в свою дочернюю ветку, что технически возможно при неаккуратной реализации и на практике ломает всё дерево до нечитаемого состояния. Папку можно удалить, и здесь встаёт вопрос, что делать с файлами внутри — по умолчанию мы поднимаем их на уровень родительской папки, а не удаляем и не бросаем в никуда, чтобы удаление структуры не превращалось в удаление контента.

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

Что действительно оказалось важным на практике — это категория «неразобранное». Когда вы включаете модуль на магазине с уже существующими сорока тысячами файлов, все они по умолчанию оказываются вне какой-либо папки. Это создаёт понятную психологическую проблему: владелец видит цифру «39 800 файлов без папки» и у него опускаются руки, потому что раскладывать вручную сорок тысяч файлов по папкам — это работа на месяцы. Мы не обещаем волшебной автоматической раскладки, потому что честно: у нас нет способа надёжно угадать, в какую папку каталога должна попасть случайно названная фотография «IMG_4821.jpg» без привязки к товару. Но мы даём инструмент, чтобы двигаться постепенно — отфильтровать неразобранные файлы, выделить группу, перекинуть в нужную папку за один клик, и так, раздел за разделом, за несколько недель навести порядок, вместо того чтобы делать это одним подвигом на выходных.

Ещё один момент, который на первый взгляд кажется мелочью, а на практике экономит массу нервов — поиск и фильтр внутри конкретной папки. Открыв папку «Каталог → Гидравлические масла», вы можете дополнительно отфильтровать её содержимое по типу файла — только изображения, только PDF-документы — и по текстовому поиску внутри этой ограниченной выборки. Разница на первый взгляд не бросается в глаза, но она принципиальна: искать «гост» среди трёхсот файлов одной папки — секундное дело, искать «гост» среди сорока тысяч файлов всей библиотеки — это долистывание через десятки нерелевантных совпадений, потому что слово «гост» может встретиться и в имени баннера, и в старом черновике описания. Папка — это в буквальном смысле сужение контекста поиска, и именно оно превращает медиатеку обратно в инструмент, а не в архив, куда страшно заходить.

Для тех, кто уже жил с Real Media Library и решил перейти на наш модуль — например, потому что захотел единую систему без стороннего плагина, за который нужно платить отдельно и который не связан с остальной логикой магазина — мы сделали отдельный миграционный сервис. Он читает собственные таблицы RML прямо из базы данных (`wp_realmedialibrary` и `wp_realmedialibrary_posts`), сначала показывает статистику — сколько папок обнаружено, сколько файлов в них разложено, — затем переносит структуру папок в нашу таксономию с сохранением иерархии и названий, и только после этого переносит привязки файлов, пачками, чтобы не положить сервер одним огромным запросом на магазине с реально большим объёмом медиафайлов. После переноса мы сверяем количество файлов в каждой папке до и после — если где-то разошлось хотя бы на единицу, показываем расхождение прямо в отчёте, а не тихо замалчиваем. И да, у миграции есть путь назад — если что-то пошло не так, откат возвращает состояние до переноса, не трогая сами файлы на диске, потому что риск необратимо испортить единственную копию структуры для сорока тысяч файлов — это тот риск, который мы не готовы допускать ни при каких обстоятельствах.

Мусор от миниатюр: как один файл становится десятью

Вот что мало кто осознаёт, пока не увидит цифры своими глазами: когда вы загружаете одну фотографию в WordPress, на диске у вас появляется не один файл, а от шести до десяти. WordPress по умолчанию генерирует несколько стандартных размеров — миниатюру для админки, средний размер, средний-большой, большой. WooCommerce добавляет свои — миниатюру товара в каталоге, размер для одиночной карточки, миниатюру для галереи. Тема сайта нередко добавляет ещё пару собственных размеров под конкретные блоки вёрстки. И если исходная фотография была не оптимизирована — то есть пришла прямо с зеркалки фотографа в разрешении шесть тысяч на четыре тысячи пикселей и весом в двенадцать мегабайт, — то WordPress честно нарежет её на все эти размеры, и весить они всё равно будут прилично, потому что маленький файл из большого делается быстро, а вот сам исходник остаётся лежать на диске во всей своей необрезанной красе.

Давайте посчитаем на пальцах. Один товар — обычно три-пять фотографий: основное фото плюс галерея. Каждая фотография — это не один файл, а условно восемь версий с учётом всех размеров, которые генерирует связка WordPress плюс WooCommerce плюс тема. Пять фотографий на товар, умножить на восемь версий — сорок физических файлов на диске ради пяти картинок, которые видит покупатель. Умножьте это на каталог в несколько тысяч позиций, и вы получите ровно ту картину, которую я увидел на клиентском сервере с сорока одним гигабайтом медиатеки при полутора тысячах товаров.

Это не баг WordPress — это осознанное архитектурное решение, и в целом оно правильное: генерировать миниатюры на лету при каждом показе страницы было бы в разы медленнее, чем один раз нарезать их при загрузке и потом просто отдавать готовые файлы. Проблема не в самом механизме, а в том, что он никогда не чистит за собой сам. Если вы удалили товар — WordPress по-хорошему должен удалить и вложение, и все его размеры. Но если вы просто заменили фотографию через сторонний импорт, или дизайнер перезалил баннер вручную, старые размеры нередко остаются сиротами на диске — вложение к ним больше не привязано, а сами файлы никуда не делись, потому что физическое удаление файла — отдельная операция, которую надо явно вызвать, и не каждый механизм замены об этом заботится.

Мы столкнулись с этой проблемой не абстрактно, а на собственной боевой инфраструктуре, когда строили модуль автоматического обесфонивания товарных фото — отдельная история, но зацепленная за ту же самую тему замены изображений. Когда наш сервис заменяет исходный JPG на прозрачный PNG после обработки, механика продумана так: сначала делается резервная копия оригинала, затем новый файл записывается во временное имя, атомарно переименовывается на место старого, метаданные вложения пересчитываются заново — то есть WordPress честно перегенерирует все размеры под новый файл, — и только после успешной проверки, что новый URL действительно отдаёт корректное изображение, старый JPG и все его осиротевшие размеры физически удаляются с диска. Мы специально сделали удаление старых файлов последним шагом, а не первым — потому что если что-то пойдёт не так на середине процесса, лучше остаться с лишним файлом на диске, чем с товаром без изображения вообще. Место на диске стоит копейки. Битая карточка товара стоит вам покупателя.

Этот же принцип — «сначала убедись, что новое рабочее, потом чисти старое» — лежит в основе того, как вообще стоит подходить к чистке миниатюр в любой большой медиабиблиотеке. Наивный подход «найти все файлы старше года и удалить» — это рецепт катастрофы, потому что дата файла ничего не говорит о том, используется он или нет. И вот тут мы подходим к самой чувствительной части всей этой истории.

Есть и отдельная категория, про которую все забывают, пока она не пропадёт. Всё, о чём я говорил выше, касалось фотографий, и это справедливо — на большинстве магазинов картинки составляют абсолютное большинство файлов в медиатеке. Но для промышленных и B2B-каталогов есть отдельная категория, про которую вспоминают, только когда она пропадает: технические документы. Паспорта безопасности, сертификаты соответствия, технические условия, инструкции по применению, декларации — всё это тоже загружается в ту же самую медиабиблиотеку WordPress, потому что физически это тоже файлы, прикреплённые к товару, просто в формате PDF, а не JPG.

И вот с этими файлами свалка ощущается даже острее, чем с фотографиями, потому что документ нельзя узнать по превью — иконка PDF в медиабиблиотеке выглядит одинаково что для сертификата на партию 2023 года, что для просроченного паспорта безопасности пятилетней давности. Я видел магазин, где на карточке товара красовался сертификат соответствия, срок действия которого истёк два года назад — просто потому, что новый документ загрузили в медиатеку, но забыли отвязать старый файл от карточки, а найти его среди сотен одинаковых иконок PDF, чтобы вручную удалить, было решительно невозможно. Юридически это уже не вопрос удобства, а вопрос репутационного и в отдельных случаях регуляторного риска — показывать покупателю просроченный сертификат хуже, чем не показывать никакого.

Организация по папкам решает эту проблему тем же способом, что и с фотографиями — документы конкретного раздела каталога лежат в своей папке, и при обновлении сертификации у поставщика видно, какие файлы устарели и подлежат замене, просто потому что они физически сгруппированы, а не растворены среди десятков тысяч файлов вперемешку с фотографиями баннеров. А отдельный практический бонус в том, что если у вас подключён умный поиск — я подробно писал о том, как мы построили поиск на Typesense быстрее встроенного WooCommerce в пятьдесят раз — содержимое этих самых PDF-документов тоже индексируется, и инженер по закупкам может ввести номер ГОСТа или конкретного OEM-допуска, и найти товары, в чьей документации этот стандарт упоминается. Но эта функция работает настолько хорошо, насколько хорошо организованы сами документы: индексировать пять актуальных сертификатов в правильной папке — осмысленная задача, а индексировать три тысячи PDF вперемешку с устаревшими версиями — верный способ получить в выдаче поиска старый документ вместо актуального.

Безопасная чистка: не снести то, что вставлено в блок

Есть десятки бесплатных плагинов, которые обещают «найти и удалить неиспользуемые изображения одним кликом». Я относился бы к любому такому обещанию с большой настороженностью, и вот почему. Стандартное понятие WordPress «неприкреплённое вложение» — то есть файл, у которого поле `post_parent` равно нулю, — звучит логично: если файл ни к чему не привязан, значит, он не используется, верно? На практике — нет, категорически нет. Возьмём типичный интернет-магазин на нашем плагине. Изображение может быть featured-картинкой товара — оно физически не «привязано» через `post_parent`, эта связь хранится в отдельном поле `_thumbnail_id`. Оно может быть частью галереи товара WooCommerce — эта связь живёт в мета-поле `_product_image_gallery`, где перечислены ID вложений через запятую. Оно может быть просто вставлено в текст блока Gutenberg — тогда единственный след его существования — это класс `wp-image-{ID}` где-то в теле контента страницы. Ни один из этих трёх случаев не отражается в поле `post_parent`, и формально WordPress считает такое изображение «неприкреплённым». Плагин, который бездумно чистит «неприкреплённые» файлы, с равной вероятностью удалит мусорную фотографию, оставшуюся от давно забытого черновика, и главное фото хитового товара, которое просто было загружено через блочный редактор, а не через классическую форму товара.

Я видел последствия такой «чистки» своими глазами на одном проекте, куда меня позвали уже после случившегося. Владелец магазина установил популярный плагин очистки, нажал «Найти неиспользуемые изображения», увидел список из четырёх тысяч файлов и удалил их одним нажатием, не проверяя. Через два дня клиенты начали жаловаться, что на трети карточек товаров пропали фотографии в описании — потому что эти фотографии были вставлены прямо в текст описания через редактор, и `post_parent` у них не совпадал с ID товара, хотя фактически они на этом товаре использовались. Восстанавливать пришлось из бэкапа, который, к счастью, у них был — но не у всех он есть, и это отдельная больная тема, о которой я уже писал в другой раз применительно к резервному копированию магазина.

Именно поэтому наш подход к чистке построен не на формальном признаке «привязан/не привязан», а на реальном поиске использования файла по всем местам, где он может встретиться. Когда мы решаем, действительно ли изображение можно считать неиспользуемым, мы проверяем последовательно несколько источников. Сначала — прямую привязку через `post_parent`, то есть классический случай вложения к записи. Затем — является ли изображение featured-картинкой какой-либо публикации, проверяя мета-поле `_thumbnail_id` по всей базе, а не только у конкретной записи. Дальше — фигурирует ли ID изображения в галерее товара WooCommerce, для чего мы разбираем содержимое `_product_image_gallery`. И наконец — встречается ли характерный класс `wp-image-{ID}` в тексте любой публикации, страницы, товара или созданного нами спецконтента — это последнее и ловит как раз тот сценарий, который сломал карточки на проекте, о котором я рассказал выше.

Только если ни один из этих четырёх источников не находит совпадений, изображение действительно можно считать кандидатом на удаление. И даже тогда мы не удаляем его автоматически — мы показываем список таких файлов с превью и с полным перечнем того, где мы искали, чтобы владелец магазина мог визуально убедиться, что перед ним действительно мусор, а не забытая, но нужная фотография. Это на порядок медленнее, чем нажать одну кнопку «удалить всё», и я считаю это осознанным компромиссом: скорость чистки менее важна, чем гарантия, что вы не потеряете то, что реально работает на сайте. Лучше потратить лишние двадцать минут на просмотр списка кандидатов, чем восстанавливать карточки товаров из бэкапа недельной давности, теряя при этом все правки, внесённые за эту неделю.

Отдельно хочу сказать про сценарий, который часто упускают: дубликаты. Помните историю с тремя версиями одной фотографии, загруженными с разницей в год? Формально все три версии «используются» — по крайней мере одна из них точно привязана к товару. Но остальные две просто занимают место без всякой пользы. Здесь нет универсального технического решения, потому что определить визуальную идентичность двух изображений разного разрешения и сжатия — задача не для простого сравнения байтов, а для сравнения контента. Это тот случай, где я рекомендую честно смотреть глазами на группы файлов с похожими именами или близкими датами загрузки в рамках одной папки — благо после наведения порядка с папками такие группы становится видно физически, а не искать их в свалке из сорока тысяч безымянных файлов.

Я сознательно не стал обещать читателю автоматическое избавление от визуальных дублей, хотя технически можно было бы прикрутить сравнение хешей изображений и с той или иной точностью ловить похожие кадры. Причина простая: цена ошибки здесь асимметрична. Если алгоритм пропустит один дубль — вы потеряете несколько лишних мегабайт на диске, невелика беда. Если алгоритм ошибочно сочтёт дублями два разных, но визуально похожих товара — скажем, два фото канистр одного и того же бренда разной фасовки, отличающихся только цифрой на этикетке, — и предложит удалить одну из них как «дубль», это уже прямой риск для каталога. Я предпочитаю там, где цена ошибки высока, а автоматика ненадёжна, дать человеку удобный инструмент для быстрого визуального решения, а не переложить решение на алгоритм, который будет ошибаться в непредсказуемый момент.

Alt-тексты пачками и вес страницы, о котором забывают

Пока мы разбирались с диском, всплыл ещё один параметр, который я всегда проверяю на магазинах с большой медиатекой — процент изображений, у которых заполнен alt-текст. На том самом магазине с сорока одним гигабайтом медиатеки этот показатель был чуть выше двадцати процентов. То есть у восьмидесяти процентов фотографий — товарных, блоговых, баннерных — атрибут alt был либо пустым, либо содержал сгенерированное автоматически имя файла вроде «img-20230411-142207.jpg», что для поисковика и для человека с экранным диктором эквивалентно полному отсутствию описания.

Alt-текст — это не формальность для галочки в SEO-чек-листе, хотя часто его воспринимают именно так. Это единственный способ, которым поисковик и человек, использующий программу для чтения с экрана, вообще узнают, что изображено на картинке. Google Images — отдельный, немаленький источник трафика для магазинов с хорошими фотографиями товара, и без alt-текста эта картинка для поиска по изображениям попросту не существует. С точки зрения доступности — это ещё и вопрос элементарной вежливости к посетителям с нарушениями зрения, для которых экранный диктор в буквальном смысле читает содержимое alt-атрибута вместо показа картинки.

Массово проставить alt-текст руками для сорока тысяч файлов — это тот же самый разговор, что я уже вёл в статье про то, как AI генерирует описания для тысячи товаров за ночь — там я подробно разбирал экономику и механику пакетной AI-генерации текста, и не буду повторяться здесь по цифрам стоимости. Применительно конкретно к медиатеке важно другое: прежде чем бросаться генерировать alt-текст для всех подряд, стоит понять, какая доля файлов вообще нуждается в этом внимании. Мы показываем в интерфейсе простую статистику — сколько всего изображений в медиатеке, у скольких заполнен alt, у скольких пуст, и какой процент покрытия это даёт. Дальше можно отфильтровать именно пустые, и внутри этого списка ещё раз разделить на «используемые» и «неиспользуемые» — потому что нет смысла тратить токены API на генерацию описания для картинки, которая всё равно является мусорным дублем и её давно пора удалить, а не описывать. Тот же самый механизм поиска реального использования, который я описывал выше применительно к безопасной чистке, здесь работает во второй раз — сначала отфильтровать шум, потом заняться содержательной работой.

Кстати, если вам интересно, как именно alt-текст вписывается в общую картину SEO конкретной карточки товара — я подробно разбирал это в статье про автоматический аудит SEO карточек товаров: там alt-текст — лишь один из пятнадцати с лишним критериев, по которым проверяется каждая страница, наряду с длиной мета-описания, уникальностью title и заполненностью FAQ. Здесь же для меня важнее подчеркнуть другое: alt-текст без организованной медиатеки — это латание дыр по одной, а с организованной медиатекой и понятной картиной, что реально используется, — это системная работа, которую можно спланировать и закрыть волнами, а не тушить пожар там, где он вдруг разгорелся.

Сама генерация идёт пакетно, с задержкой между запросами, чтобы не упереться в лимиты API поставщика модели, и в фоне — то есть можно поставить в очередь сотни файлов и вернуться через час к готовому результату, а не сидеть и наблюдать за прогресс-баром. И, что мне кажется особенно важным, для изображений, привязанных к конкретной записи, мы в принципе можем предложить осмысленный alt-текст даже без обращения к внешнему AI — на основе названия товара, страницы или статьи, к которой файл фактически привязан, потому что зачастую этого достаточно для честного, не притянутого за уши описания, а платить за токены имеет смысл там, где действительно нужен развёрнутый, контекстно богатый текст — например, для карточек с несколькими ракурсами одного товара, где просто название явно недостаточно, чтобы различить фото спереди и фото сзади.

И раз уж мы заговорили про вес — отдельная тема, которая напрямую вытекает из истории с сорока одним гигабайтом. Проблема не только в занятом месте на диске сервера, хотя и это реальные деньги — на многих тарифах хостинга дисковое пространство ограничено, и превышение лимита либо стоит отдельных денег, либо вообще блокирует сайт до расширения тарифа. Проблема ещё и в том, что каждая неоптимизированная фотография — это лишние килобайты, которые загружает браузер каждого посетителя вашего сайта, и на мобильном соединении, особенно не самом быстром, эти килобайты напрямую конвертируются в секунды ожидания. Фотография, снятая на профессиональную камеру фотографом, который готовил её для печатного каталога, а не для сайта, легко весит четыре-шесть мегабайт в оригинальном разрешении. Такую фотографию физически невозможно грамотно «сжать на лету» без потери качества — единственный надёжный путь — заранее приводить исходники к разумному разрешению перед загрузкой, доверяя WordPress уже дальнейшую нарезку на стандартные размеры для разных мест показа, включая формирование `srcset`, чтобы браузер сам выбирал подходящий по размеру вариант под конкретное устройство, а не тянул огромный оригинал туда, где достаточно миниатюры.

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

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

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

Показательно, что почти никогда эта проблема не решается сама собой с ростом бизнеса — скорее наоборот, чем крупнее магазин и чем больше людей с ним работает, тем быстрее нарастает энтропия. Я видел, как владельцы откладывают наведение порядка в медиатеке из года в год под предлогом «сейчас не до этого, есть более срочные задачи» — и в этом есть своя логика, потому что заказы и выручка действительно важнее переставленных местами файлов. Но в какой-то момент накопленный технический долг медиатеки начинает бить по тем же самым заказам и выручке напрямую: сайт грузится медленнее из-за раздутых фотографий, менеджер тратит время на поиск вместо обработки заявок, а карточка товара без alt-текста и без актуального сертификата теряет и трафик из картиночного поиска, и доверие покупателя, который хочет убедиться, что товар действительно сертифицирован. Порядок в медиатеке — это одна из тех задач, которые почти никогда не кажутся срочными, пока не превращаются в срочные разом, все вместе, в одно неудачное понедельничное утро.

Отдельно скажу и вот что: наведение порядка не обязано быть подвигом на выходных с полной остановкой работы магазина. Мы намеренно строили модуль так, чтобы порядок можно было наводить итеративно — сегодня разобрали одну категорию, завтра почистили дубли в другой, послезавтра прогнали пакетную генерацию alt-текстов для товаров с наибольшим трафиком. Каждый такой шаг занимает от получаса до часа, и через месяц-два таких маленьких итераций накопленный за годы хаос действительно становится управляемым — без риска что-то сломать одним неосторожным нажатием кнопки «удалить всё».

Если у вас WooCommerce-магазин старше пары лет и вы ни разу не проверяли, сколько реально весит папка `uploads` и какой процент ваших фотографий имеет заполненный alt-текст — потратьте пять минут и посмотрите. Скорее всего, цифры вас удивят. А дальше решать вам — разбирать чердак по коробке за раз или найти инструмент, который покажет, где лежит хлам, а где реальные вещи.

---

Модуль Media Folders в COS WP Woo — это ровно тот инструмент: папки поверх медиабиблиотеки без переноса файлов на диске, миграция с Real Media Library с проверкой расхождений, поиск реального использования файла по вложению, featured-картинке, галерее товара и содержимому блоков, и пакетная генерация alt-текстов там, где это действительно нужно. Мы не единственный модуль в плагине, который решает узкую задачу — есть ещё смысл посмотреть на то, как один плагин заменяет собой зоопарк из двадцати расширений, потому что медиатека — лишь одна из многих точек, где WordPress без доработки начинает трещать по швам на реальном объёме интернет-магазина.