Использование BrowserSession и HtmlAgilityPack для входа в Facebook через .NET.

Я пытаюсь использовать Rohit Agarwal BrowserSession вместе с HtmlAgilityPack для входа в систему и последующего перемещения по Facebook.

Ранее мне удалось сделать то же самое, написав собственный HttpWebRequest. Тем не менее, он работает только тогда, когда я вручную извлекаю cookie из своего браузера и вставляю новую строку cookie в запрос каждый раз, когда я делаю новый "сеанс". Теперь я пытаюсь использовать BrowserSession для более разумной навигации.

Здесь текущий код:

BrowserSession b = new BrowserSession();

b.Get(@"http://www.facebook.com/login.php");
b.FormElements["email"] = "[email protected]";
b.FormElements["pass"] = "xxxxxxxx";
b.FormElements["lsd"] = "qDhIH";
b.FormElements["trynum"] = "1";
b.FormElements["persistent_inputcheckbox"] = "1";

var response = b.Post(@"https://login.facebook.com/login.php?login_attempt=1");

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

Однако, когда я пытаюсь сделать что-то вроде этого:

var profilePage = b.Get(@"https://m.facebook.com/profile.php?id=1111111111");

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

Ответы

Ответ 1

Извините, я мало знаю о пакете гибкости HTML или классе BrowserSession, о котором вы говорили. Но я попробовал тот же сценарий с HtmlUnit, и он работает нормально. Я использую .NET-оболочку (исходный код которой можно найти здесь и объясняется немного больше здесь), и вот код, который я использовал (некоторые детали удалены для защиты невинных):

var driver = new HtmlUnitDriver(true);
driver.Url = @"http://www.facebook.com/login.php";

var email = driver.FindElement(By.Name("email"));
email.SendKeys("[email protected]");

var pass = driver.FindElement(By.Name("pass"));
pass.SendKeys("xxxxxxxx");

var inputs = driver.FindElements(By.TagName("input"));
var loginButton = (from input in inputs
                   where input.GetAttribute("value").ToLower() == "login"
                   && input.GetAttribute("type").ToLower() == "submit"
                   select input).First();
loginButton.Click();

driver.Url = @"https://m.facebook.com/profile.php?id=1111111111";
Assert.That(driver.Title, Is.StringContaining("Title of page goes here"));

Надеюсь, что это поможет.

Ответ 2

Я установил основную причину этого, если кто-то заботится. Оказывается, файлы cookie сохраняются в CookieContainer объекта REQUEST, а не объект ответа. Я также добавил возможность загрузки файла (при условии, что файл основан на строках). Код определенно НЕ является потокобезопасным, но объект не был потокобезопасным для начала:

public class BrowserSession
{
    private bool _isPost;
    private bool _isDownload;
    private HtmlDocument _htmlDoc;
    private string _download;

    /// <summary>
    /// System.Net.CookieCollection. Provides a collection container for instances of Cookie class 
    /// </summary>
    public CookieCollection Cookies { get; set; }

    /// <summary>
    /// Provide a key-value-pair collection of form elements 
    /// </summary>
    public FormElementCollection FormElements { get; set; }

    /// <summary>
    /// Makes a HTTP GET request to the given URL
    /// </summary>
    public string Get(string url)
    {
        _isPost = false;
        CreateWebRequestObject().Load(url);
        return _htmlDoc.DocumentNode.InnerHtml;
    }

    /// <summary>
    /// Makes a HTTP POST request to the given URL
    /// </summary>
    public string Post(string url)
    {
        _isPost = true;
        CreateWebRequestObject().Load(url, "POST");
        return _htmlDoc.DocumentNode.InnerHtml;
    }

    public string GetDownload(string url)
    {
        _isPost = false;
        _isDownload = true;
        CreateWebRequestObject().Load(url);
        return _download;
    }

    /// <summary>
    /// Creates the HtmlWeb object and initializes all event handlers. 
    /// </summary>
    private HtmlWeb CreateWebRequestObject()
    {
        HtmlWeb web = new HtmlWeb();
        web.UseCookies = true;
        web.PreRequest = new HtmlWeb.PreRequestHandler(OnPreRequest);
        web.PostResponse = new HtmlWeb.PostResponseHandler(OnAfterResponse);
        web.PreHandleDocument = new HtmlWeb.PreHandleDocumentHandler(OnPreHandleDocument);
        return web;
    }

