如何定制一款12306抢票浏览器——实现自动查询和预订功能
? ? ? ? 檢查是否進入訂票頁面
? ? ? ? 判斷是否進入訂票頁面,我是確定了兩個標準:(轉載請指明出于breaksoftware的csdn博客)
? ? ? ? 1 網址是否為http://www.12306.cn/mormhweb/kyfw/
? ? ? ? 2 該頁面否有查詢按鈕
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的同學可能馬上就會想到“跨域”問題。其實在瀏覽器層面,跨域問題是很好解決的。
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可以如下調用
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為節點,執行“查詢按鈕”查找的操作。
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查詢”查詢“按鈕的代碼是
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下插入“開始”和“停止”按鈕。
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;
}
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 DEBUGif ( FALSE == IsStartButtonExist(spDiv) ) {DebugBreak();}
#endif}if ( FALSE == IsStopButtonExist(spDiv) ) {hr = InsertStopButton(spDiv);CHECKHR(hr);
#ifdef DEBUGif ( FALSE == IsStopButtonExist(spDiv) ) {DebugBreak();}
#endif}} while (0);return hr ;
}
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 ;
}
#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"
#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。
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);
}? ? ? ? 點擊停止按鈕原理同點擊開始按鈕原理一致。此處不再贅述。
? ? ? ? 當用戶選擇好出發地和目的地及時間后,用戶點擊查詢按鈕。并點擊“開始”按鈕。我們的“人”線程就開始了自動查詢操作。
? ? ? ? 查詢是否存在票,有票則預訂,無票則再次查詢
? ? ? ? 當我們執行完一次查詢后,我們要查看下搜索結果列表信息中用戶選擇的車次是否存在票。我們先看一下頁面結構
? ? ? ? 其查找該節點的方法如下
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中。
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行,即是點擊“預訂”按鈕的操作。
? ? ? ? 如果沒有票,則我們點擊“查詢”按鈕。
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;
}
? ? ? ? 如此,我們便實現了自動查詢和自動訂票的功能。
總結
以上是生活随笔為你收集整理的如何定制一款12306抢票浏览器——实现自动查询和预订功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何定制一款12306抢票浏览器——启动
- 下一篇: 如何定制一款12306抢票浏览器——处理