• 巧用C#webbrowser以及Application.DoEvents()实现采集动态网页的爬虫机器人


    作者:finallyliuyu (转载请注明作者:finallyliuyu,出处:博客园)

    从事网络数据抓取采集从本科毕设算起已有一年多的时间,最开始是针对静态网页,写正则表达式,从网络上抓取信息。但是随着工作的深入,
    发现很多网页单单用正则表达式并不能完成抓取工作,比如很多网页的下一页链接是由JavaScript函数生成的比如
    <li><a href="#" onclick="javascript:gotoPage('2')">2</a></li>这样的网页,即便你用正则表达式,提取到了href也无法获得下一页链接。
    另外 如果url中还有“#”字段的,用httpresponse,httprequest获取的网页源码流与你在浏览器中所看到的页面视图也是不同的,因此单单用正则表达式,则处理起还有js脚本的动态网页就显得力不从心了。
    怎么办?

    可以采用DOM+正则+浏览器组件来解决上面的问题。

    DOM Document Object Model),是一个接口标准,该接口是将html网页解析成为树的格式,关于DOM的教程,请见:http://www.w3.org/DOM/  虽然上面讲的是JavaScript的 DOM 接口函数,但是由于DOM是一个接口标准,其他语言实现的DOM接口也是大同小异的。

    正则表达式:在完成文本匹配方面有着不可或缺的作用,这个powerful的工具,DOM是无法取代的。

    浏览器组件: 包含解释JS语句的功能,有了浏览器组件的帮忙,我们的工作会更加省力(另外:园子里有网友建议什么Xpath,webrequest等等,没有用过,如果有人在这方面比较熟悉不妨交流下)

    本功能采用VS2008 C# Winform 平台

    在此平台下调用正则要在程序的头部加入声明:

    using System.Text.RegularExpressions;

    调用DOM组件,需要在工程的引用中加入Microsoft.mshtml

    浏览器组件用的是webbrowser

    首先我们要在程序中构造一个简单的浏览器,要有一个combobox列表框(显示当前网页的URL),前进和后退按钮,控制浏览器刷新视图 实现代码如下:

    程序中实现简易浏览器的前进和后退功能
     private void btnGo_Click(object sender, EventArgs e)
            {
                
    string url = comboBox1.Text.Trim();

                webBrowser1.Navigate(url);
            }

     
    private void btnBack_Click(object sender, EventArgs e)
            {
                webBrowser1.GoBack();
            }

    光有前进和后退还不够,我们希望当浏览器视图刷新后,combobox里面的URL也跟着刷新,所以要再给浏览器添加一个Navigated事件,更新combobox显示的文本。代码如下:

     private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
            {
                comboBox1.Text 
    = webBrowser1.Url.ToString();
            }

     这还不够,当你实现上述代码时你会发现,你点击webbrowser里面的链接时,会在本地IE中显示新网页,所以我们还需要添加一个NewWindow事件代码如下

    webbrowser NewWindow
    private void webBrowser1_NewWindow(object sender, CancelEventArgs e)
            {
                e.Cancel 
    = true;
                
    if (webBrowser1.Document.ActiveElement != null)
                {
                    webBrowser1.Navigate(webBrowser1.Document.ActiveElement.GetAttribute(
    "href"));
                    comboBox1.Text 
    = webBrowser1.Document.ActiveElement.GetAttribute("href");
                }

            }

     实现了如上代码,那么程序中就配置好了一个简易的IE浏览器了。剩下的问题就是如何设计爬虫逻辑,形成自动爬虫机器人了(这里声明一下:本篇博文仅提供一个自动爬虫机器人的框架性思路,至于如何捕获具体的网页信息块儿BOI(block of interest)还需要根据网页的具体情况配置不同的模板。

    为了方便大家理解,下面给出我的任务需求

    一级索引页面包括若干指向二级索引页面的链接,二级索引页面又包含若干指向正文页的链接,我们的目的是从一级索引页获取指向二级索引页的链接,并且遍历所有二级索引页,提取出其指向正文的链接,保存下来。 其中难点在于一级索引页指向的是二级索引页的首页,二级索引页还有若干后续页。二级索引页的首页以及后续页上都有指向正文页的链接。可以这么打个比方,比如一个小论坛(可以视为一级索引页) 有三个板块 生活(视为二级索引页),美食,IT。不同板块内部又分了N多页,每个页面上都有正向具体内容(正文页)的链接。

    所以,实现需求需要在二级索引页的首页进行向下翻页。下面给出二级索引页体现链接到下一页的形式:

     

     或者:

     并且,所有的URL 都是用javascript函数生成的,这样就没有办法用正则来解决了。

    我的思路是这样的,定位当前页,用DOM 定位当前页下一页的anchor并且模拟点击。

    方法是首先定位当前页面的页码(这个不难办到,因为所有的页码链接信息在一个块中,并且当前页面对于当前页面没有链接),然后取出当前页面的所有页码,并且按照从大到小排序,

    并且比较curpageId+1与当前页面最大页码之间的关系,如果curpage+1<<maxPageId,则说明在当前页面能够定位到当前页的下一页;反之,就看是否有还有Next的aTag,如果有则Next就是当前页面的下一页,如果不存在含有Next的aTag,则说明已经到了尾页。

     完成上面的功能,最复杂的部分是处理Webbrowser的异步更新问题,在网上找了若干资料,觉得讲得比较好的还是:http://www.hackpig.cn/post/28.html

    本文的方法就是参照了链接中的博文内容,并进行改进。 下面上个图片,说一下我的程序的工作机制

    实际工作中仅用到了两个按钮,journapmap 和buildwokflow

    按下journalmap,则获取了一个全局数据结构,存放各个二级索引页的首页地址

    按下buildworkflow,则爬虫开始自动遍历所有二级索引页的首页地址,翻页爬取正文页的URL

    在工作中,要先按journalmap按钮,提示二级索引页首页地址提取出后,在按下buildworkflow按钮,让程序自动工作。

    为了保证程序运行逻辑,为窗体声明四个信号变量

    public bool mysignal1;//btnworkflow按钮是否被点击
    public bool mysignal2;
    public bool loading;//工作流按钮与webbrowser进行交互的通信按钮
    public bool subloading;

     并做如下初始赋值:

    信号变量初始赋值
    public Form1()
            {
                InitializeComponent();
                mysignal1 
    = false;
                mysignal2 
    = false;
                loading 
    = true;
                subloading 
    = true;
                issuesMap 
    = new List<string>();
            }

    下面给出BuidWorkFlow和webbrowser.documentcompleted的代码,看看这两者是如何交互工作的

    工作流按钮代码,点击此按钮,则爬虫自动工作
    private void btnworkflow_Click(object sender, EventArgs e)
            {
                mysignal1 
    = true;
                List
    <ArticlePage> arListCurrentPage;
                
    foreach (string s in issuesMap)
                {

                    loading 
    = true;
                    
    string tmpurl =  s;
                    webBrowser1.Navigate(tmpurl);
                    
    while (loading == true)
                    {
                        Application.DoEvents();
                    }
                   
                     
                        arListCurrentPage 
    = GetArticlePageInfoFromCurrentDirpage();
                         
    if (arListCurrentPage != null)
                         {
                             InsertTitleUrlToDataBase(arListCurrentPage);
                         }
                        
                   

                        mysignal2 
    = true;
                        
    while (AnchorNextPage())
                        {
                            subloading 
    = true;
                            
    while(subloading)
                            {
                                Application.DoEvents();

                            }
                            arListCurrentPage 
    = GetArticlePageInfoFromCurrentDirpage();
                            
    if (arListCurrentPage != null)
                            {
                                InsertTitleUrlToDataBase(arListCurrentPage);
                            }

                            

                        }
                        mysignal2 
    = false;
                        
                        
    //获得当前页面的下一页链接
                    
                       
                }
                    

                       
            }
    webbrowserCompleted更新信号
      private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
          {
                
    if (webBrowser1.ReadyState ==WebBrowserReadyState.Complete)
                {
                    
    if (mysignal1)
                    {
                        
    if (!mysignal2)
                        {
                            loading 
    = false;
                        }
                        
    else
                        {
                            subloading 
    = false;
                        }
                        
                    }
                    
                    
                }
                                     
                      
                   
                

                
                    
                


            }

     解释一下 btnworkflow和webbrowser交互工作的原理。 按下btnworkflow按钮,mysingal1的值就为真,这时候 webbrowser文档加载完毕后给loading赋值为假,使btnworkflow循环后面的代码得以执行,即将二级索引页首页中所包含的正文URL提取出来保存到数据库,之后mysignal2的值为真,这时候,webbrowser文档加载完毕后给subloading赋值为假,使得btnworkflow中子循环得以不断运行,周而复始地完成提取当前页正文URL链接,翻到下一页,直到没有下一页可翻了,btnworkflow的子循环退出,mysignal2被赋值为假,webbrowser文档加载完毕后更新loading为假,使得下一个二级索引首页的内容能够提取出来。

    几点要说明的是:我在完成此功能的时候,参考了网络上很多代码片段,很多的代码片段在webbrowser_documentcompleted函数中,完成解析内容,获取下一页链接,翻页的代码。这样做

    很容易出现信息提取重复(即一个页面提取了两到三次)。

     最后给出程序中利用DOM点击下一页的代码:

    点击当前页下一页的函数
     private bool AnchorNextPage()
            {   
    bool rstStatus=false;
                 .......中间的代码是利用正则表达式和DOM函数(如GetElementByTagName,GetElementById等)定位到当前页的下一页链接
                 
    if (htmlElemNext != null)
                    {



                        mshtml.IHTMLElement anchor 
    = (mshtml.IHTMLElement)htmlElemNext.DomElement;
                        anchor.click();
    //模拟点击
                        rstStatus=true;
                        
                                        


                }
                
    return rstStatus;
                
                
                



            }

     附:感谢 naber 千羽 碧水寒潭 等几位园友 在博问中为我解惑。

  • 相关阅读:
    Leangoo:用敏捷开发管理思维做团队协作的SaaS软件
    张江男的逆袭,我如何使用leangoo提升团队效率
    探索leangoo常用快捷键
    Tkinter教程之Event篇(3)
    Tkinter教程之Event篇(2)
    Tkinter教程之Event篇(1)'
    Tkinter教程之Grid篇
    Tkinter教程之Pack篇
    Tkinter教程之Canvas篇(4)
    Tkinter教程之Canvas篇(3)
  • 原文地址:https://www.cnblogs.com/finallyliuyu/p/1863691.html
Copyright © 2020-2023  润新知