    /// <summary>
    /// Event handler for HtmlWeb.PreRequestHandler. Occurs before an HTTP request is executed.
    /// </summary>
    protected bool OnPreRequest(HttpWebRequest request)
    {
        AddCookiesTo(request);               // Add cookies that were saved from previous requests
        if (_isPost) AddPostDataTo(request); // We only need to add post data on a POST request
        return true;
    }

    /// <summary>
    /// Event handler for HtmlWeb.PostResponseHandler. Occurs after a HTTP response is received
    /// </summary>
    protected void OnAfterResponse(HttpWebRequest request, HttpWebResponse response)
    {
        SaveCookiesFrom(request, response); // Save cookies for subsequent requests

        if (response != null && _isDownload)
        {
            Stream remoteStream = response.GetResponseStream();
            var sr = new StreamReader(remoteStream);
            _download = sr.ReadToEnd();
        }
    }

    /// <summary>
    /// Event handler for HtmlWeb.PreHandleDocumentHandler. Occurs before a HTML document is handled
    /// </summary>
    protected void OnPreHandleDocument(HtmlDocument document)
    {
        SaveHtmlDocument(document);
    }

    /// <summary>
    /// Assembles the Post data and attaches to the request object
    /// </summary>
    private void AddPostDataTo(HttpWebRequest request)
    {
        string payload = FormElements.AssemblePostPayload();
        byte[] buff = Encoding.UTF8.GetBytes(payload.ToCharArray());
        request.ContentLength = buff.Length;
        request.ContentType = "application/x-www-form-urlencoded";
        System.IO.Stream reqStream = request.GetRequestStream();
        reqStream.Write(buff, 0, buff.Length);
    }

    /// <summary>
    /// Add cookies to the request object
    /// </summary>
    private void AddCookiesTo(HttpWebRequest request)
    {
        if (Cookies != null && Cookies.Count > 0)
        {
            request.CookieContainer.Add(Cookies);
        }
    }

    /// <summary>
    /// Saves cookies from the response object to the local CookieCollection object
    /// </summary>
    private void SaveCookiesFrom(HttpWebRequest request, HttpWebResponse response)
    {
        //save the cookies ;)
        if (request.CookieContainer.Count > 0 || response.Cookies.Count > 0)
        {
            if (Cookies == null)
            {
                Cookies = new CookieCollection();
            }

            Cookies.Add(request.CookieContainer.GetCookies(request.RequestUri));
            Cookies.Add(response.Cookies);
        }
    }

    /// <summary>
    /// Saves the form elements collection by parsing the HTML document
    /// </summary>
    private void SaveHtmlDocument(HtmlDocument document)
    {
        _htmlDoc = document;
        FormElements = new FormElementCollection(_htmlDoc);
    }
}

/// <summary>
/// Represents a combined list and collection of Form Elements.
/// </summary>
public class FormElementCollection : Dictionary<string, string>
{
    /// <summary>
    /// Constructor. Parses the HtmlDocument to get all form input elements. 
    /// </summary>
    public FormElementCollection(HtmlDocument htmlDoc)
    {
        var inputs = htmlDoc.DocumentNode.Descendants("input");
        foreach (var element in inputs)
        {
            string name = element.GetAttributeValue("name", "undefined");
            string value = element.GetAttributeValue("value", "");

            if (!this.ContainsKey(name))
            {
                if (!name.Equals("undefined"))
                {
                    Add(name, value);
                }
            }
        }
    }

    /// <summary>
    /// Assembles all form elements and values to POST. Also html encodes the values.  
    /// </summary>
    public string AssemblePostPayload()
    {
        StringBuilder sb = new StringBuilder();
        foreach (var element in this)
        {
            string value = System.Web.HttpUtility.UrlEncode(element.Value);
            sb.Append("&" + element.Key + "=" + value);
        }
        return sb.ToString().Substring(1);
    }
}

Ответ 3

Возможно, вы захотите использовать WatiN (тестирование веб-приложений в .Net) Или Selenium, чтобы управлять вашим браузером. Это поможет убедиться, что вам не нужно возиться с куки файлами и выполнять какую-либо обычную работу, чтобы выполнять последующие запросы, поскольку вы имитируете фактического пользователя.

