界面如下 我觀察了下12306的頁面,它預留了5個乘客信息,所以我也就預留了5個乘客信息。因為我不會寫HTML和javascript,我就從12306中copy出相應的頁面元素,並加以修改。在此感謝下12306網頁設計同學,幫我完成了不少我不會的東西。 一般來說,我們可能一次性不會購買5個人的票。所以在上面的界面中,你想填多少人信息就填多少人信息,我會在代碼中讀取這些人的信息。當我們填完乘客信息後,我們要輸入車次信息。然後我們要點擊一下確定,我會在代碼中捕獲點擊確定的操作,並將已經填寫的信息讀入內存。在之後的搶票過程中,我們將使用到這些信息。最後,我們就要點擊最下面那個超鏈接,跳轉到12306這個頁面,開始我們真正的搶票工作。 我們來看一下源代碼。首先是界面的,我列一個人的信息代碼出來: [cpp] <tr class="passenger_class" id="passenger_1"> <td style="width: 6%"> <div id="passenger_1_index">第1位</div> </td> <td id="seat"> <select> <option value="1">硬座</option> <option value="2">軟座</option> <option value="3">硬臥</option> <option value="4">軟臥</option> <option value="6">高級軟臥</option> <option value="M">一等座</option> <option value="O">二等座</option> <option value="P">特等座</option> <option value="9">商務座</option> </select> </td> <td style="width: 20%"> <label><strong>請確認所選車次有該坐席</strong> </label> </td> <td id="ticket"> <select> <option value="1">成人票</option> <option value="2">兒童票</option> <option value="3">學生票</option> <option value="4">殘軍票</option> </select> </td> <td id="name"> <input name="passenger_1_name" type="text" id="passenger_1_name" size="12" maxlength="20" class="input_20txt" value=""/> </td> <td id="cardtype"> <select> <option value="1">二代身份證</option> <option value="2">一代身份證</option> <option value="C">港澳通行證</option> <option value="G">台灣通行證</option> <option value="B">護照</option> </select> </td> <td id="cardno"> <input name="passenger_1_cardno" type="text" id="passenger_1_cardno" size="20" maxlength="35" style="text-transform: uppercase;" class="input_20txt" value=""/> </td> <td id="mobileno"> <input name="passenger_1_mobileno" type="text" id="passenger_1_mobileno" size="11" maxlength="20" class="input_20txt" value=""/> </td> </tr> 因為我並不知道用戶選擇的車次有什麼類型的座位,所以我將所有的座位都列了出來。 [cpp] <select> <option value="1">硬座</option> <option value="2">軟座</option> <option value="3">硬臥</option> <option value="4">軟臥</option> <option value="6">高級軟臥</option> <option value="M">一等座</option> <option value="O">二等座</option> <option value="P">特等座</option> <option value="9">商務座</option> </select> 這兒要特別注意下所有option的value字段,這些值不是我亂取的。而是我檢查了12306頁面的很多火車信息後收集到的。我們會在之後記錄用戶所選席別時,記錄這些值,因為這些值將在操作12306頁面時派上用場。 其他元素應該沒什麼可以解釋的,只是要注意所有Select下的Option的Value值和12306上對應的元素的Value值一致。 我們保存單個用戶的結構體是 [cpp] struct StSinglePassengerInfo{ ListCString ListSeat; CString cstrTicket; CString cstrName; CString cstrCardtype; CString cstrCardNo; CString cstrMobileNo; }; 注意一下ListSeat這個字段,這個字段保存的一個CString的隊列。它記錄著一系列席別代碼。在我最開始設計這個軟件時,我是希望用戶可以選擇一系列可以接受的席別,同時是按優先級關系排列。這樣可以最大程度上滿足用戶的需求。但是我已無心把這個功能繼續做下去,所以設計界面時,只能讓用戶選擇一個席別。 還有一個需要我們關注的是“確定”超鏈接的代碼 [html] <td> <a style="width: 60px;" href="http://settingok">確定</a> </td> 我們點擊“確定”按鈕後,頁面理論上要跳轉到“http://settingok”這個頁面。而實際上,我們只是利用“跳轉”這個操作,讓我們的C++代碼中捕獲到用戶已經設置OK了。我們並不希望頁面真的發生跳轉。所以我們對BeforeNavigate2消息映射函數做了處理,讓跳轉到“http://settingok”的請求終止,並讀取用戶設置的乘客信息和車次信息。 [cpp] void CBrowserHost::BeforeNavigate2(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel) { do { if ( NULL != url ) { CString cstrUrl((LPWSTR)(url->bstrVal)); if ( 0 == cstrUrl.CompareNoCase(SETTINGOK) ) { *Cancel = VARIANT_TRUE; CComPtr<IWebBrowser2> spWeb; HRESULT hr = pDisp->QueryInterface(IID_IWebBrowser2, (LPVOID*)&spWeb); CHECKHRPOINTER(hr, spWeb); CComPtr<IDispatch> dispDoc; hr = spWeb->get_Document(&dispDoc); CHECKHRPOINTER(hr, dispDoc); CComPtr<IHTMLDocument2> spDoc; hr = dispDoc->QueryInterface( IID_IHTMLDocument2, (LPVOID*)&spDoc); CHECKHRPOINTER(hr, spDoc); StTrainNoPassengerInfo stTrainPassenger; hr = m_dealSettingPage.GetTrainNoPassengersInSettingPage(spDoc, stTrainPassenger); hr = m_AutoMan.SetTrainNoPassengers(stTrainPassenger); } …… } …… } while (0); } 上面代碼中m_dealSettingPage是我處理頁面的類CDeal12306WebPage的對象。GetTrainNoPassengersInSettingPage將解析網頁保存乘客和車次信息。m_AutoMan是我們之前說的“人”線程,此時我們將告訴該線程所有信息,讓它准備開始工作。 [cpp] HRESULT CDeal12306WebPage::GetTrainNoPassengersInSettingPage( CComPtr<IHTMLDocument2> & spDoc, StTrainNoPassengerInfo & stTrainPassenger ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spBody; hr = spDoc->get_body(&spBody); CHECKHRPOINTER(hr, spBody); CComPtr<IHTMLElement> spTable; hr = GetElementByID(spBody, L"passengertable", spTable); CHECKHRPOINTER(hr, spTable); CComPtr<IHTMLElement> spTBody; hr = GetElementByIndex( spTable, 0, spTBody); CHECKHRPOINTER(hr, spTBody); for ( int i = 0; i < MAXPASSENGERCOUNT; i++ ) { CString cstrTrID; cstrTrID.Format(PASSENGERID, i + 1); CComPtr<IHTMLElement> spTr; hr = GetElementByID( spTBody, cstrTrID, spTr); CHECKHRPOINTER(hr, spTr); StSinglePassengerInfo stSinglePassenger; hr = GetPassengerInfo(spTr, stSinglePassenger); CHECKHR(hr); if ( FALSE == stSinglePassenger.cstrName.IsEmpty() && FALSE == stSinglePassenger.cstrTicket.IsEmpty() && FALSE == stSinglePassenger.cstrCardNo.IsEmpty() && FALSE == stSinglePassenger.cstrCardtype.IsEmpty() && FALSE == stSinglePassenger.cstrMobileNo.IsEmpty() && 0 != stSinglePassenger.ListSeat.size()) { stTrainPassenger.vecPassengerInfo.push_back(stSinglePassenger); } } hr = GetTrainNoInSettingPage(spDoc, stTrainPassenger.cstrTrainNo); } while (0); return hr; } 這段代碼大致意思是在“設置”頁面中,找到id為passengertable的元素spTable,然後找到spTable下第一個元素spTBody。spTBody下保存著每個乘客的信息,其中第一個乘客信息保存在id是“passenger_1”的元素下,第二個保存在“passenger_2”元素下……當單個乘客所有信息都不為空時,將其保存在一個stTrainPassenger.vecPassengerInfo中。最後我們要獲取火車車次的信息,將其保存在stTrainPassenger.cstrTrainNo中。 上面的函數大部分是經過封裝的。其中幾個經常用的函數是 [cpp] HRESULT GetElementCollection(CComPtr<ihtmlelement> & spElem, CComPtr<ihtmlelementcollection> & spElemCollection ); enum EQUERYTYPE { EID, ETAGNAME, ECLASSNAME, }; // 通過ID獲取指定節點下第一個ID為cstrID的子節點 HRESULT GetElementByID(CComPtr<IHTMLElement> & spElem, const CString & cstrID, CComPtr<IHTMLElement> & spResElem); // 通過ClassName獲取指定節點下第一個class為cstrClassName的子節點 HRESULT GetElementByClassName(CComPtr<IHTMLElement> & spElem, const CString & cstrClassName, CComPtr<IHTMLElement> & spResElem); // 通過TagName獲取指定節點下第一個tag為cstrTagName的子節點 HRESULT GetElementByTagsName(CComPtr<IHTMLElement> & spElem, const CString & cstrTagName, CComPtr<IHTMLElement> & spResElem); // 通過ID獲取指定節點下第lindex子節點 HRESULT GetElementByIndex(CComPtr<IHTMLElement> & spElem, LONG lIndex, CComPtr<IHTMLElement> & spResElem); HRESULT GetElement(CComPtr<ihtmlelement> & spElem, EQUERYTYPE eType, const CString & cstrValue, CComPtr<ihtmlelement> & spResElem);</ihtmlelement></ihtmlelement></ihtmlelementcollection></ihtmlelement> 對應的實現代碼是 [cpp] HRESULT CDeal12306WebPage::GetElementCollection( CComPtr<IHTMLElement> & spElem, CComPtr<IHTMLElementCollection> & spElemCollection ) { HRESULT hr = S_FALSE; do { CComPtr<IDispatch> spDispatch; hr = spElem->get_children(&spDispatch); CHECKHR(hr); hr = spDispatch->QueryInterface( IID_IHTMLElementCollection, (LPVOID*)&spElemCollection); CHECKHR(hr); } while (0); return hr; } HRESULT CDeal12306WebPage::GetElementByID( CComPtr<IHTMLElement> & spElem, const CString & cstrID, CComPtr<IHTMLElement> & spResElem ) { return GetElement( spElem, EID, cstrID, spResElem ); } HRESULT CDeal12306WebPage::GetElementByClassName( CComPtr<IHTMLElement> & spElem, const CString & cstrClassName, CComPtr<IHTMLElement> & spResElem ) { return GetElement( spElem, ECLASSNAME, cstrClassName, spResElem ); } HRESULT CDeal12306WebPage::GetElementByTagsName( CComPtr<IHTMLElement> & spElem, const CString & cstrTagName, CComPtr<IHTMLElement> & spResElem ) { return GetElement( spElem, ETAGNAME, cstrTagName, spResElem ); } HRESULT CDeal12306WebPage::GetElementByIndex( CComPtr<IHTMLElement> & spElem, LONG lIndex, CComPtr<IHTMLElement> & spResElem ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElementCollection> spElemCollecion; hr = GetElementCollection( spElem, spElemCollecion); CHECKHR(hr); LONG lCollecionCount = 0; hr = spElemCollecion->get_length(&lCollecionCount); CHECKHR(hr); if ( lCollecionCount < lIndex + 1) { break; } CComVariant VarIndex = lIndex; CComPtr<IDispatch> spDisp; hr = spElemCollecion->item(VarIndex, VarIndex, &spDisp); CHECKHRPOINTER(hr,spDisp); hr = spDisp->QueryInterface(IID_IHTMLElement, (LPVOID*)&spResElem); } while (0); return hr; } HRESULT CDeal12306WebPage::GetElement( CComPtr<IHTMLElement> & spElem, EQUERYTYPE eType, const CString & cstrValue, CComPtr<IHTMLElement> & spResElem ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElementCollection> spElemCollection; hr = GetElementCollection( spElem, spElemCollection); CHECKHRPOINTER(hr,spElemCollection); LONG lCollecionCount = 0; hr = spElemCollection->get_length(&lCollecionCount); CHECKHR(hr); for ( long i = 0; i < lCollecionCount; i++ ) { CComVariant VarIndex = i; CComPtr<IDispatch> spDispatchElem; hr = spElemCollection->item( VarIndex, VarIndex, &spDispatchElem ); CHECKHRPOINTER(hr,spDispatchElem); CComPtr<IHTMLElement> spElem; hr = spDispatchElem->QueryInterface(IID_IHTMLElement, (LPVOID*)& spElem ); CHECKHRPOINTER(hr, spElem); CComBSTR bstrValue; switch (eType) { case EID: { hr = spElem->get_id(&bstrValue); }break; case ETAGNAME: { hr = spElem->get_tagName(&bstrValue); }break; case ECLASSNAME: { hr = spElem->get_className(&bstrValue); }break; default: break; } CString cstrV((LPWSTR)bstrValue); if ( 0 == cstrV.CompareNoCase( cstrValue )) { spResElem = spElem; break; } } } while (0); return hr; } 在獲取乘客和車次信息時用到的其他封裝函數的實現是 [cpp] HRESULT CDeal12306WebPage::GetPassengerInfo( CComPtr<IHTMLElement> & spElem, StSinglePassengerInfo & stSinglePassenger ) { HRESULT hr = E_FAIL; do { CString cstrSeat; hr = GetOptionValueHelper(spElem, L"seat", cstrSeat); CHECKHR(hr); stSinglePassenger.ListSeat.push_back(cstrSeat); hr = GetOptionValueHelper(spElem, L"ticket", stSinglePassenger.cstrTicket ); CHECKHR(hr); hr = GetOptionValueHelper(spElem, L"cardtype", stSinglePassenger.cstrCardtype); CHECKHR(hr); hr = GetInputValueHelper(spElem, L"name", stSinglePassenger.cstrName); CHECKHR(hr); hr = GetInputValueHelper(spElem, L"cardno", stSinglePassenger.cstrCardNo); CHECKHR(hr); hr = GetInputValueHelper(spElem, L"mobileno", stSinglePassenger.cstrMobileNo); } while (0); return hr; } HRESULT CDeal12306WebPage::GetOptionValueHelper( CComPtr<IHTMLElement> & spElem, const CString& cstrID, CString& cstrValue ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spTd; hr = GetElementByID(spElem, cstrID, spTd); CHECKHRPOINTER(hr, spTd); CComPtr<IHTMLElement> spSel; hr = GetElementByIndex(spTd, 0, spSel); CHECKHRPOINTER(hr, spSel); CComPtr<IHTMLSelectElement> spSelect; hr = spSel->QueryInterface(IID_IHTMLSelectElement, (LPVOID*)&spSelect); CHECKHRPOINTER(hr, spSelect); CComBSTR bstrValue; hr = spSelect->get_value(&bstrValue); CHECKHR(hr); cstrValue = bstrValue; } while (0); return hr; } HRESULT CDeal12306WebPage::GetInputValueHelper( CComPtr<IHTMLElement> & spElem, const CString& cstrID, CString & cstrValue ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spTd; hr = GetElementByID(spElem, cstrID, spTd); CHECKHRPOINTER(hr, spTd); CComPtr<IHTMLElement> spInput; hr = GetElementByIndex(spTd, 0, spInput); CHECKHRPOINTER(hr, spInput); CComPtr<IHTMLInputElement> spInputElem; hr = spInput->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInputElem); CHECKHRPOINTER(hr, spInputElem); CComBSTR bstrValue; hr = spInputElem->get_value(&bstrValue); CHECKHR(hr); cstrValue = bstrValue; } while (0); return hr; } HRESULT CDeal12306WebPage::GetTrainNoInSettingPage( CComPtr<IHTMLDocument2> & spDoc, CString & cstrValue ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spBody; hr = spDoc->get_body(&spBody); CHECKHRPOINTER(hr, spBody); CComPtr<IHTMLElement> spTable; hr = GetElementByID(spBody, L"trainnotable", spTable); CHECKHRPOINTER(hr, spTable); CComPtr<IHTMLElement> spTBody; hr = GetElementByIndex( spTable, 0, spTBody); CHECKHRPOINTER(hr, spTBody); CComPtr<IHTMLElement> spTr; hr = GetElementByIndex(spTBody, 0, spTr); CHECKHRPOINTER(hr, spTr); CComPtr<IHTMLElement> spTd; hr = GetElementByID(spTr, L"trainno", spTd); CHECKHRPOINTER(hr, spTd); CComPtr<IHTMLElement> spInput; hr = GetElementByIndex(spTd, 0, spInput); CHECKHRPOINTER(hr, spInput); CComPtr<IHTMLInputElement> spInputElem; hr = spInput->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInputElem); CHECKHRPOINTER(hr, spInputElem); CComBSTR bstrValue; hr = spInputElem->get_value(&bstrValue); CHECKHR(hr); cstrValue = bstrValue; } while (0); return hr;