一、摘要
在我们编写的程序中,如果想要实现对浏览器打开的网页进行监视、模拟操纵、动态提取用户输入、动态修改......等功能,那么请你抽出宝贵的时间,继续往下阅读。本文介绍的知识和示例程序都是围绕如何遍历 HTML 中的表单(form)并枚举出表单域的属性为目标的,对于网页中的其它元素,比如图象、连接、脚本等等,应用同样的方法都可以轻松实现。
二、网页的文档层次结构
IE 浏览器,采用 DOM(文档对象模型)来管理网页的数据。它通过一个容器(IWebBrowser2/IHTMLWindow2)来装载网页文档(IHTMLDocument2),而一个文档,又可以由 0 或多个贞(frame)组成,管理这些贞的接口叫“框架集合(IHTMLFramesCollection2)”,而每个贞的容器又是IHTMLWindow2,和IWebBrowser2一样,它也装载着各自的文档(IHTMLDocument2)。因此,我们的第一个任务,就是想方设法能够得到IHTMLDocument2的接口。因为文档可能包含贞,而贞又包含着子文档,子文档可能再包含贞......,如此要得到所有的文档,这里有一个递归遍历的处理过程。
得到文档(IHTMLDocument2)后,下一步任务就是要设法取得表单了(IHTMLFormElement)。因为在一个文档中可以包含 0 或多个表单(form),而管理这些表单的又是一个表单集合(IHTMLElementCollection),所以必须先得到集合,然后再枚举出所有的表单条目了。
得到表单(IHTMLFormElement)后,接下来的事情就简单了,逐个提取表单中的元素(也叫表单域 IHTMLInputElement)就可以读写这些域的属性了。
说了半天,我估计初次接触的朋友一定没有听懂:( 呵呵,还是用图的方式表示一下吧,这样比较清晰一些。
三、程序实现
<1> 取得 IHTMLDocument2 的接口指针。根据IE浏览器的运行方式,有多种不同的方式可以获取文档指针。
<1.1> 如果你在程序中使用MFC的 CHtmlView视来浏览网页。
取得文档的方法最简单,调用 CHtmlView::GetHtmlDocument() 函数。
<1.2> 如果你的程序中使用了“Web 浏览器” 的ActiveX 控件。
取得文档的方法也比较简单,调用 CWebBrowser2::GetDocument() 函数。
<1.3> 如果你的程序是用 ATL 写的 ActiveX 控件。
那么需要调用 IOleClientSite::GetContainer 得到 IOleContainer 接口,然后就可以通过 QueryInterface() 查询得到 IHTMLDocument2 的接口。主要代码如下:
|
<1.4> 如果你的程序是用 MFC 写的 ActiveX 控件。
那么需要调用 COleControl::GetClientSite() 得到 IOleContainer 接口,然后的操作和<1.3>是一致的了。
<1.5> IE 浏览器作为独立的进程正在运行。
每个运行的浏览器(IE 和 资源浏览器)都会在 ShellWindows 中进行登记,因此我们要通过 IShellWindows 取得实例(示例程序中使用的就是这个方法)。主要代码如下:
|
<1.6> IE 浏览器控件被一个进程包装在一个子窗口中。那么你首先要得到那个进程的顶层窗口句柄(使用 FindWindow() 函数,或其它任何可行的方法),然后枚举所有子窗口,通过判断窗口类名是否是“Internet Explorer_Server”,从而得到浏览器的窗口句柄,再向窗口发消息取得文档的接口指针。主要代码如下:
|
<2> 得到了 IHTMLDocument2 接口指针后,如果网页是单贞的,那么转第<4>步骤。如果是多贞(有子框架)则还需要遍历所有的子框架。这些子框架(IHTMLWindow2),被保存在集合中(IHTMLFramesCollection2),取得集合指针的方法比较简单,取属性 IHTMLDocument2::get_frames()。
<3> 首先取得子框架的总数目 IHTMLFramesCollection::get_length(),接着就可以循环调用 IHTMLFramesCollection::item()函数一个一个地取得子框架 IHTMLWindow2 指针,然后转第<1>步。
<4> 一个文档中可能拥有多个表单,因此还是同样的道理,先要取得表单的集合(IHTMLElementCollection,其实这个不光是表单的集合,其他元素的集合,比如图片集合也是用它)。这个操作也很简单,取得属性 IHTMLDocument2::get_forms()。
<5> 属性 IHTMLElementCollection::get_length() 得到表单总数目,就可以循环取得每一个表单指针了 IHTMLElementCollection::item()。
<6> 在第<5>步中的item()函数,得到的是一个IDispatch的指针,你通过QueryInterface()查询,就可以得到 某类型输入的指针,代码如下:
|
上面的方法,由于使用具体类型的接口指针,因此程序的效率比较高。但是通过 QueryInterface 接口查询,然后再进行条件判断显然是比较烦琐的,所以这个方法适合于特定的已知网页设计内容的程序。在示例程序中,我则是直接使用 IDispatch 接口进行操作的,这个方式执行起来稍微慢一些,但程序比较简单。主要代码和说明如下:#i nclude < atlbase.h >
CComModule _Module; // 由于需要使用 CComDispatchDriver 的 IDispatch 包装类ATL智能指针,所以这个是必须的
|
四、结束语
示例程序在 VC6 下编译执行通过。运行方法:随便启动几个 IE 浏览网页,最好是有表单输入的网页。然后执行示例的 EXE 程序即可。
[/td][/tr][tr][td]-- 作者:admin
-- 发布时间:2007-11-8 21:50:00
-- 实现和IE浏览器交互的几种方法的介绍(转载)
---- 1.引言
---- 如何实现对IE浏览器中对象的操作是一个很有实际意义问题,通过和IE绑定的DLL我们可以记录IE浏览过的网页的顺序,分析用户的使用行为和模式。我们可以对网页的内容进行过滤和翻译,可以自动填写网页中经常需要用户填写的Form内容等等,我们所有的例子代码都是通过VC来表示的,采用的原理是通过和IE对象的接口的交互来实现对IE的访问。实际上是采用COM的技术,我们知道COM是和语言无关的一种二进制对象交互的模式,所以实际上我们下面所描述的内容都可以用其他的语言来实现,比如VB,DELPHI,C Builder等等。
---- 2.IE实例遍历实现
---- 首先我们来看系统是如何知道当前有多少个IE的实例在运行。
---- 我们知道在Windows体系结构下,一个应用程序可以通过操作系统的运行对象表来和这些应用的实例进行交互。但是IE当前的实现机制是不在运行对象表中进行注册,所以需要采用其他的方法。我们知道可以通过ShellWindows集合来代表属于shell的当前打开的窗口的集合,而IE就是属于shell的一个应用程序。
---- 下面我们描述一下用VC实现对当前 IE实例的进行遍历的方法。IShellWindows是关于系统shell的一个接口,我们可以定义一个如下的接口变量:
SHDocVw::IShellWindowsPtr m_spSHWinds;
然后创建变量的实例:
m_spSHWinds.CreateInstance
(__uuidof(SHDocVw::ShellWindows));
通过IShellWindows接口的方法GetCount
可以得到当前实例的数目:
long nCount = m_spSHWinds- >GetCount();
通过IShellWindows接口的方法Item
可以得到每一个实例对象
IDispatchPtr spDisp;
_variant_t va(i, VT_I4);
spDisp = m_spSHWinds->Item(va);
然后我们可以判断实例对象是不是
属于IE浏览器对象,通过下面的语句实现:
SHDocVw::IWebBrowser2Ptr spBrowser(spDisp);
assert(spBrowser != NULL)
----在得到了IE浏览器对象以后,我们可以调用IWebBrowser2Ptr接口的方法来得到当前的文档对象的指针: MSHTML::IHTMLDocument2Ptr spDoc(spBrowser->GetDocument());
---- 然后我们就可以通过这个接口对这个文档对象进行操作,比如通过Gettitle得到文档的标题。
---- 我们在浏览网络的时候,一般总会同时开很多IE的实例,如果这些页面都是很好的话,我们可能想保存在硬盘上,这样,我们需要对每一个实例进行保存,而如果我们采用上面的原理,我们可以得到每一个IE的实例及其网页对象的接口,这样就可以通过一个简单的程序来批量的保存当前的所有打开的网页。采用上面介绍的方法实现了对当前IE实例的遍历,但是我们希望得到每一个IE实例所产生的事件,这就需要通过DLL的机制来实现。
---- 3.和IE相绑定的DLL的实现
---- 我们介绍一下如何建立和IE进行绑定的DLL的实现的过程。为了和IE的运行实例进行绑定,我们需要建立一个能够和每一个IE实例进行绑定的DLL。IE的启动过程是这样的,当每一个IE的实例启动的时候,它都会在注册表中去寻找这个的一个CLSID,具体的注册表的键位置为:
HKEY_LOCALL_MACHINE\\SOFTWARE\\Microsoft\\Windows
\\CurrentVersion\\Explorer\\Browser Helper Objects
---- 当在这个键位置下存在CLSIDs的时候,IE会通过使用CoCreateInstance()方法来创建列在该键位置下的每一个对象的实例。注意对象的CLSIDs必须用子键而非名字值的形式表现,比如{DD41D66E-CE4F-11D2-8DA9-00A0249EABF4} 就是一个有效的子键。我们使用DLL的形式而非EXE的形式的原因是因为DLL和IE实例运行在同一个进程空间里面。每一个这种形式的DLL必须实现接口IObjectWithSite,其中方法SetSite必须被实现。通过这个方法,我们自己的DLL就可以得到一个指向IE COM对象的IUnknown的指针,实际上通过这个指针我们就可以通过COM对象中的方法QueryInterface来遍历所有可以得到的接口,这是COM的基本的机制。当然我们需要的只是IWebBrowser2这个接口。
---- 实际上我们建立的是一个COM对象,DLL只不过是COM对象的一种表现形式。我们建立的COM对象需要建立和实现的方法有:
----1. IOleObjectWithSite接口的方法SetSite必须实现。实际上IE实例通过这个方法向我们的COM对象传递一个接口的指针。假设我们有一个接口指针的变量,不妨设为:
----CComQIPtr< IWebBrowser2, &IID_IWebBrowser2 > m_myWebBrowser2;
---- 我们就可以在方法SetSite中把这个传进来的接口指针赋给m_myWebBrowser2。 2. 在我们得到了指向IE COM对象的接口后,我们需要把自己的DLL和IE实例所发生的事件相关连,为了实现这个目的,需要介绍两个接口:
----(1) IConnectionPointContainer。这里使用这个接口的目的是用来根据它得到的IID来建立和DLL的一个特定的连接。比如我们可以进行如下的定义:
CComQIPtr< IConnectionPointContainer,
&IID_IConnectionPointContainer >
spCPContainer(m_myWebBrowser2);
----然后,我们需要把所有IE中发生的事件和我们的DLL进行通讯,可以使用 IConnectPoint。
----(2) IConnectPoint。通过这个接口,客户可以对连接的对象开始或者是终止一个advisory循环。IConnectPoint有两个主要的方法,一个为Advice,另一个为Unadvise。对于我们的应用来说,Advise是用来在每一个IE发生的事件和DLL之间建立一个通道。而Unadvise就是用来终止以前用Advise建立的通知关系。比如我们可以定义IConnectPoint接口如下: CComPtr< IConnectionPoint > spConnectionPoint;
---- 然后,我们要使所有在IE实例中发生的事件和我们的DLL相关,可以使用 如下的方法:
hr = spCPContainer->FindConnectionPoint(
DIID_DWebBrowserEvents2, &spConnectionPoint);
----然后我们通过IConnectPoint接口的方法Advice使每当IE有一个新的事件发生的时候,都能够让我们的DLL知道。可以用如下的语句实现:
hr = spConnectionPoint- >Advise(
(IDispatch*)this, &m_dwIDCode);
----在把IE实例中的事件和我们的DLL之间建立联系以后,我们可以通过IDispatch接口的Invoke()方法来处理所有的IE的事件。
----3. IDispatch接口的Invoke()方法。IDispatch是从IUnknown中继承的一个接口的类型,通过COM接口提供的任何服务都可以通过IDispatch接口来实现。IDispatch::Invoke的工作方式同vtbl幕后的工作方式是类似的,Invoke将实现一组按索引来访问的函数,我们可以对Invoke方法进行动态的定制以提供不同的服务。Invoke方法的表示如下:
STDMETHOD(Invoke)(DISPID dispidMember,REFIID
riid, LCID lcid, WORD wFlags,
DISPPARAMS * pdispparams, VARIANT * pvarResult,
EXCEPINFO * pexcepinfo, UINT * puArgErr);
----其中,DISPID是一个长整数,它标识的是一个函数。对于IDispatch的某一个特定的实现,DISPID都是唯一的。IDispatch的每一个实现都有其自己的IID,这里dispidMemeber实际上是可以认为是和IE实例所发生的每一个事件相关的方法,比如:DISPID_BEFORENAVIGATE2,DISPID_NAVIGATECOMPLETE2等等。 这个方法中另外一个比较重要的参数是DISPPARAMS,它的结构如下:
typedef struct tagDISPPARAMS
{
VARIANTARG* rgvarg;
//VARIANTARG是同VARAIANT相同的,可以在
//OAIDL.IDL中找到。所以实际上rgvarg是一个参数数
//组
DISPID* rgdispidNameArgs; //命名参数的DISPID
unsigned int cArgs; //表示数组中元素的个数
unsigned int CnameArgs; //命名元素的个数
}DISPPARAMS
----要注意的是每一个参数的类型都是VARIANTARG,所以在IE和我们DLL之间可以传递的参数类型的数目是有限的。只有那些能够被放到VARIANTARG结构中的类型才可以通过调度接口进行传递。 比如对于事件DISPID_NAVIGATECOMPLETE2来说:第一个参数表示IE在访问的URL的值,类型是VT_BYREF|VT_VARIANT。注意DISPID_NAVIGATECOMPLETE2等DISPID已经在VC中被定义,我们可以直接进行使用。 如上说述,我们在方法Invoke中可以得到所有IE实例所发生的事件,我们可以把这些数据放到文件中进行事后的分析,也可以放到一个列表框中实时的显示。
---- 4.微软的HTML文档对象模型和应用分析
---- 下面我们来看如何得到网页文档的接口:网页文档的接口为IHTMLDocument2,可以通过调用IE COM对象的get_Document方法来得到网页的接口。使用如下的语句:
hr = m_spWebBrowser2- >get_Document(&spDisp);
CComQIPtr< IHTMLDocument2,
&IID_IHTMLDocument2 > spHTML;
spHTML = spDisp;
---- 这样我们就得到了网页对象的接口,然后我们就可以对网页进行分析,比如通过IHTMLDocument2提供的方法get_URL我们可以得到和该网页相关的URL的地址值,通过get_forms方法可以该网页中所有的Form对象的集合。实际上W3C组织已经制定了一个DOM(Document Objec Model)标准,当然这个标准不仅仅是针对HTML,同时还是针对XML制定的。W3C组织只是定义了网页对象的接口,不同的公司可以采用不同的语言和方法进行具体的实现。按照W3C组织定义的网页对象被认为是动态的,即用户可以动态的对网页对象里面所包含的每一个对象进行操作。这里的对象可以是指一个输入框,也可以是图象和声音等对象。同时按照W3C的正式文档的说明,网页对象是可以动态增加和删除的。事实上,很少有厂商实现了DOM定义的所有功能。微软对网页对象的定义也基本上是按照这个标准实现的。但是当前的接口还不支持动态的增加和删除元素,但是可以对网页中的基本元素进行属性的修改。比如IHTMLElementCollection表示网页中一些基本的元素的集合,IHTMLElement表示网页中的一个基本的元素。而象IHTMLOptionElement接口就表示一个特定的元素Option。基本的元素都有setAttribute和geAttribute方法来动态的设置和得到元素的名称和值。
---- 较为常见的一个应用是我们能够分析网页中是否有需要填写的Forms,如果这个网址的Forms以前已经填写过而且数据我们已经保存下来的话,我们就可以把数据自动放到和该URL下的Forms的相关的位置中去。另外,我们可以总结网页上需要填写的Form的数据项,先对这些数据项进行赋值,以后碰到有相同的数据项的时候就自动把我们赋值的内容填写进去。实际上Form是对象,Form中包含的元素,比如INPUT,OPTION,SELECT等类型的输入元素都是对象。
---- 另外一个可以想到的应用是自动对网页中的文本进行翻译,因为我们可以修改网页中任何对象的属性,所以我们可以把里面不属于本国语言的部分自动翻译成本国语言,当然真正的实现还要靠自然语言理解方面技术的突破,但是IE浏览器的接口和对象的形式使我们能够灵活的控制整个IE,无论是从事件对象还是到网页对象。
---- 5.小结
---- 上面我们分析了如何得到所有IE的实例,同时介绍了和IE实例相捆绑的DLL的详细的实现机制,同时对网页的对象化进行了分析。并且介绍了几个相关的应用和实现的方法及存在的技术问题。IE是一个组件化的以COM为基础的浏览器,它具有强大的功能,同时为应用开发者留下了广阔的空间,当然它也存在体积比较大,速度相对比较慢的缺点。但是它的体系结构代表了微软先进的创新的技术,因此具有强大的生命力。 [/td][/tr][tr][td]-- 作者:admin
-- 发布时间:2007-11-8 21:52:00
-- WebBrowser的用法
WebBrowser是一个简单的浏览器,我们可以通过它完成很多关于网页的操作。
在Delphi中,需要引用ShDocVw和MsHtml,下面会逐渐介绍一些使用办法
创建WebBrowser
WebBrowser := TWebBrowser.Create(nil);
WebBrowser.ParentWindow := Self.Handle;
WebBrowser.Height := 100;
WebBrowser.Width := 100;
WebBrowser.Silent := true; //不会因为网页错误弹出对话框
WebBrowser.OnBeforeNavigate2 := OnNewWindow; //可屏蔽不相关的网页
WebBrowser.OnDocumentComplete := OnComplete; //流程处理的关键
打开某个页面
WerBrowser.Navigate(\'http://www.zixunwang.biz:8080/\');procedure OnNewWindow(...);
begin
cancel := Pos(\'http://www.zixunwang.biz:8080/\', URL) = 0;//屏蔽不相关的网页,在这里会屏蔽http://www.zixunwang.biz:8080/以外的网页
end;
procedure OnComplete(...);
begin
if url = \'http://www.zixunwang.biz:8080/login.aspx\' then LoginProcess();if pos(\'default.aspx\', url) <> 0 then defaultProcess();
end;
有的时候,我们想获取网页中的元素的信息,或是想设置网页中元素的信息,那么如下代码应该是你关心的了
以下的代码是演示设置信息,函数通过遍历网页中所有元素,将符合元素设置合适的值
procedure login;
var
Doc: IHtmlDocument2;
i: Integer;
Input: IHTMLInputElement;
Item: IDispatch;
Data: String;
begin
Doc := WebBrowser.Document as IHtmlDocument2;
Data := Doc.body.innerHTML;
for i := 0 to Doc.all.length - 1 do
begin
Item := Doc.all.item(i, 0);
if SUCCEEDED(Item.QueryInterface(IHTMLInputElement, Input)) then
begin
if Input.name = \'FSI-UserName\' then Input.value := \'用户名\';
if Input.name = \'FSI-Password\' then Input.value := \'密码\';
end;
end;
end;
问题1:\'FSI-UserName\'和\'FSI-Password\'是怎么得到的?
解答1:你可以通过查看网页原文件获得这些ID,这些ID都是唯一的
问题2:IHTMLInputElement是什么东西?
解答2:IHTMLInputElement是一个系统定义的接口,在网页中的所有元素,如button,textarea,select,frame,image等都定义有接口,选择合适的接口类型对网页元素进行转换,就可以得到该元素详细的值,具体请参看MSHTML中的定义或给我留言
有的网页内有frame或iframe,我们如何取到其中的信息?
//取得frame个数
function GetFrameCount: Integer;
var
Doc: IHTMLDocument2;
begin
Doc := WerBrowser.Document as IHTMLDocument2;
Result := Doc.Frame.Length;
end;
//取得第n个frame内容
function GetFrameDoc(n: Integer);
var
Doc: IHTMLDocument2;
OleContainer: IOleContainer;
enum: IEnumUnknown;
unk: IUnknown;
Fetched: PLongint;
begin
Doc := WebBrowser.Document as IHTMLDocument2;
Fetched := nil;
OleContainer:= Doc as IOleContainer;
OleContainer.EnumObjects(OLECONTF_EMBEDDINGS, enum);
Enum.Skip(n);
Enum.Next(OLECONTF_EMBEDDINGS, Unk, Fetched);
IE := Unk as IWebbrowser2;
return IE.Document as IHTMLDocument2;
end;
你说的都是在自己创建的WebBrowser中进行操作,能否在已经打开的浏览器中进行呢?
这个问题其实分2步,第1是找到你关注的浏览器窗口,第2是把这个窗口转换为IWebBrowser2
function getIEFromBrowser(URL: String): IWebBrowser2;
var
ShellWindow: IShellWindows;
nCount, i: integer;
vi: OleVariant;
spDisp: IDispatch;
begin
ShellWindow := CoShellWindows.Create;
nCount := ShellWindow.Count;
for i := 0 to nCount - 1 do
begin
vi := i;
spDisp := ShellWindow.Item(vi);
if (spDisp = nil) then Continue;
spDisp.QueryInterface(iWebBrowser2, Result);
if (Result = nil) then Continue;
if Pos(UpperCase(URL), UpperCase(Result.Get_LocationURL)) = 0 then Continue;
exit;
end;
result := nil;
end;
[/td][/tr][tr][td]-- 作者:admin
-- 发布时间:2007-11-9 4:11:00
-- 111
222 [/td][/tr][tr][td]-- 作者:admin
-- 发布时间:2007-11-9 4:36:00
-- topic
Body......... [/td][/tr][tr][td]-- 作者:guest
-- 发布时间:2007-11-9 9:36:00
-- topic
Body......... [/td][/tr][tr][td]-- 作者:admin
-- 发布时间:2007-11-9 11:01:00
-- 获取其它进程中的Web Browser中的HTML源码
void test()
{
HWND H1,H2,H3,H4,hw;
H1=H2=H3=H4=hw=NULL;
H1=::FindWindow("IEFrame","温馨小筑-网页技术-帖子列表 - Microsoft Internet Explorer");
if (H1) H2=::FindWindowEx(H1,NULL,"Shell DocObject View",NULL);
if (H2) H3=::FindWindowEx(H2,NULL,"Internet Explorer_Server",NULL);
if (H3) hw=H3;
else
{
AfxMessageBox("No Found the Dialog with IE!");
return;
}
IHTMLDocument2 *pHTMLDocument=NULL;
DWORD lRes;
HRESULT hres;
UINT MSG = RegisterWindowMessage("WM_HTML_GETOBJECT");
SendMessageTimeout(hw, MSG, 0, 0, SMTO_ABORTIFHUNG, 1000, &lRes);
hres=ObjectFromLresult(lRes,__uuidof(IHTMLDocument2),0,(void**)&pHTMLDocument);
if(hres==S_OK)
{
IPersistStreamInit *pPSI=NULL;
IStream *pStream=NULL;
HGLOBAL hHTMLText;
if (FAILED(pHTMLDocument->QueryInterface(&pPSI))) return;
hHTMLText = GlobalAlloc(GMEM_FIXED, 65534);
CreateStreamOnHGlobal(hHTMLText, TRUE, &pStream);
pPSI->Save(pStream, FALSE);
char *pText = (char*)hHTMLText;
FILE *f=fopen("data_from_explorer.txt","w");
fwrite(pText,1,strlen(pText),f);
fclose(f);
AfxMessageBox("the source codes Saved in data_from_explorer.txt");
pStream->Release();
pPSI->Release();
}
}