Автор: Kairos

Описание работы ViKing.ApplicationFramework

Описание работы ViKing.ApplicationFramework

Давайте теперь разберемся в работе ViKing.ApplicationFramework более детально. В этой статья я попытался подробно описать работу фреймворка, поэтому статье получилось скучной и похожей на документацию. Тем не менее рекомендую ее прочитать, чтобы понять как все работает.

Основные функции

Основной класс Job состоит из нескольких функций (для использования их нужно перегрузить):

  • StartWork
  • DoWork
  • FinishWork

Вот что происходит при нажатии кнопки старт:

  • Создается новый инстанс класса Job
  • Инициализируются все поля
  • Выполняется конструктор класса (нам он не понадобится)
  • Выполняется функция StartJob
  • Запускается цикл, который бесконечно создает потоки (сколько задано пользователем) и выполняет на них функцию DoWork, до тех пор, пока не будет вызвана функция StopJob или юзер нажмет на стоп. Кнопка HardStop в интерфейсе вызывает Thread.Abort на всех потоках
  • После этого новые потоки не запускаются и фреймворк ждет, когда старые закончат выполняться
  • После этого вызывается функция FinishWork, в которой можно обработать и сохранить результат работы. Она будет вызвана даже если пользователь отменил выполнение задания

Также есть функции ApplicationStartup и ApplicationShutdown, которые выполняются при запуске и закрытии приложения.

Настройки

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

[PropertyDescription(Caption="Enter text", DefaultValue="example")]
public static string Prop { get; set; }

Для хранения текущего состояния задачи рекомендую использовать локальные поля и инициализировать их сразу при описании.

private ResourceManager<string> links = new ResourceManager<string>("links");

Во фреймворке есть свойства GlobalCookies и ThreadCookies. Эти куки подставляются в каждый запрос, сделанный функцией Request, если в ней явно не указан другой контейнер с куками. GlobalCookies общие для всех потоков, а ThreadCookies для каждого потока свои. То же самое и с проксями GlobalProxy и ThreadProxy.

Дополнительные инструменты

При работе с прокси рекомендую пользоваться встроенным менеджером. Для его включения нужно повесить атрибут [GuiSettings(UseProxies=true)] к классу Job. Он автоматически создаст необходимые элементы интерфейса, инициализирует ProxyManager, и будет выдавать каждому потоку в DoWork новый ThreadProxy. Если на потоке возникает ошибка, то она приписывается к текущему прокси. Если у сервера много ошибок, то он выкидывается из менеджера.

Аналогичным образом включается antigate. Добавляем [GuiSettings(UseAntigate=true)], если нужно включить антигейт и прокси, все записывается в одном атрибуте. Разгадывать капчу нужно с помощью функции PostCaptcha, при выключенном антигейте она просто будет выдавать окно с капчей пользователю.

Пример их использования есть в статье регер почты Rambler в 15 строк кода.

Списки и статистика

При выполнении задания юзеру показывается статистика. Показывать ее можно с помощью объекта Stats, например Stats[“accounts”]++, инициализировать ничего не надо.

Для записи файлов удобно использовать FileWriter. Он сам следит за статсами и удобен в работе:

FileWriter w = new FileWriter("file");
w.AppendLine("test");

При указании имени файла автоматически добавляется расширение .txt (если его нет). В статистике будет отображаться количество записанных строк.

Для чтения используется ResourceManager<T>. Остановимся на нем подробнее. Этот класс представляет из себя потокобезопасную очередь. Для инициализации используется конструктор, в который передается список объектов либо имя файла, имя статсов (если не указано будет совпадать с именем файла) и следует ли останавливать задание если список пуст. Для удобства у всех коллекций IEnumerable есть метод ToResourceManager(). Вытаскиваются из списка объекты с помощью метода Pop, а с помощью метода Push можно запихать их обратно.

Если вы хотите создать хитрую очередь (например циклическую), это тоже можно сделать, подписавшись на событие ItemPopping. В него передается очередной элемент и хэндлер должен решить, стоит ли его возвращать и стоит ли удалять. Если элемент не возвращается, то ResourceManager перейдет к следующему элементу. Например для менеджера проксей:

ResourceManager<ProxyWithStats> m = new ResourceManager<ProxyWithStats>();
m.InsertTakenToEnd = true;
m.ItemPopping += (o, e) =>
{
    e.Take = e.Item.ErrorsCount < ProxyWithStats.MaxErrors;
    e.Remove = !e.Take;
};

