检查是否进入订票页面
判断是否进入订票页面,我是确定了两个标准:(转载请指明出于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 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 ; }
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; }
如此,我们便实现了自动查询和自动订票的功能。
作者:breaksoftware 发表于2013-1-28 23:20:39 原文链接
阅读:24 评论:0 查看评论