Давненько я ничего не писал про Google API в Delphi, всё-таки Google Analytics API довольно громоздкая штука и в пару недель разобраться с таким API мне пока не представляется возможным…но я продолжаю над этим работать, а пока, решил продемонстрировать Вам ещё один небольшой пример из серии «Google API в Delphi«, а именно — быструю загрузку и обработку RSS-каналов.
В принципе тема получения информации из RSS-каналов довольно избитая.
Введение
Кто, занимаясь парсингом, ни разу не парсил чей-нибудь канал ради тренировки? Наверно только самый ленивый :). При этом алгоритм работы практически всегда один и тот же:
- Отправили GET на URL канала
- Получили XML, сохранили каким-либо образом
- Обработали XML, используя библиотеки, начиная от того, что есть в Delphi и, заканчивая бблиотеками сторонних разработчиков типа NativeXML, SuperObjectXMLParser и т.д.
- Вывели результат.
Кто-то при этом может использовать дополнительно регулярные выражения и т.д., но, тем не менее смысл от этого не меняется — «тянем» из сети XML. А как нам быть в том случае, если, например, не требуется всё текущее содержимое канала, а скажем только последняя запись? Получается, что бОльшая часть того XML-документя, который мы качаем — лишняя информация, которая может «весить» достаточно, чтобы заметить процесс закачки. И тут нам на помощь приходит «AJAX API загрузки фидов Google».
Немного информации по API.
Несмотря на то, что API рассчиан на использование JavaScript, он также предоставляет достаточно возможносей и для разработчиков, пишущих на других языках, например, нам — пищущим на Delphi. В принципе, единственная «сложность» при этом заключается в том, что в результате запроса Google будет нам всегда возвращать результат ответа в формате JSON.
Но этоже обстоятельство нам и на руку. Ведь нам надо разобрать RSS-канал с максимальной скоростью, а данные в формате JSON будут занимать гораздо меньше объем, чем XML. К примеру, текущее содержимое моего RSS-канала сейчас занимает:
- в формате XML — 17,843 kb
- в формате JSON — 9,579 kb
Разница ощутима даже при том, что в мой канал транслируются только части сообщений, а не весь пост целиком.
Теперь, что касается загрузки канала. Базовый URL для загрузки выглядит следующим образом:
http://ajax.googleapis.com/ajax/services/feed/load
При этом в URL можно включать различные параметры запроса, обязательными параметрами при этом будут:
v — версия протокола (единственное действительное значение — 1.0)
q — параметр поиска. В нашем случае — экранированый URL канала.
Как можно увидеть по приведенному выше линку — дополнительно мы можем при загрузке использовать ещё три параметра: num, output и scoring. Output нам сегодня не понадобится, а вот с помощью num и scoring мы будем регулировать объем принимаемых данных — оба этих параметра регулируют количество загружаемых элементов фида.
Пишем приложение Delphi
Так как данные мы будем принимать в формате JSON, то воспользуемся для обработки таких документов замечательной библиотекой SuperObject, о которой я уже упоминал и скачать которую можно здесь.
Теперь создаем новое приложение Delphi и сразу определим константу:
const cBaseURL = 'http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=%s&num=%d&callback=response';
Для загрузки данных мы можем воспользоваться любой библиотекой (Indy, Synapse, ICS и т.д.) или воспользоваться способом, который я кратко описывал здесь, т.е. с использованием интерфейса IXMLHttpRequest из MSXML. Для сокращения исходного кода я и воспользуюсь MSXML.
Подключаем в uses два модуля:
uses msxml, superobject;
Главную форму приложения сделаем такой:
Теперь напишем следующий обработчик кнопки «Go»:
procedure TForm8.Button1Click(Sender: TObject); var request: IXMLHttpRequest; JSON: ISuperObject; iCounterPerSec: TLargeInteger; T1, T2: TLargeInteger; //значение счётчика ДО и ПОСЛЕ операции begin QueryPerformanceFrequency(iCounterPerSec); QueryPerformanceCounter(T1); request := CoXMLHTTP.Create; if not CheckBox1.Checked then request.open('GET', Format(cBaseURL,[Edit1.Text,SpinEdit1.Value]), false, EmptyParam, EmptyParam) else request.open('GET', Format(cBaseURL,[Edit1.Text,-1])+'&scoring=h', false, EmptyParam, EmptyParam); request.send(EmptyParam); JSON := so; JSON.M['response'] := response; JSON[request.responseText]; QueryPerformanceCounter(T2); label9.Caption:=FormatFloat('0.0000', (T2 - T1) / iCounterPerSec) + ' сек.'; end;
Здесь мы засекаем время выполнения операции, скачиваем данные фида, используя IXMLHttpRequest, передаем данные в ISuperObject как метод, разбираем данные и выводим данные фида вместе с общим временем выполнения операции.
Сейчас, у вас Delphi должна ругаться вот на эту строку:
JSON.M['response'] := response;
Это потому, что метод response у нас ещё не определен. Выглядит метод следующим образом:
procedure response(const This, Params: ISuperObject; var Result: ISuperObject); var obj: ISuperObject; itr:integer; Arr:TSuperArray; Keywords: TstringList; begin try Keywords:=TStringList.Create; Keywords.Delimiter:=','; case Params.I['responseStatus'] of 200: begin Arr:=TSuperArray.Create; with Form8 do begin with Params['responseData.feed'] do begin Label4.Caption:=S['title']; Label6.Caption:=S['author']; Label8.Caption:=S['type']; ListBox1.Clear; ListBox1.Items.BeginUpdate; for obj in O['entries'] do begin Keywords.Clear; Arr.Clear(true); Arr:=obj['categories'].AsArray; for itr:= 0 to Arr.Length-1 do Keywords.Add(Arr.S[itr]); ListBox1.Items.Add(obj.Format('%title% - %link%')); ListBox1.Items.Add(' Тэги: '+Keywords.DelimitedText); end; ListBox1.Items.EndUpdate; end; end; end else Form8.ListBox1.Items.Add(Params.S['responseDetails']); end; finally FreeAndNil(Keywords); end; end;
Разберемся, что здесь происходит. Во-первых, после того как мы делаем запрос, Google возвращает нам документ следующего содержания:
{"responseData" : { "feed" : { "feedUrl":"...", "title":"...", "link":"...", "author":"", "description":"...", "type":"...", "entries":[{"title":"...", "link":"...", "author":"...", "publishedDate":"...", "contentSnippet":"...", "content":"...", "categories":["...","...",..]}, {....} ]} } }, "responseDetails" : null | string-on-error, "responseStatus" : 200 | error-code }
Чтобы представить этот объект в виде метода мы ввели в запрос необязательный параметр &callback=response и в результате Google выполнил ответный вызов функции JavaScript, название которой соответствует значению параметра callback и в итоге мы получили документ, содержащий:
response(JSON-объект)
Воспользовавшись этим обстоятельством мы в итоге и смогли сделать так:
JSON.M['response'] := response;
Конечно так делать во всех случаях работы с JSON не обязательно, а иногда и невозможно (не все же сервера(-ы) поддерживают callbak’и), но в качестве примера я воспользовался такой «фишкой» SuperObject. А можно было бы действовать без дополнительного метода response и сразу разобрать объект, что, собственно мы в итоге и сделали.
Разбор документа мы провели следующим образом. Вначале мы проверили, что пришло в параметре responseStatus, если 200, то все ок — приступили к разбору JSON, иначе — вывели причину ошибки из параметра responseDetails.
Разбор JSON-объекта начали по порядку, начиная от данных по RSS-каналу, а именно вывели в label’ы название фида, автора и тип канала. Здесь стоит обратить внимание на то как выглядит чтение данных в SuperObject. Признаться нигде больше пока не встречал, чтобы название свойств объекта состояло из 1 буквы :). Вот так:
with Params['responseData.feed'] do begin Label4.Caption:=S['title']; ....
Мы прочитали название канала, которое в документе имеет путь responseData.feed.title и вывели его в Caption. А так:
Params.I['responseStatus']
Мы прочитали значение в виде целого числа. Соответственно M — представляет JSON в виде метода и т.д.
Отдельно уделим внимание вот этой части метода respose:
for obj in O['entries'] do begin ... Arr:=obj['categories'].AsArray; for itr:= 0 to Arr.Length-1 do Keywords.Add(Arr.S[itr]); ListBox1.Items.Add(obj.Format('%title% - %link%')); ... end;
Здесь мы каждый элемент, находящийся в entries представляем в виде ISuperObject, затем у каждого такого объекта считываем массив categories (тэги поста), и представляем каждый элемент массива в виде строки. Далее, одной строкой:
ListBox1.Items.Add(obj.Format('%title% - %link%'));
Мы выводим в список название поста и ссылку, используя метод ISuperObject.Format.
В результате программа после чтения канала выглядит примерно так:
Пара слов про скорость
В принципе, общее время выполнения всех операций итак будет меньше, чем при использовании данных в формате XML, т.к. см. выше объемы получаемых данных, но для интереса в программе есть небольшой счётчик, засекающий время, которое затрачивается на всё, начиная от загрузки и заканчивая выводом результатов в список. Среднее время, которое показывал этот счётчик при загрузке всех элементов из моего RSS составило порядка 0,8-0,9 сек.
Единственный пока видимый недостаток этого способа работы с RSS,который я вижу — это URL’ы элементов канала, если RSS «завернут» в FeedBurner. Для получения нормального URL’а придётся отправить дополнительно HEAD.
Книжная полка
Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
|
||
Название: О чем не пишут в книгах по Delphi
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
|
А как делать проверку на существования строки, числа, массива, объекта или ещё чего нибудь в коде? А то бывает, что какой-либо параметр не передается и в результате программа рушится с АВ.
Андрей, проверка объекта:
if Assigned(MyObject) then
//объект есть
else
//объекта нет
ну или просто проверка на nil. Это интересовало?
Не совсем, понял, не могли бы ли Вы пояснить?
Я имел ввиду объект, строку, число и так далее в самом JSON при разборе его через SuperObject. Вот например, http://vk.com/developers.php?oid=-1&p=wall.get тут сервер отвечает массивом из записей со стены, причем в каждом элементе массива могут присутствовать дополнительный массив — attachments, а может его и не быть, как может быть или не быть других параметров в любом из ячеек массива с сообщениями. Хотелось бы сделать универсальную процедуру, которая разбирает этот ответ, а красиво не получается, нужно делать кучу проверок в результате код загромождается очень сильно.
Label6.Caption:=S[‘author’];
Label8.Caption:=S[‘type’];
ListBox1.Clear;
ListBox1.Items.BeginUpdate;
for obj in O[‘entries’] do ////////Здесь делфи ругается
begin
Keywords.Clear;
[Error] main.pas(68): Operator not applicable to this operand type
[Error] main.pas(69): Expression expected but ‘BEGIN’ found
[Error] main.pas(102): Undeclared identifier: ‘CoXMLHTTP’
[Error] main.pas(102): Not enough actual parameters
[Fatal Error] demo.dpr(6): Could not compile used unit ‘main.pas’
//////////////////////////////////////////////////////////////////////////////////////////
Извините, но в чём ошибка?