最新消息:20210917 已从crifan.com换到crifan.org

【记录】研究模拟登陆百度的C#代码为何在.NET 4.0中不工作

Crawl_EmulateLogin crifan 4230浏览 0评论

【背景】

对于模拟登陆百度,之前已经写了帖子了,包括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.去拿到最新的

crifanLib.cs

然后把其中的代码,拷贝进来。整合进去。

此处,最新的_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}

如图:

dotnet 3.0 H_PS_PSSID cookie has value

而出错的.NET 4.0中,该cookie的值是空的:

dotnet 4.0 H_PS_PSSID cookie no value

由此:

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了:

dotnet added cookie key and val become to null

从而使得:

在发送真正的HttpWebRequest之前:

.NET 3.5中的cookie是3个:

dotnet 3.5 before send req cookie count is 3

而.NET 4.0中的Cookie是2个:

dotnet 4.0 before send req cookie count is 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,还是有问题。

所以,还是用自己的库:

parseSetCookie

去解析吧。

9.还是从最新的自己的库:

crifanLib.cs

中,提取出对应的解析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中不工作

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

网友最新评论 (6)

  1. .net 4.0 不工作解决方案: 在得到百度cookieid方法最后加上 foreach (Cookie cookie in curCookies) { cookie.Expires = DateTime.MaxValue; }
    xiaoyu10年前 (2014-05-28)回复
  2. 依然有Bug没有解决。response的 header里面有BAIDUID的cookie,经过你的parseSingleCookie过一次之后,BAIDUID这个部分Cookie被过滤掉了,最新的crflib里面。
    小五11年前 (2013-11-17)回复
    • 那是正常的。因为:BAIDUID被标为expired,所以经过解析后,按照正常的逻辑,丢弃了。
      crifan11年前 (2013-11-19)回复
      • 我最后查明白原因了,在baiduid=**那个 set-cookies里面,set-cookies返回了一个mag-age=****;,然后就会处理失败。我是在访问pan.baidu.com的时候出现的,你可以测一下。具体是,如果cookie带有max-age,在parseSingleCookie里面的 if (parseCookieField(eachExpression, out pair)) 会返回false,造成这个cookie不会放进gcurcookies,最后掉了一段。
        小五11年前 (2013-11-19)回复
99 queries in 0.194 seconds, using 23.55MB memory