При вызове m.Pop хэндлер либо вернет первый прокси, либо удалит его из списка и попробует вернуть следующий (либо коллекция окажется пустой). Что если вы не возвращаете элемент, то удалять его необязательно и наоборот, можно удалять возвращенные элементы.

На этом пока все, рекомендую посмотреть пару более практических статей по фреймворку и попробовать что-то сделать самому.

Регер почты Rambler в 15 строк кода

Регер почты Rambler в 15 строк кода

Многопоточный. С проксями и антигейтом. И нормальным интерфейсом и статистикой. Пишется с помощью ViKing.ApplicationFramework.

Регистратор почты Rambler

Я, конечно, не считал комментарии, пустые и автоматически сгенерированные строки (в новом ConsoleApplication уже 15 таких строк). В этом уроке я буду больше фокусироваться на работе фреймворка, спарсить запросы к рамблеру вы думаю уже умеете. Кстати, мы уже писали mail регер rambler.ru, там был подробный разбор, что делать с запросами. С тех пор сайт изменился, но принципы все те же.

Прокси и антигейт

Во фреймворке уже есть поддержка и того и другого. Для того чтобы включить их достаточно добавить к классу Job атрибут с настройками

[GuiSettings(UseAntigate=true, UseProxies=true)]
public class Job : JobBase

Фреймворк автоматически добавит нужные поля в интерфейс. Если ввести ключ antigate, то функция PostCaptcha вместо вывода юзеру окна с капчей будет отправлять ее на antigate. При включении проксей инициализируется ProxyManager. Он подставляет новый прокси в каждый поток. Если на потоке возникает ошибка, то она приписывается к текущему прокси. Если у сервера много ошибок, то он выкидывается из менеджера.

Атрибут с настройками также есть и у свойств. Например, мы можем создать свойство для файла с акками и указать значение по умолчанию и надпись в интерфейсе

[PropertyDescription(Caption="Akks file", DefaultValue="mail")]
public static string AkksFile { get; set; }

Для записи файла акков будем использовать класс FileWriter. Он ведет себя как StringBuilder, только пишет в файл и ведет статистику

FileWriter akks = new FileWriter(AkksFile);

Теперь осталось написать только код для регистрации. С прошлого урока мы помним, что у каждого потока автоматически поддерживаются свои куки и прокси, так нам ничего не нужно указывать, просто писать запросы.

Регистрация почты

Сначала нужно скачать страницу регистрации, и вытащить оттуда ключ сессии, по нему мы будем регистрироваться

var doc = Request("http://id.rambler.ru/profile/create").GetHtmlDocument();
var key = doc.SelectSingleNode("//input[@name='request.key']").Attributes["value"].Value;

Дальше нужно заполнить первую страницу регистрации

var post = String.Format("request.key={0}&action=step2&profile.firstname=qweqwe&profile.lastname=qweqwe&profile.username={1}&profile.domain=rambler.ru", key, login);
doc = Request("http://id.rambler.ru/profile/create", post).GetHtmlDocument();

Вытаскиваем адрес капчи и разгадываем ее

var img = doc.GetElementbyId("secimage").Attributes["src"].Value;
var captcha = PostCaptcha("http://id.rambler.ru" + img);

И заполняем вторую страницу регистрации

post = String.Format("request.key={0}&action=step3&profile.password1={1}&profile.password2={1}&captcha.code={2}&profile.question=qweqwe&profile.customquestion=qweqwe&profile.answer=qweqwe&profile.gender=m&profile.birthday.day=13&profile.birthday.month=12&profile.birthday.year=1985", key, pass, captcha);
var ans = Request("http://id.rambler.ru/profile/create", post);

На всякий случай проверим, что регистрация успешна и запишем акк в файл

if (ans.Headers["Location"] != "/checkcookie") throw new Exception("Not registered");
akks.AppendLine(String.Format("{0}:{1}", login, pass));

Вот и все, авторегер готов. Скачать исходник и скомпилированную прогу можно тут.

upd: Обновил совместимость с .NET 4.0 и 4.5

ViKing.ApplicationFramework

ViKing.ApplicationFramework

Наконец-то нашел время написать эту статью. Давно уже пользуюсь, пора бы и поделиться. Итак, речь пойдет о ViKing.ApplicationFramework

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

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

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

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

new viking framework project

Проект состоит из основного файла с кодом Job.cs, папки lib с движком и фреймворком и нескольких стандартных файлов. На данном этапе при запуске мы получаем следующее окно: framework_vk_parser1

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

using ...

namespace Viking_Application1
{
    public class Job : JobBase
    {
        public override void DoWork()
        {

        }
    }
}

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

