Свой консольный велосипед для проверки битых ссылок — LinkInspector

    image
    Мне понадобилось для своих сайтов запускать еженедельную проверку битых и несуществующих ссылок. Потратив пол часа на интернет-серфинг, я нашел несколько достойных консольных приложений (так как сервера у меня на Windows, то хотел использовать для этой задачи TaskSheduler). Все они оказались платные. А так как я мог выделить себе немного свободного времени, и задача на первый взгляд показалась не сложной, решил написать свое.

    Отталкиваться решил вот от этой реализации: WebSpider, но, как оно обычно бывает в конечном итоге, почти все переписал, как мне нравится.

    Составил себе небольшой список, того, что мне требуется и понемногу вычеркиваю из него таски:
    Задача Описание Статус
    Рекурсивно собрать все ссылки Пробежаться по всем страницам в рамках одного сайта и собрать все ссылки Сделано
    Проверить N ссылок В основном для отладочных целей, остановиться после проверки N ссылок Сделано
    Сохранить результат в файл Сохранение в TXT Сделано
    Сохранить результат используя html template Для удобства чтения + прикрутить плагин jquery data table для фильтрации и сортировки Сделано
    Показывать только ошибки В файл репорте показывать только битые ссылки Сделано
    Опция архивирования файла репорта Добавить поддержку 7zip Не сделано
    Посылать результат по почте Добавить поддержку консольного мейлера Не сделано
    Показывать в репорте редиректы Правильно обрабатывать все редиректы и выводить информацию о них в репорте Не сделано
    Добавить логирование Добавить библиотек Log4Net Не сделано
    Общая информация о процессе в html темплейте Показывать когда начался процесс обработки, когда закончился, и другую общую информацию в html темплейте Не сделано
    Проверить и настроить правильную обработку редиректов Не сделано
    app.config конфигурация по умолчанию Так как стало слишком много параметров для утилиты, решил, что надо сделать конфигурацию по умолчанию из app.config Не сделано


    Программа простая до безобразия:
    1. На вход подается URI, для которого скачивается контент и в контенте ищутся ссылки при помощи регулярного выражения:
    public const string UrlExtractor = @"(?: href\s*=)(?:[\s""']*)(?!#|mailto|location.|javascript|.*css|.*this\.)(?<url>.*?)(?:[\s>""'])";

    2. Все найденные ссылки, если они относятся к данному сайту, помещаются в хештейбл, где ключ — это абсолютное URI, чтобы не было дублирования.
    3. Для каждой ссылки из таблицы хешей мы создаем Request и пытаемся получить Response, и читаем возвращаемый статус:
    
     public bool Process(WebPageState state)
            {
    state.ProcessSuccessfull = false;
    
                HttpWebRequest request = (HttpWebRequest) WebRequest.Create(state.Uri);
                request.Method = "GET";
                WebResponse response = null;
    
                try
                {
                    response = request.GetResponse();
    
                    if (response is HttpWebResponse)
                        state.StatusCode = ((HttpWebResponse) response).StatusCode;
                    else if (response is FileWebResponse)
                        state.StatusCode = HttpStatusCode.OK;
    
                     if (state.StatusCode.Equals(HttpStatusCode.OK))
                    {
                        var sr = new StreamReader(response.GetResponseStream());
                        state.Content = sr.ReadToEnd();
                        
                        if (ContentHandler != null)
                            ContentHandler(state);
    
    state.ProcessSuccessfull = true;
                    }
                }
                catch (Exception ex)
                {
    //               обработка ошибок  todo: сделать отдельные catch блоки
                }
                finally
                {
                    if (response != null)
                    {
                        response.Close();
                    }
                }
                return state.ProcessSuccessfull;
            }
    


    Все остальное это красивости и энтропия.

    Из интересного: использовал для удобного парсинга консольных параметров вот этот пакет https://nuget.org/packages/ManyConsole.

    В итоге для обработки параметра, все что от меня требуется это создать вот такой вот класс:

    
    public class GetTime : ConsoleCommand
        {
            public GetTime()
            {
                Command = "get-text";
                OneLineDescription = "Returns the current system time.";
            }
    
            public override int Run()
            {
                Console.WriteLine(DateTime.UtcNow);
    
                return 0;
            }
        }
    


    P.S. И В заключении, так как проект пишу для себя и все еще в процессе, то добавил его на github https://github.com/alexsuslin/LinkInspector

    Ах, да… кому все-таки интерсно визуально посмотреть что получается в итоге, вот это в консоле:
    D:\WORK\Projects\Own\LinkInspector\LinkInspector\bin\Debug>LinkInspector.exe -u www.google.com -n=10 -ff=html -e

    Executing -u (Specify the Url to inspect for broken links.):

    ======================================================================================================
    Proccess URI: www.google.com
    Start At : 2011-12-21 04:56:09
    ------------------------------------------------------------------------------------------------------

    0/1 : [ 2.98s] [200] : www.google.com
    1/7 : [ 0.47s] [200] : accounts.google.com/ServiceLogin?hl=be&continue=http://www.google.by/
    2/6 : [ 0.22s] [200] : www.google.com/preferences?hl=be
    3/5 : [ 0.27s] [200] : www.google.com/advanced_search?hl=be
    4/7 : [ 0.55s] [200] : www.google.com/language_tools?hl=be
    5/341 : [ 0.21s] [200] : www.google.by/setprefs?sig=0_OmYw86q6Bd9tjRx1su-C4ZbrJUU=&hl=ru
    6/340 : [ 0.09s] [200] : www.google.com/intl/be/about.html
    7/361 : [ 0.30s] [200] : www.google.com/ncr
    8/361 : [ 0.21s] [200] : accounts.google.com/ServiceLogin?hl=be&continue=http://www.google.com/advanced_search?hl=be
    9/360 : [ 0.13s] [200] : www.google.com/webhp?hl=be
    ------------------------------------------------------------------------------------------------------
    Pages Processed: 10
    Pages Pending : 0
    End At : 2011-12-21 04:56:14
    Elasped Time : 0h 0m 5s 456ms
    ======================================================================================================


    или вот это вот скриншот репорта в html темплейте
    image

    P.P.S. Попросили скомпилированные бинарники, вот пожалуйста: скачать Link Inspector 0.1 alpha

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 14

      0
      Я не очень сильно шарю в сишарпе, но насколько я могу судить, вы проверяете статус ответа HttpStatusCode.OK ( то есть 200 ).
      А что будет если сервер вам ответит 301/302/403 например?
      Формально — эти ссылки не битые.
        +2
        HttpWebRequest имеет такое свойство как AllowAutoRedirect. Оно по умолчанию выставляется в true. Соотвественно мой запрос будет долбиться через все редиректы, пока не получит ответ от конечной страницы. (Кстати как вы могли заметить из статьи — правильная обработка редиректов в wish list'e).

        Так что если в конченом итоге после 301 и 302 редиректов мы получим 200 — то все хоршо.

        Что касается статуса 403 — Forbidden. То программа не сможет получить доступ к защищенному контенту и вернет мне статус Error (внутренний enum). Конечно в репорте это все отразиться и можно будет отфильтровать (если это html report) нужные нам статусы.

        Кстасти спасибо за идею, добавлю в свой to-do list, чтобы программа могла проходить аутенфикацию, если того требует функционал.
        0
        У меня вопрос. Если каждая страница сайта будет выполнять на один легкий запрос к базе данных больше, благодаря чему сам сайт не будет содержать ни одной битой ссылки с себя на себя, то это целесообразно будет или нет? Имеется в виду сравнение автоматического контроля с периодической проверкой, в первом случае повышенная нагрузка все таки.
          0
          Я не уверен, что правильно Вас понял. Попробую перефразировать:
          1. Проверка всех ссылок на сайте путем запуска вот такого вот консольного приложения с периодичностью день | неделя | месяц…
          2. На самом сайте делать проверку и писать в лог, если она неудачна при попытке доступа на страницу сайте

          Правильно? Вы просите сравнить эти 2 подхода? Я думаю у второго подхода минусов очень много:
          — Не всегда есть возможность обработать все ссылки (например IIS тогда нужно перенастраивать чтобы любой контент обрабатывал сайт, а это не хорошо для статитики, напр. картинки)
          — Идея в том, чтобы проверять ссылки на другие сайты, которые находятся на страницах нашего сайта, но такие ссылки не добавлять в очередь урлов для парсинга. В вашем случае это надо будет делать отдельно
          — Для не очень посещаемого сайта можно проводить такие проверки раз в месяц и настроить время, когда трафик на сайт минимален, во втором случае это неоправданая нагрузка на сервер

          я думаю можно еще продолжить… если придет в голову, допишу
            0
            Нет, Вы меня не правильно поняли. Я подразумеваю только ссылки с моего сайта на мой сайт. То есть если урл страницы изменился или был удален, то все ссылки на сайте ведущие на битый адрес перестанут быть ссылками и станут текстом. Это можно делать, если каждая генерируемая сервером страница будет делать на один sql запрос больше. Вот именно это того стоит?

            А индексируемые ссылки на внешние ресурсы я никогда не позволял. Нет смысла спамить — нет смысла в каптче.
              +2
              В голове сразу такой пример:

              А свежую версию программы можете скачать здесь.

              «Здесь» была сслыка на программу, но программу по этой ссылке удалили, тогда информативность этого предложения 0. Я бы даже сказал -1, так как вводит пользователя в заблуждение. Мое мнение оно того не стоит — больше вреда, чем пользы. А польза лишь в 1 случае, когда название ссылки имеет смысл в контексте контента (например статьи из википедии). На мой взгляд лучше сделать кастомный обработчик 404 статуса и как только пользователь попадает на эту страницу писать код реагирования (лог, письмо админу, заносить ошибку в БД, и т. д.)
          0
          а чем не устраивает Xenu's Link Sleuth
            0
            Изначально задача была автоматизировать такую проверку, поэтому мне нужно было запускать программу через TaskSheduler. Вот выдержка из Future List этой программы:

            Command-line parameters (actually, this has already been done, for a client who agreed to pay my development time to two people I support. If you need something similar, e-mail me, the price is a $300 donation to be sent to a person I support)
              0
              если не секрет — какая причина автоматизировать, когда можно запустить, проверить, исправить и забыть
                0
                CMS сайт обновляется не девелоперами, поэтому проверки хотя бы раз в месяц нужны, вручную можно забыть их делать.
              +1
                0
                А вот это уже интересно, спасибо за линк. Нашел бы его раньше, может бы и не изобретал бы свой велосипед
              +1
              Кстати, а если один из линков будет на например iso-шник, который будет весить 4 гига — вы его весь выкачаете или нет?
                0
                спасибо за замечание, надо добавить проверку при скачивании контента…

                1 из вариантов это вместо GET использовать HEAD и если это не файл или картинка, то делать еще 1 запрос, но уже GET… Но в таком случае мы каждую ссылку будем запрашивать 2 раза, что не есть хорошо…

              Only users with full accounts can post comments. Log in, please.