Автоматизированный аудит страниц для больших сайтов контента в оптимизации

Большие сайты содержания часто сталкиваются с значительными проблемами в поддержании видимости и контроля на тысячах страниц. Менеджеры контента изо всех сил пытаются отслеживать статус публикации, определить устаревший или неопубликованный контент и обеспечить общее качество контента. Без автоматизации аудит становится трудоемким, подверженным ошибкам и неэффективным, создавая риски в управлении, соблюдении и пользовательском опыте.

Управление контентом в масштабе предприятия сложно, потому что:

  • Ограниченная видимость – Нет консолидированного представления всего контента сайта.

  • Публикация путаницы – Трудно отслеживать то, что опубликовано, неопубликовано или в ожидании.

  • Проблемы жизненного цикла – Выявление устаревшего или истекшего контента требует ручной проверки.

  • Изменить отслеживание -Мониторинг изменений на тысячах страниц является ресурсным.

  • Экспорт данных -Ручной экспорт CSV медленные, подверженные ошибкам и не масштабируемые.

  • Высокие усилия – Аудит потребляет существенное время и административное время.

Примеры использования:

1. Запланированный отчет Экспорт:

Расширить функцию отчетов, интегрируя запланированное задание, которое автоматически экспортирует выбранную страницу и данные контента (например, заголовок, URL, автор, последняя модифицированная дата) в структурированный формат CSV или Excel для аудита и проверки контента.

2. Проверка и обновления метаданных

Определить и обновить отсутствующие или неполные метаданные (например, заголовок, описание или ключевые слова) для опубликованных страниц, чтобы поддерживать точность контента, соответствие SEO и соблюдение стандартов управления.

3. Срок действия истечения или архивного контента

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

4. Давние страницы проекта

Выделите черновые страницы, которые сидели неопубликованы в течение длительного периода. Это помогает менеджерам контента просмотреть, обновлять или удалять устаревшие черновики, чтобы обеспечить свежесть контента.

Read more:  Ozempic и другие препараты для похудения: что говорят знаменитости

5. SEO-удобные упрощенные URL-адреса

Автоматизируйте обновления для простых адресов (удобные для SEO URL-адреса), чтобы обеспечить согласованность с соглашениями об именах и улучшить видимость поиска.

6. утвержденные, но неопубликованные страницы

Определите страницы, которые были одобрены авторами или редакторами, но еще не опубликованы, что позволяет администраторам пересматривать и публиковать их эффективно.

Решение

Обычай Pagereportsjob Автоматизирует аудит страницы для больших сайтов контента. Он генерирует отчеты CSV, которые дают администраторам и менеджерам контента обзор структуры контента, статус публикации и обновлений.

Мы решаем эти проблемы с помощью автоматизированного рабочего процесса, который:

  • Автоматически сканирует и идентифицирует все типы контента и статусы.
  • Генерирует стандартизированные отчеты CSV для последовательности.
  • Эффективно обрабатывает большие наборы данных, используя пейджинг и асинхронное выполнение.
  • Обеспечивает понимание в режиме реального времени о статусе контента и обновлениях.
  • Обеспечивает доступ с разрешениями на основе ролей.
using EPiServer.Logging;
using EPiServer.PlugIn;
using EPiServer.Scheduler;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Web;
using Microsoft.Extensions.Options;
using Nito.AsyncEx;
using MyCompany.Web.Features.Reports.ExistingPages.Models;
using MyCompany.Web.Infrastructure.Cms;
using MyCompany.Web.Infrastructure.Cms.Settings;
using static MyCompany.Web.Features.Core.Constants.CommonConstants;

namespace MyCompany.Web.Features.Jobs
{
    [ScheduledPlugIn(
        DisplayName = "Page Report Job ",
        Description = "Exports existing pages to CSV for administrators and content managers",
        IntervalType = ScheduledIntervalType.None,
        SortIndex = -1004 + 'D')]
    public class PageReportJob : ScheduledJobBase
    {
        private readonly ILogger _logger;
        private readonly IMediaFileService _mediaFileService;
        private readonly IContentRepository _contentRepository;
        private readonly IContentReportQueryService _contentReportQueryService;
        private readonly IContentVersionRepository _contentVersionRepository;
        private readonly ISettingsService _settingsService;
        private readonly ExistingPagesReportOptions _options;

