日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

如何定制一款12306抢票浏览器——处理预订页面和验证码自动识别功能

發布時間:2023/11/27 生活经验 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何定制一款12306抢票浏览器——处理预订页面和验证码自动识别功能 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

判斷是否進入預訂頁面

? ? ? ? 我們先看一下預訂頁面的結構(轉載請指明出于breaksoftware的csdn博客)


? ? ? ? 可以見得,這個頁面也是嵌入了兩個IFrame。關于IFrame的跨域問題,我已經在前一篇文章中講述了解決辦法。

? ? ? ? 我判斷是否是預訂頁面是通過兩個依據:

? ? ? ? 1 URL是否是http://www.12306.cn/mormhweb/kyfw/

? ? ? ? 2 是否可以在最里層IFrame中找到class是“table_qr”的元素該元素對應于

? ? ? ? 具體的查找過程我這兒就不再贅述,我們通過代碼來解讀

BOOL CDeal12306WebPage::IsBookingPage( CComPtr<IHTMLDocument2> & spDoc, CComBSTR & bstrUrl )
{HRESULT hr = E_FAIL;do  {CString cstrUrl = CString((LPWSTR)bstrUrl);if ( 0 == cstrUrl.CompareNoCase(LOGIN12306URL) ) {CComPtr<IHTMLElement> spTableQrTbody;hr = GetTableQrTbody( spDoc, spTableQrTbody);CHECKHRPOINTER(hr, spTableQrTbody);}} while (0);return FAILED(hr) ? FALSE : TRUE;
}
HRESULT CDeal12306WebPage::GetTableQrTbody( CComPtr<IHTMLDocument2> & spDoc,CComPtr<IHTMLElement> & spElem )
{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 = GetElementByID( spEnter_wElem, L"confirmPassenger", spForm);CHECKHRPOINTER(hr, spForm);CComPtr<IHTMLElement> spTable;hr = GetElementByClassName( spForm, L"table_qr", spTable);CHECKHRPOINTER(hr, spTable);hr = GetElementByIndex( spTable, 0, spElem);CHECKHRPOINTER(hr, spElem);} while (0);return hr;
}

插入用戶信息,并設置相應的選項

? ? ? ? 我們看下用戶填寫信息的位置的HTML代碼結構


? ? ? ? 我們可以看到5個passenger可填寫區域。目前只有第一個顯示出來,而其他四個還沒有顯示。在上圖的最下面是個超鏈接,其對應于“添加1位乘車人”按鈕。可以想象,該按鈕的一個操作就是將不能顯示的tr顯示出來。我們“人”線程填寫用戶信息的過程和人的行為是一致的:填寫一個人信息后 ,點擊“添加1位乘車人”,再填寫一個……我們用代碼說明這個過程。

HRESULT CDeal12306WebPage::AddPassengerInfo( CComPtr<IHTMLElement>& spTableQrTbody,const VecStSinglePassengerInfo& vecStSingleinfo )
{HRESULT hr = E_FAIL;do {// 下標沒有從0開始!int i = 1;for ( VecStSinglePassengerInfoCIter it = vecStSingleinfo.begin(); it != vecStSingleinfo.end();i++ ) {CString cstrPassengerId;cstrPassengerId.Format(PASSENGERID, i);hr = BookSinglePassenger( spTableQrTbody, cstrPassengerId, it);CHECKHR(hr);it++;if ( it != vecStSingleinfo.end() ) {AddPassenger(spTableQrTbody);}}} while (0);return hr;
}

? ? ? ? 上面代碼我們將枚舉用戶設置的乘客信息。第12行,我們將在table中填寫一個乘客信息。第16行,我們將判斷最新加入的用戶是否是最后一個,如果不是最后一個,則點擊“添加1位乘車人”。

HRESULT CDeal12306WebPage::AddPassenger( CComPtr<IHTMLElement> & spTableQrTbody )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spTr;hr = GetElementByIndex(spTableQrTbody, 6, spTr);CHECKHRPOINTER(hr, spTr);CComPtr<IHTMLElement> spTd;hr = GetElementByIndex(spTr, 1, spTd);CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spA;hr = GetElementByIndex(spTd, 0, spA);CHECKHRPOINTER(hr, spA);hr = spA->click();} while (0);return hr;
}

? ? ? ? 填寫每個乘客信息的代碼是

