уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

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

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

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

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

  • Яндекс
  • 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.
Ну, а пока Вы тренируетесь в работе с поисковыми системами я всё-таки попробую сделать компонент, который будет парсить всё, что потребуется в потоках. Тем более, что Серёга уже как можно более подробно постарался донести до нас информацию по разработке многопоточных приложений.

0 0 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
11 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
pilezkiy
pilezkiy
15/07/2010 03:31

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

dkdk
dkdk
06/08/2010 04:48

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

dkdk
dkdk
06/08/2010 13:54

Сорри, не прикрепился( [code] 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&hl=en&newwindow=1&tbs=isch%3A1&sa=1&q='+sxnamepl+'+'+sxalbumpl+'&aq=f&aqi=&aql=&oq=&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('×',e)-2); z:=0; while z0 then begin delete(ssize,pos(' ',ssize),1); dec(z); end; if pos(#$A,ssize)>0 then begin delete(ssize,pos(#$A,ssize),1); dec(z); end; if pos(#$D,ssize)>0 then begin delete(ssize,pos(#$D,ssize),1); dec(z); end; end; go[goj].w:=StrToInt(ssize); ssize:=Copy(e,Pos('×',e)+8, pos('-',e)-pos('×',e)-8); z:=0; while z0 then begin delete(ssize,pos('… Подробнее »

dkdk
dkdk
06/08/2010 14:52

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

dkdk
dkdk
06/08/2010 15:25

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

dkdk
dkdk
06/08/2010 18:03

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

trackback

[…] и в-третьих, в одном из постов рассматривался вопрос парсинга выдачи поисковиков. Там я никак не затрагивал вопрос «Что делать если […]