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

пятница, 31 октября 2008 г.

Автоматизация постинга в дневники на блогхостингах с WordPress. Часть II

Часть I здесь.

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

Итак, после проектирования структуры переходим к интерфейсу. Делаем его таким, с каким привыкли работать. Я в левой части разместила список дневников, сгруппированных по блогхостингам (на будущее, если понадобится расширять их набор). При выборе дневника в правой части появляется список сообщений. Его можно фильтровать. Для себя я определила фильтры:

- Сообщения на темы дневника, не опубликованные в этом дневнике
- Сообщения на темы дневника, не опубликованные нигде
- Любые неопубликованные сообщения
- Все сообщения

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

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

poster interface

Свойства каждого дневника можно просмотреть и изменить (пароль, темы, описание).

(На форме у меня несколько закладок, но на остальных пока пусто — но там, по логике, в последствии обязательно что-то появится ;) )

Вот, в принципе, все, что надо сделать на этом этапе. Работы немало. Но она несложная, хоть и кропотливая: разместить компоненты, написать запросы и обработчики. Приступаем :) Как только все сделаем — останется самая малость: непосредственный постинг.

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

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

среда, 29 октября 2008 г.

Автоматизация постинга в дневники на блогхостингах с WordPress. Часть I

Итак, эта тема относится не столько к парсингу, сколько к автоматизации ручного труда :) Но придется кое-что поанализировать, так что в любом случае навыки даром не пропадут.

Итак, цель (промежуточная): написать программу, которая логинится на блогхостинге (в частности, на основе WP) и отправляет сообщение в дневник. Как всегда в таких случаях, в основу хочется вложить хотя бы зачатки универсальности. Берем ручку и бумагу и садимся рисовать структуру БД (поразмыслив, легко прийти к выводу, что простым INI или XML-файлом не обойтись). Кто привык работать с пакетами CASE-средств, может открыть соответствующий программный продукт.

У меня получилось что-то вроде:



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

Итак, база будет состоять из следующих таблиц:
- Таблица блогхостингов
- Таблица дневников
- Таблица тем
- Таблица связей дневников и тем
- Таблица сообщений
- Таблица публикаций сообщений

На каждом блогхостинге может быть зарегистрировано несколько дневников. Каждый дневник может освещать одну или несколько тем из набора. В базе сообщений (где мы будем ее брать — уже другой вопрос) будут находится сообщения, у каждого из которых указана тематика. В таблице публикаций будет храниться история постинга: в какой дневник, какое сообщение, когда.

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

Выбираем СУБД. Я остановила свой выбор на Firebird (бесплатная, знакомая, совместима с установленной InterBase, есть компоненты для работы... кстати, в PHP тоже есть библиотеки работы с этой СУБД). С таким же успехом вы можете выбрать любую СУБД, с которой привыкли работать... вплоть до MySQL.

Создаем БД (не забывая про индексы).

Следующим шагом будет создание интерфейса. Но это уже не сегодня. :)

Часть II здесь.

среда, 22 октября 2008 г.

Парсинг HTML для нахождения ссылок

Привет. Как оказалось, сложно наметить линию для логичного изложения материалов, по которым наилучшим образом можно научиться писать парсеры. Главная причина — это, все-таки, наличие множества потенциальных "точек приложения", "мест применения" парсеров. А изучение проще всего проводить на конкретном примере. В настоящее время у меня нет заданий на парсеры, а брать пример из головы только для того, чтобы написать и выкинуть, — не хочется. Судя по статистике фидбёрнера, этот блог уже читаю не одна я. :) Хотелось бы узнать у читателей, какие их интересуют вопросы, на примере чего лучше еще раз показать процесс создания программы.

А пока в этом посте я затрону следующую тему...

При программировании парсера могут возникать вопросы общего плана (алгоритм, модульность, структура данных) и множество мелких вопросов (исследование исходных данных, составление регулярных выражений). Хочу привести пример хода рассуждений при составлении универсального регулярного выражения для нахождения ссылок. В свое время, когда я училась составлять регулярные выражения, наткнулась на статью PHP: Parsing HTML to find Links (специально нашла еще раз). В ней автор по полочкам описывает процесс составления регулярного выражения. Его рассуждения очень полезные. Вот почему: когда мы парсим один ресурс, на котором все ссылки оформлены по единому стандарту, — мы легко составим регулярку. Но что делать, если ресурсов много? Допустим, на одном теги написаны строчными буквами, а на другом заглавными, да еще и с дополнительными атрибутами?