public static string Login { get; set; }
public static string Password { get; set; }
public static string Public { get; set; }

Парсинг будет состоять из 2 шагов: нужно один раз залогиниться, и затем в несколько потоков качать и разбирать страницы. Для однопоточной инициализации перегружаем функцию StartWork() и делаем запрос на логин с глобальными куками (они потом автоматически будут подставляться в каждый поток). Обратите внимание, что для выполнения запроса тут используется локальная функция, позже я расскажу, чем она отличается от функции из Viking.Engine.

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(). Качаем список участников сообщества:

var content = Request(String.Format("http://vk.com/al_page.php?act=box&al=1&offset={0}&oid=-{1}&tab=members", offset, Public)).Content;

Вытаскиваем из него ссылки на аккаунты:

var akks = content.Matches(@"fans_fan_lnk"" href=""/([^""]+)").GetGroup(1).ToList();

Если найдено 0 акков – пора останавливать задание:

if (akks.Count == 0) StopJop("No more members");

И в конце записываем полученые аккаунты:

File.AppendAllLines("members.txt", akks);

Все, софт готов, можно пользоваться. Вот что получилось в результате: framework_vk_parser2

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 content = Request(String.Format("http://vk.com/al_page.php?act=box&al=1&offset={0}&oid=-{1}&tab=members", offset, Public)).Content;
            var akks = content.Matches(@"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);
        }
    }
}

Я понимаю, что к концу прочтения поста могло возникнуть много вопросов. Этот пост в большей степени является рекламой фреймворка. В следующей статье я более подробно расскажу как все работает.

Исходник с комментариями можно скачать тут, сам фреймворк находится здесь.

upd: Обновил совместимость с .NET 4.0 и 4.5

Устали каждый раз писать куки и прокси в запросе?

Устали каждый раз писать куки и прокси в запросе?

Специально для этого в движке есть функция VkRequest.StaticRequest(). Она делает то же самое, что и обычная функция Request, но при этом не нужно указывать прокси и куки. Они берутся из специальных свойств, которые подразделяются на 2 набора. Первый:

  • GlobalProxy
  • GlobalCookies
  • GlobalHeaders

Значения этих свойств будут использоваться во всех вызовах функции StaticRequest. То есть достаточно один раз установить свойства

VkRequest.GlobalProxy = ...;
VkRequest.GlobalCookies = new CookieCollection();

И просто пользоваться функцией StaticRequest, указанный прокси будет использоваться автоматически и куки будут передаваться между всеми запросами.

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

  • ThreadProxy
  • ThreadCookies
  • ThreadHeaders

Это особые ThreadStatic cвойства. Это означает, что для каждого потока это свойство имеет разное значение. В остальном они работают так же как и предыдущий набор свойств. ThreadProxy и ThreadCookies имеют больший приоритет, чем свойства из предыдущего набора. То есть если указать GlobalProxy и ThreadProxy, то движок будет использовать ThreadProxy. Заголовки GlobalHeaders и ThreadHeaders работают немного по другому, движок объединяет оба эти набора в один. При этом если встречается 2 заголовка с одинаковым именем, то используется заголовок из набора ThreadHeaders.

Smile

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

Урок 8 — Как залить картинку

Урок 8 — Как залить картинку

Сегодня мы научимся заливать картинки. На большинстве серверов для этого используется формат multipart/form-data, который является довольно громоздким для написания вручную. Благодаря Viking.Engine загрузка картинок происходит так же просто как и обычный запрос.

Итак, давайте посмотрим как все это выглядит. Для примера будем заливать картинки на http://radikal.ru. Запрос, который браузер посылает при загрузке картинки выглядит вот так:

POST http://www.radikal.ru/action.aspx HTTP/1.1
Host: www.radikal.ru
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------4827543632391
Content-Length: 4760

-----------------------------4827543632391
Content-Disposition: form-data; name="upload"

yes
-----------------------------4827543632391
Content-Disposition: form-data; name="F"; filename="Error.png"
Content-Type: image/png

[данные картинки]
-----------------------------4827543632391
Content-Disposition: form-data; name="M"

640
-----------------------------4827543632391
Content-Disposition: form-data; name="JQ"

85
-----------------------------4827543632391
Content-Disposition: form-data; name="IM"

7
-----------------------------4827543632391
Content-Disposition: form-data; name="VM"

180
-----------------------------4827543632391
Content-Disposition: form-data; name="R"

0
-----------------------------4827543632391
Content-Disposition: form-data; name="VE"