        private bool _stopSignaled;

        public PageReportJob (
            IMediaFileService mediaFileService,
            IContentRepository contentRepository,
            IContentReportQueryService contentReportQueryService,
            IContentVersionRepository contentVersionRepository,
            ISettingsService settingsService,
            IOptions options)
        {
            _mediaFileService = mediaFileService;
            _contentRepository = contentRepository;
            _contentReportQueryService = contentReportQueryService;
            _contentVersionRepository = contentVersionRepository;
            _settingsService = settingsService;
            _options = options?.Value ?? new ExistingPagesReportOptions();
            _logger = LogManager.GetLogger();
            IsStoppable = true;
        }

        public override void Stop() => _stopSignaled = true;

        public override string Execute()
        {
            try
            {
                return AsyncContext.Run(GenerateReports);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.StackTrace);
                return "The job stopped due to an exception.";
            }
        }

        private async Task GenerateReports()
        {
            var reportQueryTypes = Enum.GetValues(typeof(ContentReportQueryType))
                .Cast()
                .Select(v => v.ToString())
                .ToList();

            // Adding a custom query type
            reportQueryTypes.Add("NotPublishedPages");

            foreach (var reportQueryType in reportQueryTypes)
            {
                if (_stopSignaled) break;
                await CreateReport(reportQueryType);
            }

            return "Exported all pages successfully.";
        }

        private async Task CreateReport(string reportQueryType)
        {
            try
            {
                _logger.Information($"Starting report creation for type '{reportQueryType}'.");

                var csvFolderPath = _mediaFileService.GetOrCreateFolder(
                    ExistingPageReportTypes.FolderName,
                    SiteDefinition.Current.SiteAssetsRoot);

                var pageCollection = await SetPagesByTypeOfQuery(reportQueryType);

                var pages = pageCollection
                    .Select(x => ExistingPageViewModel.Make(
                        x, 
                        _contentVersionRepository.List(x.ContentLink).Where(v => v.LanguageBranch == x.Language?.Name)))
                    .Where(x => !string.IsNullOrWhiteSpace(x.Url))
                    .ToList();

                var fileName = $"{ExistingPageReportTypes.PrefixFileName}_{reportQueryType.ToLower()}";
                var media = _mediaFileService.SaveFile(
                    fileName,
                    ExistingPageReportTypes.FileExtension,
                    csvFolderPath,
                    new byte[0]);

                _mediaFileService.WriteCsv(media, pages);
                _contentRepository.Save(media, SaveAction.Publish, AccessLevel.NoAccess);

                _logger.Information($"Report '{fileName}' created and published with {pages.Count} rows.");
            }
            catch (Exception ex)
            {
                _logger.Error($"Error in CreateReport for type '{reportQueryType}': {ex}");
                throw;
            }
        }

        private async Task SetPagesByTypeOfQuery(string typeOfQuery)
        {
            var retryCounter = 0;
            do
            {
                try
                {
                    return await ReadPageDataCollection(typeOfQuery);
                }
                catch (Exception ex)
                {
                    retryCounter++;
                    _logger.Warning($"Error in SetPagesByTypeOfQuery (attempt {retryCounter}) for query '{typeOfQuery}': {ex}");
                }
            }
            while (retryCounter < 3);

            _logger.Error($"Failed to get pages by type of query '{typeOfQuery}' after 3 attempts.");
            return null;
        }