HRESULT CDeal12306WebPage::BookSinglePassenger( CComPtr<IHTMLElement> & spElem, const CString& cstrPassengerID, VecStSinglePassengerInfoCIter iter )
{HRESULT hr = E_FAIL;do  {CComPtr<IHTMLElement> spTr;hr = GetElementByID( spElem, cstrPassengerID, spTr );CHECKHRPOINTER(hr, spTr);hr = SetName(spTr, iter->cstrName);CHECKHR(hr);hr = SetCardNo(spTr, iter->cstrCardNo);CHECKHR(hr);hr = SetMobileNo(spTr, iter->cstrMobileNo);CHECKHR(hr);hr = SetTicket(spTr, iter->cstrTicket);CHECKHR(hr);hr = SetCardtype(spTr, iter->cstrCardtype);CHECKHR(hr);hr = SetSeat(spTr, iter->ListSeat);} while (0);return hr;
}

? ? ? ? 其中填寫姓名的操作很簡單,只要找到相應控件,并向該控件中插入文字即可

HRESULT CDeal12306WebPage::SetName( CComPtr<IHTMLElement> & spElem, const CString& cstrName )
{return SetInputHelper(spElem, cstrName, 4);
}
HRESULT CDeal12306WebPage::SetInputHelper( CComPtr<IHTMLElement> & spElem, const CString& cstrValue, long lIndex )
{HRESULT hr = E_FAIL;do  {CComPtr<IHTMLElement> spTd;hr = GetElementByIndex( spElem, lIndex, spTd );CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spInputElem;hr = GetElementByIndex(spTd, 0, spInputElem);CHECKHRPOINTER(hr, spInputElem);CComPtr<IHTMLInputElement> spInput;hr = spInputElem->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInput);CHECKHRPOINTER(hr, spInput);hr = spInput->put_value( CComBSTR(cstrValue.GetString()) );CHECKHR(hr);} while (0);return hr;
}

? ? ? ? 設置席別這類Select選項則稍微復雜點,其實原理是一致的

HRESULT CDeal12306WebPage::SetSeat( CComPtr<IHTMLElement> & spElem, const CString& cstrSeat )
{return SetOptionHelper( spElem, cstrSeat, 2);
}
HRESULT CDeal12306WebPage::SetOptionHelper( CComPtr<IHTMLElement> & spElem, const CString& cstrValue, long lIndex )
{HRESULT hr = E_FAIL;do  {CComPtr<IHTMLElement> spTd;hr = GetElementByIndex( spElem, lIndex, spTd );CHECKHRPOINTER(hr, spTd);CComPtr<IHTMLElement> spSelectElem;hr = GetElementByIndex(spTd, 0, spSelectElem);CHECKHRPOINTER(hr, spSelectElem);hr = SetOptionSelect( spSelectElem, cstrValue);CHECKHR(hr);} while (0);return hr;
}
HRESULT CDeal12306WebPage::SetOptionSelect( CComPtr<IHTMLElement> & spElem, const CString& cstrValue )
{HRESULT hRes = E_FAIL;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);for ( long lindex = 0; lindex < lCount; lindex++ ) {CComVariant VarIndex = lindex;CComPtr<IDispatch> spDispatchElem;hr = spElemCollection->item( VarIndex, VarIndex, &spDispatchElem );CHECKHRPOINTER(hr,spDispatchElem);CComPtr<IHTMLOptionElement> spOption;hr = spDispatchElem->QueryInterface(IID_IHTMLOptionElement, (LPVOID*)& spOption);if ( FAILED(hr) || NULL == spOption ) {continue;}CComBSTR bstrValue;hr = spOption->get_value(&bstrValue);if ( FAILED(hr) ) {continue;}CString cstrReadValue(bstrValue);if (  0 == cstrReadValue.Compare(cstrValue) ) {hRes = spOption->put_selected(VARIANT_TRUE);break;}}} while (0);return hRes;
}

? ? ? ? 如此自動填寫乘客信息的操作就完成了。

驗證碼的自動識別

? ? ? ? 說來慚愧,這個模塊本來是我這個軟件的一個亮點。可是隨著12306將驗證碼生成方法改變,導致我原來的邏輯產生了很大的誤差。其實圖像識別這塊,我使用的是第三方庫tesseract-ocr。之前12306的驗證碼相對比較簡單,但是仍然加入了噪點和干擾線,使得tesseract-ocr識別率非常不準。于是我寫了一個bmp文件格式分析和圖片轉換類去處理原始驗證碼圖片,使得驗證碼變得清晰,同時提高了tesseract-ocr的識別準確率。我列一些以前的處理結果對比圖



