Реализация переноса медиафайлов при миграции сайта
Медиафайлы — изображения, документы, видео, архивы — нередко составляют десятки гигабайт. Перенос требует не только копирования файлов, но и обновления всех ссылок в контенте.
Инвентаризация медиафайлов
# Сводка по размерам и типам
find /var/www/uploads -type f | \
awk -F. '{print $NF}' | sort | uniq -c | sort -rn
# Общий размер
du -sh /var/www/uploads
# Файлы без соответствия в БД (возможный мусор)
# Получить список файлов из БД
mysql myapp -e "SELECT file_path FROM media ORDER BY file_path" > db_files.txt
# Сравнить с диском
find /uploads -type f -printf "%P\n" | sort > disk_files.txt
comm -23 disk_files.txt db_files.txt | head -50 # файлы на диске, которых нет в БД
Копирование через rsync
# Первоначальная синхронизация (можно запускать несколько раз)
rsync -avz --progress \
--checksum \
user@old-server:/var/www/uploads/ \
/var/www/new-site/uploads/
# С исключением ненужных директорий
rsync -avz \
--exclude='.git' \
--exclude='cache/' \
--exclude='tmp/' \
user@old-server:/var/www/uploads/ \
/var/www/new-site/uploads/
# Delta-синхронизация перед финальным переключением
rsync -avz --delete \
user@old-server:/var/www/uploads/ \
/var/www/new-site/uploads/
--checksum — сравнивать по контрольной сумме, не только по времени изменения.
--delete — удалять файлы, которых нет на источнике (для финальной синхронизации).
Загрузка в S3
# Загрузка с одновременностью
aws s3 sync /var/www/uploads/ \
s3://company-media-bucket/uploads/ \
--storage-class STANDARD \
--exclude "*.tmp" \
--exclude "cache/*" \
--acl public-read
# Проверить количество загруженных файлов
aws s3 ls s3://company-media-bucket/uploads/ --recursive | wc -l
Обновление URL в контенте
После переноса файлов ссылки в контенте указывают на старые URL. Нужно заменить все вхождения:
import re
import mysql.connector
def update_media_urls_in_content(db_conn, old_base, new_base):
cursor = db_conn.cursor()
# Найти все поля с медиа-ссылками
tables_columns = [
('posts', 'content'),
('posts', 'excerpt'),
('pages', 'body'),
('users', 'avatar_url'),
]
for table, column in tables_columns:
cursor.execute(f"SELECT id, {column} FROM {table} WHERE {column} LIKE %s",
(f'%{old_base}%',))
rows = cursor.fetchall()
for row_id, content in rows:
if content:
new_content = content.replace(old_base, new_base)
cursor.execute(
f"UPDATE {table} SET {column} = %s WHERE id = %s",
(new_content, row_id)
)
db_conn.commit()
print(f"Updated {cursor.rowcount} rows")
update_media_urls_in_content(
db_conn,
'https://old-site.com/wp-content/uploads',
'https://new-site.com/uploads'
)
Для WordPress: плагин Better Search Replace или SQL:
UPDATE wp_posts
SET post_content = REPLACE(post_content,
'https://old-site.com/wp-content/uploads/',
'https://cdn.new-site.com/uploads/')
WHERE post_content LIKE '%old-site.com/wp-content/uploads/%';
-- Не забыть про postmeta (thumbnail URLs)
UPDATE wp_postmeta
SET meta_value = REPLACE(meta_value,
'https://old-site.com',
'https://new-site.com')
WHERE meta_value LIKE '%old-site.com%';
Оптимизация при переносе
Миграция — хорошее время для оптимизации изображений:
from PIL import Image
import os
def optimize_images(source_dir, target_dir):
for root, dirs, files in os.walk(source_dir):
for filename in files:
if not filename.lower().endswith(('.jpg', '.jpeg', '.png')):
continue
source_path = os.path.join(root, filename)
rel_path = os.path.relpath(source_path, source_dir)
target_path = os.path.join(target_dir, rel_path)
os.makedirs(os.path.dirname(target_path), exist_ok=True)
img = Image.open(source_path)
# Конвертировать в WebP
webp_path = os.path.splitext(target_path)[0] + '.webp'
img.save(webp_path, 'WEBP', quality=85, method=6)
# Сохранить оригинал с оптимизацией
img.save(target_path, optimize=True, quality=85)
Верификация после переноса
import requests
import hashlib
def verify_file_integrity(source_server, dest_server, file_list):
errors = []
for path in file_list:
# Скачать с обоих серверов
src = requests.get(f"{source_server}/{path}")
dst = requests.get(f"{dest_server}/{path}")
if src.status_code != 200:
errors.append(f"Source missing: {path}")
continue
if dst.status_code != 200:
errors.append(f"Destination missing: {path}")
continue
src_hash = hashlib.md5(src.content).hexdigest()
dst_hash = hashlib.md5(dst.content).hexdigest()
if src_hash != dst_hash:
errors.append(f"Checksum mismatch: {path}")
return errors
301-редиректы для старых медиа URL
Если URL медиафайлов изменились, настроить nginx:
# Редирект старых путей WP
location ~* ^/wp-content/uploads/(.*)$ {
return 301 /uploads/$1;
}
# Или через map для индивидуальных редиректов
map $uri $media_redirect {
/wp-content/uploads/2022/01/image.jpg /uploads/2022/01/image.webp;
# ...
}
Срок выполнения
Перенос медиафайлов с обновлением ссылок в контенте для сайта объёмом до 10GB — 2–3 рабочих дня.







