вторник, 25 января 2011 г.

Фильм...фильм...фильм или как я искал описания фильмов


Добрый день коллеги.

Сегодня мы отвлечемся немного от OTL и поговорим о духовном – поговорим о кино, вернее о сайте kinopoisk.ru и поиске описаний фильмов. К сожалению кинопоиск не предполагает никого API для поиска и получения информации о фильмах и что сразу приходит на ум это парсирование страниц и вытаскивание из них нужной нам информации, что возможно но довольно таки трудоемко.
Однако не все потеряно, и поиск в интернете меня привел на следующую страничку


где один энтузиаст, написав серверную часть по выдаче информации о фильмах, описывает соответствующее API данной серверной части с помощью которой можно более просто получать информацию о фильмах без парсирования страниц кинопоиска.

По мотивам этого родился этот пост.

Итак, поиск фильмов осуществляется по ссылке

http://kinopoiskinfo.appspot.com/?action=list&format=json&title=Название, где вместо "Название" указать свою строку поиска фильма.

Информация приходит в виде строки (JSON-объектов) на примере фильма “Край”:

{"status": 200, "list":
    [
          {"год": "2010", "сериал": false, "название": "Край", "id": 460432, "оригинал": "Kray"},
          {"год": "2010", "сериал": false, "название": "Край", "id": 495724, "оригинал": "Edge"},
          {"год": "2010", "сериал": false, "название": "Край", "id": 497088, "оригинал": "La lisière"},
          {"год": "2008", "сериал": false, "название": "Фар Край", "id": 197147, "оригинал": "Far Cry"},
          {"год": "2006", "сериал": false, "название": "Дикари", "id": 271513, "оригинал": "Dikari"},
         {"год": "2004", "сериал": false, "название": "Птицы 2: Путешествие на край света", "id": 94151, "оригинал": "La marche de l'empereur"}
    ]
}

После чего получив ID фильма можно запросить информацию по фильму


Информация придет также в виде JSON:

"status": 200,
    "film":
          {"rating":
                  {"kinopoisk":
                       {"count": 5083, "value": 6.414},   "imdb": {"count": 614, "value": 6.7} },

         "сериал": false,
         "название": "Край",
         "режиссер": "Алексей Учитель",
         "оператор": "Юрий Клименко",
         "релиз на DVD": "21 октября 2010, «Мистерия Звука», «Монолит», ...",
         "сценарий": "Александр Гоноровский",
         "слоган": "-",
         "продюсер": "Алексей Учитель, Александр Максимов, Константин Эрнст",
         "композитор": "Дэвид Холмс",
         "время": "110 мин.", "год": "2010",
         "жанр": "драма, история, ...",
         "описание": "В августе 1945-го в далекий поселок с названием Край с войны возвращается бывший танкист Игнат, чья страсть — паровозы. Он узнает, что на уединенном острове с довоенных времен брошен немецкий паровоз, и принимается его восстанавливать. Но, оказывается, у паровоза уже есть имя — «Густав», и есть хозяйка — Эльза, дочь немецкого инженера, арестованного в начале войны. Да и для сельчан одержимый мечтой, свободный Игнат — чужак. Страсти закипают шекспировские, причем в них задействованы не только люди, но и несколько паровозов, почти живые существа, у каждого свое имя…",
         "сборы в России": "$5 122 737",
         "страна": "Россия",
         "релиз на Blu-Ray": "21 октября 2010, «Мистерия Звука», «Монолит», ...",
         "бюджет": "$11 000 000", "id": 460432,
         "зрители": "854.3 тыс.",
         "оригинал": ""
         }
}

Однако как мы видим информация придет не полная, не хватает артистов, их я вытаскиваю с самого кинопоиска парся html с помощью регулярных выражений.

Вообщем с теорией все ясно, практическая часть представлена выборочно ниже 