Ответ 4

У меня были подобные симптомы - логин работал, но cookie для проверки подлинности не присутствовал в контейнере cookie, и поэтому он не отправлялся на последующие запросы. Я узнал об этом, потому что веб-запрос обрабатывал заголовок Location: внутри, перенаправляя за кулисами на новую страницу, теряя файлы cookie в процессе. Я исправил это, добавив:

    request.AllowAutoRedirect = false; // Location header messing up cookie handling!

... функции OnPreRequest(). Теперь он выглядит следующим образом:

    protected bool OnPreRequest(HttpWebRequest request)
    {
        request.AllowAutoRedirect = false; // Location header messing up cookie handling!

        AddCookiesTo(request);               // Add cookies that were saved from previous requests
        if (_isPost) AddPostDataTo(request); // We only need to add post data on a POST request
        return true;
    }

Я надеюсь, что это поможет кому-то испытать ту же проблему.

Ответ 5

Сегодня я столкнулся с той же проблемой. Я также работал с Rohit Agarwal BrowserSession вместе с HtmlAgilityPack. После пробного и пробного программирования весь день, я понял, что проблема вызвана, из-за того, что вы не устанавливаете правильные файлы cookie в последующих запросах. Я не могу изменить исходный код BrowserSession для правильной работы, но добавил следующие функции и немного изменил функцию SameCookieFrom. В конце концов это сработало хорошо для меня.

Добавленные/измененные функции следующие:

class BrowserSession{
   private bool _isPost;
   private HtmlDocument _htmlDoc;
   public CookieContainer cookiePot;   //<- This is the new CookieContainer

 ...

    public string Get2(string url)
    {
        HtmlWeb web = new HtmlWeb();
        web.UseCookies = true;
        web.PreRequest = new HtmlWeb.PreRequestHandler(OnPreRequest2);
        web.PostResponse = new HtmlWeb.PostResponseHandler(OnAfterResponse2);
        HtmlDocument doc = web.Load(url);
        return doc.DocumentNode.InnerHtml;
    }
    public bool OnPreRequest2(HttpWebRequest request)
    {
        request.CookieContainer = cookiePot;
        return true;
    }
    protected void OnAfterResponse2(HttpWebRequest request, HttpWebResponse response)
    {
        //do nothing
    }
    private void SaveCookiesFrom(HttpWebResponse response)
    {
        if ((response.Cookies.Count > 0))
        {
            if (Cookies == null)
            {
                Cookies = new CookieCollection();
            }    
            Cookies.Add(response.Cookies);
            cookiePot.Add(Cookies);     //-> add the Cookies to the cookiePot
        }
    }

Что он делает: он в основном сохраняет файлы cookie из начального "пост-ответа" и добавляет тот же CookieContainer к запросу, который называется позже. Я не совсем понимаю, почему он не работал в исходной версии, потому что он каким-то образом делает то же самое в функции AddCookiesTo. (если (Cookies!= null & & Cookies.Count > 0) request.CookieContainer.Add(Cookies);) Во всяком случае, с этими добавленными функциями он должен работать нормально.

Его можно использовать следующим образом:

//initial "Login-procedure"
BrowserSession b = new BrowserSession();
b.Get("http://www.blablubb/login.php");
b.FormElements["username"] = "yourusername";
b.FormElements["password"] = "yourpass";
string response = b.Post("http://www.blablubb/login.php");

все последующие вызовы должны использовать:

response = b.Get2("http://www.blablubb/secondpageyouwannabrowseto");
response = b.Get2("http://www.blablubb/thirdpageyouwannabrowseto");
...

Я надеюсь, что это поможет многим людям, столкнувшимся с одной и той же проблемой!

Ответ 6

Вы проверили свой новый API? http://developers.facebook.com/docs/authentication/

Вы можете вызвать простой URL-адрес, чтобы получить токен доступа oauth2.0 и приложить его к остальным запросам...

https://graph.facebook.com/oauth/authorize?
    client_id=...&
    redirect_uri=http://www.example.com/oauth_redirect

Измените redirect_uri на любой URL-адрес, который вы хотите, и он будет вызван обратно с параметром, называемым "access_token". Получите это и сделайте любые автоматические вызовы SDK, которые вы хотите.