Как найти и удалить дубликаты автоматически создаваемых записей в WordPress

Дубли в WordPress редко появляются «сами по себе». Обычно это побочный эффект импорта, повторной отправки формы, нестабильного внешнего API или криво написанного обработчика, который дважды вызывает wp_insert_post(). Внешне проблема выглядит одинаково: в админке появляются одинаковые записи, а на сайте — повторяющийся контент, лишняя нагрузка и путаница в индексации.

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

Когда это действительно дубли, а не похожие записи

Сначала стоит проверить, что вы имеете дело именно с дублями, а не с однотипными материалами. Для технической чистки важны совпадения по ключевым полям: заголовок, slug, дата создания, автор, мета-поля, источник импорта. Если совпадает только тема, но разные slug и разный контент, это уже редакционная задача, а не удаление дублей.

Типичные признаки проблемы

  • одинаковые заголовки и одинаковый post_name;
  • несколько записей появляются после одного импорта;
  • дубли создаются после повторной отправки формы или webhook;
  • в логах видно два одинаковых запроса к одному и тому же endpoint;
  • после публикации запись внезапно появляется в двух экземплярах.

Диагностика: где искать источник повторного создания

Если просто удалить дубли, они могут появиться снова. Поэтому сначала нужно понять, какой участок кода или интеграция создаёт запись повторно. В WordPress это обычно один из трёх сценариев: импорт через wp_insert_post(), REST-запрос из внешнего сервиса или форма, которая отправляется повторно из-за отсутствия защиты от дубля клика.

Проверка через логирование

Самый практичный способ — временно добавить логирование в место, где создаётся запись. Если это ваш плагин или тема, фиксируйте время, тип запроса и внешний идентификатор. Пример:

function wplite_log_post_creation( $post_id, $source = '' ) {
    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
        error_log( sprintf(
            'Post created: ID=%d source=%s uri=%s',
            (int) $post_id,
            sanitize_text_field( $source ),
            isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''
        ) );
    }
}

Если запись создаётся дважды, вы увидите два одинаковых события почти в одно и то же время. Это уже повод искать повторный вызов обработчика, а не чистить базу вслепую.

Что проверить в первую очередь

  • импортеры и синхронизации по расписанию;
  • обработчики admin-post.php и REST API;
  • webhook-обработчики от CRM, ERP или внешнего сервиса;
  • кнопки «Сохранить» в кастомных формах, которые можно нажать дважды;
  • автосохранение и пользовательский JavaScript, который отправляет форму повторно.

Как безопасно удалить дубликаты вручную и через код

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

function wplite_find_duplicate_posts_by_title( $post_type = 'post' ) {
    global $wpdb;

    $sql = $wpdb->prepare(
        "SELECT post_title, COUNT(*) AS cnt
         FROM {$wpdb->posts}
         WHERE post_type = %s
           AND post_status NOT IN ('auto-draft', 'trash')
         GROUP BY post_title
         HAVING cnt > 1",
        $post_type
    );

    return $wpdb->get_results( $sql );
}

Этот запрос показывает только группы с повторяющимися заголовками. Дальше нужно открыть каждую группу и решить, какая запись остаётся основной. Удалять всё подряд нельзя: одинаковый заголовок не всегда означает дубль.

Удаление только лишних записей

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

function wplite_get_posts_by_external_id( $external_id, $post_type = 'post' ) {
    return get_posts( array(
        'post_type'      => $post_type,
        'post_status'    => array( 'publish', 'draft', 'pending', 'private' ),
        'meta_key'       => 'external_id',
        'meta_value'     => sanitize_text_field( $external_id ),
        'numberposts'    => -1,
        'fields'         => 'ids',
        'orderby'        => 'date',
        'order'          => 'ASC',
    ) );
}

После этого можно оставить первый пост, а остальные отправить в корзину:

$ids = wplite_get_posts_by_external_id( 'abc-123', 'post' );

if ( count( $ids ) > 1 ) {
    array_shift( $ids );

    foreach ( $ids as $duplicate_id ) {
        wp_trash_post( $duplicate_id );
    }
}

Перед массовым удалением обязательно сделайте резервную копию базы. Это не формальность: при ошибке в критерии отбора можно снести нормальные записи.

Сравнение подходов: плагин, SQL и код

ПодходКогда подходитПлюсыМинусы
Плагин для поиска дублейНужно быстро посмотреть проблему без разработкиБыстрый старт, меньше риска ошибиться в SQLНе всегда учитывает внешний идентификатор и кастомную логику
SQL-запросыНужен точечный аудит и большой объём данныхГибко, быстро, можно отфильтровать по полямЛегко удалить лишнее, если ошибиться в запросе
PHP-кодДубли создаёт конкретный импортёр или интеграцияМожно встроить защиту от повторного созданияНужно понимать структуру проекта и место вызова