? ? ? ? 網上有使用2012編譯tesseract-ocr的介紹。我做了點改動:在tesseract-ocr的init函數中,提供了一個指定相關目錄的參數,但是代碼底層卻優先讀取了系統環境變量TESSDATA_PREFIX的值作為相關目錄。我修改了源代碼中的這部分:即只使用我指明的程序路徑,而不是使用系統環境變量TESSDATA_PREFIX的值。

? ? ? ? 我封裝了一個文字識別的類COcr。其內容也很簡單

BOOL COcr::Init(const CString& cstrSetupFloder)
{std::string sSetupFloder = CW2A(cstrSetupFloder.GetString());int nstatus = m_Tesseract.Init(sSetupFloder.c_str(), "eng", tesseract::OEM_TESSERACT_ONLY);if ( nstatus < 0 ) {return FALSE;}m_Tesseract.SetPageSegMode(tesseract::PSM_SINGLE_BLOCK);nstatus = m_Tesseract.SetVariable( "tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwsyz" );return nstatus > 0 ? TRUE : FALSE;
}BOOL COcr::GetText( const CString& cstrImgPath, CString & cstrText )
{std::string sImgPath = CW2A(cstrImgPath.GetString());STRING text_out;if (!m_Tesseract.ProcessPages(sImgPath.c_str(), NULL, 0, &text_out)) {return FALSE;}std::string sText = text_out.string();cstrText = CA2W(sText.c_str());return TRUE;
}

? ? ? ? 簡單說明下上述代碼。代碼第4行,我們設置了語言是eng,即英語體系。因為目前12306的驗證碼還只是數字和字母。代碼第9行,告訴tesseract-ocr驗證碼中只是包含0~9A~Za~z字符。之前12306的驗證碼只有數字和大寫字母,所以那個時候設置這個參數為0~9A~Z是非常必要的。

? ? ? ? 代碼識別模塊ok后,就是如何保存驗證碼圖片的問題了。

如何保存驗證碼圖片

? ? ? ? 仔細看過12306驗證碼區域的HTML代碼的朋友,應該知道,該處的IMG的src不是指向的是一個圖片,而是一個隨機地址。

<img title="單擊刷新驗證碼" id="img_rrand_code" style="vertical-align: text-bottom; cursor: hand;" οnclick="this.src=this.src+'&'+Math.random();" src="/otsweb/passCodeAction.do?rand=randp" border="0"/>

? ? ? ? 我之前想通過Src下載圖片的方法明顯是行不通的。那么就得使用截屏技術了。下面的代碼,將驗證碼區域復制到剪貼板中,然后再將剪貼板中的圖片保存為一個32位真彩色的bmp圖片。

HRESULT CDeal12306WebPage::SaveImg( CComPtr<IHTMLElement> spElement, const CString& cstrFilePath )
{HRESULT hr = E_FAIL;do {CComPtr<IDispatch> spDispDoc;hr = spElement->get_document(&spDispDoc);CHECKHRPOINTER(hr, spDispDoc);CComPtr<IHTMLDocument2> spMainDoc;hr = spDispDoc->QueryInterface(IID_IHTMLDocument2, (LPVOID*)&spMainDoc);CHECKHRPOINTER(hr, spMainDoc);CComPtr<IHTMLElement> spBody;hr = spMainDoc->get_body(&spBody);CHECKHRPOINTER(hr, spBody);CComPtr<IHTMLElement2> spBody2;hr = spBody->QueryInterface(IID_IHTMLElement2, (LPVOID*)&spBody2);CHECKHRPOINTER(hr, spBody2);CComPtr<IDispatch> spDisp;hr = spBody2->createControlRange(&spDisp);CHECKHRPOINTER(hr, spDisp);CComPtr<IHTMLControlRange> spControlRange;hr = spDisp->QueryInterface(IID_IHTMLControlRange, (LPVOID*)&spControlRange);CHECKHRPOINTER(hr, spControlRange);CComPtr<IHTMLControlElement> spControlElem;hr = spElement->QueryInterface(IID_IHTMLControlElement, (LPVOID*)&spControlElem);CHECKHRPOINTER(hr, spControlElem);hr = spControlRange->add(spControlElem);CHECKHR(hr);VARIANT_BOOL vbReturn = VARIANT_FALSE;CComVariant vEmpty;CComBSTR bstrCmd(L"Copy");hr = spControlRange->execCommand(bstrCmd, VARIANT_FALSE, vEmpty, &vbReturn );CHECKHR(hr);if ( VARIANT_FALSE == vbReturn ) {hr = E_FAIL;break;}if(OpenClipboard(NULL)){//獲得剪貼板數據HBITMAP handle = (HBITMAP)GetClipboardData(CF_BITMAP);if ( NULL != handle ) {CImage Img;Img.Attach(handle);hr = Img.Save(cstrFilePath);}else {hr = E_FAIL;}CloseClipboard();}} while (0);return hr;
}

截屏、識別、輸入驗證碼的邏輯

HRESULT CDeal12306WebPage::SetCaptcha( CComPtr<IHTMLElement> & spTableQrTbody )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spImg;hr = GetCaptchaImgElem( spTableQrTbody, spImg);CHECKHRPOINTER(hr, spImg);CComPtr<IHTMLElement> spInput;hr = GetCaptchaInputElem( spTableQrTbody, spInput );CHECKHRPOINTER(hr, spInput);CString cstrImgPath;cstrImgPath.Format(L"%s%d.bmp", m_cstrFloder, GetTickCount());hr = SaveImg( spImg, cstrImgPath);CHECKHR(hr);CString cstrNewImgPath = cstrImgPath + ".bmp";CBmp bmp;bmp.SetFilePath( cstrImgPath, cstrNewImgPath );if ( FALSE == bmp.DealBmp() ) {hr = E_FAIL;break;}CString cstrTxet;if ( FALSE == m_ocr.GetText( cstrNewImgPath, cstrTxet) ) {hr = E_FAIL;break;}if ( CAPTCHACOUNT > cstrTxet.GetLength() ) {hr = E_FAIL;break;}cstrTxet = cstrTxet.Left(CAPTCHACOUNT);CComPtr<IHTMLInputElement> spInputElem;hr = spInput->QueryInterface(IID_IHTMLInputElement, (LPVOID*)&spInputElem);CHECKHRPOINTER(hr, spInputElem);hr = spInputElem->put_value( CComBSTR(cstrTxet.GetString()) );CHECKHR(hr);} while (0);return hr;
}

