Рубрика: Программируем под web

Регер почты 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

Урок 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

Урок 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. Если что-то непонятно, задавайте вопросы в комментариях. Также интересны ваши комментарии по поводу понятности изложения. Возможно стоит больше внимания уделить более простым урокам?

Урок 4 – Накрутчик опросов.

Урок 4 – Накрутчик опросов.

На этот раз мы напишем накрутчик опросов. На его примере мы научимся двум очень важным вещам — многопоточности и использованию прокси-серверов. Без этих вещей невозможно делать массовые рассылки. После того как вы освоите этот и следующий урок (он будет посвящен разгадыванию капчи с помощью antigate), уже можно будет писать простые программы. А дальше дело за опытом и практикой.

Для того, чтобы накрутить опрос, нужно его сначала найти (или создать). Чтобы долго не париться я ввел в гугле «создать опрос» и перешел по первой ссылке — http://www.rupoll.com/create.php. Для простоты при создании опроса я снял галочку «Использовать JavaScript для защиты от автоматической накрутки». На практике JavaScript-защита встречается не так уж и часто, например на вконтакте ее нету. После создания опроса сайт выдал мне две ссылки:

Страница опроса: http://www.rupoll.com/yrkgimhdsp.html Страница результатов опроса: http://www.rupoll.com/yrkgimhdsr.html 

Ну что-ж, включаем Fiddler, идем на страницу и голосуем. Видим такую схему:

  • Браузер отправил
POST http://www.rupoll.com/vote.php HTTP/1.1
Host: www.rupoll.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://www.rupoll.com/yrkgimhdsp.html
Cookie: voter=fzqjovli
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

poll_id=yrkgimhd&vote=1
  • Получил в ответ редирект
HTTP/1.1 302 Found
Server: nginx/0.7.62
Date: Sun, 16 Jan 2011 00:14:02 GMT
Content-Type: text/html
Connection: keep-alive
Keep-Alive: timeout=20
X-Powered-By: PHP/5.2.5
Location: http://www.rupoll.com/voteconfirm.php?id=yrkgimhd&kv=227089984&kt=1295136842
Content-Length: 0
  • Дальше перешел по этому редиректу
GET http://www.rupoll.com/voteconfirm.php?id=yrkgimhd&kv=227089984&kt=1295136842 HTTP/1.1
  • В ответ получил страницу что «все ок, ваш голос учтен».

Параметр poll_id очень похож на адрес страницы и название переменной намекает что эта переменная будет всегда одинаковая. А vote=1 похоже обозначает выбраный вариант ответа. Давайте проверим это проголосовав еще раз за другой вариант. Просто так нам не дают этого сделать, вместо кнопки голосования появляется надпись «Вы уже голосовали в этом опросе». Решить эту проблему можно очень просто, очистив куки в браузере. Кстати, для того чтобы почистить куки для одного сайта есть отличный плагин firecookie, к тому же он позволяет эти куки редактировать. Итак, голосуем еще раз и видим что наши предположения верны. Конечно, после нажатия нам выдается «Кажется, вы уже проголосовали в этом опросе.», но мы-то уже увидели какой запрос отправился. Получается чтобы сделать голос за первый вариант нам нужно отправить POST-запрос «poll_id=yrkgimhd&vote=1» на http://www.rupoll.com/vote.php. Дальше браузер переходил по редиректу на страницу voteconfirm.php. Название страницы намекает нам, что если не перейти на нее, то голос не зачтется. Но это еще нужно будет проверить, может быть можно обойтись и без этого.

Почти все опросы имеют защиту от нескольких голосов с 1 ip-адреса. Чтобы обойти это ограничение мы будем использовать проскси-сервера. Каждый голос будем делать с нового прокси. Список таких серверов можно собрать, например, программой из первого урока, и не забывайте что перед использованием их желательно проверить на работоспособность специальным софтом.

Начинаем все как обычно. Создаем в Visual Studio проект Console(!) Application, подключаем ViKing.Engine.dll, добавляем сверху код

using System.IO;using ViKing.Engine;

Для начала нам понадобится очередь проксей. Создадим ее и загрузим из файла «proxies.txt»

