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

суббота, 6 сентября 2008 г.

Написание примитивного парсера ключевых слов. Часть II

Продолжение. Начало смотрите здесь.

Шаг 3 — Структура данных и алгоритм
Итак, мы обходимся без БД, значит, данные будем хранить только во время сеанса. Создадим для описания наших классов отдельный юнит (допустим, uKeyWordClass). В объекте TKeyWord будем хранить само ключевое слово, количество запросов и флаг посещения (если по слову проверялся список ключевиков — true). Список всех кейвордов, TKeyWordList, наследник TList, будет содержать ссылки на объекты TKeyWord. Определим для него несколько функций и процедур.

  TKeyWord  = class
KeyWord : string;
RequestCnt : integer;
Checked : boolean;
end;

TKeyWordList = class(TList)
MaxCount : integer; // ограничение на количество слов в списке
UseMaxCount : boolean; // использовать ограничение
function AddKeyWord(KW : TKeyWord) : boolean;
procedure MakeKeyCheked(KW : string);
function CountNotChecked : integer;
function FindNextNotCheckedKeyWord : string;
constructor Create;
end;

Алгоритм:
1. При загрузке программы из ini-файла считывается регулярное выражение для парсинга. Кстати, что касается регулярного выражения. Его надо отлаживать не просто на содержимом страницы, скопированном через буфер обмена, а на содержимом, сохраненном в процессе загрузки через программу. Так, например, вместо \n в работающем варианте пришлось использовать (?:\x0D\x0A), чтобы правильно идентифицировать переводы строк. Итоговое выражение получилось: \d{1,}">(.*?)</a></td>(?:\x0D\x0A|).*?(\d{1,})</td>.

2. После нажатия на кнопку "Старт" программа начинает парсинг: тупо, без всяких заморочек загружаем первую страницу, выбираем слова, заносим их в список. Отмечаем слово, которое проверили. Ищем следующее непроверенное слово и загружаем его. Выбираем слова, добавляем в список (дубли не добавляем). Если количество слов в списке не превысило желаемое (или допустимое), то продолжаем цикл.


Если программа прошлась по всем ссылкам, но нужного количества слов не набралось, — она все равно останавливается.

У меня обработчик получился таким:

procedure TfrmMain.btnStartClick(Sender: TObject);
var
i, j, cnt : integer;
flagStop : boolean;
tmpKW : TKeyWord;
currWord,
sFileName,
UTF8Str,
LinkStr : String;
SLBody : TStringList;
currBody : string;
RE : TRegExp;
mc : MatchCollection;
sm : SubMatches;
mm : Match;
f : TextFile;
begin
flagStop := false;
cnt := seCNT.Value;
currWord := edtKeys.Text;
StatusBar1.Panels[5].Text := 'В процессе';

while (KeyWordList.Count <= cnt) and (flagStop = false) do
begin
// загрузка страницы
LinkStr := 'http://direct.yandex.ru/stat/wordsstat.pl?text='+currWord;
// загружаем страницу
try
SLBody:=TStringList.Create;
RE := TRegExp.Create(Self);

sFileName:=ExtractFilePath(Application.ExeName) + 'cache.txt';
currBody:='';
if DownloadFile(LinkStr,sFileName) then
SLBody.LoadFromFile(sFileName);
currBody:=SLBody.Text;
UTF8Str := UTF8ToAnsi(currBody);
DeleteFile(sFileName);

// ищутся слова
RE.Pattern := RegExpr;
RE.Global := true;
RE.IgnoreCase := true;
RE.Multiline := true;

mc := RE.Execute(UTF8Str) as MatchCollection;

for j := 0 to mc.Count-1 do
begin
mm := mc[j] as Match;
sm := mm.SubMatches as SubMatches;
tmpKW := TKeyWord.Create;
tmpKW.KeyWord := sm.Item[0];
tmpKW.RequestCnt := sm.Item[1];
if KeyWordList.AddKeyWord(tmpKW) = false then
tmpKW.Free;
end;

finally
begin
SLBody.Free;
RE.Free;
end;
end;

// отметить, что курВорд - посещено
KeyWordList.MakeKeyCheked(currWord);
// найти следующий кейворд
currWord := KeyWordList.FindNextNotCheckedKeyWord;
if currWord = '' then flagStop := true;

if (KeyWordList.CountNotChecked = 0) then flagStop := true;
if (KeyWordList.UseMaxCount=true) and (KeyWordList.Count >= KeyWordList.MaxCount) then flagStop := true;

StatusBar1.Panels[1].Text := IntToStr(KeyWordList.Count);
StatusBar1.Panels[3].Text := IntToStr(StrToInt(StatusBar1.Panels[3].Text) + 1);

actShowWords.Execute;
Sleep(1000*seSec.Value);
end;

actShowWords.Execute;
StatusBar1.Panels[5].Text := 'Парсинг завершен';
end;

