【背景】
对于模拟登陆百度,之前已经写了帖子了,包括C#版本的教程和代码:
【教程】手把手教你如何利用工具(IE9的F12)去分析模拟登陆网站(百度首页)的内部逻辑过程
中的
【教程】模拟登陆网站 之 C#版(内含两种版本的完整的可运行的代码)
我代码中用的是.NET 2.0,但是后来有很多人反映:
.NET 3.5之前(.NET 2.0,3.0,3.5等等)都是工作的,但是.NET 4.0中,示例代码不工作。
比如
“尝试过很多次,使用.net framework3.5完全可以登陆,但是4.0就不行了,不知道为什么?麻烦有时间看看”
所以现在,抽空去研究看看。
【折腾过程】
1.去试了试,果然是.NET 4.0中,同样的代码,但是不工作。
2.参考了:
HttpWebRequest.GetResponse() hangs on .NET 3.5 but works on .NET 4
提到的:
How to: Send Data Using the WebRequest Class
感觉像是:
自己的代码,没有合适的close掉之前的HttpWebResponse,StreamReader等东西
不过自己更新版本的代码,已经做了这些了。
所以:
可以试试最新的crifanLib.cs中的代码,看看是否工作。
3.去拿到最新的
然后把其中的代码,拷贝进来。整合进去。
此处,最新的_getUrlResponse中,已经是包含了对应的各个资源的close了:
if (respStream != null) { respStream.Close(); } if (sr != null) { sr.Close(); } if (resp != null) { resp.Close(); }
然后再去测试,结果错误依旧。
还是之前的:
在btnGetToken_Click中,
getUrlRespHtml(getapiUrl);
而得到的respHtml是:
var bdPass=bdPass||{}; bdPass.api=bdPass.api||{}; bdPass.api.params=bdPass.api.params||{}; bdPass.api.params.login_token='the fisrt two args should be string type:0,1!'; bdPass.api.params.login_tpl='mn'; document.write('<script type="text/javascript" charset="UTF-8" src="https://passport.baidu.com/js/v2ApiUsedTangramFunctions.js?v=20130905"></script>'); document.write('<script type="text/javascript" charset="UTF-8" src="https://passport.baidu.com/js/pass_api_login.js?v=20130905"></script>');
即:
the fisrt two args should be string type:0,1! |
错误了。
4.结果参考别人帖子,想到去用fiddler去抓包,结果竟然抓不到正在调试的.NET 4.0的程序发出的http。
5.调试了下,出错的4.0中,返回的response是:
{Transfer-Encoding: chunked Connection: keep-alive Content-Type: text/html Date: Wed, 11 Sep 2013 03:23:50 GMT P3P: CP=" OTI DSP COR IVA OUR IND COM " Set-Cookie: BAIDUID=913C35AA5A54ED01949757FB6FD33FB8:FG=1; max-age=946080000; expires=Fri, 04-Sep-43 03:23:50 GMT; domain=.baidu.com; path=/; version=1,HOSUPPORT=1; expires=Sun, 28-Nov-2021 03:23:50 GMT; path=/; domain=passport.baidu.com; httponly Server: Vary: Accept-Encoding Content-Encoding: }
正常的3.5返回的response是:
{Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Content-Encoding: Content-Type: text/html Date: Wed, 11 Sep 2013 03:24:37 GMT Set-Cookie: HOSUPPORT=1; expires=Sun, 28-Nov-2021 03:24:37 GMT; path=/; domain=passport.baidu.com; httponly Server: }
但是无法看出错误在哪。
6.继续调试,发现问题了:
在发送HttpWebRequest之前:
正常的.net 3.0中,cookie是正常的:
H_PS_PSSID是有值的:
[1] = {H_PS_PSSID=3280_1458_2784_2980_3311_3225} |
如图:
而出错的.NET 4.0中,该cookie的值是空的:
由此:
cookie值不正常,返回结果出错,就在意料之中了。
7.所以,去分析,在.NET 4.0中,此处我所用的cookie处理的代码,为何会出错。
然后发现问题原因了:
在.NET 4.0中,正常的,之前三个cookie的值,保存在curCookies中,然后经过:
req.CookieContainer.Add(curCookies);
后,结果,其中的那个cookie:
H_PS_PSSID
应该是:
由于之前其Expires的值是:
Expires {1/1/0001 12:00:00 AM} System.DateTime |
而使得.NET 4.0中,认为此cookie过期,而丢掉了,从而导致:
此H_PS_PSSID的cookie,在CookieContainer.Add后,就被丢掉了,值变成null了:
从而使得:
在发送真正的HttpWebRequest之前:
.NET 3.5中的cookie是3个:
而.NET 4.0中的Cookie是2个:
从而导致,此处出错。
8.所以,手动去改动一下:
.NET 4.0中CookieContainer.Add之前,把对应的那个cookie,即H_PS_PSSID的Expires时间改动一下,保证不过期:
从:
{1/1/0001 12:00:00 AM} |
想要去改动,但是发现是此处cookie是只读的,所以还改不了,那么就去之前获得cookie,解析出来的时候,去改过期时间
结果又发现,此处通过resp.Cookies得到的cookie,也是只读,没办法改。
所以,在去看看对应的此处返回的resp的header信息是:
{Connection: Keep-Alive Proxy-Connection: Keep-Alive BDPAGETYPE: 1 BDUSERID: 0 BDQID: 0xbe6988d200c62691 Content-Length: 11054 Cache-Control: private Content-Type: text/html;charset=utf-8 Date: Wed, 11 Sep 2013 05:19:08 GMT Expires: Wed, 11 Sep 2013 05:19:08 GMT Set-Cookie: BDSVRTM=3; path=/,H_PS_PSSID=1457_2785_3311_3224; path=/; domain=.baidu.com,BAIDUID=F0B41B3EC03B9C15C1B00F6B9FA6A52F:FG=1; expires=Wed, 11-Sep-43 05:19:08 GMT; path=/; domain=.baidu.com Server: BWS/1.0 Via: 1.1 SC-SZ-06 P3P: CP=" OTI DSP COR IVA OUR IND COM " }
很明显,其中的:
BDSVRTM和H_PS_PSSID两个cookie,都是没有指定expires的值的
换句话说,应该解析为:永不过期
把expires的值,设置为最大,比如2043年之类的时间的。
所以,.NET 4.0库,解析cookie,还是有问题。
所以,还是用自己的库:
去解析吧。
9.还是从最新的自己的库:
中,提取出对应的解析cookie的这些函数,然后整合进来:
此处改动的部分的代码为:
bool noDeisignateExpires = true; foreach (string eachExpression in fieldExpressions) { //parse key and value if (parseCookieField(eachExpression, out pair)) { // add to cookie field if possible addFieldToCookie(ref ck, pair); if (string.Equals(pair.key, constStrExpires)) { noDeisignateExpires = false; } } else { // if any field fail, consider it is a abnormal cookie string, so quit with false parsedOk = false; break; } } if (noDeisignateExpires) { //for those not designate expires field //set to max expires date -> let it not expires ck.Expires = DateTime.MaxValue; }
10.期间对于一个日期的解析,折腾了半天:
【记录】C#中尝试用DateTime.TryParse去解析特定日期时间的格式:"Wed, 11-Sep-43 05:54:19 GMT"
结果却是百度返回的日期字符串有误,导致解析始终失败->浪费我半天时间->百度你妹啊!!!
11.最后经过修改N多处的代码,最后终于可以了。
对应的代码,已放在原帖了:
【教程】模拟登陆网站 之 C#版(内含两种版本的完整的可运行的代码)
【总结】
再一次的证明了:
C#中,HttpWebRequest对于cookie方面的处理,是多么的不合理和多么的有bug。
至少,我认为,这种:
BDSVRTM=1; path=/ H_PS_PSSID=1431_2784_3092_3311_3225; path=/; domain=.baidu.com |
即,没有指定expires域的cookie,应该处理为:
将cookie的Expires的值,设置为很大,比如我在C#中设置的:
ck.Expires = DateTime.MaxValue;
使得该cookie,不是过期,expired域始终为false。
这样才符合原先的没有指定expires域的cookie的本意:没有指定过期时间,说明就是永不过期;
转载请注明:在路上 » 【记录】研究模拟登陆百度的C#代码为何在.NET 4.0中不工作