Queue<;Proxy> proxies = new Queue<;Proxy>(); // создаем объект "Очередь" для хранения проксейstring[] file = File.ReadAllLines("proxies.txt");  //читаем все строки из файла "proxies.txt"foreach (var line in file) // из каждой строки  формате "адрес:порт" создаем объект прокси и помещаем в очередьproxies.Enqueue(new Proxy(line, ProxyTypes.HTTP));

Если у вас другой тип проксей просто укажите ProxyTypes.Socks5 место HTTP. А вот более короткий вариант, который делает то же самое:

var proxies = new Queue<;Proxy>(File.ReadAllLines("proxies.txt").Select(q => new Proxy(q, ProxyTypes.HTTP)));

Теперь очередь есть, нужно каждым прокси ответить на опрос. Для того чтобы сделать запрос с помощью прокси, достаточно в уже известной нам функции Request написать proxy: p, где p это объект типа Proxy (а если быть долее точным то интерфейс IProxy). Вот так это делается:

while (proxies.Count > 0) // цикл пока прокси не закончатся{Proxy p = proxies.Dequeue(); // достать очередной прокси из очередиvar answer = VkRequest.Request("http://www.rupoll.com/vote.php", "poll_id=yrkgimhd&vote=1", proxy: p);// Здесь возможно понадобится перейти по редиректу, который мы получили на предыдущей странице// VkRequest.Request(answer.Headers["Location"], proxy: p);Console.Write("."); // после выполнения запроса нарисовать точку в консоли}

Я оставил в виде комментария команду, которая переходит по редиректу, указанному в заголовке Location, потому что еще не изестно понадобится она или нет. Кстати, как вы видите все заголовки, полученные  ответе от сервера располагаются в массиве Headers. Теперь попробуем запустить получившуюся программу, дать ей время сделать несколько голосов и затем посмотрим на результаты опроса. Если вы используете некачественные прокси, работа программы будет прерываться из-за ошибок при запросах к таким проксям, чтобы избежать этого, используйте блок try-catch

while (proxies.Count > 0) // цикл пока прокси не закончатся{try{Proxy p = proxies.Dequeue(); // достать очередной прокси из очередиvar answer = VkRequest.Request("http://www.rupoll.com/vote.php", "poll_id=yrkgimhd&vote=1", proxy: p);// Здесь возможно понадобится перейти по редиректу, который мы получили на предыдущей странице// VkRequest.Request(answer.Headers["Location"], proxy: p);Console.Write("."); // после выполнения запроса нарисовать точку в консоли}catch { Console.Write("-"); } // при неудачном запросе рисуем -}

Проверка показывает что наши голоса не засчитываются. На этот случай у нас есть «план Б», раскомментируем строку с переходом на редирект и попробуем еще раз. Работает 🙂 Но очень медленно. Причина в том, что от каждого прокси приходится длительное время ждать ответа. Гораздо эффективнее разослать запрос всем поксям одновременно и ждать пока какой-то из них ответит. от для этого и нужна многопоточность. Она позволяет исполнять несколько функций одновременно. Давайте посмотрим как это делается. Для начала нам придется разделить код на 2 функции — то что выполняется один раз, и то что выполняется много раз на 1 потоке:

