В данном посте учусь решать типичные задачи парсинга, используя библиотеку MSHTML – искать ссылки, заголовки, мета-теги, выделять другие элементы HTML страницы. Для примера буду просто выгружать найденное в Memo. Работаем в FireMonkey.
Подготовительная работа
О том как загрузить страницу я писал в предыдущих постах.
Как загрузить страницу? (В данном посте будем использовать способ №2 так как работаем с MSHTML).
Также, возможно Вам понадобится пост
Как победить глюк всплывающего окна?
В результате загрузки страницы мы получим глобальную интерфейсную переменную idoc:IHTMLDocument2; C ней-то и будем работать в данном посте.
Загрузка страницы
Через интерфейс IHTMLDocument2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// uses activex,mshtml,ComObj ... var idoc:IHTMLDocument2; ... procedure TForm2.Button1Click(Sender: TObject); var V: OleVariant; HTML: String; begin try //Сначала просто загружаем в html как строку idhttp1.HandleRedirects:=true; html:=idhttp1.Get('http://www.vesti.ru/'); //Создаем вариантный массив v:=VarArrayCreate([0,0],VarVariant); v[0]:= HTML; // присваиваем 0 элементу массива строку с html // Создаем интерфейс {Можно загружать через CreateComObject либо через coHTMLDocument.Create} //iDoc:=CreateComObject(Class_HTMLDOcument) as IHTMLDocument2; idoc:=coHTMLDocument.Create as IHTMLDocument2; //пишем в интерфейс idoc.write(PSafeArray(System.TVarData(v).VArray)); {все, теперь страницу можно обрабатывать при помощи MSHTML} finally end; end; |
Как получить список имен всех тегов?
В предыдущем посте, я уже рассматривал как получить список имен всех тегов,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
procedure TForm2.Button2Click(Sender: TObject); var i:integer; s:string; idisp:IDispatch; iElement : IHTMLElement; begin for i := 1 to idoc.all.length do begin idisp:=idoc.all.item(pred(i),0); idisp.QueryInterface(IHTMLElement,iElement);// <<Тот самый <b>QueryInterface</b> str(pred(i),s); s:=s+' '; if assigned(ielement) then begin S:=S+' tag '+iElement.tagName+' '; end; memo1.Lines.Add(s); end; end; |
Но, на мой взгляд это достаточно длинно. Можно слегка укоротить. В данном посте, я уже больше разобрался и предлагаю более короткий код.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TForm2.ShowAllTags2Click(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; begin memo1.Lines.Clear; for i := 0 to idoc.all.length-1 do begin element:=idoc.all.item(i,0) as IHTMLElement; memo1.Lines.Add(inttostr(i)+' '+element.tagName); end; end; |
Как получить список ссылок?
Универсальный код
Под универсальностью я понимаю то, что можно получать не только теги ссылок ‘A’, но и любые другие теги ‘div’ ‘p’ и так далее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure TForm2.LinksClick(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; link:string; begin memo1.Lines.Clear; Collection:=idoc.all.tags('A') as IHTMLElementCollection; for i := 0 to Collection.length-1 do begin Element := Collection.Item(i,0) as IHtmlElement; link:=Element.getAttribute('href',0); if (Pos('http:',link)>0) or (Pos('https:',link)>0) then memo1.Lines.Add(link); end; end; |
Здесь мы вытащили атрибут href, можно вытащить таким образом любой атрибут! А можно и просто innerHTML outerHTML innerText outerText и так далее.
Вообще говоря, таким образом можно получить любой атрибут любого тега! Функция getAttribute() достаточно универсальная. Можно, например искать элемент по ID таким образом…
1 2 3 4 |
//Опираясь на код выше... ... if Element.getAttribute('id',0)='ID нашего элемента' then... что-то делаем..; ... |
Ниже я приведу пример как получить элемент по ID, а сейчас пока займемся другими вопросами.
Альтернативный более короткий код. Как получить список ссылок?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TForm2.links2Click(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; begin memo1.Lines.Clear; for i := 0 to idoc.links.length-1 do begin element:=idoc.links.item(i,0) as IHTMLElement; memo1.Lines.Add(element.outerHTML); end; end; |
Для element можно выводить outerHTML innerHTML, а можно обратиться к атрибуту методом element.getAttribute() как в примере выше. Словом дальнейший код уже зависит от задачи. IDE сама все подскажет, что можно вытащить из element, просто нажмите точку и увидите выпадающий список… Ну а дальше – экспериментируйте и подстраивайтесь под задачу.
Как получить список заголовков H1-H5?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure TForm2.LinksClick(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; link:string; begin memo1.Lines.Clear; Collection:=idoc.all.tags('H2') as IHTMLElementCollection; for i := 0 to Collection.length-1 do begin Element := Collection.Item(i,0) as IHtmlElement; link:=Element.innerTEXT; // << <H2>Текст между тегами </H2> if link<>'' then memo1.Lines.Add(link); end; end; |
Как получить содержимое мета-тегов?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure TForm2.MetaClick(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; meta:string; begin memo1.Lines.Clear; Collection:=idoc.all.tags('meta') as IHTMLElementCollection; for i := 0 to Collection.length-1 do begin Element:=Collection.item(i,0) as IHTMLElement; meta:=Element.getAttribute('name',0); if (lowercase(meta)='description') or (lowercase(meta)='keywords') then memo1.Lines.Add(Element.getAttribute('content',0)); end; end; |
Альтернативный код – как получить содержимое мета-тегов?
Допустим мы исследовали некоторую страницу, скажем http://www.vesti.ru/ и в коде страницы увидели что-то вроде…
1 2 3 4 5 6 7 8 |
<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> ...продолжение кода... |
Допустим, нам нужно сначала найти все теги со значением
1 |
http-equiv="X-UA-Compatible" |
И вытащить оттуда содержание атрибута content… то есть, в выше приведенном участке кода нам нужно вытащить значение IE=edge.
В этом случае можно воспользоваться не универсальным способом, а специальным интерфейсом для мета-тегов IHTMLMetaElement. Вообще, с опытом использование специальных интерфейсов сильно упрощает задачу.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
procedure TForm2.Meta2Click(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLMetaElement; // Объявляем специальный интерфейс для мета тегов begin memo1.Lines.Clear; Collection:=idoc.all.tags('meta') as IHTMLElementCollection; // Выделяем только мета-теги for i := 0 to Collection.length-1 do begin Element:=Collection.item(i,0) as IHTMLMetaElement; if Element.httpEquiv='X-UA-Compatible' {Интерфейс обладает специальными методами для работы с атрибутами мета-тегов} then memo1.Lines.Add(Element.content); end; end; |
Как получить подписи рисунков?
Этот материал на основе поста Влада с блога WebDelphi. Я лишь повторяю его здесь, чтобы проверить и закрепить свои знания.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
procedure TForm2.PicsClick(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; meta:string; begin memo1.Lines.Clear; Collection:=idoc.all.tags('img') as IHTMLElementCollection; for i := 0 to Collection.length-1 do begin Element:=Collection.item(i,0) as IHTMLElement; //Проверки атрибутов if length(Element.getAttribute('alt',0))>0 then memo1.Lines.Add('Alt ='+Element.getAttribute('alt',0)); if length(Element.getAttribute('title',0))>0 then memo1.Lines.Add('title ='+Element.getAttribute('title',0)); if length(Element.getAttribute('longdesc',0))>0 then memo1.Lines.Add('Title ='+Element.getAttribute('title',0)); end; end; |
Альтернативный, более короткий код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
procedure TForm2.PICS2Click(Sender: TObject); var i:integer; Element:IHTMLElement; begin memo1.Lines.Clear; for i:=0 to idoc.images.length-1 do begin element:=idoc.images.item(i,0) as IHTMLElement; if length(Element.getAttribute('alt',0))>0 then memo1.Lines.Add('Alt ='+Element.getAttribute('alt',0)); if length(Element.getAttribute('title',0))>0 then memo1.Lines.Add('title ='+Element.getAttribute('title',0)); if length(Element.getAttribute('longdesc',0))>0 then memo1.Lines.Add('Title ='+Element.getAttribute('title',0)); end; end; |
Продолжаем с картинками. Получение OuterHTML для тегов IMG
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TForm2.Pics3Click(Sender: TObject); var i:integer; Element:IHTMLElement; begin memo1.Lines.Clear; for i:=0 to idoc.images.length-1 do begin element:=idoc.images.item(i,0) as IHTMLElement; memo1.Lines.Add(element.outerHTML); end; end; |
Как получить элемент по ID?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
procedure TForm2.iDClick(Sender: TObject); var i:integer; s:string; idisp:IDispatch; iElement : IHTMLElement; iInputElement:IHTMLInputElement; begin memo1.Lines.Clear; for i := 1 to idoc.all.length do begin idisp:=idoc.all.item(pred(i),0); idisp.QueryInterface(IHTMLElement,iElement); if assigned(ielement) then begin if ielement.getAttribute('id',0)='vgtrk_bar' then begin memo1.Lines.Add('Найден элемент с атрибутом id=vgtrk_bar'); memo1.Lines.Add(ielement.outerHTML); // << здесь можем выводить innerHTML outerHTML innerText outerText и др. end; end; end; end; |
Альтернативный, более короткий путь – как получить элемент по ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
procedure TForm2.iD2Click(Sender: TObject); var i:integer; Element:IHTMLElement; begin memo1.Lines.Clear; for i := 0 to idoc.all.length-1 do begin element:=idoc.all.item(i,0) as IHTMLElement; if element.id='vgtrk_bar' then begin memo1.Lines.Add('Найден элемент с атрибутом id=vgtrk_bar'); memo1.Lines.Add(element.outerHTML); end; end; end; |
Как получить Body?
1 2 3 4 5 |
procedure TForm2.BodyClick(Sender: TObject); begin memo1.Lines.Clear; memo1.Lines.Add(idoc.body.outerHTML); end; |
Как получить текст из Body без тегов?
1 2 3 4 5 |
procedure TForm2.BodyInnerTextClick(Sender: TObject); begin memo1.Lines.Clear; memo1.Lines.Add(idoc.body.innerText); end; |
Как получить Title?
1 2 3 4 5 |
procedure TForm2.TitleClick(Sender: TObject); begin memo1.Lines.Clear; memo1.Lines.Add(idoc.title); end; |
…
Эта библиотека просто огромна!!! В ней очень много возможностей, можно по аналогии получать скрипты, анкоры, все, что находится внутри документа HTML можно вытащить с помощью библиотеки MSHTML. Моей целью было разобраться на примерах как это работает, далее я сделаю обобщающие выводы, и на этом тему парсинга HTML буду заканчивать. Останется только разобраться с тем, что такое innerHTML outer HTML innerText outerText, а также с тем, как заменять содержание тегов. Но это в других постах.
Выводы
Видно, что после того как мы загрузили страницу в интерфейсную переменную, у нас получилось как минимум 3 пути дальнейшей работы.
1 Путь простой универсальный – обращаемся к idoc.all
Все HTML элементы хранятся в idoc.all Cледовательно, можно обратиться к любому из них по названию тега, например, то есть так… …на примере парсинга ссылок…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TForm2.link3Click(Sender: TObject); var i:integer; Element:IHTMLElement; begin memo1.Lines.Clear; for i:= 0 to idoc.all.length-1 do begin element:=idoc.all.item(i,0) as IHTMLElement; if element.tagName='A' then memo1.Lines.Add(element.outerHTML); end; end; |
Можно обратиться к элементу по ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
procedure TForm2.iD2Click(Sender: TObject); var i:integer; Element:IHTMLElement; begin memo1.Lines.Clear; for i := 0 to idoc.all.length-1 do begin element:=idoc.all.item(i,0) as IHTMLElement; if element.id='vgtrk_bar' then begin memo1.Lines.Add('Найден элемент с атрибутом id=vgtrk_bar'); memo1.Lines.Add(element.outerHTML); end; end; end; |
Работа с idoc напрямую
Касаясь нашего примера – мы создали интерфейсную переменную idoc. Многие элементы хранятся не в idoc.all, а напрямую в idoc, например
1 2 3 4 5 6 7 8 |
idoc.images; idoc.title; idoc.body; idoc.links; ... |
И так далее…. Чтобы ознакомиться с этим списком, достаточно после получения idoc в редакторе IDE DELPHI, в обработчике кнопки, например, набрать idoc, набрать точку, и дождаться выпадения списка
И далее, работать можно таким образом… …на примере парсинга ссылок
1 2 3 4 5 6 7 8 9 10 11 12 13 |
procedure TForm2.links2Click(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; begin memo1.Lines.Clear; for i := 0 to idoc.links.length-1 do begin element:=idoc.links.item(i,0) as IHTMLElement; memo1.Lines.Add(element.outerHTML); end; end; |
2 путь – создание предварительной коллекции
На примере поиска ссылок
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
procedure TForm2.LinksClick(Sender: TObject); var i:integer; Collection: IHTMLElementCollection; Element:IHTMLElement; link:string; begin memo1.Lines.Clear; Collection:=idoc.all.tags('A') as IHTMLElementCollection; for i := 0 to Collection.length-1 do begin Element := Collection.Item(i,0) as IHtmlElement; link:=Element.getAttribute('href',0); if (Pos('http:',link)>0) or (Pos('https:',link)>0) then memo1.Lines.Add(link); end; end; |
3 Путь – с использованием iDispatch
На примере поиска по ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
procedure TForm2.iDClick(Sender: TObject); var i:integer; s:string; idisp:IDispatch; iElement : IHTMLElement; iInputElement:IHTMLInputElement; begin memo1.Lines.Clear; for i := 1 to idoc.all.length do begin idisp:=idoc.all.item(pred(i),0); idisp.QueryInterface(IHTMLElement,iElement); if assigned(ielement) then begin if ielement.getAttribute('id',0)='vgtrk_bar' then begin memo1.Lines.Add('Найден элемент с атрибутом id=vgtrk_bar'); memo1.Lines.Add(ielement.outerHTML); // << здесь можем выводить innerHTML outerHTML innerText outerText и др. end; end; end; end; |
На этом, думаю, пора данный пост закончить. Удачи в разработке!!!