yes
-----------------------------4827543632391--

Основные отличия от обычного запроса это заголовок Content-Type: multipart/form-data; boundary=… и формат POST-данных. В запросах такого формата каждый парамер идет со специальным разделителем и может иметь свои заголовки. Аналогичный простой POST-запрос выглядел бы вот таким образом:

POST http://www.radikal.ru/action.aspx HTTP/1.1
Host: www.radikal.ru
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded;
Content-Length: 4230

upload=yes&M=640&JQ=85&IM=7&VM=180&R=0&VE=yes&F=[данные картинки]

Согласитесь, выглядит гораздо короче. Я тоже так подумал, и написал класс, который преобразует обычные запросы в multipart/form-data формат. Вот как им пользоваться:

Image img = Image.FromFile(@"...");
string post = MultipartContentFormatter.MakeString("upload=yes&M=640&JQ=85&IM=7&VM=180&R=0&VE=yes&F={0}", img);
var ans = VkRequest.Request("http://www.radikal.ru/action.aspx", post, additionalHeaders: MultipartContentFormatter.Header);

Функция MakeString() преобразует обычный запрос в multipart-формат. Ее использование аналогично String.Format(), при этом в параметрах можно вставлять классы Image и byte[]. Если вам понадобится больше функциональности (например указать формат картинки или дополнительный заголовок), то можно создать новый экземпляр этого класса и использовать его по аналогии со StringBuilder.

Обратите также внимание, что в запросе присутствует дополнительный заголовок MultipartContentFormatter.Header, он всегда равен multipart/form-data; boundary=b358fdj4ha19fdk

Вот и все на сегодня, дальше думаю сами легко разберетесь. Вот исходник урока: RadikalUploader

Движок теперь поддерживает HTTPS!

Движок теперь поддерживает HTTPS!

Вышла новая версия Viking.Engine, теперь можно делать https запросы! Для того чтобы этим воспользоваться достаточно просто сделать запрос к https странице, все работает само. При этом только следует учесть, что далеко не все HTTP проски могут работать с HTTPS, так что советую для этого использовать соксы.

Еще появилась возможность жаловаться на неправильно разгаданную капчу. Делается это вот так:

string captchaId;
VkRequest.PostCaptcha("http://website.com/captcha?sid=234fre", out captchaId);
// Узнаем что капча неправильная
VkRequest.ReportBadCaptcha(captchaId);

И последнее, что хочу отметить – новый параметр запроса: FollowRedirects, который позволяет автоматически переходить по редиректам.

VkRequest.Request("http://vkontakte.ru/", FollowRedirects: true);

Все подробности про новую версию можно узнать на странице движка.

Вышла новая версия движка и help!

Вышла новая версия движка и help!

На самом деле новые версии выходили вместе с каждым уроком, и из-за этого могла возникать некоторая путанница. Теперь все будет подругому – появилась страница с историей версий, на которой будут публиковаться все новые версии с указанием изменений. А еще теперь к движку можно скачать помощь и XML-документацию. К сожалению, я пока что описал далеко не все классы, но помощь тоже будет обновляться и современем станет полной. Для тех кто не знает: XML-документация нужна для того, чтобы Visual Studio могла выводить всплывающие подсказки к функциям движка. Чтобы она работала просто киньте .xml файл в папку к .dll

Урок 7 – работаем с почтой

Урок 7 – работаем с почтой

Зачастую чтобы зарегистрироваться на каком-то сайте нужно выполнить активацию по почте: зайти на свой ящик, найти там письмо со ссылкой и перейти по ней. Сегодня мы научимся работать с почтой через протокол POP3. Этот протокол используют все известные почтовые клиенты и он довольно прост в обращении.

Сегодня мы потренируемся на примере сервиса mamba.ru, там как раз необходима активация по почте. Для начала заведем себе почту, например в рамблере. У нас как раз уже есть регистратор акков для rambler.ru. После этого регаем акк в мамбе и видим такое сообщение:

Давайте теперь напишем прогу, которая найдет нужное письмо. Все классы, необходимые для работы с почтой находятся в пространствах имен ViKing.Engine.Mail.Pop3 и ViKing.Engine.Mail.Mime. Нам сейчас нужно только первое. Для начала нужно подключиться к почтовому ящику:

Pop3Client pop = new Pop3Client()
pop.Connect("pop.rambler.ru", 110);
pop.Authenticate("0r3zgv00h5t", "t1nz4rvoefm", AuthenticationMethod.UsernameAndPassword);

