以编程方式登录网站的技术

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

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

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

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

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

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

你仍然需要一个好的工具来帮助调试。 除了调试器中的内容之外,能够检查http / https流量,因为它在线路上向后传输是无价的。 由于您的代码将要发出这些请求,而不是您的浏览器,因此FireBug对调试代码没有多大帮助。 有各种各样的数据包嗅探工具,但对于HTTP / HTTPS调试,我认为你不能超越Fiddler 2的易用性和强大function。 最新版本甚至附带一个firefox插件,可以通过提琴手快速转移请求。 由于它还可以充当无缝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); } } 

这是我如何解决它:

 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上运行,所以我没有问题产生一个进程来加载这个单独的应用程序来提取令牌。

可以使用本机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"); } } } } 

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