        private async Task ReadPageDataCollection(string typeOfQuery)
        {
            const int MaximumRows = 50;
            int receivedRecords, pageNumber = 0;

            var queryType = string.Equals("NotPublishedPages", typeOfQuery, StringComparison.OrdinalIgnoreCase)
                ? "ReadyToPublish"
                : typeOfQuery;

            var typeEnum = GetType(queryType);

            var startDate = (typeEnum == ContentReportQueryType.Changed && _options?.ChangedWindowDays is int days && days > 0)
                ? DateTime.Now.AddDays(-days)
                : new DateTime(1900, 1, 1);

            if (typeEnum == ContentReportQueryType.Changed)
            {
                _logger.Information($"Using ChangedWindowDays of {_options.ChangedWindowDays}, start date {startDate:yyyy-MM-dd}");
            }

            var query = new ContentReportQuery
            {
                Root = ContentReference.StartPage,
                StartDate = startDate,
                EndDate = DateTime.Now,
                PageSize = 1,
                PageNumber = pageNumber,
                TypeOfQuery = typeEnum,
                IsReadyToPublish = string.Equals("ReadyToPublish", typeOfQuery, StringComparison.OrdinalIgnoreCase)
            };

            var totalRecords = await Task.Run(() =>
            {
                _contentReportQueryService.Get(query, out int outRows);
                return outRows;
            });

            var pageDataCollection = new PageDataCollection(totalRecords);

            do
            {
                query.PageNumber = pageNumber;
                query.PageSize = MaximumRows;

                var enumerable = await Task.Run(() => _contentReportQueryService.Get(query, out int _));
                foreach (ContentReference item in enumerable)
                {
                    if (_contentRepository.Get(item) is PageData page)
                    {
                        pageDataCollection.Add(page);
                    }
                }

                pageNumber++;
                receivedRecords = MaximumRows * pageNumber + 1;
            }
            while (totalRecords > receivedRecords);

            _logger.Information($"Retrieved {pageDataCollection.Count} pages for query type '{typeOfQuery}'.");

            var filtered = ApplyGuards(pageDataCollection, typeEnum, typeOfQuery);
            return new PageDataCollection(filtered);
        }

        private IEnumerable ApplyGuards(IEnumerable items, ContentReportQueryType type, string typeOfQuery)
        {
            var now = DateTime.Now;

            return type switch
            {
                ContentReportQueryType.Published => items.Where(p =>
                    p.CheckPublishedStatus(PagePublishedStatus.Published) &&
                    p.StartPublish <= now &&
                    (!p.StopPublish.HasValue || p.StopPublish > now)),

                ContentReportQueryType.ReadyToPublish when 
                    string.Equals(typeOfQuery, "NotPublishedPages", StringComparison.OrdinalIgnoreCase) =>
                    items.Where(p => (!p.StopPublish.HasValue || p.StopPublish > now) && (!p.StartPublish.HasValue || p.StartPublish > now)),

                ContentReportQueryType.ReadyToPublish => items,
                ContentReportQueryType.Expired => items.Where(p => p.StopPublish.HasValue && p.StopPublish.Value <= now),
                ContentReportQueryType.Changed => items,
                ContentReportQueryType.SimpleAddress => items.Where(p => !string.IsNullOrWhiteSpace(p.URLSegment)),

                _ => items
            };
        }

        private ContentReportQueryType GetType(string type) =>
            type switch
            {
                "Published" => ContentReportQueryType.Published,
                "ReadyToPublish" => ContentReportQueryType.ReadyToPublish,
                "Expired" => ContentReportQueryType.Expired,
                "Changed" => ContentReportQueryType.Changed,
                "SimpleAddress" => ContentReportQueryType.SimpleAddress,
                _ => ContentReportQueryType.Published
            };
    }
}

Система автоматически генерирует шесть типов отчетов о страницах: опубликовано, не опубликовано, готова к публикации, истек, изменена и простые адресные страницы.

🧾 Пример: отчет по аудиту страницы (вывод CSV)

Заключение:
А PAGESAUDITJOB представляет собой надежное, предпринимательское решение для аудита контента и отчетности. Его сочетание запланированной автоматизации, доступности веб -интерфейса и комплексных возможностей отчетности делает его важным инструментом для поддержания качества контента и стандартов управления в системе управления контентом.

Надеюсь, этот пост поможет вам укрепить и упростить процесс аудита страницы.

Ваше здоровье! 🥂

06 октября 2025 года

По теме

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.