【发布时间】:2015-10-11 04:09:54
【问题描述】:
请耐心等待这个稍微冗长的描述,但我遇到了一个奇怪的问题,即 C# 屏幕抓取 ASP.NET Web 表单页面。我正在尝试执行的步骤如下:-
1) 该站点使用基于 HTTPS 的基本身份验证进行保护,因此我需要正确登录。
2) 我正在页面上执行 GET 请求以检索 __VIEWSTATE 值(如果我不设置这个东西,该死的东西什么都不做!)
3) 登录后,有几个表单字段需要填写,然后是一个提交按钮,该按钮将表单发送到服务器
4) 当按下提交按钮时,表单被 POST'd 到服务器,响应是相同的页面和表单,但现在表单底部有一个额外的小 HTML 表,其中包含一些我需要获取的数据.
到目前为止,我已经设法使用 WebClient 类对登录和表单帖子进行排序。我使用提琴手(和萤火虫)来检查正常使用浏览器完成表单时发送的 POST 字段值。我可以成功地从 POST 请求中获得响应,相关数据表按预期显示在表单下方。然而问题是,虽然表格中填充了数据,但它填充了我不期望的数据。出现的数据是,如果我在浏览器中正常填写表单,但将一个特定参数(下拉列表)设置为与将 POST 请求传递给服务器不同的值。我已经使用 fiddler 和 firebug 确认我传递的 POST 参数与使用 Web 浏览器人工完成的表单正常发送的参数完全相同。我现在完全不知道为什么服务器没有“考虑”这个参数?
一个区别是这个特定的控件是一个选择列表,它在更改时执行页面重新加载或“回发”。但是,除了在表单后面更改一些其他选择列表内容之外,这似乎没有任何作用。
我想我在问还有什么我遗漏的东西会导致这种情况吗?我完全把头发扯掉了。任何人都可以帮忙吗?我已经发布了下面的代码(为了隐私,地址和参数被屏蔽了)。
// a place to store the html
string responseBody = "";
// create out web client to handle the request
using (WebClient webClient = new WebClient())
{
// space to store responses from the remote site
byte[] responseBytes;
// site uses basic authentication over HTTPS so we'll need to login
CredentialCache credentials = new CredentialCache();
credentials.Add(new Uri(Url), "Basic", new NetworkCredential(Username, Password));
// set the credentials in the web client
webClient.Credentials = credentials;
// a place for __VIEWSTATE
string viewState = "";
// try and get __VIEWSTATE from the web site
try
{
responseBytes = webClient.DownloadData(Url);
viewState = GetHtmlInputValue(Encoding.UTF8.GetString(responseBytes), "__VIEWSTATE");
}
catch (Exception e)
{
bool cancel = false;
ComponentMetaData.FireError(10, "Read web page data", "Error whilst trying to get __VIEWSTATE from web page: " + e.Message, "", 0, out cancel);
}
// add our POST parameters (don't forget the __VIEWSTATE or it won't work as its an ASP.NET web page)
NameValueCollection requestParameters = new NameValueCollection();
// add ASP.NET fields
requestParameters.Add("__EVENTTARGET", __EVENTTARGET);
requestParameters.Add("__EVENTARGUMENT", __EVENTARGUMENT);
requestParameters.Add("__LASTFOCUS", __LASTFOCUS);
// add __VIEWSTATE
requestParameters.Add("__VIEWSTATE", viewState);
// all other form parameters
requestParameters.Add("btnSubmit", btnSubmit);
/* I've hidden the rest of the parameters hidden for privacy just in case */
// see if we can connect and get data
try
{
// set content type
webClient.Headers.Clear();
webClient.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
// 'POST' the form data using web client and hope we get a response
responseBytes = webClient.UploadValues(Url, "POST", requestParameters);
// transform the response to a string
responseBody = Encoding.UTF8.GetString(responseBytes);
}
catch (Exception e)
{
bool cancel = false;
ComponentMetaData.FireError(10, "Read web page data", "Error whilst trying to connect to web page: " + e.Message, "", 0, out cancel);
}
}
请忽略“ComponentMetaData”引用,因为这是 SSIS 脚本源的一部分。
任何想法或帮助将不胜感激 - 干杯!
RE:感谢您的快速回复,我只能对这些 cmets 说...
有正常的 ASP 会话 cookie,但 cookie 中没有值(当然除了会话 ID),我认为该站点使用的是基本身份验证而不是表单身份验证,我可以忽略 cookie - 因为我是进入网站并返回数据,这没关系。我想这值得一试,但我只需要更改代码以使用 WebRequest 类方法...
至于选择列表javascript,没有javascript在页面加载后更改选择列表的值。选择列表中唯一的 javascript 是执行“回发”的 onchange 事件,它似乎只会更改表单上的其他一些选择列表,这些列表在最终 POST 中仍然是空的。请注意,我在生成 POST 请求时包含了所有 POST 参数,即使它们是空的,我还包括所有“网络表单”特殊字段,例如 __VIEWSTATE、__EVENTTARGET 等...
我不是 Web 表单方面的专家(我自己是 MVC 人),但是 Web 表单“引擎”还有其他什么期望吗?我已经为“application/x-www-form-urlencoded”的“Content-Type”发送了 1 个标头,但我尝试设置其他标头,例如从原始 POST 复制“User-Agent”标头,但这最终我从服务器收到 500 错误,不知道为什么会发生这种情况??
这是“GetHtmlInputValue”的代码,它有点简单/基本,可以做得更好,但是:-
private string GetHtmlInputValue(string html, string inputID)
{
string valueDelimiter = "value=\"";
int namePosition = html.IndexOf(inputID);
int valuePosition = html.IndexOf(valueDelimiter, namePosition);
int startPosition = valuePosition + valueDelimiter.Length;
int endPosition = html.IndexOf("\"", startPosition);
return html.Substring(startPosition, endPosition - startPosition);
}
【问题讨论】:
-
您的浏览器中是否传递了任何cookie?您可能需要在请求中包含这些内容。
-
您是否在页面加载后检查了选择框的值是否使用 javascript 设置?这可以解释为什么它与您的预期不同。
-
您是否尝试使用
webClient.AppendUploadString(PostFieldName, PostFieldValue)来传递输入控件的值? -
您是否捕获了选择列表回发后的__VIEWSTATE。也许正在改变存储在 ViewState 中的服务器端?
-
根据下面的答案,这是解决方案,所以我也投票支持这条评论,谢谢老兄