Создаем новый Pop3Client, соединяемся с сервером pop.rambler.ru по порту 110 и авторизуемся используя имя (адрес ящика) 0r3zgv00h5t и пароль t1nz4rvoefm. 110 — стандартный порт для протокола POP3, если вы используете шифрование SSL, то порт обычно 995. Адрес почтового сервера почти у всех почтовых сервисов pop.[основной домен] (например pop.mail.ru, pop.rambler.ru). Точный адрес и номер порта можно прочитать в руководстве по настройке почтовых программ на вашем почтовом сайте. Есть еще такой нюанс: некоторым почтовым сервисам в качестве имени пользователя нужно указывать полный адрес ящика, то есть не «0r3zgv00h5t», а «[email protected]».

Теперь давайте искать письмо от «[email protected]»:

int count = pop.GetMessageCount();
for (int i = 1; i <= count; i++)
{
	var mes = pop.GetMessageHeaders(i);
	if (mes.From.Address == "[email protected]")
	{
		...
	}
}

Запрашиваем у ящика количество писем и делаем по ним цикл. Внимание! В протоколе POP3 письма нумеруются начиная с 1, а не с 0. Соответственно мы используем цикл от 1 до count включительно(!). Также следует помнить, что у некоторых почтовых сервисов сначала идут самые старые письма, а у некоторых сначала новые. У рамблера, как и у большинства, новые письма вначале. И еще, если вы вдруг залезете в используемый ящик, советую ограничить количество скачиваемых писем. Например в моем ящике их около 15 000.

Дальше мы качаем заголовки писем (заголовки весят очень мало, в отличие от самого письма) и ищем письмо от «[email protected]». После того, как письмо найдено, нужно скачать его целиком и достать нужную ссылку:

string text = pop.GetMessage(i).FindFirstHtmlVersion().GetBodyAsText();
string link = Regex.Match(text, @"href=""(http://mamba.ru/tips[^""]+)""").Groups[1].Value;

В письме часто содержатся несколько версий — текстовая и html. Мы берем html версию и преобрезуем ее в строку, после чего применяем Regex, который ищет ссылку, начинающуюся на «http://mamba.ru/tips». «[^»]+» означает продолжать, пока не встретятся кавычки, а Groups[1].Value содержит только текст, заключенный в круглые скобки.

Вот собственно и все. В работе с почтой нет ничего сложного, особенно когда под рукой есть такая вещь как ViKing.Engine. Напоследок хочу отметить еще один момент. Наша программа работает с Pop3Client, который в свою очередь использует сокеты. Сокеты — это unmanaged-ресурcы и нам нужно позаботиться, чтобы эти ресурсы освободились после того, как мы закончим с ними работать. Иначе если ваша программа будет многопоточной, то она либо сожрет всю память, либо достигнет лимита открытых сокетов (сами по себе они не закрываются). Самый простой способ сделать это — заключить наш код в блок using:

using (Pop3Client pop = new Pop3Client())
{
	// код
}

Это замечание относится не только к Pop3Client, но и ко всем другим unmanaged-ресурсам, таким как файловые потоки, GDI+ ресурсы и т.п. Подробнее о using и IDisposable можно посмотреть на MSDN

На этом заканчиваю, исходник урока: ActivateMail.rar

Урок 6 — парсим HTML

Урок 6 — парсим HTML

Часто нам бывает нужно вытащить из веб-страницы какую-то информацию, например профиль из социальной сети. Многие до сих пор делают это с помощью регулярных выражений (или вообще с помощью поиска подстроки). В простейших случаях это работает, но в целом подход неправильный. Тут мне сразу же вспоминается известное обсуждение на Stackoverflow. Кстати говоря, полностью спарcить HTML с помощью регулярных выражений невозможно в принципе, т.к. это языки разных уровней в иерархии Хомского. К счастью в наш движок уже встроен парсер HTML, остается только им воспользоваться. И в этой статье я собираюсь показать как это делается на примере профиля Павла Дурова ВКонтакте.

Итак, начнем. Воспользоваться парсером довольно просто:

var doc = VkRequest.Request("http://vkontakte.ru/durov").GetHtmlDocument();

Все. Теперь у нас в переменной doc полностью спарсенный документ, представляющий собой древовидную структуру тегов. Все классы, связанные с парсингом находятся в пространстве имен ViKing.Engine.Html, но чтобы не прописывать его через using мы будем использовать ключевое слово var. Давайте для примера найдем адрес аватарки. Открываем страницу Дурова в Firefox и включаем Firebug. Смотрим на код для аватарки:

