Подписка

Проекты

Сборник идей для разработок в Delphi и использования их в Интернет. Участвуй в работе коллективного разума!

Google API в Delphi - проект с открытым исходным кодом.


А тут я коплю на лицензию Delphi 2011. Сумма пожертвования не фиксирована.

Друзья блога

Блоги и сообщества

DelphiFeeds.ru - Все Delphi-блоги Рунета О раскрутке блога по программированию Сообщество умных людей VR-Online.RU Бесплатный журнал для программистов и всех, кто интересуется IT Статьи и уроки по Delphi Статьи по Delphi

Счётчики


Анализ веб сайтов

Рейтинг блогов




Система Orphus

  • 19Oct

    В прошлой статье про выдачу Яндекса я привел лишь один вариант и один пример парсинга выдачи с целью определения позиции сайта в результатах поиска Яндекс. На самом деле нет ничего сверхъестественного в написании подобных компонентов под свои нужды.

    парсинг поисковиков

    А если учесть то обстоятельство, что мы не используем для парсинга всякого рода ухищрения наподобие прокси, потоков и т.д., а лишь делаем небольшую паузу между запросами документов, то задача становится и вовсе простой. Всё, что по сути от нас требуется – определить шаблон страницы с выдачей, написать регулярку и чуть-чуть исправить первоначальный алгоритм парсинга, чем мы сегодня и займемся.

    ' '

    В качестве своих “подопытных” я выбрал следующие поисковые системы:

    • Яндекс
    • Google
    • Bing
    • Rambler
    • Mail.ru
    • Yahoo
    • Апорт

    В принципе, для наглядного примера этих ПС будет вполне предостаточно. Если возникнет желание дополнить список, то думаю, после прочтения этой статьи, проблем у Вас не возникнет.

    Первое с чего я обычно начинаю подобного рода работы – забиваю в поисковую форму запрос и пробую найти шаблон URL для перехода по страницам поисковой выдачи. При этом также следует учесть одно очень важное обстоятельство – чем больше ссылок на одной странице вернет нам поисковая система, тем меньше нам потребуется обращаться к ней, а следовательно и сам парсинг пройдет быстрее. Для этого желательно пользоваться расширенным поиском, которые имеет практически любая поисковая система. Итак, приступим. Про Яндекс уже говорили, поэтому сегодня первым на очереди стоит Google. Сразу же заходим на страницу расширенного поиска и забиваем любой запрос, например “Delphi в Internet”

    Google расширенный поиск

    Количество результатов выбираем по максимумы – 100. Жмем “Поиск в Google” и попадаем на страницу с результатами. Получаем URL следующего вида:

    http://www.google.ru/search?as_q=Delphi+%D0%B2+Internet&hl=ru&newwindow=1&num=100&btnG=Поиск+в+Google&as_epq=&as_oq=&as_eq=&lr=&cr=&as_ft=i&as_filetype=&as_qdr=all&as_occt=any&as_dt=i&as_sitesearch=&as_rights=&safe=images

    Длинно, непонятно и вообще не эстетично. Пробуем упростить URL без потери первоначального содержания страницы. У меня получился вот такой:

    http://www.google.ru/search?as_q=Delphi+в+Internet&num=100

    Но это только пол дела – первая страница, а надо определить общий для всех страниц поиска шаблон. Не долго думая, переходим на вторую страницу и опять производим те же манипуляции с URL. В результате Вы должны получить, что-то наподобие:

    http://www.google.ru/search?hl=ru&num=100&q=Delphi+в+Internet&start=100

    Что нам это дало? А то, что мы теперь можем легко сопоставить два URL и вывести общий шаблон для этих и последующих страниц выдачи. Проводим последнюю проверку:

    Если в URL второй страницы поиска поставить в параметр start вместо 100 ноль, то мы должны попасть на первую страницу поиска. Если это произойдет – шаблон найден. Проверяем:

    http://www.google.ru/search?hl=ru&num=100&q=Delphi+в+Internet&start=0

    И попадаем аккурат на первую страничку с теме же результатами выдачи Google. Шаблон найден и выглядит довольно простенько:

    http://www.google.ru/search?hl=ru&num=100&q=[ЗАПРОС]&start=[СТРАНИЦА*100]

    Двигаемся дальше – составляем регулярное выражение для парсинга. Здесь, признаться я несколько потерялся. Дело в том, что Google может выдавать для одного домена несколько результатов в которых один – основной, второй – дополнительный и оба видны на странице выдачи. Выглядит это примерно так:

    Google результаты выдачи

    Общее число URL сайтов на странице, включая дополнительные результаты, как и было задано 100. И как тут определить позицию в выдаче: с дополнительными результатами или без? Так как я далеко не web-мастер и не SEO-шник, то решил немного подстраховаться – написал сразу два регулярных выражения:

    Для всей выдачи регулярка получилась такая:

    *.?


    Если же требуется отсеять повторяющиеся домена, то можно написать регулярное выражение так:

  • *.?


  • Думаю, что смысл поиска шаблона страницы Вам понятен. Регулярные выражения тоже из себя ничего сверхъестественного не представляют, просто в отдельную группу выносится адрес сайта и все. Поэтому, чтобы не расписывать ещё пять раз этот же алгоритм – просто приведу список шаблонов страниц и регулярных выражений для каждого поисковика из списка и приступим уже, как говориться, кодить.

    Bing

    Шаблон страницы поиска:

    http://www.bing.com/search?q=[ЗАПРОС]&filt=all&first=[КОЛИЧЕСТВО_URL_НА_СТРАНИЦЕ+1]&FORM=PERE1

    К сожалению не нашел в этом поисковике где регулируется количество выдаваемых запросов на страницу.

    Регулярное выражение:

    Rambler

    Шаблон страницы поиска:

    http://nova.rambler.ru/srch?query=[ЗАПРОС]&pagelen=50&page=[СТРАНИЦА]

    Выдается 50 результатов на страницу. В отличие от Яндекс и Google у Рамблера нумерация страниц идет не с нуля (как полагается у программистов :)), а с единицы и это обстоятельство надо будет учесть при построении алгоритма.

    Регулярное выражение:

  • class="title n_title_.*?href=(.*?)">
  • Mail.ru

    Шаблон страницы результатов:

    http://go.mail.ru/search?q=[ЗАПРОС]&num=40&sf=[НОМЕР_СТРАНИЦЫ*40]

    Нумерация страницы начинается с нуля.

    Yahoo

    Шаблон страницы результатов:

    http://ru.search.yahoo.com/search?n=100&ei=UTF-8va=[ЗАПРОС]&xargs=0&pstart=1&b=[НОМЕР_СТРАНИЦЫ*100+1]

    Регулярное выражение

    <a class="yschttl spt" href="http:.*?http.*?//(.*?)"

    Апорт

    Шаблон страницы результатов:

    http://sm.aport.ru/scripts/template.dll?r=[ЗАПРОС]&p=[НОМЕР_СТРАНИЦЫ]

    Нумерация, как и полагается у порядочных программистов, начинается с нуля.

    Регулярное выражение:

    .*n.*s.*?

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

    Теперь, имея в руках все необходимые данные для парсинга, можно приступать к программированию на Delphi.

    Я решил, что лучше всего не создавать 7 отдельных компонентов для каждого поисковика, а просто доработать немного уже готовый компонент для парсинга выдачи Яндекс. И так как основные сведения о нем уже достаточно подробно описаны, то здесь я расскажу только то, что касается непосредственной его доработки. Итак, во-первых, теперь каждый поисковик может быть задан в свойствах компонента. Для того, чтобы облегчить немного процесс настройки компонента, каждый поисковик в компоненте описан константой следующего вида (для Google):

    GoogleStr: array [1..2] of string = ('http://www.google.com/search?q=%s&&num=100&&hl=ru&&start=%d',
    '
    

    ')

    Чтобы выбрать необходимую для парсинга выдачи поисковую систему, в компоненте определено свойство типа:

    type
      TIncFormula = (tiYandex, tiGoogle, tiBing, tiYahoo, tiMail, tiRambler, tiAport);

    которая определяет:

    1. Шаблон страницы с выдачей и регулярное выражение

    2. Номер в URL, определяющий номер страницы с выдачей в поисковой системе.

    Номер в URL, определяющий номер страницы, определяется следующим образом:

    function TYaParser.NextInc(var CurrPage: integer): integer;
    begin
      case FIncFormula of
        tiYandex,tiAport: result:=CurrPage;
        tiRambler: result:=CurrPage+1;
        tiGoogle:  result:=CurrPage*100;
        tiBing:    result:=CurrPage*10+1;
        tiYahoo:   result:=CurrPage*100+1;
        tiMail:    result:=CurrPage*40;
      end;
    end;

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

    function TYaParser.FindPos(Key: string): integer; // поиск позиции домена
    var
      Page, i, LinkNum: integer;
      TextList: TStringList;
      isFind: boolean;
      RE: IRegExp2;
      mc: MatchCollection;
      mm: Match;
      sm: SubMatches;
      FindedLink: string;
    begin
      Page := 0;//принимаем, что первая скачиваемая страница имеет порядковый номер 0
      LinkNum := 0;
      while (isFind = false) and (LinkNum < FCountPos) do
        begin
          if FActive then     begin       {скачиваем страницу, сразу определяя все необходимые данные для шаблона}
          if  DownloadPage(StringBuilder(FNextPage, Key, NextInc(Page)), TextList) then
              begin
                 TextList.Text := Utf8ToAnsi(TextList.Text); //перекодируем в ANSI
    // начинаем парсить страницу
                  try
                    try
                       RE := coRegExp.Create as IRegExp2;
                       RE.Pattern := FRegular;
                       RE.Global := true;
                       RE.IgnoreCase := true;
                       RE.Multiline := true;
                       mc := RE.Execute(TextList.Text) as MatchCollection;
                       for i := 0 to mc.Count - 1 do // проходим по порядку все совпадения
                         begin
                           mm := mc[i] as Match;
                           sm := mm.SubMatches as SubMatches;
                           FindedLink := sm.Item[0];// проверка, является ли ссылка искомой (входит ли она подстрокой в найденную)
                           inc(LinkNum);
                          if Pos(FDomain, FindedLink) > 0 then
                            begin
                              isFind := true;
                              Result := LinkNum;
                              break;
                            end;
                end;
              except
                if Assigned(FOnError) then
                  OnError('Ошибка парсинга полученных данных');
              end;
              if mc.Count = 0 then
                LinkNum := FCountPos; // если совпадения не были найдены
            finally
              RE := nil;
              FreeAndNil(TextList);
            end;
            inc(Page);
            Sleep(FTimeShift); // ждем заданное время перед повторным скачиванием
          end
          else
          begin
            Result := -1; // если была ошибка при скачивании страницы, то результат не достигнут
            Exit;
          end;
        end
      end;
    end;

    При этом функция StringBuilder производит простую замену ключевых подстрок %s и %d на поисковый запрос и номер соответственно:

    function TYaParser.StringBuilder(const Template, Key: string; Page: integer)
      : string;
      function StrReplace(const Str, Str1, Str2: string): string;
      var
        P, L: integer;
      begin
        Result := Str;
        L := length(Str1);
        repeat
          P := Pos(Str1, Result); // ищем подстроку
          if P > 0 then
          begin
            Delete(Result, P, L); // удаляем ее
            Insert(Str2, Result, P); // вставляем новую
          end;
        until P = 0;
      end;
    begin
      Result := StrReplace(Template, '%d', IntToStr(Page));
      Result := StrReplace(Result, '%s', Key);
    end;

    Вот так, не прибегая к коренной переделке всего алгоритма парсинга выдачи Яндекс мы смогли разработать компонент для парсинга сразу семи поисковых систем. Кстати сказать, Вы и сейчас можете использовать компонент парсинга Яндекс (старый вариант) для парсинга, например Апорта – просто задайте в компоненте вручную шаблон и регулярное выражение. В шаблоне обязательно должня быть подстроки %s и %d. Кстати, раз уж речь зашла о шаблонах, то могу Вам порекомендовать красивые шаблоны сайтов для joomla
    Ну, а пока Вы тренируетесь в работе с поисковыми системами я всё-таки попробую сделать компонент, который будет парсить всё, что потребуется в потоках. Тем более, что Серёга уже как можно более подробно постарался донести до нас информацию по разработке многопоточных приложений.

    Related posts:

    1. Простейший компонент Delphi 2010 для парсинга выдачи Яндекс.
    2. Сбор статистики поисковых систем. Компонент Delphi 2010.

Автор Vlad в 8:50 am

Метки: , , , , , , , ,

10 Comments

WP_Cloudy
  • pilezkiy пишет:

    Для Google, Yahoo, Bing уже не актуально.
    У них есть очень удобные Search API. Выдавать могут как XML, так и JSON.
    Ключевая фраза для поиска: “{Google|Yahoo|Bing} Search API”

  • Vlad пишет:

    Ну, что тут сказать. К сожалению посты в блоге имеют свой срок давности. Когда писал этот пост, то, по крайней мере Bing только начинал работатать и про API речи там не шло :)

  • dkdk пишет:

    А у меня странная ситуация получается с Гуглом. с Картинками.
    При первом запросе он все отлично ищет и выдает. Потом в программе меняется запрос для поиска, поиск проводится еще раз (разумеется, с обнулением всех доступных параметров и с динамическим созданием и уничтожением idhttp), при этом между поисками есть задержка по времени – и гугл выдает только did not match any documents. Если запустить программу заново, то все найдется (в первый раз). Непонятно…need help!

  • dkdk пишет:

    Сорри, не прикрепился(


    procedure google;
    var s: TStringstream; e,ssize,slink: string; jtmp,z:Byte; idhttp12:TIdHTTP;

    begin
    s:=TStringstream.Create('');
    e:='';
    goj:=0;
    idhttp12:=tidhttp.Create(nil);
    for jtmp:=1 to 21 do begin
    go[jtmp].url:='';
    go[jtmp].w:=0;
    go[jtmp].h:=0;
    end;
    slink:='http://www.google.com/images?um=1&amp;hl=en&amp;newwindow=1&amp;tbs=isch%3A1&amp;sa=1&amp;q='+sxnamepl+'+'+sxalbumpl+'&amp;aq=f&amp;aqi=&amp;aql=&amp;oq=&amp;gs_rfai=';
    repeat
    try begin
    IdHTTP12.Disconnect;
    IdHTTP12.AllowCookies:=false;
    idHTTP12.Response.KeepAlive:=true;
    IdHTTP12.request.useragent:=useragents[Random(8)+1];IdHTTP12.Disconnect;
    IdHTTP12.Get(slink,s);
    end;
    finally
    idhttp12.Free;
    end;
    e:=s.DataString;
    if e'' then Break;
    until False;
    repeat
    if Pos('["/imgres?imgurl\x3d',e)=0 then begin
    break;
    end;
    Delete(e,1,Pos('["/imgres?imgurl\x3d',e)+length('["/imgres?imgurl\x3d')-1);
    inc(goj);
    go[goj].url:=Copy(e,1,Pos('\',e)-1);
    for jtmp:=1 to 18 do begin
    Delete(e,1,pos('"',e));
    end;
    ssize:=Copy(e,1,Pos('&times;',e)-2);
    z:=0;
    while z0 then begin
    delete(ssize,pos(' ',ssize),1);
    dec(z);
    end;
    if pos(#$A,ssize)&gt;0 then begin
    delete(ssize,pos(#$A,ssize),1);
    dec(z);
    end;
    if pos(#$D,ssize)&gt;0 then begin
    delete(ssize,pos(#$D,ssize),1);
    dec(z);
    end;
    end;
    go[goj].w:=StrToInt(ssize);
    ssize:=Copy(e,Pos('&times;',e)+8, pos('-',e)-pos('&times;',e)-8);
    z:=0;
    while z0 then begin
    delete(ssize,pos(' ',ssize),1);
    dec(z);
    end;
    if pos(#$A,ssize)&gt;0 then begin
    delete(ssize,pos(#$A,ssize),1);
    dec(z);
    end;
    if pos(#$D,ssize)&gt;0 then begin
    delete(ssize,pos(#$D,ssize),1);
    dec(z);
    end;
    end;
    go[goj].h:=StrToInt(ssize);
    if (go[goj].h&gt;picparam) or (go[goj].w&gt;picparam) then
    Dec(goj);
    until False;
    s.free;
    end;

    В первый раз все отлично находится, потом на вход подаются другие sxname и sxalbum, и Pos(‘[“/imgres?imgurl\x3d’,e)=0 и гугл выдает страницу с надписью did not match any documents, хотя на деле там все есть. Где мог какой хвост остаться? Или еще как?

    Заранее спасибо,
    _dk_

  • Vlad пишет:

    Вообще я с Indy не дружу :) но что-то я немного не понял вот этот кусок кода:
    repeat
    try
    begin
    idhttp12.Disconnect;
    idhttp12.AllowCookies := false;
    idhttp12.Response.KeepAlive := true;
    idhttp12.request.useragent := useragents[Random(8) + 1];
    idhttp12.Disconnect;
    idhttp12.Get(slink, s);
    end;
    finally
    idhttp12.Free;
    end;
    e := s.DataString;
    if e '' then
    Break;
    until false;

    А точнее, почему дважды происходит Disconnect, когда по сути ещё ничего не взяли с сервера. И как компонент остается в рабочем состоянии после первого прохода в цикле? Он ведь убивается в конце, а в начале цикла не создается. Или я не прав?

  • dkdk пишет:

    с дисконнектами это нормально – их рекомендуют вообще ставить едва ли не перед каждой работой с idhttp, иначе вероятно образование ошибок, я с этим сталкивалась ранее

  • Vlad пишет:

    А по поводу idHTTP12.Free? Он ведь в одном цикле убивается, но не создается по новой.

  • dkdk пишет:

    это поправила, спасибо
    оно только в исключительных случаях срабатывало бы, поэтому основные сложности сохраняются.

  • dkdk пишет:

    смену юзерагента он не любит почему-то.))
    все заработало
    спасибо за помощь)

  • Vlad пишет:

    да, собственно, я и помочть-то не упел :) Так что – не за что.

Ваш ответ

Внимание: Все комментарии модерируются, и это может вызвать задержку их публикации. Отправлять комментарий заново не требуется.

Пожалуйста, заключайте исходный код в тэги [code][/code].