Наконец-то нашел время написать эту статью. Давно уже пользуюсь, пора бы и поделиться. Итак, речь пойдет о ViKing.ApplicationFramework (писать будем на C#) Основная цель этой библиотеки — сделать процесс написания всевозможных парсеров и накрутчиков максимально быстрым при минимальном объеме кода. Фреймворк сам генерирует интерфейс и занимается потоками, остается только написать код, обрабатывающий запросы. На практике для большинства задач достаточно создать проект и написать всего пару десятков строк кода. Давайте рассмотрим процесс работы с фреймворком на примере создания парсера подписчиков публичных страниц вконтакте. Здесь я хочу в общих чертах показать, как выглядит процесс создания простого приложения, более подробное описание функционала библиотеки можно посмотреть в моем блоге . Сначала библиотеку нужно скачать и установить. Подробнее здесь. Далее создаем новый проект: Проект состоит из основного файла с кодом Job.cs, папки lib с движком и фреймворком и нескольких стандартных файлов. На данном этапе при запуске мы получаем следующее окно: Содержимое файла 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); Все, софт готов, можно пользоваться. Вот что получилось в результате: Полный исходник приложения PHP: using ...; namespace Viking_Application1 { public class Job : JobBase { public static string Login { get; set; } public static string Password { get; set; } public static string Public { get; set; } int page = 0; 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); 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 == 0) StopJop("No more members"); File.AppendAllLines("members.txt", akks); } } } Исходник с комментариями можно скачать тут , сам фреймворк находится здесь. Если у достаточного количества людей возникнет интерес к проекту, я могу активнее развивать его и делать больше примеров.
http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags Не знаю сколько раз я уже давал здесь эту ссылку... Парсить HTML плохой тон. Ты выпустишь программу а завтра новый верстальщик контакта Вася Пупкин зайдет и случайно в блокноте поставить пробел здесь fans_fan_lnk" __ href=" и всё, твоя программа не будет работать. Используй DOM parser хотябы MSHTML который есть везде где установлен IE (все равно твоя программа не кроссплатформенна).
Во фреймворк встроен парсер HTML agility pack. Можно им пользоваться при желании, в первой статье Regex используется для простоты. ВК возвращает невалидный HTML на ajax запросы, там все равно прийдется срезать служебные символы регексом.
Фреймворк)))))))))))))) А по сути: Я не понял, а в чем преимущество этой библиотеки? В вашем примере кроме пары готовых контролов я ничего не увидел. Не охота самому копаться - возможно вы могли привести другой пример, который бы более наглядно показал преимущества библиотеки?
Update: Скачал и установил, поделюсь первым впечатлением: Не работает Viking.Engine.chm - отображает перечань классов и методов слева в колонке, однако при нажатии на любой из них - описание не появляется. На этом казалось бы можно и закончить, так как без нормального описания даже не знаешь, что и где искать. Но упрямства не занимать, решил посмотреть в визуальке в выпадающем списке, что там есть. И тут меня ждало второе разочарование - документация была составлена весьма кратко, толком ознакомиться не удалось. Хотел посмотреть, что есть из методов в наличии, тут следующая шпала - плохое отображение описания параметров, то пишет вместо названия переменной в выпадающем списке [int russian = 0], то [string vaiablename = null] . Вывод: Без нормальной документации нормально работать не получится - как будто не знаешь, что у игрушки внутри и перебираешь по ниточке. P.S. если пустую форму запустить и нажать на кнопку Pause, вылетает exception NullReference. Да и где у вас там темплейт с дизом окна лежит? Если уж где то и использовать, то хотелось бы иметь возможность интеграции диза под нужды проекта. Короче даешь иходники народу и запускаешь опенсорс проект.
В первый раз с таким сталкиваюсь, спасибо что просветили. to Kairos, извините, снимаю свое замечание по поводу нерабочего файла с докой. Буду завтра разбираться, что там у вас в наличии. По поводу остального все в силе.
Фреймворк нельзя назвать многофункциональным, расширяемым или гибким. Он не предназначен для написания серьезного софта. Он довольно узкоспециализирован. Если вам нужен движок для коммерческого софта - лучше искать другие библиотеки. Основная задача фреймворка - сделать очень быстрым написание определенного круга небольших программ, как правило одноразовых. То, что все обычно делают через консольные приложения. Скажем, увидели вы в сети опрос и захотели его по быстрому накрутить. Или понадобилось спарсить где-то контент. Открыли студию, быстро создали проект из шаблона, написали несколько строк кода - и вуаля, получили нормальное работоспособное приложение. По поводу документации - лучше всего посмотреть еще пару статей на моем блоге, например регер почты Rambler в 15 строк кода. Я не ставил себе задачи сделать либу, которой все будут пользоваться. Я писал ее для себя. И да, баги там присутствуют. Сюда выложил чтобы показать свои наработки и оценить интерес к ним. Если никому не интересно - тему закроем и я просто буду продолжать пользоваться этим сам.
Я тоже делал когда-то подобное для себя, но делал с помощью плагинов. Имелась форма по умолчанию, на ней вкладки "Главная" (общие настройки), "Статистика" (здесь выводилась стата в табличном виде), "Лог", "Справка". На главной вкладке находился комбобокс, а в нем происходил выбор нужного плагина. Плагины находились в подпапке 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<State> StateChanged; } если FormType вернул тип отличный от типа дефолтной формы, то создается инстанс типа FormType и устанавливается в качестве главной формы приложения. свойство Settings используется для привязки настроек приложения к PropertyGrid'у. а PropertyGrid это вещь гибкая и настраиваемая, как с помощью атрибутов вроде DisplayName так и с помощью TypeDescroptor'a. например класс настроек мог бы выглядеть так: PHP: [DisplayName("Логин")] [Description("Логин для входа в Вконтакте")] [Category("Данные авторизации")] public string Login { get; set; } [DisplayName("Пароль")] [Description("Пароль для входа в Вконтакте")] [Category("Данные авторизации")] public string Password { get; set; } // Stats же привязывался как DataSource к DataGrid'у например.
В таком варианте приложение, конечно, более гибкое и расширяемое. Но обьем кода плагина будет заметно больше.
Kairos для многопоточных приложений имелся базовый класс плагина, который прятал в себе сложность работы с потоками и сетью. Достаточно было наследоваться от него и переопределить защищенный виртуальный метод, по семантике близкий к твоему DoWork(). этот базовый плагин использовал внутри себя: ThreadManager - инкапсуляция работы с потоками в удобной форме: запуск, останов, регулирование количества запущенных потоков и т.д.; HttpClientManager - пул http-клиентов, сортировка прокси по качеству/скорости, обновление списка прокси, логгирование если нужно и т.д.
Согласен, самые сложные вещи прячутся от пользователя. У меня тоже в начале все было более толстым: был отдельный класс для настроек и возможность делать кастомный интерфейс. Потом я заметил, что чаще всего нужно просто перегрузить Start() и написать несколько свойств в Settings. И интерфейс никогда не менялся, нужна была просто возможность редактировать свойства. В итоге было принято решение объеденить все классы в общий Job, а класс интерфейса из шаблона убрать. Было желание оставить в шаблоне нового проекта самый минимум. В итоге получился всего один класс, ничего лишнего. Статичные свойства для настроек, приватные поля для общих переменных для всех потоков и функция DoWork().
кстати я тоже юзал подобный подход (в смысле синтаксиса) для хранения статы. только это была потокобезопасная реализация IDictioanary<T, Y>. а что, удобно же: stats["SentCount"]++ в итог вообще даже начал использовать свою реализацию DynamicObject, чтобы с одной стороны не описывать каждый раз класс статы, а с другой н еприбегать к применению мерзких литералов типа такого PHP: dynamic stats = new Stats(); stats.SentCount = 0; class Stats : DynamicObject { // } и к этому делу удалось прикрутить привязку данных через все тот же TypeDescriptor. ps посмотрел твой блог. смотрю много общего есть в твоих решениях и подходах с моими. видимо через одно и то же поле с граблями проходили
Я чтобы не описывать каждый раз сделал Dictionary<string, StatsElement> и повесил на StatsElement несколько конвертеров и implicit операторов, и потокобезопасность в них реализовал. Получается можно делать и Stats["count"]++ и Stats["count"].VisibleIfZero = true; Статсы там, конечно, сделаны криво и на коленке, но пользоваться удобно.