<div id="profile_avatar"><a id="profile_photo_link" onclick="бла-бла-бла" href="/photo1_205600277"><img src="http://cs1495.vkontakte.ru/u00001/a_a806ae7c.jpg" alt="" /></a></div>

Отсюда видно, что изображение аватара, находится внутри тега a (ссылка) c id="profile_photo_link". Вот так выглядит код для получения адреса картинки:

string avatar = doc.GetElementbyId("profile_photo_link").ChildNodes[0].Attributes["src"].Value;

Сначала мы находим элемент с id=profile_photo_link, затем обращаемся к массиву дочерних элементов. Он содержит 1 элемент — img. После этого мы берем значение атрибута src, который и указывает на адрес изображения. Если нужно получить саму картинку, то ее придется скачать:

Image avatarImage = VkRequest.Request(avatar).GetImage();

Следующий пример — получение всех постов со стены Павла. Текст поста на стене выглядит примерно так:

<div class="wall_post_text">Текст на стене</div>

Код для их получения:

var postNodes = doc.GetElementsByClassName("wall_post_text");List posts = new List();foreach (var item in postNodes){posts.Add(item.InnerText);}

Тут все очень просто. Мы находим на странице все элементы класса wall_post_text и берем у них свойство InnerText. Если вам нужно получить html-код поста, используйте InnerHtml. Тот же самый код можно гораздо короче записать через Linq:

var posts = doc.GetElementsByClassName("wall_post_text").Select(a => a.InnerText).ToList();

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

А теперь самая важная функция в парсере — XPath. Он позволяет строить запросы, с помощью которых можно очень быстро найти нужные теги. Подробнее о его синтаксисе можно прочитать на w3schools или по русски, но на 20 страницах вместо одной. Для примера давайте найдем адреса фотографий из раздела фотографии на странице профиля. Их код выглядит вот так:

<tr class="photos"><td><a onclick="бла-бла" href="/photo1_222740876?all=1"><img src="http://cs871.vkontakte.ru/u00001/127202895/s_d5bb4880.jpg"></a></td><td>... </td> ... </tr>

В принципе здесь тоже можно было бы применить GetElementsByClassName("photos"), а затем пройтись по массиву ChildNodes. Но здесь еще возникает дополнительная проблема, что на странице есть еще и ссылка с таким же классом, пришлось бы еще писать дополнительное условие, что название тега должно быть tr. С помощью XPath это делается гораздо проще:

var photoNodes = doc.DocumentNode.SelectNodes("//tr[@class='photos']/td/a/img");List photos = new List();foreach (var item in photoNodes){photos.Add(item.Attributes["src"].Value);}

Или через Linq:

var photos = doc.DocumentNode.SelectNodes("//tr[@class='photos']/td/a/img").Select(a => a.Attributes["src"].Value).ToList();

Первой строкой мы получаем список нужных тегов img, а затем берем из них атрибут src. Сначала стоит отметить, что запрос применяется к DocumentNode, то есть к корневому тегу документа. Запросы можно также применять и к любому дочернему тегу. Давайте теперь разберемся в том, как устроен запрос. «//» означает, что нужно искать тег по всему документу, независимо от расположения. Дальше идет имя тега «tr». Затем условие, что атрибут ‘class’ должен равняться ‘photos’
«[@class=’photos’]». Для обозначения имени атрибута всегда используется «@». Потом идет «/td/a/img», Путь от найденного тега tr, к тегу, который мы хотим получить.

И последний пример — давайте спарсим город, в котором живет Павел. Вот исходный html:

<div class="clear_fix "><div class="label fl_l">Город:</div><div class="labeled fl_l"><a onclick="..." href="...">Санкт-Петербург</a></div></div>

Это можно сделать вот такой командой:

string city = doc.DocumentNode.SelectSingleNode("//div1/../div[contains(@class, 'labeled')]").InnerText;

Мы находим на странице div, текст которого равняется ‘Город:’, затем поднимаемся на уровень выше, к родительскому тегу. Тут все как с папками и файлами: «..» означает родительский элемент, «.» — текущий. После этого мы ищем тег, класс которого содержит текст ‘labeled’, полный класс этого тега равняется «labeled fl_l». Обратите внимание, что здесь я использовал функцию SelectSingleNode, которая возвращает первый найденный тег. Кстати говоря, вместо doc.DocumentNode.SelectNodes можно писать просто doc.SelectNodes.

На этом все, подробнее про XPath читаем по указанным выше ссылкам. Архив с исходником: HtmlParser.rar

