程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 如何定制一款12306搶票浏覽器——實現自動查詢和預定功能

如何定制一款12306搶票浏覽器——實現自動查詢和預定功能

編輯:C++入門知識

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved