巧用C#webbrowser以及Application.DoEvents()實現採集動態網頁的爬蟲機器人

作者:finallyliuyu (轉載請註明作者:finallyliuyu,出處:博客園)

 

從事網絡數據抓取採集從本科畢設算起已有一年多的時間,最開始是針對靜態網頁,寫正則表達式,從網絡上抓取信息。但是隨着工作的深入,
發現很多網頁單單用正則表達式並不能完成抓取工作,比如很多網頁的下一頁鏈接是由JavaScript函數生成的比如
<li><a href="#" οnclick="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;
        }

 

發佈了13 篇原創文章 · 獲贊 9 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章