Если вкратце, то автор статьи выделяет следующие шаги:

1. Составляем самое-самое простое регулярное выражение для нахождения ссылки:
/<a href=\"([^\"]*)\">(.*)<\/a>/iU

Это элементарное регулярное выражение. В нем можно выделить несколько частей:
- ссылка начинается с <a href="
- далее до следующей закрывающей скобки (") идет набор символов, представляющий собой непосредственно URL
- строка ">
- набор символов, представляющий собой текст ссылки
- окончание ссылки </a>

Далее заменяем в нем пробелы на символы пробелов.

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

3. Допускаем, что ссылка может квотироваться, а может и не квотироваться (то есть может быть и не заключена в кавычки).

4. Вносим еще несколько поправок:
- Знак "=" может быть обрамлен пробелами (но они могут и отсутствовать);
- Если нам нужны только ссылки с "http" (а не с ftp и т.д.) - отмечаем это... и т.д.

В общем, очень и очень неплохой пример стандартизации и универсализации :)

вторник, 14 октября 2008 г.

Часто используемые регулярные выражения

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

Регулярные выражения используются не только для парсинга (содержимого тегов, набора данных), но и для проверки валидности введенной информации (имени пользователя, ссылки, емейла, даты). У меня есть небольшая коллекция, собранная в сети. Для примера хочу здесь поместить несколько ссылок, где вы можете увидеть примеры готовых регулярок. На ресурсах они могут повторяться, но это даже к лучшему — полезно видеть разные подходы. Советую прочитать и разобраться во всех примерах — это будет очень полезно. Кроме того, некоторые регулярные выражения сопровождаются готовыми функциями на PHP для их нахождения/проверки. Если есть время, то почитайте и комментарии к записям. В них можно найти интересные и важные дополнения.

Итак, ссылки:
Regular Expression Library
8 Practical PHP Regular Expressions
5 Regular Expressions Every Web Programmer Should Know
10+ Useful JavaScript Regular Expression Functions to improve your web applications efficiency

Может, у вас есть своя коллекция? Добавляйте! :)

среда, 8 октября 2008 г.

Парсинг выдачи поисковиков. Определение позиции сайта. Часть II

В предыдущей части инструкции по созданию парсинга выдачи поисковиков я описала начало процесса создания утилиты. Идем дальше.

Остановлюсь на содержимом INI-файла. В секции MAIN разместим ссылку на сайт, который чаще всего проверяем и ключевые слова через разделитель. В секции SITE01 разместим информацию для парсинга Яндекса. Регулярное выражение для парсинга выдачи Яндекса составила, не особо заморачиваясь. Но оно недолговечно, может измениться, это надо помнить.


[MAIN]

DefSite=http://parsing-and-i.blogspot.com/
DefWords=парсинг|парсер на Delphi

[SITE01]

Name=Yandex.ru
NextPageTmpl=http://yandex.ru/yandsearch?p=[NumPage]&text=[Word]&numdoc=50
LinkRegExp=<a tabindex="\d{1,}".*href="(.*)" target="_blank">(.*)</a>(?:\x0D\x0A|\n|)</div>


Вместо [NumPage] при реализации алгоритма будем подставлять номер страницы в выдаче, вместо [Word] — поисковый запрос.

Алгоритм будет следующий: по очереди проходимся по списку поисковых систем, для каждой системы рассматриваем список ключевиков. Загружаем страницу результатов выдачи по запросу, выделяем все позиции сайтов, проверяем, есть ли сайт в выдаче. Если есть — смотрим место и переходим к следующему запросу. Если нет — переходим на следующую страницу (не заходя за рамки указанного для просмотра количества позиций). И так все операции в цикле. Результат выводим в грид.

Я намеренно не привела код на данном этапе. Алгоритм работает, все парсится. Оттестировав программу на Яндексе, я захотела продолжить вечеринку с Гуглем. Но...

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

[SITE02]

Name=Google.ru
NextPageTmpl=http://www.google.com/search?q=[Word]&num=100&hl=ru&start=[NumPage]


Тут обнаружилось, что в Яндексе надо указать просто номер страницы, начиная с нуля, а в гугле надо указать номер позиции, с которой начинается отсчет (нумерация с нуля, как у всех программистов :) ). Похоже, придется делать заплату. Ввожу KoeffForPages. Внутри алгоритма буду умножать номер страницы на этот коэффициент. Для Яндекса он будет равен единице, а для Гугля — количеству выводимых позиций на странице.

