前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何定制一款12306抢票浏览器——实现自动查询和预订功能

如何定制一款12306抢票浏览器——实现自动查询和预订功能

作者头像
方亮
发布2019-01-16 10:34:54
7620
发布2019-01-16 10:34:54
举报
文章被收录于专栏:方亮方亮

检查是否进入订票页面

        判断是否进入订票页面,我是确定了两个标准:(转载请指明出于breaksoftware的csdn博客)

        1 网址是否为http://www.12306.cn/mormhweb/kyfw/

        2 该页面否有查询按钮

代码语言:javascript
复制
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的同学可能马上就会想到“跨域”问题。其实在浏览器层面,跨域问题是很好解决的。

代码语言: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可以如下调用

代码语言:javascript
复制
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为节点,执行“查询按钮”查找的操作。

代码语言:javascript
复制
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查询”查询“按钮的代码是

代码语言:javascript
复制
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下插入“开始”和“停止”按钮。

代码语言:javascript
复制
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;
}
代码语言:javascript
复制
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 ;
}
代码语言:javascript
复制
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 ;
}
代码语言:javascript
复制
#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"
代码语言:javascript
复制
#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。

代码语言:javascript
复制
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);
}

        点击停止按钮原理同点击开始按钮原理一致。此处不再赘述。         当用户选择好出发地和目的地及时间后,用户点击查询按钮。并点击“开始”按钮。我们的“人”线程就开始了自动查询操作。 查询是否存在票,有票则预订,无票则再次查询

        当我们执行完一次查询后,我们要查看下搜索结果列表信息中用户选择的车次是否存在票。我们先看一下页面结构

        其查找该节点的方法如下

代码语言:javascript
复制
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中。

代码语言:javascript
复制
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行,即是点击“预订”按钮的操作。         如果没有票,则我们点击“查询”按钮。

代码语言:javascript
复制
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;
}

        如此,我们便实现了自动查询和自动订票的功能。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2013年01月28日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档