Использование функциональности IE или заметки о WebBrowserDelphi , Интернет и Сети , БраузерИспользование функциональности IE или заметки о WebBrowser
Оформил: DeeCo Автор: Игорь Осов'як Содержание:
Введение.
Довольно часто современному программисту приходится решать вопросы, которые связанные с отображением или обработкой информации, представленной в виде html-ресурсов. Например, на некотором сайте приводятся ежедневные котировки акций и Вам нужно собирать и обрабатывать статистку за определенный период времени. Или нужно создать сторожа, который "наблюдает" за он-лайн прайс-листом конкурента и который должен "предупреждать" об изменениях цены на определенные позиции. Или нужно написать "паука", который должен пробегать по некоторому сайту в поисках определенного текста, причем в процессе пробежки ему нужно заполнить несколько регистрационных форм, а фрагменты текста, которые он отыскал - выделить определенным цветом. Можно назвать бесконечно много подобных примеров, но суть их сводится к одному - получение веб-страницы, извлечение из нее определенной части HTML-кода программными средствами (парсинг), и, возможно, программное влияние на эту часть кода. Целью написания этого цикла статей есть демонстрация некоторых приемов использования функциональности ActiveX-контрола WebBrowser в прикладных дельфийских программах. Автор не претендует на какое-то новшество в этом вопросе. Все, что Вы прочитаете далее, уже более-менее подробно описано в многочисленных веб-ресурсах. То небольшое, что отличает этот материал (по мнению автора) от аналогичных - это во-первых, ориентация на Delphi, а во вторых - обобщение личного опыта автора, а не пересказ выдержек с MSDN. Автор рассчитывает на то, что читатель уже имеет некоторый опыт программирования на Delphi и хотя бы в общих чертах знаком с COM-технологиями. Хотя автор "разборку" с WebBrowser и COM делал одновременно. Для тех читателей, которые не знакомы с COM, я постараюсь по ходу дела делать маленькие отступления, которые конечно, не заменят специализированного материала, но, надеюсь, дадут хотя бы направление поиска в случае затруднений. Где в Delphi живет WebBrowser? Для любого зарегистрированного в палитре ActivX-контрола Delphi при импорте создает класс-оболочку, которая наследуется от TOleControl . Для начала не станем углубляться в особенности TOleControl и производных от нее оболочек - ибо сие дело поначалу может не так прояснить, как запутать ситуацию. Отметим только, что оболочка и сам ActiveX есть несколько разные вещи. Собственно TOleControl и производные от него оболочки есть не более, чем средство, которое обеспечивают возможность работы с ActiveX, как с "родными" VCL-компонентами. Для WebBrowser от IE такой оболочкой есть TWebBrowser. Если Вы используете Delphi5, то соответствующий компонент можно отыскать на закладке "Internet " палитры компонентов. Если Вы работаете с Delphi4 , то Вам нужно провести импорт соответствующего ActiveX-контрола. Для этого следует воспользоваться меню "Import ActiveX Control" и в списке ActiveX выбрать "Microsoft Internet Controls" (разумеется, у Вас должен быть установлен IE). Компонент-оболочка по умолчанию устанавливается на закладку "ActiveX" палитры компонентов. Если Вам нужен не только компонент для отображения Web-страниц, а Вы еще собираетесь проводить парсинг загруженных страниц, то Вам также следует провести импорт mshtml.dll. Это можно сделать при помощи меню Import Type Library, выбрав в списке строчку Microsoft HTML Object Library. uses mshtml, shdocvw; придется использовать uses mshtml_tbl, shdocvw_tbl; Если Вы проведете импорт, то Вы наверняка обратите внимание на то, что помимо упоминаемого TWebBrowser рядышком будет TWebBrowser_V1. Что это за зверь? Ответ довольно прост - это совместимый с IE3 контрол. В IE4 он введен для совместимости с теми прикладными программами, которые разрабатывались в расчете на IE3. const True = $00000001; И если Вы делаете импорт в Delphi4 - то никакого переименования не происходит. B как следствие в каком-то безобидном месте наподобие нижеследующего implemantation uses mshtml_tbl; function IsOk: boolean; begin result := true; // ..... end; получаете сообщение компилятора о несовместимости типов. Что делать? result := system.true; или "научить" Delphi4 обходному маневру: перед импортом mshtml.dll добавить в DELPHI\BIN\tlibimp.sym две строчки: True False Начало
Итак, мы уже разобрались, где наш контрол живет. И как его импортировать в случае необходимости. Несколько слов о реализации простого веб-броузера и не только .. procedure TFormSimpleWB.btGoClick(Sender: TObject); var _URL, Flags, TargetFrameName, PostData, Headers: Olevariant; begin _URL := selUrl.Text; Flags := 0; TargetFrameName := 0; Postdata := 0; Headers := 0; WebBrowser1.Navigate2(_URL, Flags, TargetFrameName, PostData, Headers); end; Исходники этого "шедевра" (Delphi5) c теми дополнениями, о которых речь идет ниже, можно взять Ну а теперь несколько более подробно. Для начала посмотрим список методов и свойств TWebBrowser. Здесь следует отличать "обычные" методы и свойства компонентов VCL, и те, которые "отражают" методы соответствующего ActiveX элемента. Первые нас не очень интересуют (если читатель имеет хотя бы небольшой опыт работы с Delphi, то по отношению к чисто VCL-свойствам типа Align , TabOrder ему должно быть все понятно.) Остановимся на второй группе свойств и методов.Их можно разделить на две группы - те, которые "отражают" default-интерфейс (в нашем случае это IWebBrowser2 и те, которые "отражают" нотификационный интерфейс DWebBrowserEvents2 ). Итак, маленькое лирическое отступление в сторону COM COM - это есть во первых, некий набор не нарушаемых ни при каких условиях правил, согласно которым одни программные объекты могут воспользоваться ресурсами других программных обьектов, а также средства операционной системы, которые обеспечивают это взаимодействие. Причем те объекты, которые используют ресурсы (далее клиенты), никогда не получают полного контроля над объектами, которые эти ресурсы отдают (далее - компоненты, или объекты COM)... Мало того, клиенту даже не обязательно иметь представление об общем устройстве объекта COM. Для их взаимодействия важно наличие оговоренного интерфейса взаимодействия и гарантии того, что этот интерфейс никогда не будет изменен. Внимательный читатель задаст вопрос - а как же с разделением адресного пространства, ведь довольно часто клиент и объект COM живут в разных процессах, и следовательно их адресные пространства изолированы друг от друга? Как же тогда клиент и объект COM работают с одной таблицей? Подытоживая, можно сказать,что интерфейс есть спецификация, которая на на уровне бинарного кода "отражается" в таблицу вызовов в памяти. В завершение разговора об COM, я хотел бы упомянуть о некоторых методах базового интерфейса IUnnknown, так как во первых, эти методы присутствуют в любом интерфейсе (вспомним о наследовании деклараций и о том, что любой интерфейс наследуется от IUnnknown) и во вторых, на использовании этих методов строится вся идеология работы с COM. Итак, разрешите представить - QueryInterface. С помощью этого интерфейса клиент может определить, поддерживает ли COM-обьектом какой либо другой интерфейс, который известен клиенту, и получить указатель на тот интерфейс, если он поддерживается объектом. При работе с СOM, это пожалуй самый популярный вызов. В Dеlphi он иногда вызывается явно, иногда неявно. Неявный вызов происходит при применении оператора as для интерфейсных ссылок. Сейчас, пожалуй, самое время время взглянуть на mshtml.pas - как видим он почти на 100% состоит из одних деклараций интерфейсов - ведь нам как клиенту важно знать спецификацию. И совсем не обязательно быть в курсе особенностей реализации. И напоследок два слова о нотификационных интерфейсах. Довольно часто бывает так, что COM-обьект должен сообщать клиенту о некоторых событиях. В таком случае клиент должен реализовать так называемый нотификационный интерфейс, который известен серверу и сообщить серверу о том, что им поддерживается этот интерфейс. Тогда сервер сможет извещать клиента об определенных событиях, делая вызовы методов нотификационного интерфейса. То есть в этом случае COM-сервер и клиент как бы меняются ролями. выше. procedure TForm1.SubmitPostForm; var strPostData: string; Data: Pointer; URL, Flags, TargetFrameName, PostData, Headers: OleVariant; begin { <!-- submit thishtml form:--> <form method="post" action= "http://127.0.0.1/cgi-bin/register.pl"> <input name= "FIRSTNAME" value="Hans"> <input name= "LASTNAME" value="Gulo"> <inputname="NOTE"value="thatsit"> <inputtype="submit"value="thatsit"></form>} strPostData := 'FIRSTNAME=Hans&LASTNAME=Gulo&NOTE=thats+it'; PostData := VarArrayCreate([0, Length(strPostData) - 1], varByte); Data := VarArrayLock(PostData); try Move(strPostData[1], Data^, Length(strPostData)); finally VarArrayUnlock(PostData); end; URL := 'http://127.0.0.1/cgi-bin/register.pl'; Flags := EmptyParam; TargetFrameName := EmptyParam; Headers := EmptyParam; // TWebBrowser will see that we are providing // post data and then should automatically fill // this Headers with appropriate value WebBrowser1.Navigate2(URL, Flags, TargetFrameName, PostData, Headers); end;Важным есть property Busy . Если это свойство активно (равно True), то это свидетельствует о том, что наш АктивИкс еще не закончил загрузки страницы или выполняет некоторую команду. И может быть, что он проигнорирует новую команду. Так что в этом случае лучше подождать, когда это свойство станет равным false (или когда идет загрузка, то остановить ее можно с помощью метода Stop). Теперь несколько слов о событиях, которые сопровождают процесс загрузки. Они, как отмечалось выше, есть своего "продолжение" соответствующих методов DWebBrowserEvents2 . Наиболее существенными из них есть (они возникают для каждого фрейма):
procedure TFormSimpleWB.WebBrowser1NewWindow2(Sender: TObject; var ppDisp: IDispatch; var Cancel: WordBool); var newForm: TFormSimpleWB; begin newForm := TFormSimpleWB.Create(Application); newForm.Show; ppDisp := newForm.WebBrowser1.ControlInterface; end;С остальными методами должно быть более-менее понятно из их названия. Если это не так - можно посмотреть уже упоминаемую статью Александра Лозовюка. Но на остаток я хотел бы немного рассказать еще о двух свойствах, при использование которых можно немного попасть впросак.
Первым делом это TWebBrowser.Document:IDispatch . Через это свойство можно получить доступ к интерфейсу IHtmlDocument2.. Далее через этот интерфейс можно получить доступ к большинству средств по взаимодействию с загруженным документом. То есть это очень интересное и "нужное" свойство. Но немного забегая наперед, скажу, что если Вы попытаетесь использовать TWebBrowser.Document:IDispatch, то Вы рано или поздно заметите довольно странную "утечку" памяти в процессе навигации. В чем же дело? После анализа ситуации, удалось определить, что для любой интерфейсной ссылки на документ, которая получена через этот свойство, счетчик использования "необоснованно" увеличивается на 1 и соответствующий COM-обьект никогда не будет освобожден. При более детальном изучении нашлась и создательница этой проблемы - function TOleControl.GetIDispatchProp(Index: Integer): IDispatch;, через которую и работает TWebBrowser.Document:IDispatch (я речь веду о Delphi5, возможно в Delphi4 все нормально, не проверял). Детальный рассказ об этой ситуации выходит за рамки этой статьи..
Также хочется упомянуть о property LocationURL: WideString; Как утверждается в вышеупомянутой статье Александра Лозовюка , оно содержит URL ресурса, загруженного в браузер. Того же мнения придерживается и контекстная справка от Delphi5. Мало того - об этом также говорится в implementation {$R *.DFM} uses mshtml; procedure TFormSimpleWB.WebBrowser1DocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant); begin // Caption := WebBrowser1.LocationURL + ' || ' + ((pDisp as IWebBrowser).Document as IHtmlDocument2).URL; end; Обратите внимание на включение модуля mshtml, который позволяет использовать функциональность mshtml.dll.
Взглянем на заголовок окна нашей формы - до символов || есть значение, которое возвращает property LocationURL , после - действительный адрес того ресурса, который отображается браузером по окончании загрузки. Простой парсинг
А теперь пожалуй пришло время очень сильно подружится с интерфейсами, ибо вся работа с основными вкусностями WebBrowser возможна только через них. Декларации основных интерфейсов Вы найдете в модулях mshtml и SHDocVw. Перед тем, как организовать взаимодействие с составляющими документа, естественно что нужно этот документ разобрать на составляющие, то есть провести парсинг. Это довольно просто можно сделать при помощи интерфейса IHtmlDocument2, который предоставляет средства по доступу к документу, который загружен в соответствующий броузер. Сам же IHtmlDocument2 можно получить, имея "в руках" интерфейс IWebBrowser2 на броузер, в котором этот документ содержится. Как уже отмечалось, для документа самого верхнего уровня это сделать довольно просто: var doc: IHtmlDocument2; ..... if assigned(WebBrowser1.ControlInterface.Document) then WebBrowser1.ControlInterface.Document.QueryInterface(IHtmlDocument2, doc);Хотел бы обратить Ваше внимание на условие "if" - это связано с тем, что если броузер еще не делал навигации, то свойство Document не будет проинициализировано. Также я надеюсь, Вы помните, почему используется конструкция WebBrowser1.ControlInterface.Document а не WebBrowser1.Document А как же получить доступ к вложенным фреймам? Это можно сделать как минимум двумя способами
type TDoerOneDoc = procedure(iDoc: IHtmlDocument2); procedure DoWithFrames(iDoc: IHtmlDocument2; aDoer: TDoerOneDoc); { процедура aDoer будет вызвана для каждого IHtmlDocument2, начиная с главного и для каждого IHtmlDocument2 с любого уровня вложенности фреймов} var frames: IHTMLFramesCollection2; i: integer; ov1: OleVariant; iDisp: IDispatch; IWindow2: IHTMLWindow2; begin if not assigned(aDoer) then Exit; aDoer(iDoc); frames := iDoc.frames; if not assigned(frames) then exit; if frames.length = 0 then exit; for i := 1 to frames.length do begin ov1 := i - 1; try iDisp := frames.item(ov1); iDisp.QueryInterface(IHTMLWindow2, IWindow2); if assigned(IWindow2) then DoWithFrames(IWindow2.document, aDoer); except { ShowMessage('Find error !!!');} end; end; end;Итак, имея в руках IHtmlDocument2 можно приступить и к парсингу ... Наиболее простой способ для этого - использование метода All интерфейса IHtmlDocument2, который позволяет получить список или всех тегов или только тегов определенного вида. Посмотрим пример для получения списка всех тегов: procedure TFormSimpleWB.WebBrowser1DocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant); var i: integer; iDoc: IHtmlDocument2; iDisp: IDispatch; iElement: IHTMLElement; iInputElement: IHTMLInputElement; S: string; begin Memo1.Clear; iDoc := (pDisp as IWebBrowser).Document as IHtmlDocument2; for i := 1 to iDoc.All.Get_length do begin iDisp := iDoc.Get_all.item(pred(i), 0); iDisp.QueryInterface(IHTMLElement, iElement); Str(pred(i), S); S := S + ''; if assigned(iElement) then begin S := S + 'tag=' + iElement.Get_tagName + ' '; iElement.QueryInterface(IHtmlInputElement, iInputElement); if assigned(iInputElement) then begin S := S + 'name=' + iInputElement.Get_name; end; Memo1.Lines.Add(S); end; end; end;Как Вы догадались, здесь тип каждого тега заносится в компонент TMemo. Также делается попытка определить, есть ли очередной тег элементом ввода (поддерживает ли он соответствующий интерфейс), и если это так, то делается попытка получить значение специфического для элементов ввода свойства. Далее посмотрим пример получения списка тегов определенного типа: procedure TFormSimpleWB.btPutDataClick(Sender: TObject); var iDoc: IHtmlDocument2; i: integer; ov: OleVariant; iDisp: IDispatch; iColl: IHTMLElementCollection; iInputElement: IHTMLInputElement; begin // WebBrowser1.ControlInterface.Document.QueryInterface(IHtmlDocument2, iDoc); if not assigned(iDoc) then begin ShowMessage(' !!!??? Nothing dowloaded ... '); Exit; end; ov := 'INPUT'; IDisp := iDoc.all.tags(ov); if assigned(IDisp) then begin IDisp.QueryInterface(IHTMLElementCollection, iColl); if assigned(iColl) then begin for i := 1 to iColl.Get_length do begin iDisp := iColl.item(pred(i), 0); iDisp.QueryInterface(IHTMLInputElement, iInputElement); if assigned(iInputElement) then begin if iInputElement.Get_name = 'mn' then iInputElement.Set_value('Ihor'); if iInputElement.Get_name = 'pw' then iInputElement.Set_value('PASSWORD'); end; end; end; end; end; В этом примере получаем список тегов типа "INPUT", а потом для некоторых тегов (которые отбираем по имени) делается попытка сделать "ввод данных". Полностью этих два примера (как проект) можно взять Ну, для начала пожалуй и хватит. Если у Вас есть вопросы к автору, их можно задать на И снова об WebBrowser В нашем случае ActiveX нам предоставляет интерфейс IWebBrowser2 и ожидает от нас, что мы предоставим ему реализацию нотификационного интерфейса DWebBrowserEvents2 . К счастью, всю необходимую работу за нас сделал эксперт импорта ActiveX, "наследовав" TWebBrowser от TOleControl, инкапсулировав при этом IWebBrowser2 посредством соответствующих методов и свойств и реализовав обработчики для каждого метода нотификационного интерфейса. Перед тем, как продолжить рассказ, хотелось бы обратить Ваше внимание на такой момент. Как Вы знаете, веб-документ может состоять из одного фрейма (более корректно - не иметь фреймов) или состоять из нескольких фреймов. Каждый фрейм - это тот же WebBrowser, который входит в WebBrowse более высокого уровня. WebBrowser самого верхнего уровня и есть тот AxtivX, который инкапсулируется VCL-компонентом TWebBrowser. Он как бы живет все время, пока живет TWebBrowser, тогда как WebBrowser более нижнего уровня могут динамически создаваться и уничтожатся в зависимости от того, делаем мы навигацию к много-фреймовым или к одно-фреймовым документам. Так вот, к методам WebBrowser верхнего уровня мы можем получить доступ как через методы и свойства соответствующего TWebBrowser, так и через соответствующие интерфейсные ссылки. К методам "вложенных" WebBrowser - только через интерфейсные ссылки. Интерфейсную ссылку на WebBrowser верхнего уровня можно получить через свойство TWebBrowser.ControlInterface или через TWebBrowser.DefaultInterface Получить интерфейсные ссылки на WebBrowser нижнего уровня можно посредством простого парсинга или при помощи некоторых обработчиков событий, которые сопровождают процесс навигации (но об этом ниже). Рассмотрим вкратце сначала основные методы и свойства "от" IWebBrowser2, а затем обработчики "от" DWebBrowserEvents2 . В первую очередь следует упомянуть метод Navigate. Этот метод дает команду WebBrowser начать навигацию к указанному ресурсу. Синтаксис этого метода следующий: procedure Navigate(const URL: WideString; var Flags: OleVariant; var TargetFrameName: OleVariant; var PostData: OleVariant; var Headers: OleVariant); overload; Здесь
Пример вызова этого метода для обычной навигации можно посмотреть в примере, ссылка на который была It seems like you're discussing the Delphi Web Browser component and its interactions with COM (Component Object Model) interfaces. You've provided a comprehensive overview of how to use the `TWebBrowser` control, including navigating to web pages, parsin Комментарии и вопросыПолучайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.
|
||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |