坦白地说,我很想弄明白网上那些自动注册网站并发帖的程序是怎么写的?
之前发过一个帖子:浏览器是怎么解析表单并POST信息的?
eduyu兄曾在里面给予了一些极有启发的回答,但是仍然有个问题没搞明白。
我在尝试用程序向一个网站POST注册信息时,发现该网站的html文件中除了form元素之外,还有很多非常复杂的JavaScript在表单提交之前对用户输入进行检测,这些复杂的动作使模拟发送失败的几率上升,然而又找不到更好的办法。在那个帖子里面,杜宇兄曾建议:
“其实,我建议自动化IE来提交表单,这样就不用管这些细节了,只需要每个表单写一个简单Javascript就可以提交。性能虽然没有http协议级别来得快,不过更加方便,更加可编程。性能上每分钟提交四五十个不在话下。”
对这句话我还不明白怎么自动化IE来提交表单。
恳请指点!

解决方案 »

  1.   

    可以用诸如HttpWatch, IE Analyzer 之类的HTTP抓包工具来检测提交表单的具体内容,然后,在VC++里面有三种方案实现程序自动提交:
    1、用WebBrowser控件模拟IE。这是最直观的,但也是效率最低的。
    2、用Socket遵循HTTP协议来提交数据包。这是最底层的,可能也是效率最高的,但有些细节实现起来比较复杂,要考虑很多方面。
    3、利用MFC提供的与wininet.dll相关的类,如CHttpSession,CHttpConnection等来模拟IE。这是折衷的方法,MFC封装了一些HTTP的操作在这些类里面,实现起来没Socket那么复杂,但灵活性又比WebBrowser要高一些。我目前是选这种方案。
      

  2.   

    就是让你创建一个自定义的浏览器,有现成的类供你使用,MFC中有CDHtmlDialog和CHtmlView类,ATL中有CAxDialogImpl和CAxWindow类都能加载WebBrowser控件,加载后执行Navigate方法打开需要的网页,等待网页加载完成,从浏览器控件获取IHTMLDocument2接口,随后进入DOM世界,自由填写表单内容,最后执行form.submit方法即可。
      

  3.   

    我想我大概知道是怎么回事了。
    我在网上找到个DOM遍历表单元素的代码:
    // EnumFormVal.cpp : Defines the entry point for the console application.
    //#include "stdafx.h"
    #include "EnumFormVal.h"#include <atlbase.h>CComModule _Module; // 由于要使用 CComDispatchDriver ATL的智能指针,
    // 所以声明它是必须的#include <mshtml.h> // 所有 IHTMLxxxx 的接口声明
    #include <atlcom.h>#ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif/////////////////////////////////////////////////////////////////////////////
    // The one and only application objectusing namespace std;void EnumIE( void ); //枚举浏览器函数
    void EnumFrame( IHTMLDocument2 * pIHTMLDocument2 ); //枚举子框架函数
    void EnumForm ( IHTMLDocument2 * pIHTMLDocument2 ); //枚举表单函数int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
    {
    ::CoInitialize(NULL); //初始化 COM 公寓 EnumIE(); //枚举浏览器 ::CoUninitialize(); //释放 COM 公寓 cout << _T("======完成======") << endl;
    getchar(); //等待回车 return 0;
    }void EnumIE( void )
    {
    cout << _T("开始扫描系统中正在运行的浏览器实例") << endl; CComPtr< IShellWindows > spShellWin;
    HRESULT hr = spShellWin.CoCreateInstance( CLSID_ShellWindows );
    if ( FAILED ( hr ) )
    {
    cout << _T("获取 IShellWindows 接口错误") << endl;
    return;
    } long nCount = 0; // 取得浏览器实例个数(Explorer 和 IExplorer)
    spShellWin->get_Count( &nCount );
    if( 0 == nCount )
    {
    cout << _T("没有在运行着的浏览器") << endl;
    return;
    } for(int i=0; i<nCount; i++)
    {
    CComPtr< IDispatch > spDispIE;
    hr=spShellWin->Item(CComVariant( (long)i ), &spDispIE );
    if ( FAILED ( hr ) ) continue; CComQIPtr< IWebBrowser2 > spBrowser = spDispIE;
    if ( !spBrowser ) continue; CComPtr < IDispatch > spDispDoc;
    hr = spBrowser->get_Document( &spDispDoc );
    if ( FAILED ( hr ) ) continue; CComQIPtr< IHTMLDocument2 > spDocument2 = spDispDoc;
    if ( !spDocument2 ) continue; // 程序运行到此,已经找到了 IHTMLDocument2 的接口指针 // 删除下行语句的注释,把浏览器的背景改变看看
    // spDocument2->put_bgColor( CComVariant( "green" ) );

    EnumForm( spDocument2 ); //枚举所有的表单
    }
    }void EnumFrame( IHTMLDocument2 * pIHTMLDocument2 )
    {
    if ( !pIHTMLDocument2 ) return; HRESULT hr; CComPtr< IHTMLFramesCollection2 > spFramesCollection2;
    pIHTMLDocument2->get_frames( &spFramesCollection2 ); //取得框架frame的集合 long nFrameCount=0; //取得子框架个数
    hr = spFramesCollection2->get_length( &nFrameCount );
    if ( FAILED ( hr ) || 0 == nFrameCount ) return; for(long i=0; i<nFrameCount; i++)
    {
    CComVariant vDispWin2; //取得子框架的自动化接口
    hr = spFramesCollection2->item( &CComVariant(i), &vDispWin2 );
    if ( FAILED ( hr ) ) continue; CComQIPtr< IHTMLWindow2 > spWin2 = vDispWin2.pdispVal;
    if( !spWin2 ) continue; //取得子框架的 IHTMLWindow2 接口 CComPtr < IHTMLDocument2 > spDoc2;
    spWin2->get_document( &spDoc2 ); //取得字框架的 IHTMLDocument2 接口 EnumForm( spDoc2 ); //递归枚举当前子框架 IHTMLDocument2 上的表单form
    }
    }void EnumForm( IHTMLDocument2 * pIHTMLDocument2 )
    {
    if( !pIHTMLDocument2 ) return; EnumFrame( pIHTMLDocument2 ); //递归枚举当前 IHTMLDocument2 上的子框架fram HRESULT hr;
    CComBSTR bstrTitle;
    pIHTMLDocument2->get_title( &bstrTitle ); //取得文档标题 USES_CONVERSION;
    cout << _T("====================") << endl;
    cout << _T("开始枚举“") << OLE2CT( bstrTitle ) << _T("”的表单") << endl;
    cout << _T("====================") << endl; CComQIPtr< IHTMLElementCollection > spElementCollection;
    hr = pIHTMLDocument2->get_forms( &spElementCollection ); //取得表单集合
    if ( FAILED( hr ) )
    {
    cout << _T("获取表单的集合 IHTMLElementCollection 错误") << endl;
    return;
    } long nFormCount=0; //取得表单数目
    hr = spElementCollection->get_length( &nFormCount );
    if ( FAILED( hr ) )
    {
    cout << _T("获取表单数目错误") << endl;
    return;
    }

    for(long i=0; i<nFormCount; i++)
    {
    IDispatch *pDisp = NULL; //取得第 i 项表单
    hr = spElementCollection->item( CComVariant( i ), CComVariant(), &pDisp );
    if ( FAILED( hr ) ) continue; CComQIPtr< IHTMLFormElement > spFormElement = pDisp;
    pDisp->Release(); long nElemCount=0; //取得表单中 域 的数目
    hr = spFormElement->get_length( &nElemCount );
    if ( FAILED( hr ) ) continue; for(long j=0; j<nElemCount; j++)
    {
    CComDispatchDriver spInputElement; //取得第 j 项表单域
    hr = spFormElement->item( CComVariant( j ), CComVariant(), &spInputElement );
    if ( FAILED( hr ) ) continue; CComVariant vName,vVal,vType; //取得表单域的 名,值,类型
    hr = spInputElement.GetPropertyByName( L"name", &vName );
    if( FAILED( hr ) ) continue;
    hr = spInputElement.GetPropertyByName( L"value", &vVal );
    if( FAILED( hr ) ) continue;
    hr = spInputElement.GetPropertyByName( L"type", &vType );
    if( FAILED( hr ) ) continue; LPCTSTR lpName = vName.bstrVal?
    OLE2CT( vName.bstrVal ) : _T("NULL"); //未知域名
    LPCTSTR lpVal  = vVal.bstrVal?
    OLE2CT( vVal.bstrVal  ) : _T("NULL"); //空值,未输入
    LPCTSTR lpType = vType.bstrVal?
    OLE2CT( vType.bstrVal ) : _T("NULL"); //未知类型
    ///////////////////////////////////这一段代码是我加上去的,但是并没有起到作用,不知道错误在哪里/////////////////
    LPCTSTR lpname=_T("username");
    CString Tmp=_T("wangdahai");
    CComVariant   vSet(Tmp);
    if(lpName==lpname)
    {
    spInputElement.PutPropertyByName( L"value", &vSet);
    }
    /////////////////////////////////////////////////////////////////////////////////////                        
    cout << _T("[") << lpType << _T("] ");
    cout << lpName << _T(" = ") << lpVal << endl;
    }
    //想提交这个表单吗?删除下面语句的注释吧
    //pForm->submit();
    }
    }但是不知道怎么PutPropertyByName?
      

  4.   

    我用的是一楼说的第一种方法。不论哪一个方法,都要对提交表单的页面一个个分析。如果采用一楼说的WinINet/libcurl这个层次去提交表单,分析是比较复杂的,需要解析出原始的HTTP协议数据。
    如果采用自动化IE这个层次去,分析就省事儿多了,就是模拟用户使用网页的过程。比如填写啦,点击啦。拿IE Developer Tools这样的DOM工具,找到表单的DOM特征,注入一段Javascript代码,让它改成自己想要的值并提交至于你说的由于有了客户端验证脚本的问题。我是这样解决的。首先是客户端验证即便绕过去,提交上去的数据由于不合法,一般是没有意义的。所谓问题就成了怎么程序化捕获验证失败的问题。这是自动化IE的关键点之一。最弱的网站程序会直接用alert/confirm/prompt来提示用户。这个可以通过对webbrowser控件的编程捕获。进一步是脚本错误,这属于对方程序比较次,测试不够的结果。这个也可以通过js脚本引擎的接口来捕获。剩下的做得比较好的网站程序。比如百度新浪搜狐,这些网站有自己的逻辑层错误捕获机制,这样的也不复杂,往往就是一个js的对象或者一个全局函数,找到就成。
      

  5.   

    CComVariant vVal = "abc";
    spInputElement.PutPropertyByName( L"value", vVal );
      

  6.   

    spInputElement.PutPropertyByName( L"value", vVal );
    这段代码中vVal应该用&vVal引用吧?要不然会报错error C2664: “ATL::CComPtr<IDispatch>::PutPropertyByName”: 不能将参数 2 从“ATL::CComVariant”转换为“VARIANT *”而且找你的方法修改之后情况还是没改变。
    调试中发现if判断中,命名name元素的名字是username,但是并没得出lpName==lpname的结果,于是if内的代码就跳过去了,这是为何?
    LPCTSTR lpname=_T("username");
    CComVariant   vSet="abcdefg";
    if(lpName==lpname)
    {
    spInputElement.PutPropertyByName( L"value", vSet);
    }
      

  7.   

    1.用C++去操作DOM是吃力不讨好的。
    我一般写成js,调用这个就成了:
    IHTMLWindow2->execScript();2.编程获取脚本错误:
    http://support.microsoft.com/kb/2610033.我有个WAIE(web application interaction engine),傻瓜式的,
    只需写一些简单的js脚本就可以操作IE对web应用做包括提交表单,上传文件等交互。
    可加速你项目开发,可授权ActiveX方式使用,如果需要,也可以附源码。当然,你如果想学原理,可能还是要多看MSDN的关于hosting mshtml/webbrowser控件的部分。
      

  8.   

    对,笔误,应该是&vVal;你可以通过IHTMLDocument3::getElementById("username")来获得input元素
      

  9.   

    具体怎么用,
    CString str=_T("username");
    BSTR bStr=str.AllocSysString();
    IHTMLElement **login;
    IHTMLDocument3  * myIHTMLDocument3;
    myIHTMLDocument3->getElementById(bStr,login);
    CComVariant   vSet="abcdefg";
    login->PutPropertyByName( L"value", &vSet);这样有什么问题?
      

  10.   

    看看这个,正好满足你的需求.简单方便,写点脚本就行了:
    http://www.grscript.com/