? ? ? ? 如果識別的字符數不對,則會認為失敗,這樣我們會刷新驗證碼,并重新識別。

HRESULT CDeal12306WebPage::SetCaptchaEx( CComPtr<IHTMLElement>& spTableQrTbody )
{HRESULT hr = E_FAIL;do {for ( int n = 0; n < CAPTCHARETRYCOUNT; n++ ) {hr = SetCaptcha( spTableQrTbody );if ( FAILED(hr) ) {// 如果失敗刷新驗證碼再來一次CComPtr<IHTMLElement> spImg;hr = GetCaptchaImgElem( spTableQrTbody, spImg);CHECKHRPOINTER(hr, spImg);spImg->click();Sleep(CAPTCHAWAITTIME);}else {break;}}} while (0);return hr;
}

? ? ? ? 驗證碼輸入完畢后,我們將點擊“提交訂單”按鈕。現在有個問題冒出來了:如果我們驗證碼輸入錯誤,那么網頁會alert一下提示“驗證碼錯誤”,這個迫使我們得去點擊這個按鈕。如何去點擊這個按鈕呢?這個問題困擾了我一下,最后我決定還是繞過這個問題——徹底屏蔽Alert彈框,并記錄Alert準備彈出的內容。在點擊完按鈕后,我將根據保存的Alert準備彈出的內容判斷是否成功和失敗。

屏蔽Alert

? ? ? ? 我們的窗口要繼承IDocHostShowUI接口,并修改該接口的一個方法:
STDMETHODIMP CBrowserHost::ShowMessage( 
/* [in] */ HWND hwnd, 
/* [annotation][in] */ __in __nullterminated LPOLESTR lpstrText, 
/* [annotation][in] */ __in __nullterminated LPOLESTR lpstrCaption, 
/* [in] */ DWORD dwType, 
/* [annotation][in] */ __in __nullterminated LPOLESTR lpstrHelpFile, 
/* [in] */ DWORD dwHelpContext, 
/* [out] */ LRESULT *plResult )
{*plResult = 0;return S_OK;
}

? ? ? ? 從上面代碼看,我并沒有記錄alert的內容。因為我發現了一個更為有效和簡單的辦法去判斷是否成功了。我們看下提交沒有成功時HTML網頁結構


? ? ? ? 我們再看下提交成功的頁面的網頁結構


? ? ? ? 可以見得,提交成功的頁面中新增了兩個Div。其中最下面那個Div就是確認信息的HTML代碼


? ? ? ? 于是完整的預訂流程是

HRESULT CDeal12306WebPage::BookTickets( CComPtr<IHTMLDocument2> & spDoc )
{HRESULT hr = E_FAIL;do  {CComPtr<IHTMLElement> spTableQrTbody;hr = GetTableQrTbody( spDoc, spTableQrTbody);CHECKHRPOINTER(hr, spTableQrTbody);if ( m_stTrainNoPassenger.vecPassengerInfo.size() > MAXPASSENGERCOUNT) {ATLASSERT(FALSE);}hr = AddPassengerInfo( spTableQrTbody, m_stTrainNoPassenger.vecPassengerInfo );CHECKHR(hr);DWORD dwCount = 0;Sleep(6*1000);do {hr = SetCaptchaEx( spTableQrTbody );CHECKHR(hr);hr = ClickSubmitButton(spTableQrTbody);CHECKHR(hr);dwCount++;} while ( FAILED(ConfirmOrd(spDoc)));} while (0);return hr;
}
HRESULT CDeal12306WebPage::ConfirmOrd( CComPtr<IHTMLDocument2> & spDoc )
{HRESULT hr = E_FAIL;do {CComPtr<IHTMLElement> spDiv;hr = GetOrderConfirm( spDoc, spDiv);CHECKHRPOINTER(hr, spDiv);CComPtr<IHTMLElement> spOkButton;hr = GetConfirmOKElem(spDiv, spOkButton);CHECKHRPOINTER(hr, spOkButton);hr = spOkButton->click();CHECKHR(hr);} while (0);return hr;
}

總結

以上是生活随笔為你收集整理的如何定制一款12306抢票浏览器——处理预订页面和验证码自动识别功能的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 日日夜夜一区二区 | www.色妞| 特级西西444www大精品视频免费看 | 精品免费囯产一区二区三区 | 亚洲日本综合 | 亚洲欧洲精品成人久久奇米网 | 久久涩综合 | jvid在线| 日韩日韩日韩日韩日韩 | 极品少妇一区二区三区 | 99精品视频免费版的特色功能 | 中文字字幕在线中文 | 亚洲阿v天堂 | 少妇精品导航 | 九九视频在线 | 小镇姑娘1979版| 中文字幕日韩经典 | 丁香六月婷婷激情 | 黑人满足娇妻6699xx | 久久国产91| 青娱乐免费在线视频 | 国产做a | 福利精品 | 进去里片欧美 | a天堂在线观看视频 | 黄色网页在线观看 | 美女脱了裤子让男人捅 | 一级片黄色的 | 国产一级片自拍 | 亚洲黄色在线观看视频 | 精品无码一区二区三区的天堂 | 久久久久久久久久久99 | 末路1997全集免费观看完整版 | 国产福利在线导航 | 午夜视频1000| 四虎音影 | 欧美日韩在线视频免费 | 在线观看的黄色网址 | 亚洲人成免费 | 日日操夜夜操狠狠操 | 麻豆亚洲av成人无码久久精品 | 欧美精品系列 | 青青视频在线播放 | 午夜久久乐 | 操欧美美女 | 快色视频 | 国产成人精品女人久久久 | 香蕉av在线 | 国产精品大片 | 茄子视频色 | 精品无码久久久久久久久久 | 一区二区三区韩国 | 西西毛片 | 51精产品一区一区三区 | 香蕉视频二区 | 中文字幕一区二区三区不卡 | 亚洲av片一区二区三区 | av女大全列表 | 欧美在线一区视频 | 性欧美大战久久久久久久免费观看 | 国产熟女一区二区三区五月婷 | 国产玖玖视频 | 爱逼综合 | 乳色吐息在线观看 | 欧美一区二区三区久久久 | 免费在线看黄视频 | 91香蕉视频黄色 | 三级黄色视屏 | 传媒视频在线观看 | 首尔之春在线观看 | 成人日韩欧美 | 国产成人午夜视频 | 丰满岳妇乱一区二区三区 | 国产香蕉精品视频 | 婷婷五月小说 | 国产成人午夜精品 | se94se欧美 | 日韩欧美在线观看免费 | 日韩精品成人无码专区免费 | 日本不卡视频在线播放 | 黄色片中国| 女人高潮娇喘声mp3 乱色视频 | 一级淫片免费看 | 插吧插吧网 | 可以免费观看的毛片 | 亚洲成a人v欧美综合天堂麻豆 | 欧美美女在线 | 成人午夜精品无码区 | 在办公室被c到呻吟的动态图 | 一级日批片 | 黑人巨大国产9丨视频 | 美国av一区二区 | 国产夫妻性生活视频 | 成人综合精品 | 手机看片91 | 男生脱女生衣服 | 国产精品一区二区人妻喷水 | 啪视频在线 | 一本大道熟女人妻中文字幕在线 |