4

我正在尝试自动登录 Photobucket 以供需要使用存储凭据自动下载照片的项目的 API 使用。

API 生成一个用于登录的 URL,使用 Firebug 我可以看到正在发送/接收的请求和响应。

我的问题是,如何使用 HttpWebRequest 和 HttpWebResponse 来模拟 C# 浏览器中发生的情况?

是否可以在 C# 应用程序中使用 Web 浏览器组件,填充用户名和密码字段并提交登录信息?

4

4 回答 4

5

我以前做过这种事情,最后得到了一个很好的工具包来编写这些类型的应用程序。我已经使用这个工具包来处理非平凡的 back-n-forth Web 请求,所以这是完全可能的,而且不是非常困难。

我很快发现从头开始做HttpWebRequest/确实比我想要处理的要低级。HttpWebResponse我的工具完全基于 Simon Mourier 的HtmlAgilityPack。这是一个优秀的工具集。它为您完成了很多繁重的工作,并且使解析获取的 HTML变得非常容易。如果您可以摇滚 XPath 查询,那么 HtmlAgilityPack 就是您想要开始的地方。它也可以很好地处理格式不佳的 HTML!

你仍然需要一个好的工具来帮助调试。除了调试器中的内容之外,能够检查通过网络来回传输的 http/https 流量是无价的。由于您的代码将发出这些请求,而不是您的浏览器,因此 FireBug 不会对调试您的代码有太大帮助。有各种各样的数据包嗅探器工具,但对于 HTTP/HTTPS 调试,我认为您无法击败Fiddler 2的易用性和强大功能。最新版本甚至带有一个用于 Firefox 的插件,可以通过 fiddler 快速转移请求并返回。因为它还可以充当无缝 HTTPS 代理,所以您也可以检查 HTTPS 流量。

试一试,我相信它们将成为你黑客攻击中不可或缺的两个工具。

更新:添加了以下代码示例。这是从一个不大的“会话”类中提取的,该类登录到网站并为您保留相关的 cookie。我之所以选择这个,是因为它不仅仅是一个简单的“请为我获取该网页”代码,而且它还有一两行 XPath 针对最终目标页面的查询。

public bool Connect() {
   if (string.IsNullOrEmpty(_Username)) { base.ThrowHelper(new SessionException("Username not specified.")); } 
   if (string.IsNullOrEmpty(_Password)) { base.ThrowHelper(new SessionException("Password not specified.")); }

   _Cookies = new CookieContainer();
   HtmlWeb webFetcher = new HtmlWeb();
   webFetcher.UsingCache = false;
   webFetcher.UseCookies = true;

   HtmlWeb.PreRequestHandler justSetCookies = delegate(HttpWebRequest webRequest) {
      SetRequestHeaders(webRequest, false);
      return true;
   };
   HtmlWeb.PreRequestHandler postLoginInformation = delegate(HttpWebRequest webRequest) {
      SetRequestHeaders(webRequest, false);

      // before we let webGrabber get the response from the server, we must POST the login form's data
      // This posted form data is *VERY* specific to the web site in question, and it must be exactly right,
      // and exactly what the remote server is expecting, otherwise it will not work!
      //
      // You need to use an HTTP proxy/debugger such as Fiddler in order to adequately inspect the 
      // posted form data. 
      ASCIIEncoding encoding = new ASCIIEncoding();
      string postDataString = string.Format("edit%5Bname%5D={0}&edit%5Bpass%5D={1}&edit%5Bform_id%5D=user_login&op=Log+in", _Username, _Password);
      byte[] postData = encoding.GetBytes(postDataString);
      webRequest.ContentType = "application/x-www-form-urlencoded";
      webRequest.ContentLength = postData.Length;
      webRequest.Referer = Util.MakeUrlCore("/user"); // builds a proper-for-this-website referer string

      using (Stream postStream = webRequest.GetRequestStream()) {
         postStream.Write(postData, 0, postData.Length);
         postStream.Close();
      }

      return true;
   };

   string loginUrl = Util.GetUrlCore(ProjectUrl.Login); 
   bool atEndOfRedirects = false;
   string method = "POST";
   webFetcher.PreRequest = postLoginInformation;

   // this is trimmed...this was trimmed in order to handle one of those 'interesting' 
   // login processes...
   webFetcher.PostResponse = delegate(HttpWebRequest webRequest, HttpWebResponse response) {
      if (response.StatusCode == HttpStatusCode.Found) {
         // the login process is forwarding us on...update the URL to move to...
         loginUrl = response.Headers["Location"] as String;
         method = "GET";
         webFetcher.PreRequest = justSetCookies; // we only need to post cookies now, not all the login info
      } else {
         atEndOfRedirects = true;
      }

      foreach (Cookie cookie in response.Cookies) {
         // *snip*
      }
   };

   // Real work starts here:
   HtmlDocument retrievedDocument = null;
   while (!atEndOfRedirects) {
      retrievedDocument = webFetcher.Load(loginUrl, method);
   }


   // ok, we're fully logged in.  Check the returned HTML to see if we're sitting at an error page, or
   // if we're successfully logged in.
   if (retrievedDocument != null) {
      HtmlNode errorNode = retrievedDocument.DocumentNode.SelectSingleNode("//div[contains(@class, 'error')]");
      if (errorNode != null) { return false; }
   }

   return true; 
}


