检查是否进入订票页面
判断是否进入订票页面,我是确定了两个标准:(转载请指明出于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;
}
复制
如此,我们便实现了自动查询和自动订票的功能。