Каждый SEO-специалист, интернет-маркетолог или директолог знает: тяжелые картинки на сайте — это гарантированный провал в Google PageSpeed Insights, долгая загрузка посадочных страниц и ухудшение поведенческих факторов.
Обычно эту проблему решают двумя путями, и у каждого есть жирные минусы:
- Онлайн-сервисы (вроде TinyPNG): имеют жесткие лимиты на количество и вес файлов в бесплатной версии, заставляют скачивать архивы, распаковывать их, а также забирают конфиденциальные макеты клиентов на свои сервера.
- Плагины для CMS (WordPress, Bitrix и др.): часто платные, нагружают базу данных сайта лишними скриптами, замедляют админку и нередко оставляют скрытые ссылки или рекламу от разработчиков в коде.
Сегодня мы решим эту проблему раз и навсегда. Мы соберем собственное локальное веб-приложение на Python, которое будет работать полностью на вашем компьютере: бесплатно, без интернета и ограничений.
Вы просто выбираете пачку картинок в браузере, пишете им нормальные названия на русском языке (внедряя ключевые слова), а программа сама мгновенно сжимает их, переводит в современный формат WebP и генерирует идеальные латинские ЧПУ-урлы для Яндекса и Google.
Что нам понадобится для работы
Для запуска инструмента вам не нужно быть программистом. Достаточно установить две бесплатные программы и два расширения.
| Компонент | Что это и где взять | Зачем это нужно в проекте |
| Python | Язык программирования. Скачивается с официального сайта python.org | Главный вычислительный «движок», который будет обрабатывать графику и текст. |
| VS Code | Удобный и бесплатный редактор кода от Microsoft. Скачивается с code.visualstudio.com | Наша рабочая среда, где мы сохраним файл и запустим наше приложение. |
| Библиотека Pillow | Модуль для Python. Устанавливается одной командой в терминале. | Отвечает за «умное сжатие»: удаляет скрытый мета-мусор из файлов и жмет вес без потери качества. |
| Библиотека Flask | Модуль для Python. Устанавливается одной командой в терминале. | Создает локальный веб-сервер, чтобы у вас в браузере открылся красивый и удобный интерфейс с кнопками. |
Шаг 1. Установка базового софта (Python и VS Code)
- Зайдите на сайт python.org, скачайте актуальную версию для Windows/Mac и запустите установщик.
- ⚠️ КРИТИЧЕСКИ ВАЖНО: В самом первом окне установщика внизу обязательно поставьте галочку напротив пункта «Add python.exe to PATH». Если этого не сделать, компьютер не поймет команды запуска! Нажмите Install Now и дождитесь окончания.
- Скачайте и установите VS Code, нажимая кнопку «Далее».
Шаг 2. Подготовка рабочей папки и установка библиотек
- Создайте на Рабочем столе обычную папку и назовите её, например, seo_converter.
- Откройте программу VS Code.
- В левом верхнем меню выберите File → Open Folder (Открыть папку) и укажите на созданную папку seo_converter.
- Теперь откроем командную строку. В верхнем меню VS Code выберите Terminal → New Terminal (Новый терминал). Внизу появится панель.
- Скопируйте следующую команду, вставьте её в терминал и нажмите Enter:
- Компьютер сам скачает нужные пакеты. Дождитесь появления финальной строки, подтверждающей успешную установку.
python -m pip install Flask pillow
Шаг 3. Создание файла приложения и код
- В левой панели VS Code нажмите на иконку листка с плюсом (New File) и назовите файл строго
web_app.py. - Скопируйте весь код, приведенный ниже, и вставьте его в созданный файл:
- Сохраните файл, нажав сочетание клавиш Ctrl + S
import os
import re
import webbrowser
from threading import Timer
from flask import Flask, request, render_template_string
import flask.cli # Импортируем модуль интерфейса Flask для отключения баннера
from PIL import Image
# Полностью отключаем страшное красное предупреждение и системный баннер Flask
flask.cli.show_server_banner = lambda *args: None
app = Flask(__name__)
# --- НАСТРОЙКИ ---
OUTPUT_FOLDER = "ready_images" # Папка для готовых SEO-оптимизированных WebP
QUALITY = 80 # Качество сжатия WebP
PORT = 5000 # Порт для локального сервера
# ------------------
if not os.path.exists(OUTPUT_FOLDER):
os.makedirs(OUTPUT_FOLDER)
# Функция правильной SEO-транслитерации
def seo_translit(text):
cyrillic_map = {
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'yo',
'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'j', 'к': 'k', 'л': 'l', 'м': 'm',
'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u',
'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch', 'ш': 'sh', 'щ': 'shch',
'ъ': '', 'ы': 'y', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya'
}
text = text.lower().strip()
result = []
for char in text:
if char in cyrillic_map:
result.append(cyrillic_map[char])
elif char.isalnum() or char == '-':
result.append(char)
elif char == ' ':
result.append('-')
res_str = "".join(result)
res_str = re.sub(r'-+', '-', res_str)
return res_str.strip('-')
HTML_INTERFACE = '''
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>SEO WebP Конвертер PRO</title>
<style>
body { font-family: 'Segoe UI', system-ui, sans-serif; background-color: #1e1e2e; color: #cdd6f4; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 20px; }
.container { background-color: #252538; padding: 40px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); text-align: center; width: 700px; }
h1 { color: #a6e3a1; margin-bottom: 5px; font-size: 26px; }
.sub { color: #a6adc8; font-size: 14px; margin-bottom: 25px; }
.file-input-label { display: block; background-color: #89b4fa; color: #11111b; padding: 14px; border-radius: 8px; cursor: pointer; font-weight: bold; margin-bottom: 20px; transition: background 0.2s; }
.file-input-label:hover { background-color: #b4befe; }
input[type="file"] { display: none; }
.file-table { width: 100%; margin-top: 20px; border-collapse: collapse; text-align: left; display: none; }
.file-table th, .file-table td { padding: 12px 10px; border-bottom: 1px solid #45475a; vertical-align: middle; }
.file-table th { color: #f5c2e7; font-size: 14px; }
.thumb { width: 50px; height: 50px; object-fit: cover; border-radius: 6px; border: 1px solid #45475a; background-color: #1e1e2e; }
.input-name { width: 92%; padding: 8px; border-radius: 6px; border: 1px solid #45475a; background-color: #1e1e2e; color: #cdd6f4; font-size: 14px; }
.input-name:focus { border-color: #a6e3a1; outline: none; }
.delete-btn { color: #f38ba8; font-size: 22px; cursor: pointer; font-weight: bold; user-select: none; transition: color 0.2s; display: block; text-align: center; }
.delete-btn:hover { color: #d20f39; }
button { background-color: #a6e3a1; color: #11111b; border: none; padding: 14px 30px; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; width: 100%; margin-top: 25px; }
button:hover { opacity: 0.9; }
.status { margin-top: 20px; padding: 15px; background-color: #313244; border-left: 4px solid #a6e3a1; border-radius: 4px; text-align: left; color: #a6e3a1; font-size: 14px; line-height: 1.6; }
.log-item { margin-bottom: 12px; border-bottom: 1px dashed #45475a; padding-bottom: 8px; }
.log-item:last-child { margin-bottom: 0; border-bottom: none; padding-bottom: 0; }
.log-stats { color: #fab387; font-size: 13px; }
</style>
</head>
<body>
<div class="container">
<h1>SEO WebP Конвертер</h1>
<div class="sub">Визуальный контроль макетов и ЧПУ-транслитерация</div>
<form method="POST" action="/upload" enctype="multipart/form-data">
<label class="file-input-label">
Выбрать исходные изображения (пачкой)
<input type="file" id="image_input" name="images" accept=".png, .jpg, .jpeg, .webp" multiple required onchange="handleFiles(this)">
</label>
<table class="file-table" id="table">
<thead>
<tr>
<th style="width: 10%;">Превью</th>
<th style="width: 30%;">Исходный файл</th>
<th style="width: 53%;">Новое название (на русском)</th>
<th style="width: 7%; text-align: center;">Удал.</th>
</tr>
</thead>
<tbody id="table_body"></tbody>
</table>
<button type="submit" id="submit_btn" style="display: none;">Сжать и перевести в WebP</button>
</form>
{% if logs %}
<div class="status">
<strong style="color: #cdd6f4; display: block; margin-bottom: 15px;">Готово! Все файлы сохранены в папку /''' + OUTPUT_FOLDER + ''':</strong>
{% for log in logs %}
<div class="log-item">• {{ log | safe }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<script>
let dtContainer = new DataTransfer();
function handleFiles(input) {
const files = input.files;
dtContainer = new DataTransfer();
for (let i = 0; i < files.length; i++) {
dtContainer.items.add(files[i]);
}
renderTable();
}
function renderTable(savedValues = []) {
const tbody = document.getElementById('table_body');
const table = document.getElementById('table');
const submitBtn = document.getElementById('submit_btn');
tbody.innerHTML = '';
if (dtContainer.files.length > 0) {
table.style.display = 'table';
submitBtn.style.display = 'block';
for (let i = 0; i < dtContainer.files.length; i++) {
const file = dtContainer.files[i];
const tr = document.createElement('tr');
const imgUrl = URL.createObjectURL(file);
const currentText = savedValues[i] || '';
tr.innerHTML = `
<td><img src="${imgUrl}" class="thumb" alt="preview"></td>
<td style="word-break: break-all; font-size: 13px;">${file.name}</td>
<td>
<input type="text" name="custom_name_${i}" class="input-name" value="${currentText}" placeholder="Введите название на русском" required>
</td>
<td>
<span class="delete-btn" onclick="removeFile(${i})">×</span>
</td>
`;
tbody.appendChild(tr);
}
} else {
table.style.display = 'none';
submitBtn.style.display = 'none';
}
document.getElementById('image_input').files = dtContainer.files;
}
function removeFile(index) {
const inputs = document.getElementsByClassName('input-name');
const savedValues = [];
for (let i = 0; i < inputs.length; i++) {
if (i !== index) {
savedValues.push(inputs[i].value);
}
}
const newDt = new DataTransfer();
for (let i = 0; i < dtContainer.files.length; i++) {
if (i !== index) {
newDt.items.add(dtContainer.files[i]);
}
}
dtContainer = newDt;
renderTable(savedValues);
}
</script>
</body>
</html>
'''
@app.route('/')
def index():
return render_template_string(HTML_INTERFACE)
@app.route('/upload', methods=['POST'])
def upload_files():
uploaded_files = request.files.getlist('images')
logs = []
for index, file in enumerate(uploaded_files):
if file.filename == '':
continue
russian_name = request.form.get(f'custom_name_{index}')
if russian_name:
clean_name = seo_translit(russian_name)
else:
clean_name = seo_translit(os.path.splitext(file.filename)[0])
if not clean_name:
clean_name = f"image_{index + 1}"
try:
file.seek(0, os.SEEK_END)
orig_size = file.tell()
file.seek(0)
orig_size_kb = orig_size / 1024
img = Image.open(file.stream)
output_file_name = f"{clean_name}.webp"
output_path = os.path.join(OUTPUT_FOLDER, output_file_name)
img.save(output_path, format='WEBP', quality=QUALITY, optimize=True)
new_size = os.path.getsize(output_path)
new_size_kb = new_size / 1024
saved_percent = ((orig_size - new_size) / orig_size) * 100 if orig_size > 0 else 0
log_text = (
f"<strong>{file.filename}</strong> → <strong>{output_file_name}</strong><br>"
f"<span class='log-stats'>Было: {orig_size_kb:.1f} KB | Стало: {new_size_kb:.1f} KB | Сжато на: {saved_percent:.1f}%</span>"
)
logs.append(log_text)
except Exception as e:
logs.append(f"<span style='color: #f38ba8;'>Ошибка при обработке {file.filename}: {e}</span>")
return render_template_string(HTML_INTERFACE, logs=logs)
def open_browser():
webbrowser.open(f"http://127.0.0.1:{PORT}")
if __name__ == '__main__':
print(f"\n[Запуск] Локальный сервер Flask стартует...")
print(f"[Успешно] Браузер сейчас откроется автоматически на http://127.0.0.1:{PORT}\n")
Timer(1.0, open_browser).start()
app.run(debug=False, port=PORT)
Шаг 4. Запуск и использование приложения
- Нажмите кнопку Play (маленький треугольник в правом верхнем углу VS Code) или введите команду
python web_app.pyв терминале и нажмите Enter. - Система выдаст лог запуска, и на вашем компьютере автоматически откроется новая вкладка в браузере по адресу
http://127.0.0.1:5000.
⚠️ Важное примечание: не пугайтесь красного текста!
При запуске скрипта в терминале VS Code вы увидите строчку яркого красного цвета: «WARNING: This is a development server...».
Не переживайте — это не ошибка, у вас всё работает правильно. Этим текстом встроенный движок Python просто сообщает, что программа запущена локально на вашем компьютере, а не на сервере в интернете. Инструмент полностью безопасен и готов к работе.
Как работает сам интерфейс:
- Вы нажимаете на синюю кнопку и выбираете пачку изображений (например, 1.png, 2.jpg, которые вы только что выгрузили из Figma или папки загрузок).
- Скрипт мгновенно строит таблицу. Слева отображается мини-превью картинки (50px) — вы точно видите, какой макет переименовываете.
- Напротив каждого файла пишите его нормальное человеческое название на русском языке (например: продвижение сайтов или настройка яндекс директ цена).
- Если вы случайно добавили лишний файл, нажмите на красный крестик «Удал.». Строка сотрется, а все остальные поля ввода сохранят ваш текст без сброса и сдвигов!
- Нажмите зеленую кнопку «Сжать и перевести в WebP».
Готово! Страница обновится и выдаст подробный аудит-лог по каждому файлу (сколько КБ весил оригинал, сколько стал весить новый WebP и на сколько процентов он сжался).
Сами готовые изображения автоматически сохраняются в созданную папку ready_images прямо внутри директории скрипта.
Подведем итоги
Мы создали полностью контролируемый, приватный инструмент автоматизации, который экономит часы рутины. Картинки сжимаются в среднем на 60–80%, текст на латинице пишется без ошибок, а на вашем сайте нет ни одного лишнего плагина. Пользуйтесь, ускоряйте свои сайты и повышайте конверсии!
Получите готовый инструмент для привлечения клиентов уже через 14 дней.