Автор: Kairos

Урок 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 может вытаскивать нужные данные из произвольного текста. О них я расскажу чуть позже.