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

Comments are closed.