檢查是否進入訂票頁面
判斷是否進入訂票頁面,我是確定了兩個標准:
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;
}
如此,我們便實現了自動查詢和自動訂票的功能。