檢查是否進入訂票頁面 判斷是否進入訂票頁面,我是確定了兩個標准: 1 網址是否為http://www.12306.cn/mormhweb/kyfw/ 2 該頁面否有查詢按鈕 [cpp] BOOL CDeal12306WebPage::IsQueryPage( CComPtr<IHTMLDocument2> & spDoc, CComBSTR & bstrUrl ) { HRESULT hr = E_FAIL; do { CString cstrUrl = CString((LPWSTR)bstrUrl); if ( 0 == cstrUrl.CompareNoCase(LOGIN12306URL) ) { CComPtr<IHTMLElement> spQueryButton; hr = GetQueryButtonInQueryPage( spDoc, spQueryButton); CHECKHRPOINTER(hr, spQueryButton); } } while (0); return FAILED(hr) ? FALSE : TRUE; } URL很好檢測,那麼我們如何判斷是否存在查詢按鈕呢?我們先看一下訂票頁面的頁面特征。 解決跨域問題 可以見得訂票頁面內部嵌入了兩個Iframe,而我們關心的那塊頁面恰恰就是最裡面一層IFrame。那我們直接通過最外層的Doc獲取到最裡面的Doc,然後在最裡面的Doc執行有關的查詢操作即可。然而熟悉javascript的同學可能馬上就會想到“跨域”問題。其實在浏覽器層面,跨域問題是很好解決的。 [cpp] HRESULT CDeal12306WebPage::GetIFrameDoc( CComPtr<IHTMLDocument2>& spDoc, const CString& cstrIFrameName, CComPtr<IHTMLDocument2>& spInnerDoc ) { HRESULT hr = E_FAIL; do { CComQIPtr<IHTMLFramesCollection2> spFrameCollection; hr = spDoc->get_frames(&spFrameCollection); CHECKHRPOINTER(hr, spFrameCollection); CComVariant IframeNameReq = CComBSTR(cstrIFrameName.GetString()); CComVariant FramePage; hr = spFrameCollection->item(&IframeNameReq, &FramePage); CHECKHRPOINTER(hr,FramePage.pdispVal); CComPtr<IHTMLWindow2> spIFramePage; hr = FramePage.pdispVal->QueryInterface(IID_IHTMLWindow2, (LPVOID*)&spIFramePage); CHECKHRPOINTER(hr, spIFramePage); hr = spIFramePage->get_document(&spInnerDoc); if ( E_ACCESSDENIED == hr ) { CComQIPtr<IServiceProvider> spServiceProvider = spIFramePage; CHECKPOINT(spServiceProvider); CComQIPtr<IWebBrowser2> spInnerWebBrowser; hr = spServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (LPVOID*)&spInnerWebBrowser); CHECKHRPOINTER(hr, spInnerWebBrowser); CComPtr<IDispatch> spDisp; hr = spInnerWebBrowser->get_Document(&spDisp); CHECKHRPOINTER(hr, spDisp); hr = spDisp->QueryInterface(IID_IHTMLDocument2, (LPVOID*)&spInnerDoc); CHECKHRPOINTER(hr, spInnerDoc); } } while (0); return hr; } 上面這個函數試圖在spDoc頁面中獲取其內嵌的名字是cstrIFrameName的IFrame的Doc。於是我們要獲取其中最裡面一層Iframe的Doc可以如下調用 [cpp] HRESULT CDeal12306WebPage::GetIFrameNamedIFramePageDoc( CComPtr<IHTMLDocument2> & spDoc, CComPtr<IHTMLDocument2> & spInnerDoc ) { HRESULT hr = E_FAIL; do { hr = GetIFrameDoc(spDoc, L"iframepage", spInnerDoc); CHECKHRPOINTER(hr, spInnerDoc); } while (0); return hr; } HRESULT CDeal12306WebPage::GetIFrameNamedMainDoc( CComPtr<IHTMLDocument2> & spIFramPageDoc, CComPtr<IHTMLDocument2> & spMainDoc ) { HRESULT hr = E_FAIL; do { hr = GetIFrameDoc(spIFramPageDoc, L"main", spMainDoc); CHECKHRPOINTER(hr, spMainDoc); } while (0); return hr; } HRESULT CDeal12306WebPage::GetMainDoc( CComPtr<IHTMLDocument2> & spDoc, CComPtr<IHTMLDocument2> & spMainDoc ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLDocument2> spIFramePageDoc; hr = GetIFrameNamedIFramePageDoc(spDoc, spIFramePageDoc); CHECKHRPOINTER(hr, spIFramePageDoc); hr = GetIFrameNamedMainDoc(spIFramePageDoc, spMainDoc); CHECKHRPOINTER(hr, spMainDoc); } while (0); return hr; } 當我們獲得最裡層的Doc後,我們將根據頁面結構獲取Class為cx_from的Table元素。 獲取這個Table的原因是,之後我們會以該Table為節點,執行“查詢按鈕”查找的操作。 [cpp] HRESULT CDeal12306WebPage::GetQueryButtonInQueryPage( CComPtr<IHTMLDocument2> & spDoc, CComPtr<IHTMLElement> & spQueryButtonElem ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLDocument2> spMainDoc; hr = GetMainDoc( spDoc, spMainDoc); CHECKHRPOINTER(hr, spMainDoc); CComPtr<IHTMLElement> spEnter_wElem; hr = GetEnter_wElement(spMainDoc, spEnter_wElem ); CHECKHRPOINTER(hr, spEnter_wElem); CComPtr<IHTMLElement> spQueryTable; hr = GetQueryTable(spEnter_wElem, spQueryTable); CHECKHRPOINTER(hr, spQueryTable); CComPtr<IHTMLButtonElement> spQueryButton; hr = GetQueryButtonInQueryPage(spQueryTable, spQueryButton); CHECKHRPOINTER(hr, spQueryButton); hr = spQueryButton->QueryInterface(IID_IHTMLElement, (LPVOID*)& spQueryButtonElem); CHECKHRPOINTER(hr, spQueryButtonElem); } while (0); return hr; } 查詢按鈕在這個table中的位置是 於是通過該Table查詢”查詢“按鈕的代碼是 [cpp] HRESULT CDeal12306WebPage::GetQueryButtonInQueryPage( CComPtr<IHTMLElement>& spQueryTable, CComPtr<IHTMLButtonElement> & spQueryButton ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spTBody; hr = GetElementByIndex(spQueryTable, 0, spTBody); CHECKHRPOINTER(hr, spTBody); CComPtr<IHTMLElement> spFirstTR; hr = GetElementByIndex(spTBody, 0, spFirstTR); CHECKHRPOINTER(hr, spFirstTR); CComPtr<IHTMLElement> spEighthTR; hr = GetElementByIndex(spFirstTR, 8, spEighthTR); CHECKHRPOINTER(hr, spEighthTR); CComPtr<IHTMLElement> spButtonTemp; hr = GetElementByIndex(spEighthTR, 0, spButtonTemp); CHECKHRPOINTER(hr, spButtonTemp); hr = spButtonTemp->QueryInterface(IID_IHTMLButtonElement, (LPVOID*)&spQueryButton); CHECKHRPOINTER(hr, spQueryButton); } while (0); return hr; } 插入開始和停止自動查詢按鈕 為了在該頁面中提供給用於控制開啟和關閉自動查詢功能的按鈕,我插入了兩個按鈕。如下圖 我們看下”單程“和”返程“按鈕的頁面結構 我會在Name為querySingleForm的form下的class為cx_tab的Div下插入“開始”和“停止”按鈕。 [cpp] HRESULT CDeal12306WebPage::InsertButtonInQueryPage( CComPtr<IHTMLDocument2> & spDoc ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLDocument2> spMainDoc; hr = GetMainDoc( spDoc, spMainDoc); CHECKHRPOINTER(hr, spMainDoc); CComPtr<IHTMLElement> spEnter_wElem; hr = GetEnter_wElement(spMainDoc, spEnter_wElem ); CHECKHRPOINTER(hr, spEnter_wElem); CComPtr<IHTMLElement> spForm; hr = GetQuerySingleForm(spEnter_wElem, spForm); CHECKHRPOINTER(hr, spForm); hr = InsertButtons( spForm ); } while (0); return hr; } [cpp] HRESULT CDeal12306WebPage::InsertButtons(CComPtr<IHTMLElement> & spEnter_wElem ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spDiv; hr = GetInsertButtonElem(spEnter_wElem, spDiv); if ( FALSE == IsStartButtonExist(spDiv) ) { hr = InsertStartButton(spDiv); CHECKHR(hr); #ifdef DEBUG if ( FALSE == IsStartButtonExist(spDiv) ) { DebugBreak(); } #endif } if ( FALSE == IsStopButtonExist(spDiv) ) { hr = InsertStopButton(spDiv); CHECKHR(hr); #ifdef DEBUG if ( FALSE == IsStopButtonExist(spDiv) ) { DebugBreak(); } #endif } } while (0); return hr ; } [cpp] HRESULT CDeal12306WebPage::GetInsertButtonElem( CComPtr<IHTMLElement> & spForm, CComPtr<IHTMLElement> & spDiv ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElement> spCx_TabDiv; hr = GetElementByClassName(spForm, L"cx_tab", spCx_TabDiv); CHECKHRPOINTER(hr, spCx_TabDiv); hr = GetElementByIndex(spCx_TabDiv, 0, spDiv); CHECKHRPOINTER(hr, spDiv); } while (0); return hr; } HRESULT CDeal12306WebPage::InsertStartButton( CComPtr<IHTMLElement> & spElem ) { HRESULT hr = E_FAIL; do { CComBSTR bstrWhere(L"beforeEnd"); CString cstrHTML; cstrHTML.Format( BUTTONFORMAT, STARTBUTTONID, STARTCOMD, L"開始" ); CComBSTR bstrHTML(cstrHTML.GetString()); hr = spElem->insertAdjacentHTML( bstrWhere, bstrHTML ); CHECKHR(hr); } while (0); return hr ; } HRESULT CDeal12306WebPage::InsertStopButton( CComPtr<IHTMLElement> & spElem ) { HRESULT hr = E_FAIL; do { CComBSTR bstrWhere(L"beforeEnd"); CString cstrHTML; cstrHTML.Format( BUTTONFORMAT, STOPBUTTONID, STOPCMD, L"停止" ); CComBSTR bstrHTML(cstrHTML.GetString()); hr = spElem->insertAdjacentHTML( bstrWhere, bstrHTML ); CHECKHR(hr); } while (0); return hr ; } [cpp] #define BUTTONFORMAT L"<li id=\"%s\"><a href=\"%s\" style=\"width:50px;height:30px;\">%s</a></li>" #define STARTBUTTONID L"StartButton" #define STOPBUTTONID L"StopButton" [cpp] #define STARTCOMD L"http://www.12306.cn/mormhweb/kyfw/StartQuery.fl" #define STOPCMD L"http://www.12306.cn/mormhweb/kyfw/StopQuery.fl" 當我們點擊開始按鈕是,頁面將試圖跳轉到http://www.12306.cn/mormhweb/kyfw/StartQuery.fl,此時,我將終止該跳轉,同時將“開啟查詢”標志設置為TRUE。 [cpp] view plaincopy 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) ) { …… } else if ( 0 == cstrUrl.CompareNoCase(STARTCOMD) ) { *Cancel = VARIANT_TRUE; m_AutoMan.SetStart(TRUE); break; } else if ( 0 == cstrUrl.CompareNoCase(STOPCMD) ) { *Cancel = VARIANT_TRUE; m_AutoMan.SetStart(FALSE); break; } } *Cancel = VARIANT_FALSE; } while (0); } 點擊停止按鈕原理同點擊開始按鈕原理一致。此處不再贅述。 當用戶選擇好出發地和目的地及時間後,用戶點擊查詢按鈕。並點擊“開始”按鈕。我們的“人”線程就開始了自動查詢操作。 查詢是否存在票,有票則預定,無票則再次查詢 當我們執行完一次查詢後,我們要查看下搜索結果列表信息中用戶選擇的車次是否存在票。我們先看一下頁面結構 其查找該節點的方法如下 [cpp] HRESULT CDeal12306WebPage::QueryTicketsInfo( CComPtr<IHTMLDocument2> & spDoc ) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLDocument2> spMainDoc; hr = GetMainDoc( spDoc, spMainDoc); CHECKHRPOINTER(hr, spMainDoc); CComPtr<IHTMLElement> spEnter_wElem; hr = GetEnter_wElement(spMainDoc, spEnter_wElem ); CHECKHRPOINTER(hr, spEnter_wElem); CComPtr<IHTMLElement> spIDGridbox; hr = GetElementByID( spEnter_wElem, L"gridbox", spIDGridbox); CHECKHRPOINTER(hr, spIDGridbox); CComPtr<IHTMLElement> spTable; hr = GetElementByIndex( spIDGridbox, 0, spTable); CHECKHRPOINTER(hr, spTable); CComPtr<IHTMLElement> spTbody; hr = GetElementByIndex( spTable, 0, spTbody); CHECKHRPOINTER(hr, spTbody); CComPtr<IHTMLElement> spTr; hr = GetElementByIndex( spTbody, 1, spTr); CHECKHRPOINTER(hr, spTr); CComPtr<IHTMLElement> spTd; hr = GetElementByIndex(spTr, 0, spTd); CHECKHRPOINTER(hr, spTd); CComPtr<IHTMLElement> spDiv; hr = GetElementByIndex(spTd, 0, spDiv); CHECKHRPOINTER(hr, spDiv); CComPtr<IHTMLElement> spDiv2; hr = GetElementByIndex(spDiv, 0, spDiv2); CHECKHRPOINTER(hr, spDiv2); CComPtr<IHTMLElement> spTable2; hr = GetElementByIndex(spDiv2, 0, spTable2); CHECKHRPOINTER(hr, spTable2); CComPtr<IHTMLElement> spTbody2; hr = GetElementByIndex(spTable2, 0, spTbody2); CHECKHRPOINTER(hr, spTbody2); CComPtr<IHTMLElementCollection> spElemCollection; hr = GetElementCollection(spTbody2, spElemCollection ); CHECKHRPOINTER(hr, spElemCollection); long lCount = 0; hr = spElemCollection->get_length(&lCount); CHECKHR(hr); for ( long lindex = 0; lindex < lCount; lindex++ ) { if ( 0 == lindex ) { continue; } CComVariant VarIndex = lindex; CComPtr<IDispatch> spDispatchElem; hr = spElemCollection->item( VarIndex, VarIndex, &spDispatchElem ); CHECKHRPOINTER(hr,spDispatchElem); CComPtr<IHTMLElement> spChildTr; hr = spDispatchElem->QueryInterface(IID_IHTMLElement, (LPVOID*)& spChildTr); CHECKHRPOINTER(hr, spChildTr); hr = GetQueryInfoInTr( spChildTr ); if ( SUCCEEDED(hr) ) { // 點擊了訂購按鈕了 break; } } } while (0); return hr; } 上述代碼執行到第57行時,for循環將逐個讀取每列車的信息。為了最快速達到點擊“預定”按鈕,我將判斷的操作放在GetQueryInfoInTr中。 [cpp] HRESULT CDeal12306WebPage::GetQueryInfoInTr( CComPtr<IHTMLElement> & spElem) { HRESULT hr = E_FAIL; do { CComPtr<IHTMLElementCollection> spElemCollection; hr = GetElementCollection(spElem, spElemCollection ); CHECKHRPOINTER(hr, spElemCollection); long lCount = 0; hr = spElemCollection->get_length(&lCount); CHECKHR(hr); StTrainInfo stTraininfoItem; for ( long lindex = 0; lindex < lCount; lindex++ ) { CComVariant VarIndex = lindex; CComPtr<IDispatch> spDispatchElem; hr = spElemCollection->item( VarIndex, VarIndex, &spDispatchElem ); CHECKHRPOINTER(hr,spDispatchElem); CComPtr<IHTMLElement> spChildTd; hr = spDispatchElem->QueryInterface(IID_IHTMLElement, (LPVOID*)& spChildTd); CHECKHRPOINTER(hr, spChildTd); hr = GetQueryInfoSubItem( spChildTd, stTraininfoItem, lindex ); CHECKHR(hr); } CHECKHR(hr); CComPtr<IHTMLElement> spTd; hr = GetElementByIndex( spElem, lCount - 1, spTd); CHECKHRPOINTER(hr, spTd); CComPtr<IHTMLElement> spButton; hr = GetElementByIndex( spTd, 0, spButton ); CHECKHRPOINTER(hr, spButton); CComBSTR bstrClassName; hr = spButton->get_className(&bstrClassName); CHECKHR(hr); CString cstrClassName = bstrClassName; if ( 0 == cstrClassName.CompareNoCase(HAVETICKETSACLASS) ) { hr = spButton->click(); } else { // 還沒有票 } m_VecTrainInfo.push_back(stTraininfoItem); } while (0); return hr; } 我這兒做了簡化:只要“預定”按鈕變成可點擊,即點擊之。其實這兒應該做更多的判斷,比如用戶的席別是否有票。上述代碼第44行,即是點擊“預定”按鈕的操作。 如果沒有票,則我們點擊“查詢”按鈕。 [cpp] HRESULT CDeal12306WebPage::StartQueryInQueryPage( CComPtr<IHTMLDocument2> & spDoc ) { HRESULT hr = S_FALSE; do { CComPtr<IHTMLElement> spQueryButton; hr = GetQueryButtonInQueryPage( spDoc, spQueryButton); CHECKHRPOINTER(hr, spQueryButton); hr = spQueryButton->click(); } while (0); return hr; } 如此,我們便實現了自動查詢和自動訂票的功能。