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

вторник, 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 слов :)

четверг, 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 останется то, что нам надо. Как вы видите, содержимое страницы загружается в файл, потом читается из файла, а сам файл, как ненужный рудимент, удаляется.

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

Вот это да!

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

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

Автоматизация добавления RSS блога в агрегаторы. Часть II

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

1. Зная, что придется работать с однотипными структурами, объявляем класс:
  TRSSAgregator = class
url,
field_url,
field_rss_url,
field_rss_name,
field_dsc,
field_keys,
field_email,
key_delimiter : string;
end;

Про значения полей я упоминала в прошлом посте. При загрузке программы считываем данные из ini-файла. В ini-файле данные разбиты на секции, каждая секция соответствует одному ресурсу. Вот процедура чтения секций с однотипными данными:
procedure TfrmMain.actINILoadExecute(Sender: TObject);
var
IniFile : TIniFile;
Sections : TStringList;
SecParams: TStringList;
RSSAgr : TRSSAgregator;
i : integer;
sVal : string;
begin
IniFile := TIniFile.Create(ExtractFilePath(Application.ExeName)+'/'+IniFileName);
try
Sections := TStringList.Create();
try
// читаем имена всех секций
IniFile.ReadSections(Sections);

// перебираем имена всех секций, и для секций с именами
// вида SITEnn создаем объекты
SecParams := TStringList.Create();
try
for i:=1 to Sections.Count do
begin
sVal := Sections[i-1];
if (Pos('SITE', sVal) = 1) and
(StrToIntDef(Copy(sVal, 5, Length(sVal)-4), -1)>0) then
begin
// читаем параметры секции
IniFile.ReadSectionValues(sVal, SecParams);

if SecParams.Count > 0 then
begin
RSSAgr := TRSSAgregator.Create;
RSSAgr.url := SecParams.Values['url'];
RSSAgr.field_url := SecParams.Values['field_url'];
RSSAgr.field_rss_url := SecParams.Values['field_rss_url'];
RSSAgr.field_rss_name := SecParams.Values['field_rss_name'];
RSSAgr.field_dsc := SecParams.Values['field_dsc'];
RSSAgr.field_keys := SecParams.Values['field_keys'];
RSSAgr.field_email := SecParams.Values['field_email'];
RSSAgr.key_delimiter := SecParams.Values['key_delimiter'];
RSSAgregators.Add(RSSAgr);
end
end;
end;
finally
SecParams.Free();
end;
finally
Sections.Free();
end;
finally
IniFile.Free();
end;
end;

RSSAgregators — TList, в котором хранятся указатели на объекты типа TRSSAgregator. Он создается при создании формы, при закрытии формы - очищаем память.
После загрузки данных из ini-файла помещаем их в Grid.

2. Вот, в принципе, и все. Осталось только обработать двойной клик по ячейке грида. На него мы повесим экшн:
procedure TfrmMain.actGoToSiteExecute(Sender: TObject);
var
i : integer;
url : string;
tmpURL : TRSSAgregator;
strKeys : string;
begin
url := vRSS.DataController.Values[vRSS.DataController.FocusedRecordIndex,vRSSUrl.Index];

for i := 0 to RSSAgregators.Count-1 do
begin
if TRSSAgregator(RSSAgregators[i]).url = url then
tmpURL := RSSAgregators[i];
end;

PageLoaded := false;
WebBrowser.Navigate(tmpURL.url);
while PageLoaded = false do
Application.ProcessMessages;

try
// заполнение полей
if tmpURL.field_url <> '' then
WebBrowser.OleObject.document.getElementById(tmpURL.field_url).Value := edtURL.Text;
if tmpURL.field_rss_url <> '' then
WebBrowser.OleObject.document.getElementById(tmpURL.field_rss_url).Value := edtRSSURL.Text;
if tmpURL.field_rss_name <> '' then
WebBrowser.OleObject.document.getElementById(tmpURL.field_rss_name).Value := edtTitle.Text;
if tmpURL.field_dsc <> '' then
WebBrowser.OleObject.document.getElementById(tmpURL.field_dsc).Value := edtDSC.Text;
if tmpURL.field_email <> '' then
WebBrowser.OleObject.document.getElementById(tmpURL.field_email).Value := edtEmail.Text;
// ключевики
strKeys := edtKeys.Text;
if (tmpURL.key_delimiter <> '') and (edtKeys.Text <> '') then
begin
strKeys := StringReplace(edtKeys.Text,',',tmpURL.key_delimiter,[rfReplaceAll]);
WebBrowser.OleObject.document.getElementById(tmpURL.field_keys).Value := strKeys;
end;
except
end;
end;

PageLoaded становится true по событию OnDocumentComplete объекта TWebBrowser.

Вот сейчас точно все. :)

____

Update: новая версия RSSAdder-а.
____


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

понедельник, 1 сентября 2008 г.

Автоматизация добавления RSS блога в агрегаторы. Часть I

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

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

url= /*ссылка на страницу, на которой расположена форма для добавления RSS*/
field_url= /*название (id) поля формы, в которое надо вписать ссылку на сайт (если потребуется)*/
field_rss_url= /*название (id) поля формы, в которое надо вписать ссылку на RSS*/
field_rss_name= /*название (id) поля, в которое надо вписать название блога*/
field_dsc= /*название (id) поля, в которое надо вписать описание блога (если потребуется)*/
field_keys= /*название (id) поля, в которое надо вписать ключевики*/
key_delimiter= /*разделитель ключевых слов. В программе все ключевые слова будем разделять запятыми. А если на ресурсе разделитель будет отличаться — заменим его.*/


Названия полей необходимы для обращения к этим полям. Как видите, программа получается универсальная. Самым трудоемким будет обойти ресурсы и записать в конфигурационный файл id полей. Я преднамеренно включила максимальное количество полей для того, чтобы в будущем эту же программу можно было использовать для добавления ссылок в каталоги соц. закладок и т.д.. Принцип программы будет чрезвычайно прост: после двойного щелчка по ссылке на ресурс (в левой части формы) страница откроется в WebBrowser-е и поля в соответствии с конфигурационным файлом заполнятся данными из верхней части формы. Человеку останется только просмотреть на всякий случай, все ли поля заполнены, ввести где надо капчу, выбрать категорию (если понадобится) и нажать кнопку.

Интерфейс получился такой:

автоматическое добавление ссылки
Итак, в принципе, алгоритм описан. Реализовать его на Delphi — достаточно просто. Выкладываю прототип программы. В ее ini-файле пока только 3 ресурса. По аналогии вы можете добавить, сколько угодно (новые разделы в INI-файле должны называться SITEnn, где nn - порядковый номер. Названия полей (инпутов) ищутся в html-коде страниц и вносятся для каждого сайта отдельно). Крайне полезный список ссылок на RSS-агрегаторы можно найти на блоге ЖПО - Жизнь Поисковой Оптимизации. Было бы здорово, если бы кто-нибудь добавил все ссылки и выложил получившийся файл. Может, у меня дойдут руки на этой неделе... Но пока не до этого. Ждите продолжения данной статьи. Там я остановлюсь на некоторых технических моментах написания программы и приведу кое-какие листинги.

Качайте и пробуйте прототип программы. Надеюсь, что пригодится.

Поделиться