Урок 5 – продвинутый регер rambler.ru.

Урок 5 – продвинутый регер rambler.ru.

На этот раз мы будем учиться на примере более серьезной программы, которую, например, можно продать и на полученные деньги пойти купить себе сникерс :). Это будет усовершенствованный регер почты rambler.ru, быстрый, многопоточный, с поддержкой антигейта и всех видов проксей. Программа эта более длинная чем предыдущие, поэтому я не буду объяснять как работает весь исходник, а расскажу только про важные части. Остальное в основном связано с работой интерфейса и можно понять просто глядя на код.

Прокси

При работе с прокси-серверами нужно привыкнуть к тому, что при запросах может возникать куча ошибок. Например, многие прокси, когда у них запрашиваешь сайт, скажем www.ya.ru, выдают вместо него какую-то левую страницу типа «добро пожаловать на наш офигенный прокси». Естественно программа ничего не поймет в этом ответе. При этом зачастую на следующий запрос прокси отвечает нормально. Также часто бывает что прокси поддерживает GET-запросы, но не поддерживает POST. Самая частая ситуация, это когда прокси просто забивает на то, что его попросили скачать какую-то страницу, и программа просто зря ждет надеясь получить ответ. Кстати, для настройки, сколько времени ждать ответа от сервера используйте параметр VkRequest.Timeout.

Начнем с того, что напишем свой класс прокси, чтобы можно было учитывать количество ошибок в процессе работы программы и эффективно отсеивать нерабочие сервера.

