Поиск по блогу

суббота, 25 июля 2009 г.

Парсинг для начинающих. Практическая работа. Часть II

В предыдущей части мы рассмотрели алгоритм обхода раздела сайта для сбора информации. Представленная ниже (вторая и последняя) часть инструкции будет напичкана кодом.

Классы для работы я описала еще в первой части. Небольшие пояснения относительно функций для класса списка компаний (TCompanies).

function IsNewCompany (sLink : string) : boolean;

- проверяет, есть ли уже в списке такая фирма. В качестве параметра — ссылка на информацию о фирме. Листинг:
function TCompanies.IsNewCompany(sLink: string): boolean;
var
i : integer;
tempC : TCompany;
begin
Result := true;
for i := 0 to Count-1 do
begin
tempC := TCompany(Items[i]);
if (tempC.CInfoURL = sLink) then
begin
Result := false;
break;
end;
end;
end;


procedure LoadFromQuery ( Query : TpFIBDataSet );

Листинг:
procedure TCompanies.LoadFromQuery(Query: TpFIBDataSet);
var
tempC : TCompany;
v : Variant;
begin
Query.First;
while (not Query.Eof) do
begin
tempC := TCompany.Create;
v := Query.FieldValues['N'];
tempC.N := Integer(v);
tempC.CName := Query.FieldValues['COMP_NAME'];
tempC.CInfoURL := Query.FieldValues['INFO_URL'];
Add(tempC);
Query.Next;
end;
end;

- для удобства, загрузка из выборки базы. Допустим, парсим всю информацию не за раз, а за несколько (объемы большие, как никак). После первого запуска напарсенные компании складываем в базу. Через день запускаем парсер снова. И, чтобы не парсить информацию о некоторых предприятиях повторно (выдача в список может меняться, простого указания страницы, с которой начинать, - недостаточно), мы загружаем в список уже присутствующие в базе компании. А потом после парсинга перед добавлением проверяем, есть ли компания в списке по InfoURL (с помощью IsNewCompany). После проведения парсинга записываем в базу только новые компании (их можно выделить по отсутствию у них внутреннего идентификатора). Я все записывала в фаербед, но вы с таким же успехом можете все писать в MySQL.

Итак, после загрузки в список уже напарсенных предприятий, переходим к основной части нашего марлезонского балета.

В программе будет две основных процедуры. Первая — обход списка предприятий по страницам и сбор названий предприятий и ссылок на более подробную информацию о них, вторая — сбор информации о каждом предприятии.

Принтскрин примерной формы, которая уменя получилась, я приводила в предыдущей части инструкции. Поясню только, что на форме у меня:

cxSpinEdit1 — с какой страницы начинать (нумерация с нуля)
cxSpinEdit3 — по какую страницу
cxSpinEdit2 — таймаут между запросами, в секундах
mLog - TMemo просто для отладки

procedure TMainF.StartParsingHH;
var
i, j, k : integer;
S,
ReqStr,
StrLink : string;
NeedNextPage : boolean;
v : OleVariant;
Doc : IHTMLDocument2;
DocAll,
DocA : IHTMLElementCollection;
DocElement : IHtmlElement;
idHttp1 : TidHttp;
tmpComp : TCompany;
begin
StatusBar.Panels[0].Text := 'Идет парсинг';
for i := 0 to 10 do
begin
Application.ProcessMessages;
Sleep(10);
end;

// цикл по страницам
i := cxSpinEdit1.Value;
NewVacCnt := 0;
NeedNextPage := true;
vStop := false;
while (NeedNextPage) and (not vStop) do
begin
// загрузка страницы
ReqStr := StringReplace(HHCompanyPageURL,'[PageNum]',IntToStr(i),[rfReplaceAll,rfIgnoreCase]);
sLog('log.txt','[HH] ReqStr = '+ReqStr);
NeedNextPage := true;
try
idHttp1 := TidHttp.Create(nil);
idHttp1.Request.UserAgent := 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.3) Gecko/2008092417 AdCentriaIM/1.7 Firefox/3.0.3';
idHttp1.Request.AcceptLanguage := 'ru';
idHttp1.Request.Referer := 'http://hh.ru';
idHttp1.ConnectTimeout := 5000;
idHttp1.ReadTimeout := 5000;
idHttp1.Response.KeepAlive := true;
idHttp1.HandleRedirects := true;
try
// опущу проверку EIDHttpProtocolException, хотя по правилам надо сделать
S := idHttp1.Get(ReqStr);
except on e : Exception do
begin
sLog('log.txt','[HH] Не удалось скачать страницу '+ReqStr);
sLog('log.txt','Error: '+e.Message);
vStop := true;
end;
end;
S := UTF8ToAnsi(S); // опытным путем мы заметили, что надо поменять кодировку
finally
idHttp1.Free;
end;
// будем работать с DOM
Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := S;
Doc.write(PSafeArray(TVarData(v).VArray));

