C# WebBrowser类使用总结

之前使用WebRequest类来操作网页实现一些网站的外挂功能,最典型的例子就是用C#写个程序作为淘宝店铺的外挂,实现宝贝和订单管理等功能,比如我写的这个《淘宝店铺助手》实现了自动理货、自动好评、自动厨窗和掌柜推荐、自动检查订单和提醒等。

但是操蛋的淘宝老是修改登录规则,搞得这类外挂经常宕机,比如前天下午3点开始就发现我那个店铺助手无法登录,包括使用PHP curl系列函数操作店铺也挂掉了。

大概原因是淘宝修改了登录规则,在登录过程中做了一些恶邪的跳转,比如先返回登录界面再延时跳转完成真正的登录,即间接登录,导致了使用WebRequest提交的登录请求没能返回最终的登录成功的Cookie,后续使用这个Cookie操作淘宝帐户均失败而自动转向登录界面。

也就是说淘宝这里修改影响到了那些自维护Cookie的网页访问操作,比如WebRequest和WebClient等将不能使用。但是还是有可替代的方法,比如使用WebBrowser类或控件,这些组件是IE内核,自己管理Cookie,可以绕过淘宝的那种限制。结论是淘宝的修改起不了多少作用,反而引起更多的反感,我真想对它说“去吃屎吧!”

要从WebRequest转到WebBrowser最方便的是使用WebBrowser类而不是Microsoft Web Browser COM组件,后者我试下来发现Navigate直接提交登录请求不起作用,这里就不讨论该控件,只谈WebBrowser class.

1. 定义WebBrowser类对象,设置属性和添加事件

WebBrowser _browser = new WebBrowser();
AutoResetEvent _event = new AutoResetEvent(false); // 加一个事件通知
string _response = null; // 这是页面的内容,每次调用Navigate之前清空
_browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(_browser_DocumentCompleted);
_browser.NewWindow += new System.ComponentModel.CancelEventHandler(_browser_NewWindow);
_browser.ScriptErrorsSuppressed = true;	// 忽略脚本错误
_browser.WebBrowserShortcutsEnabled = false;	// 禁止快捷键
_browser.IsWebBrowserContextMenuEnabled = false;	// 禁止右键菜单
_browser.AllowWebBrowserDrop = false;	// 禁止拖放

2. 处理事件

private void _browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
    if (_response == null && _browser.ReadyState == WebBrowserReadyState.Complete && _browser.Url.Equals(e.Url))
    {
        try
        {   // 以GBK编码读取页面内容
            StreamReader reader = new StreamReader(_browser.DocumentStream, Encoding.GetEncoding("GBK"));
            Regex rx = new Regex("[\r\n\t]");	// 把换行符、TAB符清除,便于正规表达式
            _response = rx.Replace(reader.ReadToEnd(), "");
        }
        catch (System.Exception ex)
        {
            // WriteLog("获取网页异常:" + ex.Message + "\r\n" + ex.StackTrace);
        }
        _event.Set();
    }
}
private void _browser_NewWindow(object sender, System.ComponentModel.CancelEventArgs e)
{
    e.Cancel = true;	// 禁止弹出新窗体
}

3. WebBrowser的调用

private void SubmitUrl(string url, bool isPost, string data)
{
    string posData = null;
    string header = null;
    string target = "_top";
    byte[] bytePost = null;
    if (isPost)
    {
        int pos = url.IndexOf('?');
        if (pos > 0)
        {
            posData = url.Substring(pos + 1);
            url = url.Substring(0, pos);
        }
        if (data != null)
        {
            if (posData != null)
            {
                posData += "&" + data;
            }
            else
            {
                posData = data;
            }
        }
        header = "Content-Type: application/x-www-form-urlencoded" + Environment.NewLine;
        if (posData != null)
        {
            bytePost = Encoding.UTF8.GetBytes(posData);	// 只能转为UTF8
        }
    }
    try
    {
        _browser.Navigate(url, target, bytePost, header);
    }
    catch (System.Exception ex)
    {
        // WriteLog("浏览网页异常:" + ex.Message + "\r\n" + ex.StackTrace);
    }
}

4. 提交URL并等待返回内容

_browser.Stop();	// 目的是让WB停止,好象不起使用
_event.Reset();		// 事件状态清除
_response = null;	// 内容清除
SubmitUrl(url, isPost, data); // 调上一步的函数
DateTime start = DateTime.Now;
while (!_event.WaitOne(100, false))
{
    TimeSpan span = DateTime.Now - start;
    // 超时时间WEB_TIMEOUT根据情况设定,我设的45秒
    if (span.TotalMilliseconds >= WEB_TIMEOUT)
    {
        break;
    }
    Application.DoEvents();	// 必须:允许程序处理其它消息
}
_browser.Stop();	// 目的是让WB停止,好象不起使用

目前发现的问题:
1. WebBrowser在工作的时候CPU占用很高。
2. 无法禁止WebBrowser加载图片、JS、css等资源。
3. 如果网页反应慢的话,设置的WEB_TIMEOUT超时了还不返回结果将导致内容抓取失败。
4. 有时出现卡(假)死情况,就根IE加载页面半天没反应一样,这时只能杀进程。

PS. 在使用过程中发现这个WebBrowser总是占用过高CPU和资源,而且假死很严重。受这篇文章的提醒,我感觉到淘宝的处理方式也是跳转,通过Firebug监测没得出什么结果(搞定后才想起来其实就是跳转)。我后来把提交登录后转向的页面存成文件,然后跟登录之前的页一比较,就发现原来前者多了一条“window.locaion=xxx”,这样自动抓出这个URL再继续提交,循环这样操作,最后保存的Cookie就是正确的了。

Tags:

2 Responses to “C# WebBrowser类使用总结”

  1. 你好说道:

    请问如果有验证码的,你如何登录?

    • lordong说道:

      有验证码那就没办法了,当然可以做验证码图片识别,不过比较麻烦。
      淘宝可以通过取消登录保护来避免验证码的出现,取消后偶尔有验证码出现时可以使用浏览器输入验证码登录一下就不再出来了。

Leave a Reply


提醒: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。请务必注意user必须和评论者名相匹配(大小写一致)。