В результате INI-файл содержит:
[MAIN]

DefSite=http://parsing-and-i.blogspot.com/
DefWords=парсинг|парсер на Delphi

[SITE01]

Name=Yandex.ru
NextPageTmpl=http://yandex.ru/yandsearch?p=[NumPage]&text=[Word]&numdoc=50
KoeffForPages=1
LinkRegExp=<a tabindex="\d{1,}".*href="(.*)" target="_blank">(.*)</a>(?:\x0D\x0A|\n|)</div>

[SITE02]

Name=Google.ru
NextPageTmpl=http://www.google.com/search?q=[Word]&num=100&hl=ru&start=[NumPage]
KoeffForPages=100
LinkRegExp=<h3 class=r><a href="(.*?)"


А класс в программе выглядит:
  TSearcher  = class
Name,
NextPageTmpl,
LinkRegExp : string;
KoeffForPages : integer;
end;


И так далее. Процесс программирования — вещь сама по себе очень интересная. Остальные поисковые системы я разбирать не буду.

После реализации алгоритма запускаю прогу:
парсинг позиций в поисковиках

Видали? По ключевику Парсер на Delphi я уже даже не самый лох в Google. А вот Яндекс этот блог в выдачу вообще не взял. Надеюсь, временно.

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

Скачать: исходник парсера (6 кб), скомпиленный парсер (1,12 Мб).

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

Парсинг выдачи поисковиков. Определение позиции сайта. Часть I

Парсинг выдачи поисковиков — дело тонкое. Нехитрое, но тонкое.

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

Итак, цель: создать приложение, которое рассматривает выдачу поисковика по ряду запросов и определяет, на каком месте в выдаче находится наш сайт. При частых запросах поисковик на какое-то время банит по айпишнику, поэтому надо или выставлять достаточную паузу между запросами, или использовать прокси. О прокси будем говорить чуть позже, так как тема эта довольно обширная. Для решения поставленной цели будем использовать временную задержку. Тем более, что пишем утилиту "для внутренних нужд" и пользоваться ей будем не так часто.

При написании программы всегда хочется сделать более-менее универсальную вещь. Однако известно, что каждый поисковик требует индивидуального подхода. Например, Яндекс проще было бы парсить через Яндекс XML, но там количество запросов без платного аккаунта ограничено. Но мы же все-таки просто тренируемся. Попробуем сделать простую программу, желательно с гибкой настройкой. Будем оперировать объектами.

У объекта "Поисковик" на первый взгляд должны быть свойства:
- название;
- шаблон get-запроса (с учетом возможности указания страницы);
- регулярное выражение для нахождения блока "сайт - сниппет" в выдаче поисковика.

Объявляем класс:
  TSearcher  = class
Name,
NextPageTmpl,
LinkRegExp : string;
end;

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

procedure TfMain.actLoadINIExecute(Sender: TObject);
var
IniF : TIniFile;
KW,
sVal,
IniFName : string;
Sections,
SecParams,
SL : TStringList;
i : integer;
tmpSearch : TSearcher;
begin
IniFName := ExtractFilePath(Application.ExeName)+'/'+DefaultIniFileName;
if not FileExists(IniFName) then
begin
ShowMessage('Отсутствует ini-файл.');
exit;
end;

IniF := TIniFile.Create(IniFName);
edtSite.Text := iniF.ReadString('MAIN','DefSite',IniFName);
KW := iniF.ReadString('MAIN','DefWords','');

// вывод слов по умолчанию в Memo
SL := TStringList.Create();
ExtractStrings(['|'],[],PChar(KW),SL);
for i := 0 to SL.Count-1 do
Memo.Lines.Add(SL[i]);
SL.Free;

try
Sections := TStringList.Create();
try
// Читаем имена всех поисковиков
IniF.ReadSections(Sections);

// Перебираем имена секций и для секций с именами
// вида SITE создаем объекты
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
// Читаем параметры секции.
IniF.ReadSectionValues(sVal, SecParams);

if SecParams.Count > 0 then
begin
tmpSearch := TSearcher.Create;
tmpSearch.Name := SecParams.Values['Name'];
tmpSearch.NextPageTmpl := SecParams.Values['NextPageTmpl'];
tmpSearch.LinkRegExp := SecParams.Values['LinkRegExp'];
Searchers.Add(tmpSearch);
end
end;
end;
finally
SecParams.Free();
end;
finally
Sections.Free();
end;
except
end;
IniF.Free;
end;