public void SetRequestHeaders(HttpWebRequest webRequest) { SetRequestHeaders(webRequest, true); }
public void SetRequestHeaders(HttpWebRequest webRequest, bool allowAutoRedirect) {
   try {
      webRequest.AllowAutoRedirect = allowAutoRedirect;
      webRequest.CookieContainer = _Cookies;

      // the rest of this stuff is just to try and make our request *look* like FireFox. 
      webRequest.UserAgent = @"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.3) Gecko/20070309 Firefox/2.0.0.3";
      webRequest.Accept = @"text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
      webRequest.KeepAlive = true;
      webRequest.Headers.Add(@"Accept-Language: en-us,en;q=0.5");
      //webRequest.Headers.Add(@"Accept-Encoding: gzip,deflate");
   }
   catch (Exception ex) { base.ThrowHelper(ex); }
}
于 2009-08-20T14:09:48.403 回答
4

这是我解决它的方法:

public partial class Form1 : Form {
        private string LoginUrl = "/apilogin/login";
        private string authorizeUrl = "/apilogin/authorize";
        private string doneUrl = "/apilogin/done";

        public Form1() {
            InitializeComponent();
            this.Load += new EventHandler(Form1_Load);
        }

        void Form1_Load(object sender, EventArgs e) {
            PhotobucketNet.Photobucket pb = new Photobucket("pubkey","privatekey");
            string url = pb.GenerateUserLoginUrl();
            webBrowser1.Url = new Uri(url);
            webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);
        }

        void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
            if (e.Url.AbsolutePath.StartsWith(LoginUrl))
            {
                webBrowser1.Document.GetElementById("usernameemail").SetAttribute("Value","some username");
                webBrowser1.Document.GetElementById("password").SetAttribute("Value","some password");
                webBrowser1.Document.GetElementById("login").InvokeMember("click");
            }

            if (e.Url.AbsolutePath.StartsWith(authorizeUrl))
            {
                webBrowser1.Document.GetElementById("allow").InvokeMember("click");
            }

            if (e.Url.AbsolutePath.StartsWith(doneUrl))
            {
                string token = webBrowser1.Document.GetElementById("oauth_token").GetAttribute("value");
            }
        }
    }

最后一个 if 块中的令牌捕获是继续使用 API 所需要的。这种方法对我来说很好用,因为当然需要它的代码将在 Windows 上运行,所以我可以毫无问题地生成一个进程来加载这个单独的应用程序以提取令牌。

于 2009-08-20T14:04:46.367 回答
2

可以使用本机 WebbrowserControl 登录网站。但正如您在示例中看到的,您必须先识别控件的名称。

private void webBrowserLogin_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {

        if (webBrowserLogin.Url.ToString() == WebSiteUrl)
        {
            foreach (HtmlElement elem in webBrowserLogin.Document.All)
            {

                if (elem.Name == "user_name")              // name of the username input
                {
                    elem.InnerText = UserName;               
                }

                if (elem.Name == "password")               // name of the password input
                {
                    elem.InnerText = Password;                
                } 

            }

            foreach (HtmlElement elem in webBrowserLogin.Document.All)
            {

                if (elem.GetAttribute("value") == "Login")
                {
                    elem.InvokeMember("Click");
                }
            }
        }
    }
于 2009-08-20T13:51:14.263 回答
0

查看他在此处描述的 Rohit 的 BrowserSession 类(以及此处的第 2 部分)。基于 HtmlAgilityPack,但做了一些从 FORM 填充 POST 数据的无聊工作。

于 2012-07-16T20:55:00.337 回答