procedure TFrame_VideoBase.aKinopoiskFindInfoExecute(Sender: TObject);
var
  I,I2 : Integer;
  js         : TlkJSONobject;
  Buf        : TStringList;
  NameFilms  : string;
  Res,tmp    : String;

  FilmName   : String;
  Ganr       : String;
  FilmID     : Integer;

  ListFilms  : TlkJSONbase;
  Films      : TlkJSONbase;
  ArrFilmID  : ARRAY of Integer; // массив идентификаторов найденных фильмов

  R : TRegExpr;
  P,P1,P2,P3,PE : Integer;
begin
  FormMain.LoadConfig;
  Try
   Buf := TStringList.Create;
   IdHTTP.HandleRedirects := True;
   // Настраиваем IdHttp
   IdHttp.ProxyParams.ProxyServer    := как нужно вам;
   IdHttp.ProxyParams.ProxyPort      := как нужно вам;
   IdHttp.ProxyParams.BasicAuthentication
                                     := как нужно вам;
   IdHttp.ProxyParams.ProxyUsername  := как нужно вам;
   IdHttp.ProxyParams.ProxyPassword  := как нужно вам;

   // Запрос фильма
   NameFilms := InputBox('Поиск фильма','Фильм:',’’);

   IdHTTP.Request.UserAgent := 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.55 Safari/533.4';

   Res := IdHTTP.Get('http://kinopoiskinfo.appspot.com/?action=list&format=json&title=' + URLEncode(NameFilms));
   //Res := _myFunctions.Strings.ReplaceSub(Res, '"название"', '"namefilm"');
   js := TlkJSON.ParseText(UTF8Encode(Res)) as TlkJSONobject;
   if assigned(js) then
   begin
      if VarToStr(js.Field['status'].value) <> '200' then
      begin
         KdnMessage('Данные не могут быть получены из-за ошибки или отсутствия их на сервере!');
         Exit;
      end;

      frmListFilms.ListBox.Clear;
      SetLength(ArrFilmID,0);

      ListFilms  := js.Field['list'];
      for I := 0 to ListFilms.Count-1  do
      begin
          Films := ListFilms.Child[i];
          if Assigned(Films) then
          begin
              FilmName := '';
              tmp := VarToStr( Films.Field['название'].Value );

              FilmName := tmp;

              tmp := VarToStr( Films.Field['оригинал'].Value );
              if Trim(tmp) <> '' then
                 FilmName := FilmName + ' - ' + tmp;

              tmp := VarToStr( Films.Field['год'].Value );
              if Trim(tmp) <> '' then
                 FilmName := FilmName + ' (' + tmp + ')';

              tmp := VarToStr( Films.Field['сериал'].Value );
              if tmp = 'true' then
                 FilmName := FilmName + '  *** сериал ***';

              FilmID := Films.Field['id'].Value;

              SetLength(ArrFilmID, Length(ArrFilmID) + 1);
              ArrFilmID[i] := FilmID;
              frmListFilms.ListBox.AddItem(Html2Str(FilmName),nil);
          end;
       end;
   end
   else
      Exit;

   if frmListFilms.ListBox.Items.Count = 0 then
   begin
      KdnMessage('По запросу ничего не найдено!');
      Exit;
   end
   else
      frmListFilms.ListBox.ItemIndex := 0;

   // Выбор фильма
   IF frmListFilms.ShowModal = mrOk Then
   begin
      if frmListFilms.ListBox.ItemIndex < 0 then
         P := 0
      else
         P := frmListFilms.ListBox.ItemIndex;

      // Получаем информацию о фильме
      FilmID := ArrFilmID[P];


      Res := IdHTTP.Get('http://kinopoiskinfo.appspot.com/?action=get&format=json&id=' + IntToStr(FilmID));
      if Assigned(js) then js.Free;
      js := TlkJSON.ParseText(UTF8Encode(Res)) as TlkJSONobject;
      if assigned(js) then
      begin
          if VarToStr(js.Field['status'].value) <> '200' then
          begin
             KdnMessage(''Данные не могут быть получены из-за ошибки или отсутствия их на сервере!');
             Exit;
          end;
          Films := js.Field['film'];
      end;

      // К сожалению список артистов вытаскивать нужно вручную
      Res := IdHTTP.Get('http://www.kinopoisk.ru/level/1/film/' + IntToStr(FilmID));
      // Парсим текст на выдергивание артистов
      P1 := Pos('В главных ролях:',Res);
      P2 := Pos('Роли дублировали:',Res);
      P3 := Pos('показать всех',Res);
      if P2 <> 0 then PE := P2 else PE := P3;

      Res := Copy(Res,P1,PE-P1);
      if Trim(Res) <> '' then
      begin
         //поиск
         try
          R := TRegExpr.Create;
          // ГЛОБАЛЬНЫЕ МОДИФИКАТОРЫ
          R.ModifierI := true;    // регистро-независимый режим
          R.ModifierR := false;   // русский
          R.ModifierS := true;    // если установлен, то '.' совпадает с любым символом, (если сброшен, то '.' не совпадает с LineSeparators и LinePairedSeparator
          R.ModifierM := true;    // воспринимать входной текст как многострочный
          R.Expression:= '<a href="/level/\d*/people/\d*/">([А-Яа-я\s]*)</a>';

          tmp:= '';
          if R.Exec(Res) then
          REPEAT
            tmp := tmp + Trim(r.Match[1]) + ','
          UNTIL not r.ExecNext;
          tmp := Copy(tmp,1, Length(tmp)-1);
         finally
          R.Free;
         end;
      end;

       // в  tmp – список артистов

      // Получаем инфу о фильме

      Ganr  := Html2Str(VarToStr(Films.Field['жанр'].Value));
      // по аналогии
     {
               Html2Str(VarToStr(Films.Field['режиссер'].Value));
               Html2Str(VarToStr(Films.Field['описание'].Value));
               Html2Str(VarToStr(Films.Field['оригинал'].Value));
               Html2Str(VarToStr(Films.Field['страна'].Value));
               Html2Str(VarToStr(Films.Field['год'].Value));
      }
   end;
  Finally
   if Assigned(js) then js.Free;
   Buf.Free;
  end;
end;

для работы вам понадобится JSON парсер, который вам нужно взять с


и пару вспомогательных функций:

function HTML2Str(HTML2Str : String) : String;
begin
  {Обратная замена}
  Result  := HTML2Str;
  Result  := ReplaceSub(Result,'&lt;'  ,'<');
  Result  := ReplaceSub(Result,'&gt;'  ,'>');
  Result  := ReplaceSub(Result,'&amp;' ,'&');
  Result  := ReplaceSub(Result,'&quot;','"');
  Result  := ReplaceSub(Result,'&quot;','"');
  Result  := ReplaceSub(Result,'&nbsp;',' ');
  Result  := ReplaceSub(Result,'&laquo;','"');
  Result  := ReplaceSub(Result,'&raquo;' ,'"');
  Result  := ReplaceSub(Result,'&#151;' ,'');
  Result  := ReplaceSub(Result,'&#133;' ,'');
  Result  := ReplaceSub(Result,'<br><br>' ,#10#13);
  Result  := ReplaceSub(Result,'<br /><br />',#10#13);
  Result  := ReplaceSub(Result,'<br />',#10#13);
  Result  := ReplaceSub(Result,'<br>'  ,#10#13);
end;

ReplaceSub- это замена найденной части строки на другую строку, replacesub – нестандартая функция, но ее легко заменить на стандартную функцию замены

Также для корректного формирования GET-запроса возьмем распространненые в инете функции

function DigitToHex(Digit: Integer): Char;
begin
  case Digit of
    0..9: Result := Chr(Digit + Ord('0'));
    10..15: Result := Chr(Digit - 10 + Ord('A'));
  else
    Result := '0';
  end;
end; // DigitToHex

function URLEncode(const S: string): string;
  var
    i, idx, len: Integer;
begin
  len := 0;
  for i := 1 to Length(S) do
    if ((S[i] >= '0') and (S[i] <= '9')) or
    ((S[i] >= 'A') and (S[i] <= 'Z')) or
    ((S[i] >= 'a') and (S[i] <= 'z')) or (S[i] = ' ') or
    (S[i] = '_') or (S[i] = '*') or (S[i] = '-') or (S[i] = '.') then
      len := len + 1
    else
      len := len + 3;
  SetLength(Result, len);
  idx := 1;
  for i := 1 to Length(S) do
    if S[i] = ' ' then
    begin
      Result[idx] := '+';
      idx := idx + 1;
    end
    else if ((S[i] >= '0') and (S[i] <= '9')) or
    ((S[i] >= 'A') and (S[i] <= 'Z')) or
    ((S[i] >= 'a') and (S[i] <= 'z')) or
    (S[i] = '_') or (S[i] = '*') or (S[i] = '-') or (S[i] = '.') then
    begin
      Result[idx] := S[i];
      idx := idx + 1;
    end
    else
    begin
      Result[idx] := '%';
      Result[idx + 1] := DigitToHex(Ord(S[i]) div 16);
      Result[idx + 2] := DigitToHex(Ord(S[i]) mod 16);
      idx := idx + 3;
    end;
end; // URLEncode

function URLDecode(const S: string): string;
  var
    i, idx, len, n_coded: Integer;
  function WebHexToInt(HexChar: Char): Integer;
  begin
    if HexChar < '0' then
      Result := Ord(HexChar) + 256 - Ord('0')
    else if HexChar <= Chr(Ord('A') - 1) then
      Result := Ord(HexChar) - Ord('0')
    else if HexChar <= Chr(Ord('a') - 1) then
      Result := Ord(HexChar) - Ord('A') + 10
    else
      Result := Ord(HexChar) - Ord('a') + 10;
  end;
begin
  len := 0;
  n_coded := 0;
  for i := 1 to Length(S) do
    if n_coded >= 1 then
    begin
      n_coded := n_coded + 1;
      if n_coded >= 3 then
        n_coded := 0;
    end
    else
    begin
      len := len + 1;
      if S[i] = '%' then
        n_coded := 1;
    end;
  SetLength(Result, len);
  idx := 0;
  n_coded := 0;
  for i := 1 to Length(S) do
    if n_coded >= 1 then
    begin
      n_coded := n_coded + 1;
      if n_coded >= 3 then
      begin
        Result[idx] := Chr((WebHexToInt(S[i - 1]) * 16 +
          WebHexToInt(S[i])) mod 256);
        n_coded := 0;
      end;
    end
    else
    begin
      idx := idx + 1;
      if S[i] = '%' then
        n_coded := 1;
      if S[i] = '+' then
        Result[idx] := ' '
      else
        Result[idx] := S[i];
    end;
end; // URLDecode

function HTTPEncode(const AStr: String): String;
const
  NoConversion = ['A'..'Z','a'..'z','*','@','.','_','-'];
var
  Sp, Rp: PChar;
begin
  SetLength(Result, Length(AStr) * 3);
  Sp := PChar(AStr);
  Rp := PChar(Result);
  while Sp^ <> #0 do
  begin
    if Sp^ in NoConversion then
      Rp^ := Sp^
    else
      if Sp^ = ' ' then
        Rp^ := '+'
      else
      begin
        FormatBuf(Rp^, 3, '%%%.2x', 6, [Ord(Sp^)]);
        Inc(Rp,2);
      end;
    Inc(Rp);
    Inc(Sp);
  end;
  SetLength(Result, Rp - PChar(Result));
end;

Окошко показывающее найденные фильмы списком выглядит просто:



1 комментарий:

  1. К сожалению указанный в посте апп уже не работает =( Автор, не назодили ли вы адекватной замены? Вообще любые адекватные апи для взятия описаний фильмов. Постеров например...

    ОтветитьУдалить