Да, обратите внимание: так как страница загружается в UTF-8, то надо осуществлять ее перевод с помощью UTF8ToAnsi.

В прототипе, который выкладываю, поставила ограничение на 500 слов :)

четверг, 4 сентября 2008 г.

Написание примитивного парсера ключевых слов. Часть I. Постановка задачи и анализ кода страницы

Итак, первое задание. Написать парсер ключевых слов со старницы http://direct.yandex.ru/stat/. Я не призываю парсить яндекс в промышленных масштабах, поэтому пока не буду говорить ни о многопоточности, ни об использовании прокси-серверов в обход признания роботом любого, кто делает запросы с нечеловеческой скоростью. Вообще, не очень нравятся люди, которые пытаются навариться на продаже того, чего они напарсили. Причем некоторые заламывают вообще какие-то бешеные цены.

Итак, здесь будет только безвредная теория. (Да-да, парсеры — это не только программы для кражи контента. Если их использовать с умом и в полезных целях — можно осуществить кое-какие красивые идеи (главное, чтобы эти идеи были)).

Шаг 1 — Постановка задачи и разработка интерфейса
Начнем, как всегда, с общего описания задачи. Нам необходима форма, на которой расположено поле для ввода ключевика, кое-какие настройки, грид для отображения результатов и кнопка "Старт". Этого будет достаточно.

Не мудрствуя лукаво, садимся и делаем:
parser ключевых слов
Я привыкла к компонентам DevExpress, поэтому грид у меня — cxGrid. Но вы можете использовать компоненты на свой выбор.

Шаг 2 — Исследование сайта, предназначенного для парсинга. Область поиска. Составление регулярного выражения
Перейдем к следующему шагу. Заходим на сайт, вводим слово, нажимаем кнопку, смотрим, что передается в адресной строке. Потом просматриваем html-код полученной страницы. Видим, что для того, чтобы получить выдачу, достаточно написать в адресной строке
http://direct.yandex.ru/stat/wordsstat.pl?text=СЛОВО
Запоминаем это, идем дальше. Следующая задача — составить регулярное выражение.

Открываем редактор-тестер регулярных выражений. В верхнее поле загружаем исходный код страницы и пробуем составить регулярку. Я не поленилась и составила. При составлении вы обнаружите небольшую особенность кода. Левый столбик "Что искали со словом" выдается с форматированием, отличным от форматирования правого столбика (в первом есть переводы строк после закрытия td, а во втором всё лезет в одну строку). Тем не менее, мы это предусмотрим. (Опыт показал, что регулярные выражения, использующиеся в проектах, лучше хранить в ini-файлах, чтобы при изменении каких-то параметров их можно было легко поменять без перекомпиляции проекта).

Любуемся получившимися результатами. В регулярке в первом сабматче — слово, во втором — количество запросов.

Теперь мы готовы к началу разработки программы. (Продолжение следует.)

среда, 3 сентября 2008 г.

Еще пара способов получения содержимого страницы

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

Компонент TIdHTTP
Идем на вкладку Indy Clients и из широчайшего выбора компонентов останавливаемся на TIdHTTP. Компонент TIdHTTP (Indy клиент HTTP) используется для поддержки сетевого протокола HTTP на стороне клиента (поддерживаются версии 1.0 и 1.1, включая операции GET, POST и HEAD). Кроме того, обеспечивается поддержка аутентификации пользователей и применение прокси-серверов. Можете самостоятельно поэкспериментировать с ним. Я же покажу самое простое. Киньте на новую форму этот компонент, мемо и кнопку. В обработчике клика кнопки напишите:
  try
s := IdHTTP1.Get('http://yandex.ru');
Memo1.Text := s;
except
end;
Всего одна строчка кода — и в переменной s у вас окажется всё, что вернет Get-запрос. Мы получили все, что нужно для дальнейшей обработки. Кстати, если вы еще не знакомы с протоколом HTTP, то бегом знакомиться.

UrlDownloadToFile
В uses добавляем UrlMon. Объявляем функцию:
function DownloadFile(SourceFile, DestFile: string): Boolean;

Реализация:
function DownloadFile(SourceFile, DestFile: string): Boolean;
begin
try
Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0,
nil) = 0;
except
Result := False;
end;
end;

Пример использования:
try
SLBody:=TStringList.Create;
sFileName:=ExtractFilePath(Application.ExeName) + 'cache.txt';
currBody:='';
if DownloadFile(LinkStr,sFileName) then
SLBody.LoadFromFile(sFileName);
currBody:=SLBody.Text;
DeleteFile(sFileName);
finally
SLBody.Free;
end;

В результате в строковой переменной currBody останется то, что нам надо. Как вы видите, содержимое страницы загружается в файл, потом читается из файла, а сам файл, как ненужный рудимент, удаляется.

Остальные возможные способы будем разбирать по мере надобности. Пока что этих хватит даже с избытком.

Поделиться