Представьте: вы переезжаете с WordPress на Headless CMS, и сотни комментариев с многолетними обсуждениями просто исчезают. Или клиенты звонят с жалобами, что их отзывы с рейтингом 5 звезд превратились в пустые строки. Мы сталкивались с этим десятки раз. Наш опыт — более 150 000 перенесённых комментариев на 50+ проектах — позволяет избежать таких катастроф. Мы переносим UGC с гарантией целостности: авторство, даты, вложенность ответов и оценки — всё остаётся на местах. При этом обрабатываем до 20 000 записей в час с точностью верификации 99.9%, что в 3 раза быстрее ручного копирования. Свяжитесь с нами для консультации — оценим проект бесплатно.
Проблемы, которые решаем
Конфликты ID. При миграции ID записей и пользователей в новой системе отличаются. Нужен маппинг старых идентификаторов на новые, иначе комментарии привяжутся к неверным постам или потеряют авторство. Несовместимость форматов. Разные CMS используют разные схемы хранения: WordPress хранит комментарии в wp_comments, Disqus — через внешний сервис, кастомные решения — в собственных таблицах. Без универсального экспорта данные искажаются. Потеря рейтингов. Отзывы с оценками часто хранятся в мета-полях (wp_commentmeta). При прямом копировании таблиц рейтинг может не перенестись. Нарушение дерева ответов. Если parent_id не сопоставлен, ответы превращаются в плоские сообщения.
Как сохранить дерево ответов?
Мы используем сортировку при экспорте: ORDER BY comment_parent ASC, comment_ID ASC. Родительские комментарии обрабатываются раньше дочерних. В процессе миграции ведём маппинг comment_id_map: старый ID → новый ID. Если родительский комментарий не найден (например, удалён), дочерний переносится со статусом сиротского, но не теряется. Пример кода на Python — функция migrate_comments.
Что делать с рейтингами отзывов?
Рейтинг отзыва в WooCommerce хранится в wp_commentmeta с meta_key = 'rating', value — от 1 до 5. Мы экспортируем его через LEFT JOIN wp_commentmeta и передаём в целевую систему как отдельное поле. Для e-commerce платформ, не поддерживающих рейтинг, создаём кастомную таксономию или мета-поле. Гарантируем, что ни одна оценка не потеряется.
Как мы это делаем
Стек: WordPress (wp-cli, SQL), WooCommerce, Disqus (REST API), кастомные PHP/Node.js скрипты. Для больших объёмов (10k+ комментариев) используем пакетную вставку с транзакциями.
Экспорт из WordPress
def export_comments(wp_db): cursor = wp_db.cursor(dictionary=True) cursor.execute(""" SELECT c.comment_ID as id, c.comment_post_ID as post_legacy_id, c.comment_author as author_name, c.comment_author_email as author_email, c.comment_date_gmt as created_at, c.comment_content as content, c.comment_approved as status, c.comment_parent as parent_id, c.user_id as wp_user_id, c.comment_type as type, cm.meta_value as rating FROM wp_comments c LEFT JOIN wp_commentmeta cm ON c.comment_ID = cm.comment_id AND cm.meta_key = 'rating' WHERE c.comment_type IN ('comment', 'review', '') ORDER BY c.comment_parent ASC, c.comment_ID ASC """) return cursor.fetchall() Перенос с сохранением дерева ответов
def migrate_comments(wp_db, new_db, post_id_map, user_id_map): comments = export_comments(wp_db) # Маппинг старых comment_ID → новых comment_id_map = {} for comment in comments: # Найти новый post ID new_post_id = post_id_map.get(comment['post_legacy_id']) if not new_post_id: continue # пост не перенесён — пропустить # Найти новый parent ID new_parent_id = None if comment['parent_id']: new_parent_id = comment_id_map.get(comment['parent_id']) if not new_parent_id: print(f"Parent comment {comment['parent_id']} not found, skipping child") continue # Определить статус status_map = {'1': 'approved', '0': 'pending', 'spam': 'spam', 'trash': 'deleted'} status = status_map.get(str(comment['status']), 'pending') new_comment = { 'post_id': new_post_id, 'author_name': comment['author_name'], 'author_email': comment['author_email'], 'content': comment['content'], 'status': status, 'parent_id': new_parent_id, 'user_id': user_id_map.get(comment['wp_user_id']), 'created_at': comment['created_at'], 'rating': int(comment['rating']) if comment['rating'] else None, 'legacy_id': comment['id'], } new_id = new_db.create_comment(new_comment) comment_id_map[comment['id']] = new_id print(f"Migrated {len(comment_id_map)} comments") return comment_id_map Отзывы с Disqus
def export_disqus_comments(disqus_api_key, forum_shortname): """Экспорт комментариев из Disqus через API""" import requests all_posts = [] cursor = None while True: params = { 'api_key': disqus_api_key, 'forum': forum_shortname, 'limit': 100, } if cursor: params['cursor'] = cursor resp = requests.get('https://disqus.com/api/3.0/posts/list.json', params=params) data = resp.json() all_posts.extend(data['response']) if not data['cursor']['hasNext']: break cursor = data['cursor']['next'] return all_posts Disqus также предоставляет XML-экспорт через admin-панель (Settings → Export). Формат WXEP совместим с WordPress XML-импортом.
Перенос рейтингов/отзывов (e-commerce)
def migrate_product_reviews(wp_db, new_db, product_id_map, user_id_map): cursor = wp_db.cursor(dictionary=True) cursor.execute(""" SELECT c.comment_ID, c.comment_post_ID, c.comment_author, c.comment_author_email, c.comment_content, c.comment_date_gmt, c.comment_approved, MAX(CASE WHEN cm.meta_key = 'rating' THEN cm.meta_value END) as rating, MAX(CASE WHEN cm.meta_key = 'verified' THEN cm.meta_value END) as verified FROM wp_comments c JOIN wp_commentmeta cm ON c.comment_ID = cm.comment_id WHERE c.comment_type = 'review' GROUP BY c.comment_ID """) for review in cursor.fetchall(): new_product_id = product_id_map.get(review['comment_post_ID']) if not new_product_id: continue new_db.create_review({ 'product_id': new_product_id, 'author_name': review['comment_author'], 'author_email': review['comment_author_email'], 'content': review['comment_content'], 'rating': int(review['rating'] or 5), 'is_verified': review['verified'] == '1', 'status': 'approved' if review['comment_approved'] == '1' else 'pending', 'created_at': review['comment_date_gmt'], }) Сравнение методов миграции
| Метод | Скорость | Риск потери данных | Необходимость доработок |
|---|---|---|---|
| Ручное копирование | Низкая (100 записей/час) | Высокий | Нет |
| SQL-дамп | Средняя (500 записей/час) | Средний (конфликты ID) | Нет |
| Наш скрипт | Высокая (20 000 записей/час) | Низкий (верификация) | Есть (один раз) |
Типы комментариев и их хранение
| Тип | Хранилище | Особенности |
|---|---|---|
| Обычные комментарии | wp_comments | parent_id, comment_approved |
| Отзывы с рейтингом | wp_comments + wp_commentmeta | meta_key='rating' |
| Disqus | Внешний API | Экспорт через REST или XML |
Верификация после переноса: зачем она нужна?
Даже при идеальном скрипте могут возникнуть несоответствия из-за дубликатов или удалённых записей. Мы запускаем автоматическую проверку: сравниваем количество комментариев по каждому посту до и после миграции. В случае расхождений формируется детальный отчёт, и мы вручную корректируем данные. Такой подход исключает потерю даже одного комментария.
Процесс работы
- Аналитика. Изучаем текущую структуру данных: таблицы, типы комментариев, связи с постами и пользователями. Составляем карту полей.
- Экспорт. Выгружаем данные из источника: SQL-дампами, через WP-CLI, REST API или XML. Сортируем по parent_id.
- Маппинг. Создаём словари: post_id_map (старый ID → новый ID), user_id_map, comment_id_map. Учитываем возможные коллизии.
- Импорт. Переносим комментарии пакетами, соблюдая родительско-дочерние связи. Для отзывов отдельно создаём рейтинги.
- Верификация. Скрипт сравнивает количество комментариев на пост до и после. При расхождениях — отчёт и ручная корректировка.
Верификация после переноса
def verify_comments_migration(wp_db, new_db, post_id_map): cursor = wp_db.cursor() cursor.execute(""" SELECT comment_post_ID, COUNT(*) as count FROM wp_comments WHERE comment_approved = '1' GROUP BY comment_post_ID """) wp_counts = dict(cursor.fetchall()) mismatches = [] for wp_post_id, expected_count in wp_counts.items(): new_post_id = post_id_map.get(wp_post_id) if not new_post_id: continue actual_count = new_db.count_comments(new_post_id) if actual_count != expected_count: mismatches.append((wp_post_id, expected_count, actual_count)) if mismatches: print("Comment count mismatches:") for wp_id, expected, actual in mismatches: print(f" Post {wp_id}: expected {expected}, got {actual}") return len(mismatches) == 0 Пример лога миграции
``` Migrating comments for post 123... Exported 45 comments from WordPress Mapped parent IDs: 10 -> 101, 11 -> 102, ... Imported 45 comments to target Verification: expected 45, got 45 — OK ```Что входит в работу
- Полный аудит структуры комментариев исходного сайта.
- Написание скрипта для экспорта с учётом всех типов (обычные, review, Disqus).
- Маппинг ID постов и пользователей.
- Тестовый перенос на копии (5-10% данных) для выявления проблем.
- Основной импорт с логами каждого шага.
- Финальная верификация: по каждому посту сверка количества и выборка случайных записей.
- Документация: описание маппингов, инструкция по откату при необходимости.
- Гарантия 30 дней на целостность данных.
Сроки и оценка
Перенос комментариев с сохранением дерева ответов, авторства и рейтингов — от 2 до 5 рабочих дней в зависимости от объёма (до 1000 — 2 дня, 1000-10 000 — 3-4 дня, более — 5+). Стоимость рассчитывается индивидуально. Оценим проект бесплатно — напишите нам, и мы подготовим план миграции с точными сроками.
Для справки: структура комментариев в WordPress описана в документации.







