Как-то давно видел на Хабре статью, как авторизоваться в yandex. Вдохновившись ею, решил написать аналогичную для «Вконтакте».
Сразу скажу, что в целом подход похожий, но есть и значительные отличия.
В основе всего используются HttpWebRequest и HttpWebResponse.
Шаг 1
Для сокращения кода подключаем:
using System.Collections; using System.Net; using System.IO; using System.Text.RegularExpressions; using System.Drawing;
Создаем класс, назовем его Net, в нем метод GetHtml:
class Net { string remixsid; //Id сессии public string lastCookies; //Куки public string GetHtml(string url, string postData) //Возвращает содержимое поданной страницы { string HTML = ""; Regex rex1 = new Regex("remixsid=(.*?);", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); if (url == "0") return "0"; //Проверка на ошибку HttpWebRequest myHttpWebRequest =(HttpWebRequest)HttpWebRequest.Create(url); //myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); //В перспективе можно использовать прокси if (!String.IsNullOrEmpty(postData)) myHttpWebRequest.Method = "POST"; myHttpWebRequest.Referer = "https://vk.com"; myHttpWebRequest.UserAgent = "Mozila/14.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg,image/pjpeg, application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); myHttpWebRequest.ContentType = "application/x-www-form-urlencoded"; myHttpWebRequest.KeepAlive = false; // передаем Сookie, полученные в предыдущем запросе if (!String.IsNullOrEmpty(this.remixsid)) { lastCookies = "remixchk=5;remixsid=" + this.remixsid; } if (!String.IsNullOrEmpty(lastCookies)) { myHttpWebRequest.Headers.Add(System.Net.HttpRequestHeader.Cookie, lastCookies); } // ставим False, чтобы при получении кода 302, не делать // автоматического перенаправления myHttpWebRequest.AllowAutoRedirect = false; // передаем параметры string sQueryString = postData; byte[] ByteArr = System.Text.Encoding.GetEncoding(1251).GetBytes(sQueryString); //Вконтакте использует кирилическую кодировку try { if (!String.IsNullOrEmpty(postData)) { myHttpWebRequest.ContentLength = ByteArr.Length; myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length); }; // делаем запрос HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); StreamReader myStreamReader; //Сохраняем Cookie lastCookies = String.IsNullOrEmpty(myHttpWebResponse.Headers["Set-Cookie"]) ? "" : myHttpWebResponse.Headers["Set-Cookie"]; Match matc1 = rex1.Match(lastCookies); //Если есть имя сессии, то подменяем Cookie if (matc1.Groups.Count == 2) { this.remixsid = matc1.Groups[1].ToString(); lastCookies = "remixchk=5;remixsid=" + this.remixsid; } if (myHttpWebResponse.Headers["Content-Type"].IndexOf("windows-1251") > 0) { myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding("windows-1251")); } else { myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.UTF8); } HTML = myStreamReader.ReadToEnd(); if (HTML == "") //Проверяем на редирект { HTML = this.GetHtml(myHttpWebResponse.Headers["Location"].ToString(), ""); } } catch (Exception err) { //Ошибка в чтении страницы return "0"; } return HTML; } }
В данном коде есть место, где я подменяю куки. Эту операцию пришлось сделать вынужденно, т.к. если просто загружать куки от предыдущего запроса, то сервер при очередном запросе просто сбрасывал куки. А в такой реализации все работает.
Шаг 2
Чтобы воспользоваться данным методом и авторизоваться, нужно создать объект http и отправить запрос на авторизацию.
Net http = new Net(); //Создаем объект string post = "email=" + this.login + "&pass=" + this.password + "&q=1&act=login&q=1&al_frame=1&expire=&captcha_sid=&captcha_key=&from_host=vk.com&from_protocol=http&ip_h=4e78766a2890ac1115&quick_expire=1"; string html = http.GetHtml("https://vk.com/", ""); html = http.GetHtml("https://login.vk.com/?act=login", post);
Первый запрос просто получает главную страницу, второй отправляет данные с логином и паролем.
В ответ мы должны получить примерно такой код:
<script type="text/javascript"> var _ua = navigator.userAgent; var locDomain = 'vk.com'.match(/[a-zA-Z]+\.[a-zA-Z]+\.?$/)[0]; if (/opera/i.test(_ua) || !/msie 6/i.test(_ua) || document.domain != locDomain) { document.domain = locDomain; } parent.__qlClear(); parent.onLoginDone('/id62983254'); </script>
Шаг 3
Вытягиваем из этого кода Id:
Regex rex4 = new Regex("parent\\.onLoginDone\\(\\'(.*?)\\'\\)", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); matc4 = rex4.Match(html); this.userid = matc4.Groups[1].ToString().Replace("/id", "");
Шаг 4
Прогружаем страничку пользователя и проверяем успешность регистрации:
html = http.GetHtml("https://vk.com/id" + this.userid, ""); int status = Testlogin(html);
status==1 — авторизация прошла успешно
status==2 — аккаунт заблокирован
status==3 — ошибка логина или пароля
status==4 — требуется переавторизация
Код метода проверки на авторизацию и на капчу:
private int Testlogin(string html) { if (html.IndexOf("login?act=blocked") > 0) { this.status = 2; return 2; } if (html.IndexOf("onLoginFailed") > 0) { this.status = 3; return 3; } Regex rex1 = new Regex("href=\"\\/edit\"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); Match matc1 = rex1.Match(html); if (matc1.Groups[0].Length == 0) { this.status = 4; return 4; } this.status = 1; return 1; } private string TestCaptch(string html) { Regex rex1 = new Regex("captcha_sid\\\":\\\"(\\d*)\\\"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); Match matc1 = rex1.Match(html); if (matc1.Groups[1].Length == 0) return "0"; return matc1.Groups[1].ToString(); }
Метод TestCaptch возвращает Id капчи, которую можно использовать в дальнейшем разгадывании.
Шаг 5
Разгадываем капчу.
В класс Net добавляем еще один метод:
public Image GetImg(string url, Image image) //Возвращает изображение { string postData = ""; string HTML = ""; HttpWebRequest myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create( url); //myHttpWebRequest.Proxy = new WebProxy("127.0.0.1", 8888); if (!String.IsNullOrEmpty(postData)) myHttpWebRequest.Method = "POST"; myHttpWebRequest.Referer = "http://vk.com"; myHttpWebRequest.UserAgent = "Mozila/4.0 (compatible; MSIE 6.0;Windows NT 5.1; SV1; MyIE2;"; myHttpWebRequest.Accept = "image/gif, image/x-xbitmap, image/jpeg,image/pjpeg, application/x-shockwave-flash,application/vnd.ms-excel,application/vnd.ms-powerpoint,application/msword"; myHttpWebRequest.Headers.Add("Accept-Language", "ru"); myHttpWebRequest.ContentType = "application/x-www-form-urlencoded"; myHttpWebRequest.KeepAlive = false; // передаем Cookie, полученные в предыдущем запросе if (!String.IsNullOrEmpty(this.remixsid)) { lastCookies = "remixchk=5;remixsid=" + this.remixsid; } if (!String.IsNullOrEmpty(lastCookies)) { myHttpWebRequest.Headers.Add(HttpRequestHeader.Cookie, lastCookies); } // ставим False, чтобы при получении кода 302, не делать // автоматического перенаправления myHttpWebRequest.AllowAutoRedirect = true; // передаем параметры string sQueryString = postData; byte[] ByteArr = System.Text.Encoding.GetEncoding(1251).GetBytes(sQueryString); if (!String.IsNullOrEmpty(postData)) { myHttpWebRequest.ContentLength = ByteArr.Length; myHttpWebRequest.GetRequestStream().Write(ByteArr, 0, ByteArr.Length); }; // делаем запрос try { HttpWebResponse myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse(); StreamReader myStreamReader; if (myHttpWebResponse.Headers["Content-Type"].IndexOf("windows-1251") > 0) { myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.GetEncoding("windows-1251")); } else { myStreamReader = new StreamReader(myHttpWebResponse.GetResponseStream(), Encoding.UTF8); } image = Image.FromStream(myHttpWebResponse.GetResponseStream()); HTML = myStreamReader.ReadToEnd(); myHttpWebResponse.Close(); if (HTML == "") HTML = myHttpWebResponse.Headers["Location"].ToString(); } catch (Exception err) { //Ошибка в чтении страницы return image; } return image; }
В принципе метод похож на GetHtml, за исключением того, что он возвращает объект Image с запрашиваемой капчей.
Выводим картинку на нашу форму:
public void Addcap(string sid) { this.img = "http://vk.com/captcha.php?sid=" + sid; Image image = net.GetImg(img, pictureBox1.Image); pictureBox1.Image = image; //Заранее созданный объект PictureBox, в котором будет видна капча }
Если при отправке какого-нибудь запроса, наша проверка на капчу дала положительный результат, то нам нужно разгадать капчу и отправить запрос заново, но добавить к нему два параметра &captcha_key= и &captcha_sid=
К слову, разгадать капчу можно как в ручную, так и прикрутить код с Antigate.
К примеру, повторный запрос отправки сообщения на стену будет выглядеть так:
post = "act=post&hash=" + hash + "&message=" + textu + "&al=1&captcha_key=" + captcha_key + "&captcha_sid=" + captcha_sid + "&facebook_export=&fixed=&friends_only=&from=¬e_title=&official=&signed=&status_export=&attach1_type=" + attachtype + "&attach1=" + attach + "&to_id=" + this.userid + "&type=all&url=" + System.Web.HttpUtility.UrlEncode(sendurl) + "&title=" + title + "&photo_url=" + System.Web.HttpUtility.UrlEncode(img);
captcha_key — разгаданная капча
captcha_sid — id капчи
sendurl — ссылка в теле сообщения
img — адрес картинки для ссылки
textu — сам текст сообщения
Есть еще параметр Hash, но его мне сгенерить не удалось, потому я достаю его регуляркой, предварительно зайдя на страницу группы.
Regex rex2 = new Regex("\"post_hash\":\"(.*?)\"", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); Match matc2 = rex2.Match(html); string hash = matc2.Groups[1].ToString();
P.S.
Возможно, кто-то скажет, что логичней было бы использовать API контакта, но, как показала практика, если использовать сразу несколько авторизаций с помощью API c одного IP, то через какое-то время данные аккаунты банятся. А в этом случае мы можем полностью сэмулировать действия реального пользователя через обычный браузер.
Но даже при таком подходе я бы советовал часть запросов, таких как получение информации о пользователях по их Id, выполнять через API, тем более данная операция не требует авторизации.
Если кому-то пригодится, могу в личку скинуть готовый класс под работу с Antigate. Он большой, и к теме напрямую не относится, потому тут выкладывать не стал.
ссылка на оригинал статьи http://habrahabr.ru/post/170467/
Добавить комментарий