procedure TfMain.FormCreate(Sender: TObject);
begin
Searchers := TList.Create;
actLoadINI.Execute;
actShowSearchersList.Execute;
end;

procedure TfMain.FormClose(Sender: TObject; var Action: TCloseAction);
var
i : integer;
begin
for i := 0 to Searchers.Count-1 do
TSearcher(Searchers[i]).Free;
Searchers.Free;
end;

procedure TfMain.actShowSearchersListExecute(Sender: TObject);
var
i : integer;
begin
// вывод инфы в TCheckListBox
for i := 0 to Searchers.Count-1 do
begin
SearchersList.AddItem(TSearcher(Searchers[i]).Name,Searchers[i]);
SearchersList.Checked[i] := true;
end;
end;


Интерфейс нашей программы будет примерно таким:
parser interface

Итак, по-моему, для первой части достаточно. О содержимом INI-файла подробнее поговорим в следующий раз.

Программируется гораздо быстрее, чем пишется описание процесса программирования. :)

Читайте окончание здесь.

пятница, 3 октября 2008 г.

"А можно то же самое на PHP?"

Конечно же, можно. Вопрос не в том, на каком языке писать свой парсер, а в том, чтобы внутренне чувствовать направление, в котором копать. А ознакомиться с синтаксисом команд — плёвое дело, это можно сделать, введя правильный запрос в Google.

Итак, PHP. Я вижу только один существенный плюс написания парсера в виде серверного скрипта. Это — возможность запуска его по расписанию "где-то там", а не со своей машины. Это может пригодиться для сбора статистических данных. На Delphi же я, как правило, пишу приложения, которые разово запускаю по мере возникновения необходимости.

Для написания парсера на любом языке программирования нам надо научиться:
1) получать содержимое страницы;
2) работать с регулярными выражениями средствами выбранного языка;
3) использовать объектно-ориентированное программирование для работы со структурированными данными. Этот пункт необязательный. Можно с тем же успехом работать с массивами, но, согласитесь, — неудобно.

Да, забыла самое главное! Надо научиться анализировать данные и думать :)

На PHP получить содержимое страницы можно по-разному. Моих неглубоких познаний в PHP хватит на то, чтобы посоветовать новичкам копать в стороны file_get_contents или библиотеки curl (мануал curl на инглише).

Для работы с регулярными выражениями в PHP, как и в Delphi, все есть.

Работать с классами в PHP тоже несложно, могу посоветовать новичкам подробный и качественный ряд статей по ООП на блоге "От новичка до профессионала".

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

четверг, 2 октября 2008 г.

С чего начать?

В комментариях прозвучал вопрос:
А как написать сборщик адресов сайтов из каталогов? Подскажите, пожалуйста, с чего начать?
Итак, постараюсь развернуто ответить.

Начать надо с постановки задачи. Допустим, вы решили собрать данные о сайтах из каталога. Вам нужен какой-то конкретный каталог или вы хотите написать универсальный инструмент для определенного типа каталогов? Надо ли сохранять категории и подкатегории или достаточно обойтись просто информацией о сайтах? После уточнения задачи открываете каталог и начинаете исследовать код.

Определитесь с инструментарием. Допустим, я выбираю Delphi, а собранные данные буду хранить в структурированном виде в текстовых файлах (потом при желании их можно будет легко обработать).

Уложите в голове алгоритм, сформулированный в тезисах, и прикиньте, как он будет реализован.

Расскажу, как бы я поступила, если бы надо было просто собрать информацию о сайтах. Создала бы класс "Ссылка_сайта" (свойства: непосредственно сама ссылка и "флаг посещения" (у непосещенной страницы — false, у посещенной — true)) и список объектов этого класса, загрузила бы главную страницу сайта, собрала бы с нее регуляркой все внутренние ссылки, записала бы их в список (предварительно проверив, что их в списке еще нет), проверила, нет ли на странице данных, подходящих под шаблон блока информации о каком-либо сайте (шаблон составляется заранее на основе анализа кода страницы), найденную информацию записала в базу (текстовую или иную - не имеет значения), проверив, нет ли ее там. И далее - рекурсивно обошла бы таким образом все страницы сайта. У посещенной ссылки "флаг посещения" делать равным true, чтобы посещать все страницы только один раз.

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

Поделиться