ViKing.ApplicationFramework или как писать софт быстро

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by Kairos, 27 Jun 2013.

  1. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    Наконец-то нашел время написать эту статью. Давно уже пользуюсь, пора бы и поделиться. Итак, речь пойдет о ViKing.ApplicationFramework (писать будем на C#)

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

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

    Сначала библиотеку нужно скачать и установить. Подробнее здесь.

    Далее создаем новый проект:

    [​IMG]

    Проект состоит из основного файла с кодом Job.cs, папки lib с движком и фреймворком и нескольких стандартных файлов.

    [​IMG]

    На данном этапе при запуске мы получаем следующее окно:

    [​IMG]

    Содержимое файла Job.cs для нового проекта:

    Code:
    using ...
    
    namespace Viking_Application1
    {
        public class Job : JobBase
        {
            public override void DoWork()
            {
    
            }
        }
    }
    Давайте теперь напишем основную логику. Для начала нам понадобятся данные от пользователя: аккаунт вконтакте и номер сообщества. Для создания полей ввода нужно всего лишь задать статичное свойство. Значения свойств сами сохраняются в реестр и подгружаются при следующем запуске программы. Все делается автоматически :)

    Code:
    public static string Login { get; set; }
    public static string Password { get; set; }
    public static string Public { get; set; }
    Парсинг будет состоять из 2 шагов: нужно один раз залогиниться, и затем в несколько потоков качать и разбирать страницы. Для однопоточной инициализации перегружаем функцию StartWork() и делаем запрос на логин с глобальными куками (они потом автоматически будут подставляться в каждый поток). Обратите внимание, что для выполнения запроса тут используется локальная функция, позже я расскажу, чем она отличается от функции из Viking.Engine.

    Code:
    public override void StartWork()
    {
        Request(String.Format("https://login.vk.com/?act=login&role=al_frame&email={0}&pass={1}", Login, Password), FollowRedirects: true, cookies: GlobalCookies);
    }
    Теперь пишем код для парсинга в многопоточной функции DoWork(). Качаем список участников сообщества:

    Code:
    var ans = Request(String.Format("http://vk.com/al_page.php?act=box&al=1&offset={0}&oid=-{1}&tab=members", offset, Public)).Content;
    Вытаскиваем из него ссылки на аккаунты:

    Code:
    var akks = Regex.Matches(ans, @"fans_fan_lnk"" href=""/([^""]+)").GetGroup(1).ToList();
    Если найдено 0 акков – пора останавливать задание:

    Code:
    if (akks.Count == 0) StopJop("No more members");
    И в конце записываем полученые аккаунты:

    Code:
    File.AppendAllLines("members.txt", akks);
    Все, софт готов, можно пользоваться. Вот что получилось в результате:

    [​IMG]

    Полный исходник приложения

    PHP:
    using ...;

    namespace 
    Viking_Application1
    {
        public class 
    Job JobBase
        
    {
            public static 
    string Login getset; }
            public static 
    string Password getset; }
            public static 
    string Public { getset; }

            
    int page 0;

            public 
    override void StartWork()
            {
                
    Request(String.Format("https://login.vk.com/?act=login&role=al_frame&email={0}&pass={1}"LoginPassword), FollowRedirectstruecookiesGlobalCookies);
                
    File.Delete("members.txt");
            }

            public 
    override void DoWork()
            {
                
    int offset page++ * 60;
                var 
    ans Request(String.Format("http://vk.com/al_page.php?act=box&al=1&offset={0}&oid=-{1}&tab=members"offset, Public)).Content;
                var 
    akks Regex.Matches(ans, @"fans_fan_lnk"" href=""/([^""]+)").GetGroup(1).ToList();
                
    Log("Found {0} members"akks.Count);
                
    Stats["Members"] += akks.Count;
                if (
    akks.Count == 0StopJop("No more members");
                
    File.AppendAllLines("members.txt"akks);
            }
        }
    }
    Исходник с комментариями можно скачать тут , сам фреймворк находится здесь.

    Если у достаточного количества людей возникнет интерес к проекту, я могу активнее развивать его и делать больше примеров.
     
    #1 Kairos, 27 Jun 2013
    Last edited: 27 Jun 2013
  2. Gar|k

    Gar|k Moderator

    Joined:
    20 Mar 2009
    Messages:
    1,166
    Likes Received:
    266
    Reputations:
    82
    http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags

    Не знаю сколько раз я уже давал здесь эту ссылку... Парсить HTML плохой тон.
    Ты выпустишь программу а завтра новый верстальщик контакта Вася Пупкин зайдет и случайно в блокноте поставить пробел здесь fans_fan_lnk" __ href=" и всё, твоя программа не будет работать.

    Используй DOM parser хотябы MSHTML который есть везде где установлен IE (все равно твоя программа не кроссплатформенна).
     
    _________________________
  3. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    Во фреймворк встроен парсер HTML agility pack. Можно им пользоваться при желании, в первой статье Regex используется для простоты. ВК возвращает невалидный HTML на ajax запросы, там все равно прийдется срезать служебные символы регексом.
     
    #3 Kairos, 27 Jun 2013
    Last edited: 27 Jun 2013
  4. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    461
    Likes Received:
    38
    Reputations:
    1
    Фреймворк))))))))))))))

    А по сути:
    Я не понял, а в чем преимущество этой библиотеки?
    В вашем примере кроме пары готовых контролов я ничего не увидел. Не охота самому копаться - возможно вы могли привести другой пример, который бы более наглядно показал преимущества библиотеки?
     
  5. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    461
    Likes Received:
    38
    Reputations:
    1
    Update:

    Скачал и установил, поделюсь первым впечатлением:

    Не работает Viking.Engine.chm - отображает перечань классов и методов слева в колонке, однако при нажатии на любой из них - описание не появляется.
    На этом казалось бы можно и закончить, так как без нормального описания даже не знаешь, что и где искать.
    Но упрямства не занимать, решил посмотреть в визуальке в выпадающем списке, что там есть. И тут меня ждало второе разочарование - документация была составлена весьма кратко, толком ознакомиться не удалось. Хотел посмотреть, что есть из методов в наличии, тут следующая шпала - плохое отображение описания параметров, то пишет вместо названия переменной в выпадающем списке [int russian = 0], то [string vaiablename = null] .

    Вывод: Без нормальной документации нормально работать не получится - как будто не знаешь, что у игрушки внутри и перебираешь по ниточке.


    P.S. если пустую форму запустить и нажать на кнопку Pause, вылетает exception NullReference.
    Да и где у вас там темплейт с дизом окна лежит? Если уж где то и использовать, то хотелось бы иметь возможность интеграции диза под нужды проекта.

    Короче даешь иходники народу и запускаешь опенсорс проект.
     
  6. seosimf

    seosimf Member

    Joined:
    3 Mar 2011
    Messages:
    271
    Likes Received:
    44
    Reputations:
    6
    How to display .chm content
     
    1 person likes this.
  7. Spot

    Spot Elder - Старейшина

    Joined:
    1 Mar 2007
    Messages:
    461
    Likes Received:
    38
    Reputations:
    1

    В первый раз с таким сталкиваюсь, спасибо что просветили.

    to Kairos,
    извините, снимаю свое замечание по поводу нерабочего файла с докой. Буду завтра разбираться, что там у вас в наличии.
    По поводу остального все в силе.
     
  8. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    Фреймворк нельзя назвать многофункциональным, расширяемым или гибким. Он не предназначен для написания серьезного софта. Он довольно узкоспециализирован. Если вам нужен движок для коммерческого софта - лучше искать другие библиотеки.

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

    По поводу документации - лучше всего посмотреть еще пару статей на моем блоге, например регер почты Rambler в 15 строк кода.

    Я не ставил себе задачи сделать либу, которой все будут пользоваться. Я писал ее для себя. И да, баги там присутствуют. Сюда выложил чтобы показать свои наработки и оценить интерес к ним. Если никому не интересно - тему закроем и я просто буду продолжать пользоваться этим сам.
     
    1 person likes this.
  9. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Я тоже делал когда-то подобное для себя, но делал с помощью плагинов.
    Имелась форма по умолчанию, на ней вкладки "Главная" (общие настройки), "Статистика" (здесь выводилась стата в табличном виде), "Лог", "Справка".
    На главной вкладке находился комбобокс, а в нем происходил выбор нужного плагина. Плагины находились в подпапке plugins. Это были обычные .NET сборки, но помеченные кастомным атрибутом уровня сборки (PluginAssembleyAttribute). При запуске программы создавался домен, сборки загружались в него, далее в этих сборках искались классы реализующие интерфейс IPlugin.
    интерфейс имел вид наподобие этого:
    PHP:
    interface IPlugin
    {
      
    string Title {get;}
      
    string Description {get;}
      
    string Author {get;}
      
    string Version {get;}
      
      
    Type FormType {get;}
      
      
    void Start();
      
    void Stop();
      
    void Pause(); 
      
      
    object Settings {get;}  
      
    object Stats {get;}  

      
    event Action<StateStateChanged;
    }
    если FormType вернул тип отличный от типа дефолтной формы, то создается инстанс типа FormType и устанавливается в качестве главной формы приложения.
    свойство Settings используется для привязки настроек приложения к PropertyGrid'у. а PropertyGrid это вещь гибкая и настраиваемая, как с помощью атрибутов вроде DisplayName так и с помощью TypeDescroptor'a.
    например класс настроек мог бы выглядеть так:
    PHP:
    [DisplayName("Логин")]
    [
    Description("Логин для входа в Вконтакте")]
    [
    Category("Данные авторизации")]
    public 
    string Login getset; }

    [
    DisplayName("Пароль")]
    [
    Description("Пароль для входа в Вконтакте")]
    [
    Category("Данные авторизации")]
    public 
    string Password getset; }
    //
    Stats же привязывался как DataSource к DataGrid'у например.
     
  10. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    В таком варианте приложение, конечно, более гибкое и расширяемое. Но обьем кода плагина будет заметно больше.
     
  11. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    Kairos для многопоточных приложений имелся базовый класс плагина, который прятал в себе сложность работы с потоками и сетью. Достаточно было наследоваться от него и переопределить защищенный виртуальный метод, по семантике близкий к твоему DoWork().
    этот базовый плагин использовал внутри себя:
    ThreadManager - инкапсуляция работы с потоками в удобной форме: запуск, останов, регулирование количества запущенных потоков и т.д.;
    HttpClientManager - пул http-клиентов, сортировка прокси по качеству/скорости, обновление списка прокси, логгирование если нужно и т.д.
     
  12. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    Согласен, самые сложные вещи прячутся от пользователя. У меня тоже в начале все было более толстым: был отдельный класс для настроек и возможность делать кастомный интерфейс. Потом я заметил, что чаще всего нужно просто перегрузить Start() и написать несколько свойств в Settings. И интерфейс никогда не менялся, нужна была просто возможность редактировать свойства. В итоге было принято решение объеденить все классы в общий Job, а класс интерфейса из шаблона убрать. Было желание оставить в шаблоне нового проекта самый минимум. В итоге получился всего один класс, ничего лишнего. Статичные свойства для настроек, приватные поля для общих переменных для всех потоков и функция DoWork().
     
    #12 Kairos, 2 Jul 2013
    Last edited: 2 Jul 2013
  13. GhostOnline

    GhostOnline Active Member

    Joined:
    20 Dec 2008
    Messages:
    723
    Likes Received:
    110
    Reputations:
    22
    кстати
    я тоже юзал подобный подход (в смысле синтаксиса) для хранения статы. только это была потокобезопасная реализация IDictioanary<T, Y>. а что, удобно же: stats["SentCount"]++ :)
    в итог вообще даже начал использовать свою реализацию DynamicObject, чтобы с одной стороны не описывать каждый раз класс статы, а с другой н еприбегать к применению мерзких литералов :)
    типа такого
    PHP:
    dynamic stats = new Stats();
    stats.SentCount 0;

        class 
    Stats DynamicObject
        
    {
             
    //
        
    }
    и к этому делу удалось прикрутить привязку данных через все тот же TypeDescriptor.

    ps посмотрел твой блог. смотрю много общего есть в твоих решениях и подходах с моими. видимо через одно и то же поле с граблями проходили :)
     
  14. Kairos

    Kairos Elder - Старейшина

    Joined:
    5 Oct 2009
    Messages:
    37
    Likes Received:
    21
    Reputations:
    21
    Я чтобы не описывать каждый раз сделал Dictionary<string, StatsElement> и повесил на StatsElement несколько конвертеров и implicit операторов, и потокобезопасность в них реализовал. Получается можно делать и Stats["count"]++ и Stats["count"].VisibleIfZero = true;

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