在大多數情況下,上網(wǎng)沖浪是件令人愉快的事情。但若是數百上千的超鏈接擺在你面前,而你又不得不一一點(diǎn)擊這些鏈接、進(jìn)入相應的網(wǎng)頁(yè)、手工篩選出每頁(yè)里你需要的信息、最后再將這些信息編進(jìn)數據庫中、....,你將做何感想?如果每天都從事這種繁雜、枯燥的工作會(huì )不會(huì )讓你發(fā)瘋?
“自動(dòng)上網(wǎng)機器人”或許可救你出“苦海”:你可以喝著(zhù)咖啡、聽(tīng)著(zhù)音樂(lè )、看著(zhù)“機器人”辛勤地替你工作,那感覺(jué)是不是棒極了!
本文結合實(shí)例詳盡討論了用VB實(shí)現“上網(wǎng)機器人”的技術(shù)細節。我們知道,搜集和下載資料是人們使用互聯(lián)網(wǎng)的最主要的目的之一,但有些信息資源過(guò)于龐大,用手工摘取的方法是困難的或根本就是行不通的。例如,你需要搜集歐洲進(jìn)口機械設備的公司名錄以便給他們發(fā)信邀請其參加博覽會(huì ),在網(wǎng)上找到這些信息并不難,但出于數據安全等方面的考慮,幾乎所有提供類(lèi)似信息的網(wǎng)站都沒(méi)有提供直接下載數據的功能。
要想搜集齊想要的數據,唯一可用的方法就是一頁(yè)一頁(yè)地瀏覽每個(gè)公司的信息頁(yè),摘取其中有用的數據并存入數據庫。但當公司總數超過(guò)數千時(shí),巨大的工作量會(huì )讓任何人望而卻步!其實(shí),這浩大的工作完全可以由程序來(lái)完成,因為這些任務(wù)完全是機械的重復性工作。而且,用程序完成比用手工要快得多。本文涉及的技術(shù)細節是通用的,即對實(shí)例程序稍加修改就可完成任何“自動(dòng)上網(wǎng)沖浪”任務(wù)。
自動(dòng)撥號上網(wǎng)、自動(dòng)處理中途掉線(xiàn)、任務(wù)完成后自動(dòng)掛斷,這些都是“上網(wǎng)機器人”的最基本的功能之一。它還能給你帶來(lái)明顯的經(jīng)濟回報:如果你讓“機器人”在晚間至凌晨的上網(wǎng)費優(yōu)惠期內撥號上網(wǎng)去自動(dòng)沖浪,那真可稱(chēng)得上是典型的“一石三鳥(niǎo)”----你睡覺(jué)、它工作、還省錢(qián)!有關(guān)這方面的細節將在本文的第三部分里討論。該部分提供了實(shí)現上述各功能的若干方法,并比較了這些方法各自的優(yōu)劣。
本文的第一和第二部分分別以?xún)蓚€(gè)實(shí)例討論了自動(dòng)瀏覽的技術(shù)細節:在網(wǎng)頁(yè)上的輸入區內自動(dòng)填入數據以便完成諸如用戶(hù)登錄等的操作、自動(dòng)更新CheckBox、自動(dòng)選擇下拉式列表(ComboBox)的值、自動(dòng)點(diǎn)擊網(wǎng)頁(yè)上的按鈕、從網(wǎng)頁(yè)上精確提取有用的數據并存盤(pán)、將網(wǎng)頁(yè)上二維表(Table)內的數據一一提取出來(lái)并轉換且存儲成可直接導入數據庫或 Excel的格式,以及控制瀏覽進(jìn)程的技巧等等。
第一部分 從網(wǎng)頁(yè)上精確提取數據
![]() |
本部分的實(shí)例是:下載滬深兩市全部約1100家個(gè)股的基本信息及財務(wù)數據。若用手工操作,如上圖所示,需要在股票代碼區內分別輸入1100個(gè)股票代碼,在下拉式列表(ComboBox)中分別選擇“個(gè)股資料”和“財務(wù)數據解讀”,算下來(lái)約是2200次操作!這樣的工作當然是由程序來(lái)完成劃算得多。況且手工提取數據(先選中、再使用Ctrl+C拷貝)極容易出錯(多選或漏選),又很費眼神。
1. 在輸入區內自動(dòng)填入數據
為使程序能高效地自動(dòng)瀏覽,需引入一些最基本的功能,如在輸入區內自動(dòng)填入數據、自動(dòng)點(diǎn)擊按鈕等等。雖然用變換 URL地址的方法有時(shí)也能完成任務(wù),但往往過(guò)于費力,尤其當網(wǎng)頁(yè)上的輸入區較多時(shí)更是如此。
為了在輸入區內輸入數據,需要先搜索到該對象的名字,然后將該對象的值置為要填入的數據即可。搜索名字的工作可編程完成,亦可用 FrontPage輕松獲得。
2. 自動(dòng)在下拉式列表(ComboBox)中進(jìn)行選擇
同樣地,首先要獲得下拉式列表的名字。然后根據下拉式列表的元素總數(length屬性)在列表中搜索要設置的值(列表的 Options集合中元素的Text屬性),找到后,將該元素設為選中元素(元素的Selected屬性)。
3. 自動(dòng)點(diǎn)擊按鈕
對于按鈕來(lái)講,可根據其名字訪(fǎng)問(wèn),亦可根據其值訪(fǎng)問(wèn)。按鈕的值就是顯示在按鈕上的文字。一個(gè)按鈕可能沒(méi)有名字,但一定有值。本例的程序就是根據值來(lái)訪(fǎng)問(wèn)按鈕。執行按鈕的 Click方法就相當于點(diǎn)擊了該按鈕。
![]() |
圖二中紅色箭頭所指即為程序自動(dòng)填入輸入框、自動(dòng)在ComboBox中選擇以及自動(dòng)點(diǎn)擊按鈕的情況。
4. 精確提取數據
僅將有用的數據存儲下來(lái)才是有意義的。必須研究網(wǎng)頁(yè),找出有效數據所在的Tag區(可用文本編輯器或 FrontPage),然后用該對象的innerText屬性獲得最終的文本。本例中要存儲的數據如下圖所示,其所用的Tag為“PRE”。
![]() |
下面給出的是實(shí)例程序的完整代碼:
| ‘ 程序一:從網(wǎng)頁(yè)上精確提取數據 ‘ ‘ 為運行本程序,應在“菜單->工程->部件”中添加“Microsoft Internet Controls” ‘ 并在“菜單->工程->引用”中添加“Microsoft HTML Object Library” ‘ ‘ 為了簡(jiǎn)潔,程序僅下載九只個(gè)股的基本信息 Option Explicit Private Const Form_ID = 1 Dim Code(9) As String Dim Current As Long Private Sub Form_Load() Form1.MousePointer = 11 ‘ 以下是個(gè)股代碼 ‘ 為了程序簡(jiǎn)潔,這里僅使用九只代碼。 ‘ 而在真實(shí)環(huán)境中,應從數據文件中讀入全部個(gè)股代碼。 Code(0) = "600001": Code(1) = "600002": Code(2) = "600003" Code(3) = "600005": Code(4) = "600006": Code(5) = "600007" Code(6) = "600008": Code(7) = "600009": Code(8) = "600010" Current = 0 WebBrowser1.Navigate "www.stockstar.com.cn" ‘ 起始網(wǎng)址 End Sub Private Sub WebBrowser1_DocumentComplete(ByValpDisp As Object, URL As Variant) Dim i, k Text2 = WebBrowser1.LocationURL ‘ 顯示當前網(wǎng)址 ‘ 判斷當前網(wǎng)頁(yè)是否全部調入完畢 If Not (pDisp Is WebBrowser1.Object) Then Exit Sub On Error Resume Next Select Case Text2 Case "http://www.stockstar.com.cn/home.htm" ‘ 當進(jìn)入主頁(yè)面時(shí)執行以下程序 For i = 0 To WebBrowser1.Document.Forms(Form_ID).length - 1 ‘ 找到代碼輸入框后填入個(gè)股代碼 If WebBrowser1.Document.Forms(Form_ID)(i).Name = "code" Then _ WebBrowser1.Document.Forms(Form_ID)(i).Value = Code(Current) ‘ 在下拉式列表中進(jìn)行選擇 If WebBrowser1.Document.Forms(Form_ID)(i).Name = "target" Then For k = 0 To WebBrowser1.Document.Forms(Form_ID)(i).length - 1 If WebBrowser1.Document.Forms(Form_ID)(i).Options(k).Text _ = "個(gè)股資料" Then WebBrowser1.Document.Forms(Form_ID)(i).Options(k).Selected = True Exit For End If Next k End If ‘ 點(diǎn)擊按鈕 If WebBrowser1.Document.Forms(Form_ID)(i).Value = " 查詢(xún) " Then _ WebBrowser1.Document.Forms(Form_ID)(i).Click Next Case Else ‘ 當進(jìn)入數據頁(yè)面時(shí)執行以下程序 For i = 0 To WebBrowser1.Document.All.length - 1 If WebBrowser1.Document.All(i).tagName = "PRE" Then ‘ 精確提取數據 Text1 = Text1 + Code(Current) + vbCrLf + _ WebBrowser1.Document.All(i).innerText + vbCrLf Exit For End If Next ‘ 數據存盤(pán) Open "C:\Data2.Txt" For Append As #1 Print #1, Text1: Text1 = "": Close #1 ‘ 換下一只股票 Current = Current + 1 If Current >= 9 Then ‘ 上網(wǎng)任務(wù)完成后,應在此調用自動(dòng)掛斷過(guò)程。 Form1.MousePointer = 0: MsgBox "Finished!": End End If ‘ 回退到主頁(yè)面,查詢(xún)下一只股票的信息 WebBrowser1.GoBack End Select End Sub |
第二部分 將網(wǎng)頁(yè)上的二維表導入數據庫
在上一部分中,我們討論了讓程序自動(dòng)在網(wǎng)上瀏覽并將所需的數據準確、快速地存儲下來(lái)的方法?,F在,我們將迎接更大的挑戰:將網(wǎng)頁(yè)上以表格形式存在的二維數據提取出來(lái),并存成可直接導入數據庫的“Microsoft Excel 逗號分隔值文件”(即.csv文件)。
![]() |
用手工在網(wǎng)頁(yè)上直接提取類(lèi)似上圖中所示的表格數據是非常困難的。如果這樣的表格有數十頁(yè)甚至上百頁(yè)之多,手工提取工作將是不可想象的,而且非常容易出錯。
本部分的實(shí)例是:將滬深兩市全部約1100家個(gè)股的財務(wù)評分表數據(共54頁(yè),每頁(yè)20家,如上圖所示)快速、準確地轉換成“.csv”文件。
1. 自動(dòng)設置CheckBox的值
由于只有注冊用戶(hù)才能訪(fǎng)問(wèn)上述財務(wù)評分表,因此實(shí)例程序首先演示了自動(dòng)注冊的功能。下圖顯示的是注冊前以及自動(dòng)注冊后的畫(huà)面。
![]() |
我們在上一部分中已討論了自動(dòng)填寫(xiě)輸入區以及自動(dòng)點(diǎn)擊按鈕等的方法。對于自動(dòng)設置CheckBox值,其方法完全類(lèi)似:首先要搜索到該CheckBox的名字,然后將該對象的Checked屬性置為T(mén)rue或False即可。
2. 將網(wǎng)頁(yè)上的二維表導入數據庫
首先定義一個(gè)IHTMLElementCollection對象用于收集網(wǎng)頁(yè)上所有的 Table,然后用getElementsByTagName方法執行收集工作:
| Dim Tables AsIHTMLElementCollection Set Tables = WebBrowser1.Document.getElementsByTagName("Table") |
一個(gè)網(wǎng)頁(yè)上往往有多個(gè) Table。我們用HTMLTable對象來(lái)處理每個(gè)Table:
| Dim Table1 AsHTMLTable For Each Table1 In Tables Next |
HTMLTable對象的innerText屬性記錄了整個(gè) Table的全部信息,包括字段名。因此我們可以根據字段名判斷出哪個(gè) Table是我們需要的。
為了逐行逐列地提取數據,我們還需要HTMLTableRow對象和HTMLTableCell對象:
| Dim Row AsHTMLTableRow, Cell As HTMLTableCell For i = 1 To Table1.rows.length - 1 ‘ 逐行處理 Set Row = Table1.rows(i) j = 0 For Each Cell In Row.cells ‘ 逐列處理 ‘ Row.cells(j).innerText即為當前行及當前列上的單元數據 Text1 = Text1 + Trim(Row.cells(j).innerText) + "," j = j + 1 Next ‘ 一行處理完畢后,去除行尾的逗號并加上回車(chē) Text1 = Left(Text1, Len(Text1) - 1) + vbCrLf Next |
至此,當前網(wǎng)頁(yè)上的二維表已轉換成“.csv”格式。
3. 自動(dòng)瀏覽時(shí)的頁(yè)面控制技巧
我們從上個(gè)例子中就已經(jīng)清晰地看到,自動(dòng)瀏覽程序的主體是WebBrowser控件的DocumentComplete事件。只有在當前頁(yè)面已被完全調入后,我們才能開(kāi)始對當前頁(yè)面進(jìn)行數據處理,然后再根據當前在哪個(gè)頁(yè)面來(lái)決定下一步的瀏覽方向。
需要指出的是,DocumentComplete事件的發(fā)生并不一定意味著(zhù)當前頁(yè)面已被全部調入。如果頁(yè)面上沒(méi)有其它子框架(frames),發(fā)生DocumentComplete事件即表明當前頁(yè)面(即主框架)已完成調入;若頁(yè)面上有多個(gè)框架,則每個(gè)框架完成時(shí)都會(huì )發(fā)生DocumentComplete事件;當所有子框架都完成后,主框架最后產(chǎn)生一次DocumentComplete事件。為了判斷出這最后一次DocumentComplete事件,需要比較每次事件發(fā)生時(shí)的對象(pDisp)是否是WebBrowser控件對象本身:
| Private Sub WebBrowser1_DocumentComplete(ByValpDisp As Object, _ URL As Variant) If (pDisp Is WebBrowser1.Object) Then Debug.Print "Document is finished loading." End If End Sub |
下面是實(shí)例程序的完整代碼(運行該程序可得到完整的1061行“.csv”格式的數據,分別代表1061個(gè)上市公司的財務(wù)信息。該文件可直接導入Access數據庫或 Excel中。):
| ‘ 程序二:將網(wǎng)頁(yè)上的二維表導入數據庫 ‘ ‘ 為運行本程序,應在“菜單->工程->部件”中添加“Microsoft Internet Controls” ‘ 并在“菜單->工程->引用”中添加“Microsoft HTML Object Library” ‘ Option Explicit Dim Page As Long Private Sub Form_Load() Form1.MousePointer = 11 WebBrowser1.Navigate "www.stockstar.com.cn" ‘ 起始網(wǎng)址 End Sub Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant) Dim Table1 As HTMLTable, Tables As IHTMLElementCollection Dim Row As HTMLTableRow, Cell As HTMLTableCell Dim i, j, tmp Text2 = WebBrowser1.LocationURL ‘ 顯示當前網(wǎng)址 ‘ 判斷當前網(wǎng)頁(yè)是否全部調入完畢 If Not (pDisp Is WebBrowser1.Object) Then Exit Sub On Error Resume Next Select Case Text2 Case "http://www.stockstar.com.cn/home.htm" ‘ 當進(jìn)入主頁(yè)面時(shí)執行以下程序 ‘ 用戶(hù)注冊登錄 For i = 0 To WebBrowser1.Document.Forms(0).length - 1 ‘ 找到 CheckBox 后,將其值改為 False,以防止用戶(hù)名及密碼被存儲 If WebBrowser1.Document.Forms(0)(i).Name = "checkSavePW" Then _ WebBrowser1.Document.Forms(0)(i).Checked = False If WebBrowser1.Document.Forms(0)(i).Name = "userId" Then _ WebBrowser1.Document.Forms(0)(i).Value = "kompass_china" If WebBrowser1.Document.Forms(0)(i).Name = "passwd" Then _ WebBrowser1.Document.Forms(0)(i).Value = "kompass1" ‘ 此處是按名字訪(fǎng)問(wèn)按鈕(上例中是按值訪(fǎng)問(wèn)按鈕) If WebBrowser1.Document.Forms(0)(i).Name = "continue" Then _ WebBrowser1.Document.Forms(0)(i).Click Next Case "http://my.stockstar.com/scripts/mystockstar.dll?login" ‘ 當用戶(hù)登錄完成后,準備打開(kāi)表格的第一頁(yè) WebBrowser1.Navigate "http://finance.stockstar.com/scripts/finance.dll?" + _ "showstkdfpm&begin=0&ret=1&index=2&concode=01" Page = 1 Case Else ‘ 當進(jìn)入數據頁(yè)面(表格的第一頁(yè)至最后一頁(yè))時(shí)執行以下程序 Set Tables = WebBrowser1.Document.getElementsByTagName("Table") For Each Table1 In Tables If Left(Table1.innerText, 2) = "名次" Then ‘ 找到需要的Table ‘ 將表格轉換成“.csv”格式 For i = 1 To Table1.rows.length - 1 Set Row = Table1.rows(i) j = 0 For Each Cell In Row.cells Text1 = Text1 + Trim(Row.cells(j).innerText) + "," j = j + 1 Next Text1 = Left(Text1, Len(Text1) - 1) + vbCrLf Next ‘ 數據存盤(pán) Open "C:\Data.csv" For Append As #1 Print #1, Left(Text1, Len(Text1) - 2): Text1 = "": Close #1 Exit For End If Next ‘ 準備打開(kāi)下一頁(yè) Page = Page + 1 tmp = "http://finance.stockstar.com/scripts/finance.dll?showstkdfpm&ret=" + _ Trim(Str(Page)) + "&index=2&concode=01" If Page <= 54 Then ‘ 判斷是否瀏覽結束 WebBrowser1.Navigate tmp Else ‘ 上網(wǎng)任務(wù)完成后,應在此調用自動(dòng)掛斷過(guò)程。 Form1.MousePointer = 0 MsgBox "Finished!!": End End If End Select End Sub |
以下給出的是上述程序所存數據文件的片段:
| 1,樂(lè )凱膠片,600135,材料,81.493,18.445,23.165,8.850,20.717,10.315 2,歌華有線(xiàn),600037,傳播娛樂(lè ),80.553,13.009,22.256,12.141,20.304,12.844 3,外運發(fā)展,600270,倉儲運輸,80.326,17.331,23.005,8.829,19.900,11.261 4,東方鉭業(yè),0962,有色金屬,80.312,15.160,22.483,11.648,21.290,9.730 5,雙匯發(fā)展,0895,食品,79.772,15.428,20.673,11.508,20.235,11.930 6,四川美豐,0731,化肥,79.361,15.795,23.235,11.323,16.921,12.088 ... ... ... 1059,輪胎橡膠,600623,車(chē)類(lèi),7.167,8.265,10.973,-34.411,14.120,8.219 1060,PT吉輕工,0546,日用輕工產(chǎn)品,-11.895,5.740,-49.149,7.999,14.136,9.379 1061,廣船國際,600685,機械儀器,-57.452,9.824,-1.528,-89.648,14.366,9.533 |
第三部分 自動(dòng)撥號、自動(dòng)掛斷以及自動(dòng)處理中途掉線(xiàn)
一個(gè)出色的“自動(dòng)上網(wǎng)機器人”程序應能按照既定的時(shí)間準時(shí)開(kāi)始撥號、并當所需任務(wù)已完成后立即掛斷。而且僅做到這些還不夠,它還應在發(fā)出撥號指令后跟蹤撥號操作是否真的成功、上網(wǎng)速度如何、是否需要掛斷后重新?lián)芴?、自?dòng)瀏覽過(guò)程中是否出現掉線(xiàn)、以及最終的掛斷操作是否真的成功完成,等等。
因此,“機器人”程序應定時(shí)檢查在線(xiàn)狀況,以保證瀏覽時(shí)一定在在線(xiàn)狀態(tài)、瀏覽完畢后一定不在在線(xiàn)狀態(tài)。同時(shí)還要檢查瀏覽進(jìn)度,當瀏覽速度過(guò)慢時(shí)嘗試掛斷后重新?lián)芴枴?/p>
本部分討論了實(shí)現“自動(dòng)撥號”、“檢查在線(xiàn)狀況”、以及“自動(dòng)掛斷”這三個(gè)功能的若干方法,比較了諸方法各自的優(yōu)劣,并總結給出了使用建議。本部分的示例程序將這三個(gè)功能的諸方法集成在一起,以便于大家對比使用(見(jiàn)下圖)。
![]() |
1. 自動(dòng)撥號
方法1A:使用rnaui.dll
rnaui.dll是微軟的“撥號網(wǎng)絡(luò )用戶(hù)接口”程序集,一般在“\Windows\System”目錄下。其中的RnaDial程序用于啟動(dòng)撥號。該程序可在命令行執行(在“開(kāi)始”->“運行”中鍵入):
rundll32.exe rnaui.dll,RnaDial <撥號網(wǎng)絡(luò )連接名>
其中的“RnaDial”和“<撥號網(wǎng)絡(luò )連接名>”是區分大小寫(xiě)的。
但由于上述命令僅啟動(dòng)撥號窗口而未立即開(kāi)始撥號,因此在程序中使用時(shí)還應再
送出模擬“回車(chē)”的按鍵:
| ret = Shell("rundll32.exe rnaui.dll,RnaDial " + 連接名, 1) SendKeys "{enter}", True |
方法1B:使用wininet.dll
wininet.dll是微軟的Internet擴充函數集,一般在“\Windows\System”目錄下。其中的InternetAutodial、InternetAutodialHangup和InternetGetConnectedState三個(gè)函數分別可完成自動(dòng)撥號、自動(dòng)掛斷和判斷在線(xiàn)狀態(tài)等任務(wù)。InternetAutodial的定義為:
| Private Declare Function InternetAutodial Lib "wininet.dll" _ (ByValdwFlags As Long, ByValdwReserved As Long) As Long |
若將第一個(gè)參數(dwFlags)的值設為2,該函數無(wú)需用戶(hù)干預就可自動(dòng)撥號。但使用該函數有一個(gè)前提:即必須將“Internet 屬性”->“連接”設成“始終撥打默認連接”(見(jiàn)下圖)。
![]() |
用InternetAutodial函數自動(dòng)撥號的情況可參見(jiàn)下圖。從圖中可以看出,該方法可自動(dòng)重試多次。具體的重試次數在默認連接的“設置”->“高級”中定義:
![]() |
方法1C:使用RAS
RAS 是微軟的遠程訪(fǎng)問(wèn)服務(wù)(Remote Access Service)API集合。其中的 API函數RasDial可完成撥號任務(wù)。但由于該函數在使用上略顯復雜而不太常用,故示例程序中未采納。
自動(dòng)撥號方法小結:rnaui方法使用起來(lái)最簡(jiǎn)單,又由于它不一定非要使用默認連接,因此也最靈活。但這種靈活恰恰又給它帶來(lái)了弱點(diǎn),即如果不提供連接名,該方法不會(huì )自動(dòng)調用默認連接。此外,這種方法還有兩個(gè)最大的缺點(diǎn):一是僅撥號一次,若出現占線(xiàn)或沒(méi)有響應等情況時(shí)不會(huì )自動(dòng)重試;二是調用程序不容易得到撥號是否成功的返回值。相比之下,wininet方法雖僅能撥打默認連接(無(wú)默認連接時(shí),使用第一個(gè)連接),但它可多次試撥,并且InternetAutodial函數等待撥號成功或所有試撥結束以便給調用程序返回撥號是否成功的值,因此,在“自動(dòng)上網(wǎng)機器人”的環(huán)境中wininet方法是最適宜的。
2. 檢查在線(xiàn)狀況
方法2A:wininet方法
若InternetGetConnectedState函數返回True,則為在線(xiàn)狀態(tài)。該方法最大的缺點(diǎn)是:若當前連接不是用wininet方法建立的,則返回值可能不準確。
方法2B:查找窗口法
撥號連接成功后,下圖所示的窗口一定存在(不管它是最小化在任務(wù)欄的最右端,或是開(kāi)啟為下圖所示的狀態(tài)):
![]() |
用FindWindow API函數找到該窗口即意味著(zhù)當前在線(xiàn)。此外,查找窗口法的另一個(gè)用處是查找“重新連接”窗口:當中途掉線(xiàn)時(shí),操作系統往往會(huì )詢(xún)問(wèn)你是否重新連接,找到該窗口并發(fā)出模擬“回車(chē)”按鍵即可實(shí)現再撥號。
查找窗口法的缺點(diǎn)是:由于找窗口時(shí)需要提供窗口標題,因此即使使用的是默認連接也必須事先知道默認連接名。
方法2C:RAS 方法
先用RasEnumConnections函數返回整個(gè)RAS集合,再用RasGetConnectStatus函數判斷第一個(gè) RAS連接的狀態(tài)。RAS方法的最大優(yōu)點(diǎn)是:不管當前連接是否是用wininet建立的,RAS 方法均可對在線(xiàn)狀態(tài)做出正確判斷。
方法2D:注冊表法
在線(xiàn)時(shí),注冊表的“\HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\RemoteAccess”處有鍵值“Remote Connection”,且其值不為零;不在線(xiàn)時(shí),該處無(wú)“Remote Connection”鍵值(當本次系統啟動(dòng)后從未撥號成功時(shí)),或者其值為零(表明曾撥號成功,但現在已斷掉)。
檢查在線(xiàn)狀況之方法小結:由于wininet方法的局限性,一般我們應避免使用之;查找窗口法是可靠的,只是要知道連接名;因此我們推薦使用RAS 方法和注冊表法。
3. 自動(dòng)掛斷
方法3A:wininet法
使用InternetAutodialHangup函數。同樣地,若當前連接不是用wininet方法建立的,則返回值可能不準確(即不能成功掛斷)。
方法3B:窗口查找法
找到圖九所示的窗口,然后用ShowWindow API函數使之成為當前窗口,最后發(fā)出模擬 方法3C:RAS 法 用RasHangUp函數執行掛斷。不管用何種方法建立的連接,RAS 法均能可靠地完成任務(wù)。 自動(dòng)掛斷方法小結:相比之下,窗口查找法和RAS 法是可以信賴(lài)的。 綜上所述,對于“自動(dòng)撥號”、“檢查在線(xiàn)狀況”、以及“自動(dòng)掛斷”的各種方法,我們推薦“1A-2C-3C”組合。當然各方法可綜合使用(如加入2D、3B等),以確保萬(wàn)無(wú)一失。在具體編程時(shí)還應注意:撥號后判斷結果,如不成功應重新?lián)芴?;任?wù)進(jìn)行過(guò)程中定時(shí)檢查在線(xiàn)狀態(tài),出現掉線(xiàn)后應及時(shí)處理;最后的掛斷操作后應再查在線(xiàn)狀態(tài),以確保掛斷成功。 下面是實(shí)例程序的完整代碼。源代碼中的全局定義已按照wininet、RAS、注冊表等進(jìn)行分類(lèi),各具體方法也均按序排列,以便于大家挑選使用。該程序的執行情況在本部分的開(kāi)始處已給出(圖六)。 Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=99394. 本部分總結
‘ 程序三:自動(dòng)撥號、自動(dòng)掛斷以及自動(dòng)處理中途掉線(xiàn)
‘
Option Explicit
‘ 有關(guān) wininet 的全局定義
Private Const INTERNET_AUTODIAL_FORCE_UNATTENDED = 2
Private Const INTERNET_CONNECTION_MODEM = 1
Private Declare Function InternetAutodial Lib "wininet.dll" _
(ByVal dwFlags As Long, ByVal dwReserved As Long) As Long
Private Declare Function InternetAutodialHangup Lib _
"wininet.dll" (ByVal dwReserved As Long) As Long
Private Declare Function InternetGetConnectedState Lib _
"wininet.dll" (ByRef lpdwFlags As Long, ByVal _
dwReserved As Long) As Long
‘ 有關(guān)“窗口查找”的全局定義
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Private Const SW_SHOW = 5
‘ 有關(guān) RAS 的全局定義
Private Const RASCS_DONE = &H2000&
Private Const RAS_MaxEntryName = 256
Private Const RAS_MaxDeviceType = 16
Private Const RAS_MaxDeviceName = 128
Private Type RASCONN
dwSize As Long
hRasConn As Long
szEntryName(RAS_MaxEntryName) As Byte
szDeviceType(RAS_MaxDeviceType) As Byte
szDeviceName(RAS_MaxDeviceName) As Byte
End Type
Private Type RASCONNSTATUS
dwSize As Long
RasConnState As Long
dwError As Long
szDeviceType(RAS_MaxDeviceType) As Byte
szDeviceName(RAS_MaxDeviceName) As Byte
End Type
Private Ras_Buf(255) As RASCONN
Private Ras_Status As RASCONNSTATUS
Private lpcb As Long
Private lpcConnections As Long
Private Declare Function RasEnumConnections Lib _
"rasapi32.dll" Alias "RasEnumConnectionsA" (lprasconn _
As Any, lpcb As Long, lpcConnections As Long) As Long
Private Declare Function RasGetConnectStatus Lib _
"rasapi32.dll" Alias "RasGetConnectStatusA" (ByVal _
hRasConn As Long, lpRASCONNSTATUS As Any) As Long
Private Declare Function RasHangUp Lib "rasapi32.dll" _
Alias "RasHangUpA" (ByVal hRasConn As Long) As Long
‘ 有關(guān)“注冊表”的全局定義
Private Const HKEY_LOCAL_MACHINE = &H80000002
Private Declare Function RegOpenKey Lib "advapi32.dll" Alias _
"RegOpenKeyA" (ByVal hKey As Long, ByVal lpSubKey As _
String, phkResult As Long) As Long
Private Declare Function RegQueryValueEx Lib "advapi32.dll" _
Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal _
lpValueName As String, ByVal lpReserved As Long, lpType _
As Long, lpData As Any, lpcbData As Long) As Long
Private Declare Function RegCloseKey Lib "advapi32.dll" _
(ByVal hKey As Long) As Long
Dim ret As Long
‘自動(dòng)撥號
Private Sub wininet撥號測試_Click()
If InternetAutodial(INTERNET_AUTODIAL_FORCE_UNATTENDED, 0) _
Then MsgBox "已連接(wininet法)"
End Sub
Private Sub rnaui撥號測試_Click()
ret = Shell("rundll32.exe rnaui.dll,RnaDial " + Text1, 1): DoEvents
SendKeys "{enter}", True: DoEvents
End Sub
‘檢查是否斷線(xiàn)
Private Sub wininet方法_Click() ‘ wininet法檢查是否斷線(xiàn)
If InternetGetConnectedState(INTERNET_CONNECTION_MODEM, 0) Then
MsgBox "在線(xiàn)."
Else
MsgBox "當前未連接。"
End If
End Sub
Private Sub 查找窗口法_Click() ‘ 查找窗口法檢查是否斷線(xiàn)
ret = FindWindow("#32770", "重新連接")
If ret <> 0 Then
Call ShowWindow(ret, SW_SHOW)
SendKeys "{enter}", True: Exit Sub
End If
ret = FindWindow("#32770", "連接到 The95963")
If ret <> 0 Then
MsgBox "在線(xiàn)."
Else
MsgBox "當前未連接。"
End If
End Sub
Private Sub RAS方法_Click() ‘ RAS方法檢查是否斷線(xiàn)
Ras_Buf(0).dwSize = Len(Ras_Buf(0)) + 1
lpcb = 256 * Ras_Buf(0).dwSize
ret = RasEnumConnections(Ras_Buf(0), lpcb, lpcConnections)
If ret Then
MsgBox "出錯!": Exit Sub
End If
Ras_Status.dwSize = Len(Ras_Status) + 2
ret = RasGetConnectStatus(Ras_Buf(0).hRasConn, Ras_Status)
If ret = 0 And Ras_Status.RasConnState = RASCS_DONE Then
MsgBox "在線(xiàn)."
Else
MsgBox "當前未連接。"
End If
End Sub
Private Sub 注冊表法_Click() ‘ 注冊表法檢查是否斷線(xiàn)
Dim SubKey As String, ValueName As String
Dim Data As Long, Result As Long
SubKey = "System\CurrentControlSet\Services\RemoteAccess"
ret = RegOpenKey(HKEY_LOCAL_MACHINE, SubKey, Result)
If ret = 0& Then
ValueName = "Remote Connection"
ret = RegQueryValueEx(Result, ValueName, 0&, 0&, ByVal Data, 0&)
ret = RegQueryValueEx(Result, ValueName, 0&, 0&, Data, Len(Data))
If ret = 0& And Data <> 0 Then
MsgBox "在線(xiàn)!"
Else
MsgBox "當前未連接。"
End If
RegCloseKey (Result)
End If
End Sub
‘自動(dòng)掛斷
Private Sub wininet法_Click() ‘ wininet法自動(dòng)掛斷
If InternetAutodialHangup(0) Then MsgBox "已掛斷(wininet法)"
End Sub
Private Sub 窗口查找法_Click() ‘ 窗口查找法自動(dòng)掛斷
ret = FindWindow("#32770", "連接到 The95963")
If ret <> 0 Then
Call ShowWindow(ret, SW_SHOW)
SendKeys "%c", True
MsgBox "已掛斷(窗口查找法)"
End If
End Sub
Private Sub RAS法_Click() ‘ RAS法自動(dòng)掛斷
Ras_Buf(0).dwSize = Len(Ras_Buf(0)) + 1
lpcb = 256 * Ras_Buf(0).dwSize
ret = RasEnumConnections(Ras_Buf(0), lpcb, lpcConnections)
If ret Then
MsgBox "出錯!": Exit Sub
End If
Ras_Status.dwSize = Len(Ras_Status) + 2
ret = RasGetConnectStatus(Ras_Buf(0).hRasConn, Ras_Status)
If ret = 0 And Ras_Status.RasConnState = RASCS_DONE Then
If RasHangUp(Ras_Buf(0).hRasConn) = 0 Then _
MsgBox "已掛斷(RAS法)"
End If
End Sub







