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

вторник, 9 сентября 2008 г.

Разбираемся с форматом RSS. Парсинг RSS на Delphi

Согласно Википедии:

RSS — семейство XML-форматов, предназначенных для описания лент новостей, анонсов статей, изменений в блогах и т. п. Информация из различных источников, представленная в формате RSS, может быть собрана, обработана и представлена пользователю в удобном для него виде специальными программами-агрегаторами.
То есть, парсинг RSS, по сути, ничем не отличается от парсинга XML.

Текст любого RSS-представления содержит как информацию статического плана (информацию о вашем сайте), так и информацию, которая обновилась, "новости". Каждая запись заключена внутри тега <item>, который содержит элементы TITLE, URL, DESCRIPTION, а также некоторые другие.

Я уже писала о компоненте TXMLDocument, с помощью которого достаточно легко разобрать любой XML-документ. Для парсинга RSS — то, что надо! Сначала обращаемся к ITEM, а потом и ко вложенным элементам. Перед парсингом xml-файл можно загрузить на локальную машину так, как я разбирала в предыдущем примере.

Часть кода (непосредственный парсинг XML):
XMLDoc.FileName := LocalFileName;
XMLDoc.Active:=True;
StartItemNode:=XMLDoc.DocumentElement.ChildNodes.First.ChildNodes.FindNode('item');
ANode := StartItemNode;
repeat
STitle := ANode.ChildNodes['title'].Text;
sLink := ANode.ChildNodes['link'].Text;
sDesc := ANode.ChildNodes['description'].Text;

// делаем что-нибудь с результатами, например, добавляем в листвью
with LV.Items.Add do
begin
Caption := STitle;
SubItems.Add(sLink);
SubItems.Add(sDesc)
end;

ANode := ANode.NextSibling;
until ANode = nil;

воскресенье, 7 сентября 2008 г.

Некоторые советы по анализу исходного кода html-страницы и программированию приложения для парсинга

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

1. Научиться работать с потоками и использовать их. В предыдущем примере я не показала, как их использовать, но если надо будет — могу написать. Хотя, все-таки, это вопрос программирования, а не парсинга.

2. Хранить такую информацию, как регулярные выражения, — исключительно в настройках. Таким образом, приложение получится более гибким.

3. При исследовании кода страницы не верьте своим глазам. Я уже говорила, что регулярное выражение надо тестировать не на тексте, который вы скопировали из окна "Просмотр HTML-кода", а через свою программу сделать промежуточное сохранение в файл и брать из этого файла. Во-первых, вы сразу увидите, что данные проступают в нужной вам кодировке. Во-вторых, вы наиболее точно получите представление о "скользких местах" (таких, как перевод каретки) и спецсимволах. Для визуального анализа советую использовать, например, FAR. Выбираете файл, потом нажимаете сначала F3, а потом F4. Получаете такую картинку:

parsing far code
На ней четко видно, что перевод строки — 2 кода (\x0D\x0A) (а при составлении регулярки по коду страницы, полученному через браузер, прокатывал перевод строки \n).

Отвлекусь от темы. В ближайшие три недели посты будут появляться чуть реже, так как у меня намечается отпуск. Уже во всю начала подготовку к нему. Но блог ни в коем случае не забрасывается. А после отпуска займусь более активным продвижением блога :) Может, организую возможность подписки на исходники примеров, которые здесь разбираю.

Желаю успехов на поприще программирования!

суббота, 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 слов :)

Поделиться