Привет! Этот пост — ответ на вопрос читателя блога. Конкретный ответ на конкретный вопрос. Именно им я хочу положить начало акции "Разберу на вашем примере". Подробнее об акции и ее условиях читайте в конце этой статьи.
Цитата из письма:
Мне нужно с сайта http://stock.rbc.ru/demo/micex.0/intraday/eod.rus.shtml скачать хоть одну ячейку например: цену открытия акций Газпрома, и сохранить это значение в какую-нибудь БД или таблицу (например в таблицу PARADOX7) или в EXCEL (лучше PARADOX7). И все, мне больше ничего не требуется, Ваша программа будет служить мне эталонным примером, а дальше я сам разберусь.
...
Я пытаюсь работать в среде С++Builder6 и пробовал использовать компонент XMLDocument, но кроме того как вытащить его на форму ничего не могу с ним сделать. Буду рад если вы напишите мне на С++Builder, но насколько я знаю, что С++Builder и DELPHI имеют множество сходств то и программе написанной на DELPHI буду благодарен...
Случай сложен тем, что, как мне кажется, если человек не программист, то ему исходный код на другом языке вряд ли поможет. А пример "сохранения в таблицу" не научит работе с базами данных. На скорую руку набросала поиск нужного элемента на странице, постаралась прокомментировать по-максимуму, так что дополнительно сказать нечего.
Итак, код для поиска элемента:
uses
...
UrlMon, MSHTML, activex;
function DownloadFile(SourceFile, DestFile: string): Boolean;
begin
try
Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0, nil) = 0;
except
Result := False;
end;
end;
procedure TMainF.ButtonClick(Sender: TObject);
var
StrURL,
sFileName : string;
SLBody : TStringList;
Doc : IHTMLDocument2;
v : OleVariant;
DocAll,
DocTR,
DocTD : IHTMLElementCollection;
TRElement,
TDElement : IHtmlElement;
i : integer;
begin
// сначала загружаем страницу. это можно сделать разными способами
// я выбрала загрузку страницы в файл, так как у нас могут быть
// разные версии компонентов Indy, они могут конфликтовать
StrURL := 'http://stock.rbc.ru/demo/micex.0/intraday/eod.rus.shtml';
SLBody:=TStringList.Create;
sFileName := ExtractFilePath(Application.ExeName) + 'cache.txt';
if DownloadFile(StrURL,sFileName) then
SLBody.LoadFromFile(sFileName);
DeleteFile(sFileName);
// загружаем все в "WebBrowser". Вернее, создаем объект IHTMLDocument2
// Объясню, зачем. Дело в том, что структура конкретной
// интернет-страницы достаточно сложная. Поэтому компонент XMLDocument
// для ее анализа будет не совсем удобным. Как неудобными будут и
// регулярные выражения (ведь наверняка нужно будет работать не только
// с газпромом, а, допустим, пройтись по всей таблице)
Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := SLBody.Text;
Doc.write(PSafeArray(TVarData(v).VArray));
// ищем в DOM-структуре элемент, значение которого нам нужно.
// для этого сначала анализируем страницу, чтобы определить признаки,
// которые нам помогут это сделать. Вот кусок кода:
// <TR>
// <TD><A TARGET="_top" HREF="../daily/GAZP.rus.shtml?show=all" class="n"><img src="http://pics.rbc.ru/img/up_grf.gif" width="19" height="15" border="0"> %u0413%u0430%u0437%u043F%u0440%u043E%u043C</A></TD>
// <TD class=G>158.2</TD>
// то есть надо найти элемент TR, в первом TD которого написано "Газпром",
// и взять значение из второго TD
DocAll := Doc.all;
DocTR := DocAll.Tags('TR') as IHTMLElementCollection;
for i := 0 to DocTR.length-1 do
begin
TRElement := DocTR.Item(i, 0) as IHtmlElement;
// находим все дочерние элементы (TD-элементы)
DocTD := TRElement.children as IHTMLElementCollection;
// прежде чем обращаться к первому и второму TD-элементу в коллекции,
// неплохо было бы проверить, есть ли там действительно 2 элемента (или больше)
if (DocTD.length > 1) then
begin
TDElement := DocTD.Item(0, 0) as IHtmlElement;
// если в первом столбике написано Газпром, то это то, что мы ищем
if ( AnsiCompareStr( trim(TDElement.innerText), 'Газпром') = 0 ) then
begin
ShowMessage ( (DocTD.Item(1, 0) as IHtmlElement).innerText );
exit;
end;
end;
end;
SLBody.Free;
end;
Результат нажатия на кнопку :) представлен на скрине:
Вот и все. Как это будет на CBuilder-е — не знаю.
___
А сейчас об "акции", о которой я вскользь упомянула в начале поста.
Итак, если вы хотите, чтобы я разобрала работу инструмента/метода/и т.д. конкретно на вашем примере, вы можете написать мне письмо. Постараюсь один-два примера в неделю разбирать. Такие письма приходили и раньше, и в комментариях часто проскакивали вопросы, но — и вы должны меня понять, — большинство из них я пропускала мимо ушей из-за нехватки времени. "Что же изменилось? В чем подвох?" — спросите вы.
Да, подвох есть. Вернее, мои условия.
1. Ограничение под названием "не очень наглеть". :) То есть ваша задача не должна отнять у меня более, чем полтора-два часа времени. Я просто физически не смогу пачками штамповать полноценные парсеры на все запросы. Предпочтения отдаются парсингу НЕ ПОИСКОВЫХ СИСТЕМ.
Если все-таки вы заранее не оценили сложность и прислали объемную задачу, то в этом случае я, скорее всего, максимум разберу основные моменты и напишу часть кода.
2. С вас же — "ссылка навсегда" (не в noindex) на пост, который я опубликую в ответ на ваш вопрос. Да, продаюсь за ссылки. Я в очередной раз решила уделять блогу больше времени, его развитию и продвижению. Буду рада, если таким образом вы мне поможете. Не считаю, что это дорого.
Такие дела. Посмотрим, будут ли желающие :)
Чтобы участвовать в акции, достаточно написать мне письмо с вопросом (задачей), в котором указать язык написания (Delphi или PHP) и адрес блога, на котором планируете разместить ссылку.
___
Чтобы быть в курсе обновлений блога, можно подписаться на RSS.
Ваш блог так хорош, что я уже поставил ссылки где можно :)
ОтветитьУдалитьАлексей, большое спасибо! :)
ОтветитьУдалитьСпасибо большое. для меня открылась новая область в Delphi. На этих данных написал своего ICQ бота который отправляет цитаты взятые с определенного сайта.)))
ОтветитьУдалитьMasha - умница!!!
ОтветитьУдалитьСтатья супер!
100% то что надо...
уже второй день ломаю голову, а вместе с ней TWebBrowse...
Идея моей задумки... это создание
CMS-помощника на Delphi для сайтов
И выдернуть с хостинговой базы нужное значение, было постоянно головной болью...
Ваша статья это лучше чем таблетка анальгина...
Даже в потоке работы Indy9 работает код без запинки...
ОтветитьУдалитьПодскажите пож. ответ на такой вопрос:
ОтветитьУдалитьЕсли я прочитал текст веб.страници с помощью "WebBrowser1.OleObject.Document.Body.InnerText", возможно ли както не присваивать это значение в "MEMO", а сразу пройтись по строкам в поисках нужного значения?
Анонимный, собственно, не совсем поняла, в чем вопрос и как он относится к статье. Конечно, можно не загружать ни в какое MEMO — почитайте листинг кода в статье (там нет никакого мемо) и задавайте более конкретные вопросы.
ОтветитьУдалитьК статье он относится тем что мне нужно тоже самое, но страница должна быть не скачена DownloadFile, а загружена в WebBrowser, в связи с тем что на странице мне ещё надо кликнуть (программно), а после обновления страницы адрес остаётся тот же самый и внутри под кнопкой (по которой надо кликнуть) адрес идёт не "www.сайт\страница 1", а "\страница 1".
ОтветитьУдалитьПробовал переделать этот код, но видно не хватает знаний.
А, ещё, очень не хотелось бы сохранять файл на диск, а поискать в переменной, потому и читаю с помощью "WebBrowser1.OleObject.Document.Body.InnerText" (или innerHTML).
Не вижу никаких сложностей. Как-то вы неконкретно ставите вопросы.
ОтветитьУдалитьМаша,спасибо за материал очень интересно,можно дополнить этот пример и полностью спарсить таблицу в StringGrid что то у меня е выходит.
ОтветитьУдалитьalek-erokhin, мне пока некогда. Может, позже.
ОтветитьУдалитьalek-erokhin, ради примера переделал данный парсер для вытаскивания всей инфы из таблиц в один стрингрид.
ОтветитьУдалитьОсновное отличие что это теперь процедура, и передавать ей надо параметром стринггрид в который выводить. Размер автоматом подгоняется под данные.
procedure ParseWebPageST(Str: TStringGrid);
var
sFileName : string;
Doc : IHTMLDocument2;
v : OleVariant;
DocAll,
DocTR,
DocTD : IHTMLElementCollection;
TRElement,
TDElement : IHtmlElement;
i,j : integer;
begin
//загружаем все в "WebBrowser"
Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := SLBody.Text;
Doc.write(PSafeArray(TVarData(v).VArray));
DocAll := Doc.all;
DocTR := DocAll.Tags('TR') as IHTMLElementCollection;
for i := 0 to DocTR.length-1 do
begin
TRElement := DocTR.Item(i, 0) as IHtmlElement;
// находим все дочерние элементы (TD-элементы)
DocTD := TRElement.children as IHTMLElementCollection;
// прежде чем обращаться к первому и второму TD-элементу в коллекции,
// неплохо было бы проверить, есть ли там действительно 2 элемента (или больше)
if (DocTD.length > 1) then
begin
for j := 0 to DocTD.length - 1 do
begin
TDElement := DocTD.Item(j, 0) as IHtmlElement;
if j>Str.ColCount then
Str.ColCount:=Str.ColCount+1;
if i>Str.RowCount then
Str.RowCount:=Str.RowCount+1;
Str.Cells[j,i]:=UTF8ToString((DocTD.Item(j, 0) as IHtmlElement).innerText);
end;
end;
end;
end;
Сергей, гранд мерси! )))
ОтветитьУдалить