class Program{static Queue<;Proxy> proxies; static void Main(string[] args){proxies = new Queue<;Proxy>();string[] file = File.ReadAllLines("proxies.txt");foreach (var line in file)proxies.Enqueue(new Proxy(line, ProxyTypes.HTTP)); while (proxies.Count > 0) // цикл пока прокси не закончатся{Click();}Console.ReadKey();} static void Click(){try{Proxy p = proxies.Dequeue();var answer = VkRequest.Request("http://www.rupoll.com/vote.php", "poll_id=yrkgimhd&vote=1", proxy: p);VkRequest.Request(answer.Headers["Location"], method: "HEAD", proxy: p);Console.Write(".");}catch { Console.Write("-"); }}}

Теперь можно получившуюся функцию выполнить на нескольких потока с помощью менеджера потоков, который ходит в состав ViKing.Engine

JobManager manager = new JobManager(Click); // менеджер будет запускать функцию Click на разных потокахmanager.PreferredThreadCount = 30; // мы хотим чтобы потоков было 30manager.Start(); // Запуск!

Обратите внимание, что функция Start неблокирующая, то есть программы выйдет из нее сразу, не дожидаясь пока выполнятся функции Request. Для того чтобы программа сразу не закрылась мы используем функцию Console.ReadKey(), которая будет ждать пока юзер не нажмет любую кнопку. Теперь нужно позаботиться о том, чтобы менеджер остановился когда кончатся прокси, для этого есть функция StopJob. Вместо

Proxy p = proxies.Dequeue();

пишем

Proxy p = null;if (proxies.Count == 0) { manager.StopJob("Done!"); return; }else p = proxies.Dequeue();

И тут возникает ситуация с которой мы раньше не встречались — переменная proxies может одновременно использоваться с разных потоков. Может случиться так, что 1 поток проверит что proxies.Count > 0 и соберется уже извлечь прокси p, но тем временем между этими операциями другой поток успеет извлечь последний прокси, и к моменту когда первый поток будет выполнять p = proxies.Dequeue();, проксей там уже не останется. Возникнет ошибка. Возможны и более неприятные ошибки, когда из-за параллельной записи могут испортиться все данные в переменной. Чтобы такой фигни не происходило, используется механизм синхронизации потоков:

lock (proxies){if (proxies.Count == 0) { manager.StopJob("Done!"); return; }else p = proxies.Dequeue();}

внутри блока lock может находиться только 1 поток, остальные будут ждать снаружи. Теперь почти все готово. Последний штрих, который хотелось бы добавить, это чтобы при завершении программа писала нам что все готово. Для этого нужно всего лишь подписаться на событие JobCompleted:

manager.JobCompleted += manager_JobCompleted;

и сама функция:

static void manager_JobCompleted(object sender, JobCompletedEventArgs e){Console.WriteLine(e.Reason);}

переменная e.Reason содержит параметр, который был передан в функции StopJob.

Да, еще чуть не забыл. Прокси это очень медленная штука, поэтому для ускорения их работы желательно чтобы через прокси проходило как можно меньше информации. В нашем случае через прокси отправляются 2 запроса: POST-запрос, в ответ на который приходит редирект, который содержит только заголовки. Затем следует GET-запрос по адресу, полученному из первого запроса. Здесь в ответ приходит текст страницы, которую нужно отобразить. Было бы хорошо как-то сказать серверу, что ответ нам присылать не нужно. Для этого существует специальный HEAD-запрос, который делает то же что и GET, но в ответ на него сервер присылает только заголовки без текста получившейся страницы. Таким образом можно сэкономить трафик и тем самым ускорить работу программы. выглядит это вот так:

VkRequest.Request(answer.Headers["Location"], method: "HEAD", proxy: p);

Вот теперь все. Полный исходник можно скачать здесь: Voter

Урок 3 – mail регер rambler.ru.

Урок 3 – mail регер rambler.ru.

Сегодня мы напишем регистратор почты на rambler.ru. Этот пример поможет научиться использовать сниффер для анализа запросов, которые посылает браузер. А еще мы научимся скачивать картинки и разгадывать капчу. К тому же на этот раз у нас будет windows-приложение, а не консольное. Итак, поехали.

Создаем в Visual Studio проект Windows Application, подключаем ViKing.Engine.dll, добавляем сверху код

using ViKing.Engine;using ViKing.Engine.GUI;
using System.IO;
using System.Text.RegularExpressions;

На этот раз мы также добавили строку using ViKing.Engine.GUI, она нам пригодится для того чтобы было просто отображать капчу на экране, в общем сами увидите чуть позже. Теперь откроем firefox и fiddler. Не забудьте включить фидлер в статусной строке фаерфокса (должно быть написано «Fiddler: ForceOn»). Перейдем на рамблер, ткнем зарегистрироваться и заполним форму регистрации:

Для удобства желательно пометить запрос, который появился в сниффере при открытии формы регистрации клавишей «insert» (на скриншоте у меня он красный). Дальше жмем зарегистрироваться и смотрим что за запрос отправился на сервер:

Ага, отправился POST-запрос на http://id.rambler.ru/script/newuser.cgi с параметрами

skinid
back
back_immediate
uniq_id6b9f763008ed2b609110f6d50eb92f50
actionregister
user.fnamefirstname
user.lnamelastname
user.gender1
user.bday1
user.bmonth1
user.byear1950
user.loginmail1245
user.domainrambler.ru
x.emailfree[email protected]
user.password1qweqwe
user.password2qweqwe
user.questionown.question
user.own_questionquestion
user.answeranswer
user.email2
user.captchacfmrnf

Мы видим что почти все эти параметры — поля только что заполненной нами формы. Особый интерес для нас представляют
user.login — его мы должны сделать случайным
user.captcha — нужно будет найти где располагается соответствующая картинка
uniq_id — какой-то уникальный id, давайте выясним подробнее что это такое. Для этого посмотрим в сниффер на несколько строк выше, на запрос который браузер сделал при открытии формы регистрации. В ответ на него нам пришел код страницы регистрации. Откроем код страницы в блокноте и поищем там интересующий нас id 6b9f763008ed2b609110f6d50eb92f50. Он находится в 3 местах:

Set-Cookie: rrc=6b9f763008ed2b609110f6d50eb92f50; domain=.rambler.ru; path=/; httponly
<input name="uniq_id" type="hidden" value="6b9f763008ed2b609110f6d50eb92f50" />
<img id="secimage" title="Введите показанные на картинке символы" src="http://id.rambler.ru/captcha/6b9f763008ed2b609110f6d50eb92f50.jpg" border="0" alt="Введите показанные на картинке символы" width="300" height="60" />

Первая строка располагается в заголовках и устанавливает куки. Возможно если не отправить этот кук в запросе на регистрацию, то он выдаст ошибку. Но для начала попробуем обойтись без куков, вдруг и так сойдет. Вторая строка находится на веб-форме и как-раз она и добавляет наш id в POST-запрос. Третья строка — как это ни удивительно, это тег img с изображением капчи. Адрес по которому он берет картинку содержит наш uniq_id.  Это очень удобно, значит можно найти адрес картинки и из него-же вытащить id. Ну чтож, похоже мы нашли все что нам нужно для регистрации, приступим к написанию софта. Последовательность действий должна быть такая