class MyProxy : Proxy{public const int StartErrors = 3;public const int MaxErrors = 5; public int ErrorsCount { get; set; } // эти 2 конструктора просто повторяют конструкторы родительского классаpublic MyProxy(string addressAndPort, ProxyTypes type): base(addressAndPort, type){ErrorsCount = StartErrors;} public MyProxy(string address, int port, ProxyTypes type, string login = null, string password = null): base(address, port, type, login, password){ErrorsCount = StartErrors;}}

Этот класс наследуется от класса Proxy, поэтому умеет все то же самое, что и основной класс + еще пару мелочей. У него появился параметр ErrorsCount, с помощью которого мы будем отслеживать, сколько ошибок подряд произошло при использовании данного прокси. Если их произошло больше 5 подряд прокси можно выкинуть. При этом если через прокси удалось успешно зарегистрировать аккаунт, будем скидывать счетчик на 0 — может быть получится еще один зарегистрировать. Чтобы отфильтровать совсем нерабочие прокси сразу, будем изначально устанавливать счетчик ошибок на 3. То есть, если мы взяли новый прокси, и он выдает 2 ошибки подряд, то выкинем его.

Еще одна немаловажная деталь о прокси-серверах: они как правило очень медленные. Поэтому нужно позаботиться о том, чтобы через них проходило как можно меньше трафика. Если можно сделать какой-то запрос мимо прокси-сервера, нужно так и делать. Помните мы в первой версии регера вытаскивали uniq_id из страницы регистрации? При этом нашли мы его в 3-х местах, и одно из этих мест — это куки rrc, которое выдает нам страница регистрации. А помните в прошлом уроке я рассказывал про метод HEAD? Так вот, мы можем не качать всю форму регистрации, а только попросить ее заголовки:

CookieCollection cook = new CookieCollection();// более подробно о том, как использоать куки я объясню в следующем урокеVkRequest.Request("http://id.rambler.ru/script/newuser.cgi", cookies: cook, method: "HEAD");string id = cook["rrc"];

Таким образом мы сэкономим довольно много прокси-трафика. Страница регистрации весит 30кб, в сжатом виде (движок делает это автоматически) 9кб, одни заголовки — <1кб. Получается экономия почти в 10 раз.

Дальше у нас идет скачивание капчи. Для большинства серверов (почти для всех) неважно качаете вы капчу через прокси или со своего компа. При этом скачать ее без прокси гораздо быстрее и сильно понижает вероятность ошибок, связанных с прокси (он должен успешно выполнить 2 запроса подряд вместо 3-х). Так что при скачивании капчи прокси использовать ненужно.
Благодаря этим оптимизациям, для прокси вместо 3-х запросов на 15кб данных (9кб форма регистрации, 5кб капча, 1кб POST) получается всего 2 запроса по 1кб каждый (даже меньше). К тому же мы в 2 раза уменьшили общий трафик, убрав лишние 8кб для формы регистрации.

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

Дальше у нас идет POST запрос на регистрацию. Поскольку определенный процент проксей не поддерживает POST запросы, желательно пытаться преобразовать их в GET с параметрами:

VkRequest.Request("http://id.rambler.ru/script/newuser.cgi", post);VkRequest.Request("http://id.rambler.ru/script/newuser.cgi?" + post);

Вы наверное часто видели в адресной строке браузера, что после адреса стоит знак «?» и после него идут параметры, такие же как и в POST запросах. Некоторые сервера не различают параметры переданные этими двумя способами (например vkontakte.ru), поэтому следует попробовать преобразовать POST запрос в GET и посмотреть, будет ли так работать. Если получится, то мы сможем более эффективно использовать прокси серверы. Оказывается, что для rambler.ru GET вместо POST не подходит, поэтому оставим эту строку как есть.

Antigate

Теперь о том, как пользоваться антигейтом. Все очень просто, устанавливаете переменную в VkRequest.AntigateKey ваш ключ антикапчи, и разгадываете капчу от так:

string captchaAnswer = VkRequest.PostCaptcha(captchaImage);

Эта функция подождет пока антигейт разгадает капчу и вернет результат разгадывания. Почти так же как и CaptchaWindow.

Комментарии по исходнику

Теперь как в прошлом уроке выделяем процедуру регистрации в отдельную функцию:

private void Register(){MyProxy p = null;try{// выбираем случайный проксиlock (proxies)if (proxies.Count == 0) { manager.StopJob("Готово!"); return; }else p = proxies[r.Next(proxies.Count)]; // генерируем случайные мыло и парольstring email = Path.GetRandomFileName().Replace(".", "");string pass = Path.GetRandomFileName().Replace(".", ""); // получаем уникальный id регистрацииCookieCollection cook = new CookieCollection();VkRequest.Request("http://id.rambler.ru/script/newuser.cgi", proxy: p, cookies: cook, method: "HEAD");string id = cook["rrc"];if (id == null) throw new Exception("Неверная страница регистрации"); // скачиваем и разгадыаем капчуImage captchaImage = VkRequest.Request("http://id.rambler.ru/captcha/" + id + ".jpg").GetImage();string captchaAnswer = VkRequest.PostCaptcha(captchaImage); // регистрируемся в почтеstring post = string.Format("uniq_id={0}&user.login={1}&user.captcha={3}&user.password1={2}&user.password2={2}&skin=id&back=&back_immediate=&action=register&user.fname=firstname&user.lname=lastname&user.gender=1&user.bday=1&user.bmonth=1&user.byear=1950&user.domain=rambler.ru&x.emailfree=mail1245%40rambler.ru&user.question=own.question&user.own_question=question&user.answer=answer&user.email2=",id, email, pass, captchaAnswer);var result = VkRequest.Request("http://id.rambler.ru/script/newuser.cgi", post, proxy: p); // обработка возможных ошибок регистрации}catch (Exception e){// опять обработка ошибок}}

и создаем для нее JobManager:

manager = new JobManager(Register);manager.PreferredThreadCount = (int)threads.Value;manager.Start();

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

  • Если вы хотите взаимодействовать с интерфейсом из рабочего потока (функции Register()), то вам придется использовать метод BeginInvoke, который поставит вашу функцию в очередь на выполнение для потока интерфейса (только он может изменить что-то в окне): private void log(string s){BeginInvoke(new Action(() => { logBox.AppendText(s + "\r\n"); }));} Здесь использоано lambda-выражение () => { ... }, если хотите узнать о них подробнее, читайте гугл.
  • У вас наверное бывало так, что при возникновении ошибки программа выдала сообщение о ней и сразу закрылась, хотя ошибка была незначительная? Чтобы иметь возможность продолжить работу, можно просто добавить в конструктор формы следующюю строку Application.ThreadException += (obj, e) => ExceptionDialog.Show(e.Exception);
  • В программе, которую я написал зарегистрированные аккаунты сразу же записываются в файл. Чтобы не обращаться к файлу слишком часто, реализован следующий механизм:
    • все зарегистрированные акки заносятся в список akksToWrite
    • таймер каждую секунду проверяет, есть ли что-то в списке, и если есть дописывает акки в файл и чистит список
    Таким образом получается, что все акки сохраняются и нет большой нагрузки на жесткий диск
  • Количество потоков можно менять прямо в процессе работы.

На сегодня все. Дальше изучайте исходник программы: RamblerReger.v2.0. Если что-то непонятно, задавайте вопросы в комментариях. Также интересны ваши комментарии по поводу понятности изложения. Возможно стоит больше внимания уделить более простым урокам?