Как не допустить повторного появления дублей

Удаление — это только половина задачи. Вторая половина — защита от повторного создания. Самый надёжный вариант — проверять внешний идентификатор до вставки записи. Если такой ID уже есть, обновляйте существующую запись, а не создавайте новую.

function wplite_insert_or_update_by_external_id( $data ) {
    $external_id = isset( $data['external_id'] ) ? sanitize_text_field( $data['external_id'] ) : '';

    if ( ! $external_id ) {
        return new WP_Error( 'missing_external_id', 'External ID is required' );
    }

    $existing = get_posts( array(
        'post_type'   => 'post',
        'post_status' => 'any',
        'meta_key'    => 'external_id',
        'meta_value'  => $external_id,
        'fields'      => 'ids',
        'numberposts' => 1,
    ) );

    $postarr = array(
        'post_title'   => sanitize_text_field( $data['title'] ),
        'post_content' => wp_kses_post( $data['content'] ),
        'post_status'  => 'publish',
        'post_type'    => 'post',
    );

    if ( ! empty( $existing ) ) {
        $postarr['ID'] = (int) $existing[0];
        $post_id = wp_update_post( wp_slash( $postarr ), true );
    } else {
        $post_id = wp_insert_post( wp_slash( $postarr ), true );
    }

    if ( is_wp_error( $post_id ) ) {
        return $post_id;
    }

    update_post_meta( $post_id, 'external_id', $external_id );

    return $post_id;
}

Если дубли появляются из формы, добавьте защиту от повторной отправки: nonce, блокировку кнопки на фронтенде и проверку на сервере. Если проблема в cron-задаче, убедитесь, что задача не запускается параллельно на нескольких запросах.

Проверка результата после очистки

После удаления дублей важно проверить не только админку, но и поведение системы в целом. Если источник проблемы не исправлен, дубли вернутся при следующем импорте или отправке формы.

Чек-лист проверки

  • в админке осталась одна запись на один внешний объект;
  • поиск по заголовку больше не показывает повторов;
  • страница архива и sitemap не содержат лишних URL;
  • импорт или webhook создаёт только одну запись;
  • в логах нет повторных вызовов одного и того же обработчика;
  • если запись обновляется, а не создаётся заново, меняется только нужный контент.

Полезно отдельно проверить индексацию: если дубли уже успели попасть в sitemap или были опубликованы, поисковик мог их увидеть. В таком случае удалённые записи должны отдавать корректный статус, а не оставаться доступными по старым адресам.

Частые ошибки и как их исправить

Удаляют по одинаковому заголовку, не проверяя содержимое

Одинаковый title не гарантирует дубль. Перед удалением сравните контент, slug, автора, дату и мета-поля. В проектах с импортом это особенно важно: один и тот же заголовок может встречаться у разных источников.

Чистят базу, но не чинят источник

Если обработчик формы или импортёр продолжает работать без защиты, дубли появятся снова. Сначала фиксируйте причину, потом удаляйте последствия.

Используют слишком общий SQL

Запросы без фильтра по post_type и post_status легко захватывают лишние записи. На живом сайте это риск удалить не тот контент. Всегда ограничивайте выборку конкретным типом записи и статусом.

Не делают резервную копию перед массовым удалением

Даже если операция кажется простой, ошибка в критерии отбора может затронуть десятки записей. Перед чисткой базы нужен бэкап, а после — быстрая проверка нескольких случайных записей.

Что проверить в безопасности и производительности

Если дубли создаёт внешний сервис, не оставляйте endpoint открытым без ограничений. Для REST-обработчиков и webhook-ов используйте проверку подписи, nonce или хотя бы секретный токен. Иначе любой посторонний запрос сможет плодить записи в базе.

Для больших сайтов лучше не запускать массовый поиск дублей на каждом запросе. Такие проверки должны выполняться вручную, по cron или через отдельную админ-страницу. Иначе вы получите лишнюю нагрузку на wp_posts и wp_postmeta.

Если нужна системная чистка дублей, дубликатов и мусорных сущностей, удобнее вынести часть задач в отдельный инструмент вроде Clearfy Pro: он помогает закрыть типовые проблемы с дублями и технической чисткой, но логику удаления всё равно стоит проверять на своём проекте.

Как удалить или изменить метаданные продуктов WooCommerce через код: практическое руководство
03.07.2026
Как избежать проблемы с отслеживанием изменений в WordPress
25.03.2026
Как удалить дубликаты продуктов WooCommerce с помощью кода
17.05.2026
Как удалить старые и неиспользуемые мета-поля в WordPress
09.04.2026
Использование WooCommerce хуков для расширения логики оформления заказа
18.04.2026