  1. Качаем форму регистрации
  2. Находим там uniq_id, он же адрес картинки с капчей
  3. Качаем картинку
  4. Отгадываем капчу
  5. Отправляем заполненную форму на серер
  6. Все!

1. Скачать форму — проще простого! Смотрим в фидлер на адрес и пишем код:

string page = VkRequest.Request("http://id.rambler.ru/script/newuser.cgi").Content;

2. Теперь нужно выдернуть id. Делаем вот так:

string id = Regex.Match(page, @"http://id.rambler.ru/captcha/(?<id>\w+).jpg").Groups["id"].Value;

Это регулярное выражение, мы с таким уже сталкивались. Оно ищет адрес картинки с капчей, но вместо набора букв в названии картинки, который все время меняется мы написали (?<id>\w+). «\w»означает любую букву или цифру, «+» означает что «\w » нужно повторить 1 или более раз. «(?<id>)» присваивает получившейся комбинации имя «id». Затем команда «.Groups[«id»].Value» выдергиает из получившейся комбинации только кусок с названием «id». Таким образом мы получили значение uniq_id.

3. Теперь скачаем картинку с капчей:

Image captchaImage = VkRequest.Request("http://id.rambler.ru/captcha/" + id + ".jpg").GetImage();

Обычный GET-запрос, только «.GetImage()» в конце означает что ответ сервера нужно воспринимать как картинку.

4. Для отгадывания картинки можно использовать класс CaptchaWindow из дижка викинга. Перед использованием его нужно инициализировать в конструкторе формы

public Form1(){InitializeComponent();CaptchaWindow.Initialize(this);}

Терерь его можно использовать вот так:

string captchaAnswer = CaptchaWindow.Show(captchaImage);

5. Теперь давайте сформируем POST-запрос для сервера. Для этого скопируем запрос который мы отправляли через браузер и изменим значения uniq_id, user.login и user.captcha:

string post = string.Format("uniq_id={0}&user.login={1}&user.captcha={2}&user.password1=qweqwe&user.password2=qweqwe&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, captchaAnswer);

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

string email = Path.GetRandomFileName().Replace(".", "");

Теперь отправим наш запрос на сервер. Для этого вызовем функцию Request с 2 параметрами:

var result = VkRequest.Request("http://id.rambler.ru/script/newuser.cgi", post);

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

HTTP/1.1 302 Found
Server: nginx/0.8.52
Date: Thu, 13 Jan 2011 20:55:59 GMT
Content-Type: text/html; charset=windows-1251
Connection: keep-alive
Keep-Alive: timeout=20
P3P: CP="NON DSP NID ADMa DEVa TAIa PSAa PSDa OUR IND UNI COM NAV"
Set-Cookie: rsid=d7869f1f48cfdf521adaf5898dae6821; domain=.rambler.ru; path=/; httponly
Set-Cookie: [email protected]; domain=.rambler.ru; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/
Set-Cookie: rup=MXbkSZD38i7rheLADZysFSU_; domain=.rambler.ru; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/
Pragma: no-cache, no-store
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Location: http://id.rambler.ru/script/auth.cgi?mode=checkcookie;sid=d7869f1f48cfdf521adaf5898dae6821;back=http%3A%2F%2Fid.rambler.ru%2Fscript%2Fnewuser.cgi%3F%26amp%3Baction%3Dnew_user%26amp%3Bskin%3Did%26amp%3Bback%3D
Content-Length: 0

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

HTTP/1.1 200 OK
Server: nginx/0.8.52
Date: Thu, 13 Jan 2011 20:55:59 GMT
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Keep-Alive: timeout=20
Pragma: no-cache, no-store
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Content-Length: 19382

<!-- This comment will put IE 6, 7 and 8 in quirks mode -->
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
 "http://www.w3.org/TR/html4/loose.dtd">
<html lang="ru">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8>">
 <meta http-equiv="X-UA-Compatible" content="IE=8">
 <link rel="stylesheet" type="text/css" href="/css/id.css" media="all">
 <link rel="icon" href="/favicon.ico" type="image/x-icon">
 <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

 <script type="text/javascript"><!--

 rg_errors_text = {

БЛА-БЛА-БЛА

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

if (result.Headers["Location"].Length > 0) textBox1.AppendText(email + " зарегистрирован!\r\n");else textBox1.AppendText("Ошибка\r\n");

Вот и все! Регистратор готов. Пробуем зарегистрировать программой аккаунт и зайти в него через браузер (пароль «qweqwe»). Все работает! Скачать файл с исходниками можно тут: RamblerReger

Урок 2 – парсер прокси.

Урок 2 – парсер прокси.

Ну что, думаю пора уже создать первый проект. Это будет парсер прокси. С его помощью мы научимся на практике делать первые запросы и заодно увидим как применяются простые регулярные выражения. Для примера мы будем собирать прокси с сайта www.socks24.org. Итак, откроем Visual Studio, создадим проект типа «Console Application» на C# и добавим в References Viking.Engine.dll.

Чтобы его было удобно использовать напишем сверху

using System.Text.RegularExpressions;
using ViKing.Engine;using System.IO;

Для начала наша задача — открыть главную страницу www.socks24.org, найти все что похоже на прокси (ip адрес:порт) и сохранить в файл. Первым делом нам нужно скачать текст страницы.

var page = VkRequest.Request("http://www.socks24.org/").Content;

Функция Request по умолчанию просто скачивает содержимое страницы с помощью метода GET. Свойство Content хранит содержимое страницы  стандартной кодировке windows-1251. Теперь нужно из этого текста вытащить все что похоже на прокси. Это делается следующей командой

var matches = Regex.Matches(page, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}");

Так выглядит регулярное выражение. Устроено оно следующим образом. @ перед строкой означает что мы хотим отключить esc-последовательности, то есть «\» означает именно слеш, а не спецсимвол. Дальше идет элемент «\d», который символизирует любую цифру. Затем «{1,3}» означает что цифры должны встретиться  количестве от 1 до 3. Потом стоит «\.», что означает просто точку. Напомню, что в регулярных выражениях точка обозначает любой символ, и если вы хотите видеть именно точку то нужно использовать слеш. После этого все повторяется 4 раза и дальше идет двоеточие и порт — 1-5 цифр. Теперь нужно склеить все полученные результаты в строку и записать результат в файл:

string result = "";foreach (Match m in matches)result = result + m.Value + "\r\n";File.WriteAllText("proxies.txt", result);

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

StringBuilder result = new StringBuilder();foreach (Match m in matches)result.AppendLine(m.Value);File.WriteAllText("proxies.txt", result.ToString());

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

Ну и напоследок бонус. Вот как записать эту программу в 1 строку

File.WriteAllLines("proxies.txt", Regex.Matches(VkRequest.Request("http://www.socks24.org/").Content, @"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}").OfType<;Match>().Select(q => q.Value).ToArray());

Скачать готовый парсер ProxyParser

Урок 1 – немного теории.

Урок 1 – немного теории.

Перед тем как перейти к написанию программ, автоматизирующих действия в браузере, необходимо понять, как работает этот самый браузер. Сейчас я вам расскажу об этом в общих чертах. Кое-что может быть непонятно, но вы это легко поймете потом на практике. Перед началом прочтения уроков рекомендую прочитать введение, если вы этого еще не сделали.

HTML

Все веб-страницы, которые вы видите на экране, браузер получает от сервера в виде обычного текста, который можно посмотреть в блокноте. Посмотреть этот текст в Firefox можно нажав ctrl+U, а лучше всего с помощью Firebug на закладке HTML. Этот текст составлен в специальном формате, который называется HTML. Устроен он следующим образом:

<div>
    <a href="/index.php">Index</a>
    <div class="myClass">
        <img src="/images/logo.png" alt="Logo image" />
    </div>
</div>
  

Он состоит из тегов (то что между треугольными скобками)
div, img, a — это имена тегов
href="/index.php", class="myClass" — это атрибуты,
href, class — имена атрибутов, "/index.php" и "myClass" — их значения.
Теги бывают открывающими <div>, закрывающими </div>, все что между ними называется контентом (содержимым) тега. В качестве контента так же могут быть другие теги. Таким образом теги образуют древовидную структуру, похожую на файлы и папки. Если у тега нет контента, то он может быть сразу открывающим и закрывающим, как тег img в примере. Все это вместе называется формат XML.

HTML отличается тем, что придает смысл всем этим надписям. Вот что означают самые распространенные теги:
<a href="http://example.com/index.php">Текст ссылки</a> — ссылка
<img src="http://example.com/images/logo.png" /> — изображение
<table>, <tbody>, <tr>, <td> — таблицы; tr — строка таблицы, td — столбец
<div>, <span>, <p> — эти элементы служат для отделения частей страницы друг от друга и сами по себе ничего не делают.
<head> — заголовок страницы, внутри него подключаются скрипты и стили
<body> — тело страницы, здесь находятся все видимые элементы

HTTP

Давайте теперь посмотрим как устроены запросы в браузере. Когда вы набираете в адресной строке браузера адрес сайта, например http://vkontakte.ru, происходит вот что:

  • Браузер отправляет запрос на сервер, чтобы он прислал страницу. Запрос это тоже обычный текст. Выглядит он вот так.
GET http://vkontakte.ru/ HTTP/1.1
Host: vkontakte.ru
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive

Первая строка-строка запроса содержит команду «GET», которая означает что мы хотим скачать страницу, затем идет адрес страницы и версия протокола. Дальше идут заголовки. У заголовков есть имя и значение, отделенные двоеточием.

  • В ответ контакт присылает главную страницу
HTTP/1.1 200 OK
Server: nginx/0.7.59
Date: Tue, 11 Jan 2011 15:10:42 GMT
Content-Type: text/html; charset=windows-1251
Connection: keep-alive
X-Powered-By: PHP/5.2.6-1+lenny3
Set-Cookie: remixchk=5; expires=Sun, 01-Jan-2012 20:36:31 GMT; path=/; domain=.vkontakte.ru
Pragma: no-cache
Cache-control: no-store
Vary: Accept-Encoding
Content-Length: 13110

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
<script type="text/javascript" src="/al_loader.php?act=nav&v=1094"></script>
<link rel="shortcut icon" href="/images/favicon.ico" />
<meta name="description" content="<b>ВКонтакте</b> – универсальное средство поиска знакомых. Мы хотим, чтобы друзья, однокурсники, одноклассники, соседи и коллеги всегда оставались в контакте." />
<title>В Контакте | Добро пожаловать</title>
<link rel="stylesheet" type="text/css" href="/css/al/common.css?83" />
<script type="text/javascript" src="/js/al/common.js?187"></script>
<script type="text/javascript" src="/js/lang0_0.js?1990"></script>
<script type="text/javascript" src="/js/al/index.js?9"></script>
<link type="text/css" rel="stylesheet" href="/css/al/reg.css?15"></link>
</head>

<body onresize="onBodyResize()">
<div id="system_msg"></div>
<div id="utils"></div>

Бла-бла-бла

</body>
</html>

Ответ сервера похож на запрос и отличается первой строкой, в которой содержится результат выполнения команды. Затем идут заголовки, а потом после пустой строки текст страницы которую браузер должен отобразить. Браузер читает код страницы и видит там теги <script>, <link> и <img>, которые указывают на файлы, необходимые для отображения страницы и запрашивает их все с помощью таких же GET-запросов.

  • Существуют также POST-запросы, которые используются чтобы отправить данные на сервер, например когда пользователь хочет залогиниться:
POST http://login.vk.com/?act=login HTTP/1.1
Host: login.vk.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://vkontakte.ru/
Content-Type: application/x-www-form-urlencoded
Content-Length: 111

act=login&from_host=vkontakte.ru&email=mail%40mail.ru&pass=123&q=1&al_frame=1&expire=&captcha_sid=&captcha_key=

Данные которые передаются на сервер находятся после заголовков, и обычно в формате «переменная1=значение1&переменная2=значение2«. Например в нашем случае пользователь ввел почту email=mail%40mail.ru и пароль pass=123

  • Вместо того чтобы прислать веб-страницу сервер иногда может перенаправить нас на другой адрес, например в ответ на предыдущий запрос:
HTTP/1.1 302 Found
Server: nginx/0.7.59
Date: Tue, 11 Jan 2011 15:24:18 GMT
Content-Type: text/html; charset=windows-1251
Connection: keep-alive
X-Powered-By: PHP/5.2.6-1+lenny8
Pragma: no-cache
Cache-control: no-store
Set-Cookie: remixchk=5; expires=Sun, 22-Jan-2012 04:37:25 GMT; path=/; domain=.vkontakte.ru
P3P: CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"
Location: http://vkontakte.ru/login.php?act=slogin&al_frame=1&m=4&email=mail%40mail.ru
Vary: Accept-Encoding
Content-Length: 0

Сервер вернул код «302 Found» и передал адрес целевой страницы  заголовке «Location: http://vkontakte.ru/login.php?act=slogin&al_frame=1&m=4&email=mail%40mail.ru»

Более подробно изучить запросы можно с помощью Fiddler, если хотите увидеть запросы в том виде, как их отсылает браузер, включите вкладку «Raw» <br />

Куки

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

Куки – это переменная, которая хранится в браузере и пересылается сайту в каждом запросе. У куков, как у любой переменной есть имя и значение. Помимо этого для них еще указывается срок годности и домен, для которого они подходят. Вот вам пример:

remixsid=e7e94a5a03d852aca0ea22cdaaf5ca9399e2e470a0be610d3d87c314b461; expires=Thu, 24-May-2012 04:58:49 GMT; path=/; domain=.vkontakte.ru

Имя – remixsid

Значение — e7e94a5a03d852aca0ea22cdaaf5ca9399e2e470a0be610d3d87c314b461

Годен до 2012 года

Действителен для домена vkontakte.ru и всх поддоменов (потому что перед именем домена стоит точка)

Чтобы установить куки вебсайт должен в ответе прислать заголовок:

Set-Cookie: remixsid=e7e94a5a03d852aca0ea22cdaaf5ca9399e2e470a0be610d3d87c314b461; expires=Thu, 24-May-2012 04:58:49 GMT; path=/; domain=.vkontakte.ru

Таких заголовков в ответе может быть несколько. Когда браузер отправляет запросы, он указывает вот такой заголовок:

Cookie: remixchk=5; remixsid=e7e94a5a03d852aca0ea22cdaaf5ca9399e2e470a0be610d3d87c314b461

Здесь браузер отправил 2 переменных: remixsid и remixchk. Как видете при отправке браузером все куки указываются в одном заголовке, причем там содержится только имя и значение кука. Теперь перед тем как послать страницу сервер может проверить куки и убедиться что юзер залогинен.

Заключение

С устройством браузера пока что закончим. По большей части мы буде заниматься тем, что будем качать страницы с помощью GET-запроса, выцеплять оттуда нужные данные, а потом отправлять нужный POST-запрос обратно на сервер. Для того чтобы было проще выцеплять данные нам очень пригодятся 2 вещи. Regex и XPath. XPath нужен для поиска нужных HTML тегов, как файлов на компе. А Regex может вытаскивать нужные данные из произвольного текста. О них я расскажу чуть позже.