DocAll := Doc.all;
DocA := DocAll.Tags('A') as IHTMLElementCollection;
for k := 0 to DocA.length-1 do
begin
DocElement := DocA.Item(k, 0) as IHtmlElement;
if Pos('/employer/',LowerCase(DocElement.getAttribute('href',0))) > 0 then
begin
// обрабатываем каждую ссылку, убирая about:blank (я уже писала о такой особенности ссылок в IHTMLDocument2, созданном на основе html-кода)
StrLink := 'http://hh.ru'+StringReplace(LowerCase(DocElement.getAttribute('href',0)),'about:blank','',[rfReplaceAll,rfIgnoreCase]);
mLog.Lines.Add(StrLink);
// обрабатываем таблицу выводов результатов
if Companies.IsNewCompany(StrLink) then
begin
tmpComp := TCompany.Create;
tmpComp.CInfoURL := StrLink;
tmpComp.CName := DocElement.innerText;
ExtractHHCompanyInfo(StrLink, tmpComp);
Companies.Add(tmpComp);

if vStop then break;

for j := 0 to cxSpinEdit2.Value*100 do
begin
Sleep(10);
Application.ProcessMessages;
end;
end;
end;
end;
// останавливаем, если i >= cxSpinEdit3.Value
inc(i);
if (i > cxSpinEdit3.Value) and (chbTO.Checked) then
vStop := true;
end;
end;


Процедура сбора информации следующая. На входе - URL страницы с подробной информацией и объект класса TCompany, куда надо записать полученную информацию. Все уже поняли, надеюсь, что я описываю процесс парсинга так, как я привыкла его производить :) Может, кто-то привык по-другому.

procedure TMainF.ExtractHHCompanyInfo(InfoLink: string; vComp : TCompany);
var
S : string;
Doc : IHTMLDocument2;
v : OleVariant;
DocAll,
DocDIV : IHTMLElementCollection;
DocElement : IHTMLElement;
i : integer;
idHttp1 : TidHttp;
isError : boolean;
begin
// извлечение информации о компании
try
isError := false;
idHttp1 := TidHttp.Create(nil);
idHttp1.Request.UserAgent := 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.0.3) Gecko/2008092417 AdCentriaIM/1.7 Firefox/3.0.3';
idHttp1.Request.Referer := 'http://hh.ru';
idHttp1.Request.AcceptLanguage := 'ru';
idHttp1.Response.KeepAlive := true;
idHttp1.HandleRedirects := true;
try
try
// опущу проверку EIDHttpProtocolException, хотя по правилам надо сделать
S := idHttp1.Get(InfoLink);
except
sLog('log.txt','Не удалось загрузить страницу с адресом'+#13#10+InfoLink);
isError := true;
end;
S := UTF8ToAnsi(S);
finally
idHttp1.Free;
end;
if isError then exit;

Doc := coHTMLDocument.Create as IHTMLDocument2;
if Doc = nil then
begin
ShowMessage('Ошибка создания IHTMLDocument2');
exit;
end;
v := VarArrayCreate([0,0],VarVariant);
v[0] := S;
Doc.write(PSafeArray(TVarData(v).VArray));

DocAll := Doc.all;
DocDIV := DocAll.Tags('DIV') as IHTMLElementCollection;
for i := 0 to DocDIV.length-1 do
begin
DocElement := DocDIV.Item(i, 0) as IHtmlElement;

if (LowerCase(trim(DocElement.className)) = LowerCase('b-employerpage-url')) then
vComp.CURL := DocElement.innerText;

if (LowerCase(trim(DocElement.className)) = LowerCase('g-user-content b-employerpage-desc')) then
begin
vComp.CInfo := DocElement.innerText;
// почистим результат от всяких "левых" символов
vComp.CInfo := StringReplace(vComp.CInfo,'–','-',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'«','"',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'»','"',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,' ',' ',[rfReplaceAll,rfIgnoreCase]);
vComp.CInfo := StringReplace(vComp.CInfo,'•','-',[rfReplaceAll,rfIgnoreCase]);
end;
end;

except
end;

if not vStop then
for i := 0 to cxSpinEdit2.Value-1 do
begin
Application.ProcessMessages;
Sleep(1000);
end;
end;


Небольшие пояснения. Я здесь использовала DOM. Это быстро и удобно. Тем более, что парсить такую информацию регулярками - совсем неудобно. Ссылку на сайт компании можно легко получить, взяв innerText элемента div класса "b-employerpage-url" (он такой один на всю страницу всем нам на благо). А подробную информацию можно получить, взяв innerText элемента с классом "g-user-content b-employerpage-desc". Чистим результат от символов, встречающихся в результате. Например, заменяем '–' на '-', '«' и '»' на '"' и т.д.. Необходимость этого выявляется уже чисто на практике: пробуем парсить страничку, смотрим результаты и делаем выводы.

Вот, в принципе, и все.

Что касается меня — я вернулась из отпуска, порядком отдохнувшей, загорелой и соскучившейся по интернету :) Скоро вольюсь в привычный ритм работы и придумаю что-нибудь еще ;D

Кстааати! Чтобы быть в курсе обновлений блога, можно подписаться на RSS. :)

Статьи схожей тематики:



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

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

К сожалению, у меня нет столько свободного времени, чтобы отвечать на все частные вопросы, так что, может, свой вопрос лучше задать на каком-нибудь форуме?

Поделиться