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

вторник, 23 декабря 2008 г.

Программный кликер

Хочу рассказать еще один случай, когда парсинг помог мне в автоматизации. На этот раз — в автоматизации кликинга. Я уже говорила, что когда-то "продвигала" свой город на MyMiniCity.com. Когда я начала искать информацию по поводу "продвижения" в онлайн-играх, нашла несколько кликообменников.

Что из себя представляет кликообменник? Пользователь регистрируется, в своем профиле указывает одну или несколько ссылок, на которые нужно кликать другим. Потом через интерфейс получает доступ к ссылкам других зарегистрированных пользователей. За каждый клик по чужой ссылке тебе начисляются "кредиты", за каждый клик по твоей ссылке "кредиты" списываются со счета. В итоге, чем больше ты кликнешь по чужим ссылкам, тем больше кликнут по твоим. Каждый пользователь по одной ссылке может кликнуть только раз в сутки, но на популярных кликообменниках количество пользователей очень большое, поэтому в день выходило около двух тысяч кликов по моей ссылке — и мой город рос, как на дрожжах. У вас возник вопрос, не заколебалась ли я день напролет кликать? Ответ: не заколебалась, так как после недели кликинга поняла, что надо все автоматизировать, иначе, — как в одной из серий "Саус Парка", — у меня не будет никакой личной жизни :)

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

Такую программу написать достаточно просто. Реализацию всех действий по-отдельности я уже разбирала в этом блоге: заполнение и сабмит формы, получение содержимого страницы, поиск ссылок по шаблону, открытие ссылок с использованием TWebBrowser. Так что при желании трудностей возникнуть не должно. А здесь — просто показала еще одно направление для написания программ web-автоматизации.

yarold clicker

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

———
Напоминаю: чтобы быть в курсе обновлений блога, можно подписаться на RSS.

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

четверг, 18 декабря 2008 г.

RSS в виде дерева (TTreeView в Delphi)

Приятно, когда есть обратная связь с блога. Когда уточняют что-либо или задают вопросы. Поэтому в продолжение к статье об отображении XML в виде дерева напишу еще эту — об отображении RSS в TreeView.

Про структуру RSS я уже писала. Задача сводится к малому: пройтись по всем записям, разобрать их элементы и представить в виде дерева "Адрес RSS — Записи".

Для достижения своих целей будем оперировать данными типов IXMLNode и IXMLNodeList.

nd : IXMLNode;
firstTier : IXMLNodeList;


DocumentElement — возвращает корневой объект документа.
childNodes — возвращает список прямых потомков вершины. Далее с дочерними узлами можно работать просто как с элементами списка. Например, узнать имя узла, можно будет firstTier[i].NodeName.

Итак, исследуем код RSS-фида, который генерируется feedburner-ом. Видим, что самый простой способ получить все записи, — это из списка узлов первого уровня выбрать узлы с именем entry, а потом каждый из этих узлов обработать (выделить нужные нам дочерние узлы content, published (дата публикации) и link (у узла link нам понадобится значение атрибута href)).


XMLDocument1.FileName := 'MyRSSFeed.xml';
XMLDocument1.Active := true;

nd := XMLDocument1.DocumentElement;

TreeView1.Items.AddChild(nil,edtRSSURL.Text);
firstTier := nd.childNodes;
for i := 0 to firstTier.Count-1 do
begin
if firstTier[i].NodeName = 'entry' then
begin
nd := firstTier[i];
ProcessItem();
end;
end;


Что касается обработки записей. Все просто со значениями узлов с именами content и published, так как такие узлы встречаются один раз. А вот узлов с именем link встречается несколько.

<link rel="replies" type="application/atom xml" href="http://mama-karlo.blogspot.com/feeds/6599339245902184370/comments/default" title="..." />
<link rel="replies" type="text/html" href="https://www.blogger.com/comment.g?blogID=29395559131211965&amp;postID=6599339245902184370" title="..." />
<link rel="edit" type="application/atom xml" href="http://www.blogger.com/feeds/29395559131211965/posts/default/6599339245902184370?v=2" />
<link rel="self" type="application/atom xml" href="http://www.blogger.com/feeds/29395559131211965/posts/default/6599339245902184370?v=2" />
<link rel="alternate" type="text/html" href="http://mama-karlo.blogspot.com/2008/12/blog-post_16.html" title="..." />


Нам нужно выбрать только тот узел, у которого значение атрибута rel равно alternate. В итоге получаем код:

type
PRSSFeedData = ^TRSSFeedData;
TRSSFeedData = record
URL : string;
Description : string;
Date : string;
end;

procedure ProcessItem();
var
rssFeedData : PRSSFeedData;
tn : TTreeNode;
title : string;
j : integer;
begin
New(rssFeedData);

title := nd.ChildNodes.FindNode('title').Text;

with nd.ChildNodes do
begin
rssFeedData.Description := FindNode('content').Text;
rssFeedData.Date := FindNode('published').Text;
end;

for j := 0 to nd.ChildNodes.Count-1 do
if (nd.ChildNodes[j].NodeName = 'link') and (nd.ChildNodes[j].GetAttribute('rel') = 'alternate') then
begin
rssFeedData.URL := nd.ChildNodes[j].GetAttribute('href');
break;
end;

tn := TreeView1.Items.AddChild(TreeView1.Items.GetFirstNode, title);
tn.Data := rssFeedData;
end;


Вот небольшое приложение, на примере которого можно поразбираться, как все работает.
RSS-feed in TreeView, Delphi

Исходники можно скачать здесь.

P.S. Спасибо благодарным читателям, которые шлют на пиво! %D

среда, 10 декабря 2008 г.

Парсинг на службе у геймера

Парсингу можно найти применение не только для получения контента. Сегодняшний мой рассказ будет связан с автоматизацией "прокачки" объекта в одной браузерной игре.

В последнее время в сети появилось огромное множество браузерных игр разных типов. В конце прошлого года, когда появился портал MyMiniCity, я построила себе там город NewYorg (кто сидит по траффику — лучше не заходить :) ). Суть игры заключается в том, что надо "построить" свой виртуальный город. Подробно расписывать не буду, так как уже писала про эту игру. Поигравшись пару месяцев, вручную заходя в другие города и оставляя на доске приглашения посетить Newyorg, решила все автоматизировать. Тогда и была написана программа, которая посещает города на MyMiniCity.com, заполняет поля, отгадывает капчу и сабмитит форму. Надо было только подсунуть программе файл со списком городов. Таким образом я выводила город достаточно высоко в рейтинге (но потом мне стало лень даже запускать программу :) ).

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

ranking=145,flycity.de|146,ssv-city|147,join|148,seti.germany|149,krokant|150,demonslayers|151,maschboard


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

myminicity parser

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

понедельник, 8 декабря 2008 г.

Отображение XML в виде дерева (TreeView): Delphi

Во многих программах конфиги хранятся в файлах формата XML. Встала задача — отобразить XML в виде дерева.

Создаем форму, на которой обязательно должны присутствовать кнопка открытия файла (и OpenDialog, соответственно) и TreeView. Остальное — опционально.
delphi xml editor

Подключаем библиотеки XmlDoc, XMLIntf (про XMLIntf, возможно, напишу отдельным постом).

Объявляем класс TNodeRec.

type
TNodeRec = class
TreeNode : TTreeNode;
XMLNode : iXMLNode;
end;


Переменные:

var
XDoc : TXMLDocument;
FName : String;
bkpXml : String;
CurrNode,
ClipboardNode : iXMLNode;


Функции:


function GetPathByNodeIdx (T : TTreeNode) : TStringList;
begin
Result:= TStringList.Create;
if T=nil then exit;
while T.Parent<>nil do
begin
Result.Insert(0,IntToStr(integer(T.Data)));
T:=T.Parent;
end;
Result.Insert(0,IntToStr(integer(T.Data)));
end;

function SetXmlPosByPathIdx (aSL: TStringList; aXmlDocument : TXMLDocument) : IXMLNode;
var
i : integer;
iNODE : IXMLNode;
begin
iNode:=nil;
if aSL.Count=0 then exit;
try
iNODE:=XDoc.ChildNodes.Nodes [StrToInt(aSL[0])];
for i:=1 to aSL.Count -1 do
iNode:=iNODE.ChildNodes[StrToInt(aSL[i])];
Result:=iNODE;
except
end;
end;


На OnCreate формы вешаем:


procedure TfrmMain.FormCreate(Sender: TObject);
begin
XDocNodesList:=TList.Create;
XDoc:=TXMLDocument.Create(Self);
XDoc.Active:=true;
OpenDialog.InitialDir:=ExtractFilePath(ParamStr(0));
end;


На OnDestroy:
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
ClearNodeList;
XDocNodesList.Free;
XDoc.Free;
end;


Обработчик нажатия на кнопку "Загрузить XML":
procedure TfrmMain.acLoadExecute(Sender: TObject);
var
i : integer;
tmpNode : IXMLNode;
begin
if OpenDialog.Execute then
begin
FName:=OpenDialog.FileName;
SaveDialog.FileName:=FName;
XDoc.LoadFromFile(FName);

for i:=0 to XDoc.ChildNodes.Count - 1 do
begin
tmpNode:=XDoc.ChildNodes[i];
TreeView.Items.AddChildObject(TreeView.TopItem,tmpNode.NodeName,
pointer(i));
if tmpNode.HasChildNodes then TreeView.TopItem.HasChildren:=true;
end;
end;
acRefresh.Execute;
bkpXml:=XDoc.XML.Text;
end;


После этого мы уже можем в первый раз опробовать работу программы: в TreeView загрузится XML, но нам будет видно только рутовую вершину (как на 1-ом рисунке). Чтобы можно было разворачивать узлы и просматривать их текст и атрибуты — надо написать еще несколько обработчиков.

procedure TfrmMain.acRefreshExecute(Sender: TObject);
var
i : integer;
tmpNode : IXMLNode;
begin
RebuildTree;
acRefreshMemBody.execute;
end;

procedure TfrmMain.RebuildTree;
var
i : integer;
XNodeItem : iXmlNode;
PNodeItem,
SelectedTrNode,
TrNodeItem,
TrNodeChild : TTreeNode;
NodeRec,
ChildNodeRec : TNodeRec;
Index : integer;
SL : TStringList;
begin
SL:=GetPathByNodeIdx (TreeView.Selected);
CurrNode:=SetXmlPosByPathIdx (SL, XDoc);
SL.Free;

Index :=0;
TreeView.Items.Clear;
ClearNodeList;
NodeRec:=TNodeRec.Create;
TrNodeItem:=TreeView.TopItem;
NodeRec.TreeNode:=TrNodeItem;
XNodeItem:=XDoc.Node;
NodeRec.XMLNode:=XNodeItem;
XDocNodesList.Add(NodeRec);

while XDocNodesList.Count > Index do
begin
NodeRec:=TNodeRec(XDocNodesList[Index]);
XNodeItem:=NodeRec.XMLNode;
TrNodeItem:=NodeRec.TreeNode;
if XNodeItem=CurrNode then SelectedTrNode:=TrNodeItem;
for i:=0 to XNodeItem.ChildNodes.Count -1 do
begin
if XNodeItem.ChildNodes[i].NodeName<>'#text' then
TrNodeChild:=
TreeView.Items.AddChildObject(
TrNodeItem,
XNodeItem.ChildNodes[i].NodeName,
pointer(i));

ChildNodeRec:=TNodeRec.Create;
ChildNodeRec.TreeNode:=TrNodeChild;
ChildNodeRec.XMLNode:=XNodeItem.ChildNodes[i];
XDocNodesList.Add(ChildNodeRec);
end;
inc(Index);
end;

if SelectedTrNode<>nil then SelectedTrNode.Selected:=true;
TreeView.Select(SelectedTrNode,[]);
end;


Сейчас при перемещении по узлам уже наблюдаем более-менее приемлемый результат:

xml editor delphi

редактирование XML (Delphi)

Дальше остается решить вопросы редактирования и добавления узлов и атрибутов.) Это будет домашним заданием.
___

Редакция от 13.01.2010
Продолжение цикла статей:
Добавление и удаление узлов (вершин) в XML в Delphi (с использованием компонента TTreeView). Там же вы найдете ссылку для скачивания исходников.

среда, 26 ноября 2008 г.

Парсинг RSS на PHP. Простой пример

В комментариях спрашивали про парсинг RSS на PHP. Так как этот вопрос напрямую относится к теме блога, то я его решила немного изучить и осветить.

Для начала я прочитала в интернете про разные способы парсинга на PHP (почитайте обязательно, очень полезный материал). Все оказалось просто, синтаксис очень понятный и удобный. Особенно мне понравился SimpleXML :)

Полученные знания нужно закреплять на практике.

Вы все знаете про Яндекс-блоги. И знаете, что там можно подписаться на RSS по любому запросу. Причем синтаксис запросов (такой же, как к самому Яндексу) предоставляет достаточно широкие возможности. Можно следить за всем, что происходит в блогосфере.

Составляем запрос и визуально убеждаемся, что в результате запроса получаем валидный XML.


Внутри скрипта пишем:

<?
function utf8_convert($str, $type)
{
static $conv = '';
if (!is_array($conv))
{
$conv = array();
for ($x=128; $x <= 143; $x++)
{
$conv['utf'][] = chr(209) . chr($x);
$conv['win'][] = chr($x + 112);
}
for ($x=144; $x<= 191; $x++)
{
$conv['utf'][] = chr(208) . chr($x);
$conv['win'][] = chr($x + 48);
}
$conv['utf'][] = chr(208) . chr(129);
$conv['win'][] = chr(168);
$conv['utf'][] = chr(209) . chr(145);
$conv['win'][] = chr(184);
}
if ($type == 'w')
{
return str_replace($conv['utf'], $conv['win'], $str);
}
elseif ($type == 'u')
{
return str_replace($conv['win'], $conv['utf'], $str);
}
else
{
return $str;
}
}

echo '<h1><font color="red">Конфузы блогосферы</font></h1>';
$url = 'http://blogs.yandex.ru/search.rss?text="наложил в штаны"'; //адрес RSS ленты

$rss = simplexml_load_file($url); //Интерпретирует XML-файл в объект

//цикл для обхода всей RSS ленты
foreach ($rss->channel->item as $item) {
echo '<a href="'.$item->link.'">';
echo '<h2>'.utf8_convert($item->title,"w").'</h2>'; //выводим на печать заголовок статьи
echo '</a>';
echo utf8_convert($item->description,"w"); //выводим на печать текст статьи
}

?>


Вот и все, любуемся результатом.

Если вы хотите парсить "в накопительном режиме", то у записей достаточно обрабатывать и сравнивать дату публикации pubDate. В зависимости от этой даты — записывать или не записывать в базу.

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

Работа с MySQL в Delphi

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

Умение работать с MySQL в Delphi вам может пригодиться, а может и не пригодиться. Все зависит от проекта, который вы собрались реализовать. Если, допустим, ваша программа будет работать с определенной базой (например, с базой WordPress или форума), то некоторые удобства есть в том, чтобы работать с локальной копией проекта, а потом изменения довносить на базу на сервере.

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

Ну и дальше все в таком духе: разрабатываем программу исходя в первую очередь из задачи и алгоритма синхронизации.
___

UPD: На этом же блоге вы можете прочитать про "экспресс-метод" работы с MySQL из Delphi через ADO.

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

Парсинг ников пользователей для форумов

Напарсить где-нибудь ников пользователей еще проще, чем напарсить аватаров. Для этого достаточно зайти на форум или сайт, на котором есть поиск пользователей. Желательно, чтобы там был выбор по полу. Вот, например, на сайте НайтПати зарегистрированы тысячи ночных тусовщиков из разных городов России. Идем на страницу поиска юзеров http://msk.nightparty.ru/users.php.

Изучаем get-запрос скрипта поиска, смотрим, что передается, смотрим идентификатор России и города Москвы. Смотрим идентификатор пола. Смотрим, как организована пагинация. В принципе, больше ничего и не надо.

Потом составляем какое-нибудь регулярное выражение для нахождения ников на конкретном сайте.

Рисуем формочку:
Кроме двух кнопок больше ничего и не надо. Вторая кнопка тоже исключительно для самых нетерпеливых, которым лень подождать, когда появится сообщение "Все сделано".

Привожу полностью рабочий код всего юнита целиком:

unit UserFinderU;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls, Urlmon;

const
GirlUserListFileName = 'g_users.txt';
BoyUserListFileName = 'b_users.txt';
GirlSearchURL = 'http://msk.nightparty.ru/users.php?action=dosearch&country_id=103&city_id=1043&user_sex=2&page=[PageNum]';
BoySearchURL = 'http://msk.nightparty.ru/users.php?action=dosearch&country_id=103&city_id=1043&user_sex=1&page=[PageNum]';
FindUserRegExp = 'style="padding-left:8px;"><a href="http://(?:.*?)\.at\.nightparty\.ru/"><b>(.*?)</b>';

function DownloadFile(SourceFile, DestFile: string): Boolean;

type
TForm1 = class(TForm)
btnStart: TButton;
btnStop: TButton;
cbGender: TComboBox;
Label1: TLabel;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;
vStop : boolean;

implementation

uses
VBScript_RegExp_55_TLB;

{$R *.dfm}

procedure TForm1.btnStartClick(Sender: TObject);
var
i, j, t,
PageNum : integer;
FName,
SearchURL,
LinkStr,
sFileName,
currBody
: string;
NextPage : boolean;
SLBody : TStringList;
RE : TRegExp;
mc : MatchCollection;
sm : SubMatches;
mm : Match;
f : TextFile;
UsersList : TStringList;
begin
if cbGender.ItemIndex = 0 then
begin
FName := GirlUserListFileName;
SearchURL := GirlSearchURL;
end;
if cbGender.ItemIndex = 1 then
begin
FName := BoyUserListFileName;
SearchURL := BoySearchURL;
end;

PageNum := 0;
NextPage := true;
UsersList := TStringList.Create;

//======================

vStop := false;
while NextPage and not vStop do
begin
LinkStr := StringReplace(SearchURL,'[PageNum]',IntToStr(PageNum),[rfIgnoreCase]);
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;
DeleteFile(sFileName);

// ищутся пользователи
RE.Pattern := FindUserRegExp;
RE.Global := true;
RE.IgnoreCase := true;
RE.Multiline := true;

mc := RE.Execute(currBody) as MatchCollection;
for j := 0 to mc.Count-1 do
begin
mm := mc[j] as Match;
sm := mm.SubMatches as SubMatches;
UsersList.Add(sm.Item[0]);
end;
if mc.Count = 0 then NextPage := false;

finally
SLBody.Free;
mm := nil;
sm := nil;
mc := nil;
RE.Free;
end;
inc(PageNum);

for t := 0 to 10 do
begin
Sleep(100);
Application.ProcessMessages;
end;
end;

UsersList.SaveToFile(ExtractFilePath(Application.ExeName)+FName);

UsersList.Clear;
UsersList.Free;
ShowMessage('Все сделано.');
end;

procedure TForm1.btnStopClick(Sender: TObject);
begin
vStop := true;
end;

function DownloadFile(SourceFile, DestFile: string): Boolean;
begin
try
Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0,
nil) = 0;
except
Result := False;
end;
end;

end.


Далее запускаем программу и занимаемся другими делами (например, продолжаем программировать свой другой-супер-мега-проект-который-принесет-кучу-БАБЛА %D ). Паузу между запросами я поставила по секунде, так что минут 15 уйдет на полное сканирование всех страниц с пользователями одного пола. Итак, через некоторое время мы получим файлики с мужскими и женскими никами, тех и других тысяч по 12. Можем использовать их для своих чистых дел). Чтобы лишний раз не грузить всуе упомянутый мной сайт, можете взять готовые списки у меня. Обращайтесь на почту, указанную в профиле.

=====

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

=====

Если вы пришли на блог с поисковика в надежде скачать базу ников, то вам нужно в другое место :)

среда, 12 ноября 2008 г.

Парсинг и загрузка аватаров с сайтов аватаров (Delphi)

Расскажу, как напарсить аватаров.

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

Задача разделяется на 2 подзадачи:
1. Напарсить ссылок на страницы с аватарами;
2. Напарсить аватары с этих страниц.

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

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

Для начала нарисуем интерфейс и подберем какое-нибудь более-менее "универсальное" регулярное выражение для парсинга картинок.

Интерфейс у меня вышел следующий:

парсинг аватаров

"Общее" регулярное выражение взяла такое:

<img[^>]* src=\"([^\"]*(?:gif|jpg|png))\"[^>]*>

Жесткое указание расширения (gif, jpg, png) избавит от скачивания картинок, генерируемых скриптами (счетчиков и т.д.).

Для примера я взяла простенький сайт. SRC картинок у них там абсолютные. Это значит, что если вы захотите сделать что-нибудь уж совсем универсальное, то вам надо будет предусмотреть, чтобы скачивание картинки, у которой сорс будет указан как, например ../../imgs/1.gif, а сама страница загружается по адресу http://site.com/humor/1/ — было правильным. То есть анализировать все это и составлять правильную ссылку http://site.com/humor/imgs/1.gif.

Ну и естественно, при сохранении файла в директорию на локальной машине надо проверить, нет ли там уже файла с таким названием. Если есть — добавить суффикс "_N", где N — число по порядку (я это в примере не реализовала).

Обработчик нажатия кнопки "Сохранить" у меня получился такой:

procedure TMainF.Button1Click(Sender: TObject);
var
i, j : integer;
PicLinkStr,
LinkStr : String;
SLBody : TStringList;
currBody,
UTF8Str,
sPicFileName,
sFileName,
sRegExp,
Ext : string;
RE : TRegExp;
mc : MatchCollection;
sm : SubMatches;
mm : Match;
w,h : word;
begin
for i := 0 to Memo1.Lines.Count-1 do
begin
// загрузка страницы
LinkStr := Memo1.Lines[i];
// загружаем страницу
try
SLBody:=TStringList.Create;
RE := TRegExp.Create(Self);

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

if RadioButton1.Checked then
sRegExp := edRegExpr.Text
else
sRegExp := '<img[^>]* src=\"([^\"]*(?:gif|jpg|png))\"[^>]*>';

// ищутся и скачиваются изображения
RE.Pattern := sRegExp;
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;
PicLinkStr := sm.Item[0];

sPicFileName:=edDir.Text + ExtractPicName(PicLinkStr);

if DownloadFile(PicLinkStr,sPicFileName) then
begin
// если по размерам - проверяем размеры
if RadioButton2.Checked then
begin
Ext := UpperCase( ExtractFileExt(sPicFileName) );
if Ext = '.GIF' then
GetGIFSize(sPicFileName, w, h);

if Ext = '.JPG' then
GetJPGSize(sPicFileName, w, h);

if Ext = '.PNG' then
GetPNGSize(sPicFileName, w, h);

if (w < seWidthFrom.Value) or
(w > seWidthTo.Value) or
(h < seHeightFrom.Value) or
(h > seHeightTo.Value) then
DeleteFile(sPicFileName);
end;
// если по регулярке - сохраняем все

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

end;


Процедуры для определения размеров картинок GetGIFSize и т.д. берем в интернете и не паримся. Например, я взяла отсюда.

Все, нажимаем на кнопку и получаем результат! Сейчас у нас будут аватары для создания армии пользователей! %D

P.S. Выражаю огромную благодарность SEOCoder-у за размещение ссылки на мой блог! Результаты мне очень понравились ;) Буду потихоньку вылазить со своим блогом и искать новых читателей. Может, кто-нибудь хочет обменяться ссылками? Вэлкам!

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

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

Переходим к той части автоматизации, которая позволит нам постить записи. Я уже писала, что для удобства на каждой записи с данными о блоге вызывается контекстное меню. Записи в датасете берутся из запроса:
select b.n as blog_n, b.blogname,b.title,b.url as blog_url,
b.update_date,b.n_bloghosting,
b.login,b.pass,bh.*
from blog b
left join bloghosting bh on b.n_bloghosting = bh.n
where b.n_bloghosting = :n

То есть там есть нужная нам информация о блоге и блогхостинге, на котором он расположен. Кстати, информация в таблице о блогхостинге на wordpress.com у меня получилась следующая:

INSERT INTO BLOGHOSTING (N, URL, URL_LOGIN, FORM_LOGIN, FIELD_LOGIN, FIELD_PASS, BUTTON_LOGIN, URL_UNLOGIN, URL_NEW_MESS, FIELD_TITLE, FIELD_MESS, FIELD_TAGS)
VALUES (1, 'http://wordpress.com', 'http://[BlogName].wordpress.com/wp-login.php', 'loginform', 'log', 'pwd', 'wp-submit', NULL, 'http://[BlogName].wordpress.com/wp-admin/post-new.php', 'title', 'content', 'tags-input');

[BlogName] потом при формировании ссылки для каждого дневника будет заменяться на содержимое поля BLOGNAME.

Поле BUTTON_LOGIN можно не заполнять, так как тип кнопки - submit.
Для последующего упрощения процесса программирования, советую выносить часто повторяющиеся действия в отдельные процедуры и функции. А потом оформить это в библиотеку. Например, поиск формы по ее имени:
function FindFormByName ( W : TWebBrowser; FormName : string) : Variant;
var
i : integer;
formitem : variant;
S : String;
begin
Result:=Null;
for i := 0 to w.oleobject.document.forms.length - 1 do
begin
formitem := w.oleobject.document.forms.item(i);
S:= lowercase(formitem.name);
if S = lowercase(FormName) then Result:=formitem;
end;
end;

Входные параметры: TWebBrowser, в котором загружена страница, и название формы.

Еще в отдельную функцию можно вынести заполнение поля формы:
function fillform(aformitem : variant; fieldname: string; value: string): boolean;
var
i, j: integer;
begin
result := false;
for j := 0 to aformitem.length - 1 do
begin
try
if aformitem.item(j).name = fieldname then
begin
aformitem.item(j).value := value;
result := true;
end;
except
exit;
end;
end;
end;
Входные параметры: форма, название поля и что в это поле поместить.

Сабмит (отправление) формы:
function SubmitForm( V : Variant) : boolean;
var
i : integer;
begin
Result:=false;
for i := 0 to V.length - 1 do
begin
try
if Trim(lowercase(V.item(i).type)) = 'submit' then
begin
result := true;
V.item(i).click();
exit;
end;
except
exit;
end;
end;
end;


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

procedure TFMain.N2Click(Sender: TObject);
var
sURL : string;
V : Variant;
begin
// залогиниться
// формируем ссылку для логина
sURL := StringReplace(qBlogURL_LOGIN.AsString,'[BlogName]',qBlogBLOGNAME.AsString,[rfIgnoreCase]);
ShowPage(sURL);
// найти форму по имени
V:=FindFormByName(WebBrowser,qBlogFORM_LOGIN.AsString);
if VarIsNull(V) then exit;
// ввести в форму, что надо
fillform(V,qBlogFIELD_LOGIN.AsString,qBlogLOGIN.AsString);
fillform(V,qBlogFIELD_PASS.AsString,qBlogPASS.AsString);
// нажать на кнопку
try
if submitform(v) then NowLogin := qBlogBLOG_N.AsInteger;
except
end;
end;

Функция ShowPage просто загружает URL в веббраузер.
procedure TFMain.ShowPage(URL: string);
begin
vLoaded := false;
WebBrowser.Navigate(URL);
while not vLoaded do
Application.ProcessMessages;
end;


До того, как обработалась команда нажатия на кнопку, форма выглядела так:
ввод данных в форму

После автосабмита мы оказываемся уже залогиненными:

автоматический логин

Как видите, все очень просто. В переменной NowLogin будет храниться N последнего блога, в котором залогинились. Это надо для того, чтобы при публикации каждого сообщения в один и тот же блог повторно не логинились.

Не вот и все, аналогично прописать действия для помещения записи в блог:
- если не залогинены - залогиниться;
- перейти на страницу для создания новой записи;
- ввести данные в поля;
- отправить форму.

Удачных разработок ;)

четверг, 6 ноября 2008 г.

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

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

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

Итак, Firebird. Работать с базой будет удобнее через специально разработанный инструмент. Например, IBExpert.

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

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

CREATE GENERATOR GEN_BLOG_N;

CREATE TABLE BLOG (
N INTEGER NOT NULL,
N_BLOGHOSTING INTEGER,
LOGIN VARCHAR(25),
PASS VARCHAR(25),
BLOGNAME VARCHAR(255),
TITLE VARCHAR(255),
DSC VARCHAR(500),
URL VARCHAR(255),
EMAIL VARCHAR(50),
CREATE_DATE DATE,
UPDATE_DATE DATE
);

ALTER TABLE BLOG ADD CONSTRAINT PK_BLOG PRIMARY KEY (N);

CREATE GENERATOR GEN_BLOGHOSTING_N;

CREATE TABLE BLOGHOSTING (
N INTEGER NOT NULL,
URL VARCHAR(255),
URL_LOGIN VARCHAR(255),
FORM_LOGIN VARCHAR(25),
FIELD_LOGIN VARCHAR(25),
FIELD_PASS VARCHAR(25),
BUTTON_LOGIN VARCHAR(25),
URL_UNLOGIN VARCHAR(255),
URL_NEW_MESS VARCHAR(255),
FIELD_TITLE VARCHAR(25),
FIELD_MESS VARCHAR(25),
FIELD_TAGS VARCHAR(25)
);

ALTER TABLE BLOGHOSTING ADD CONSTRAINT PK_BLOGHOSTING PRIMARY KEY (N);

Для каждой таблицы создаем ключ и генератор значения ключа (сиквенс). А также триггер BEFORE INSERT, который для каждой новой записи записывает в поле N значение сиквенса.

SET TERM ^ ;

CREATE TRIGGER BLOG_BI FOR BLOG
ACTIVE BEFORE INSERT POSITION 0
AS
begin
if (New.N is null) then
New.N = Gen_Id(gen_blog_n, 1);
end
^

SET TERM ; ^

(Я тут все привела в кодах, но в Эксперте все то же самое можно сделать через интерфейс, не заботясь о правильности синтаксиса.)

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

Парсинг форумов

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

Итак, к теме.

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

Я знаю, что существуют генераторы дорвеев в виде форумов. Но что если вам нужен не дорвей, а форум "для людей"? Установить PhpBB — это самое малое. Главное — привлечь пользователей. А на пустое место никого не привлечь.

Итак, попытаемся его самостоятельно наполнить. Я разберу наполнение форума на движке PhpBB. Так как параллельно занимаюсь еще парой направлений, то не знаю, с какой частотой буду публиковать статьи по этой теме. Но то, что они будут — это факт.

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

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

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

На следующем этапе надо подыскать форумы, родственные по тематике. Желательно, чтобы они были на одном и том же движке (это освободит от необходимости составлять для парсинга разных движков отдельные регулярные выражения). Провести парсинг информации. Нам необходимо МНОГО информации, с большим запасом. Эту информацию записать на локальную базу, изменить сами записи, изменить время публикации (равномерно распределить на определенный период). А потом — автоматически публиковать ее в сети, опираясь на время записей, чтобы поисковики видели обновляющийся ресурс.

Наверное, немного плохо сформулировала. Но в голове я ясно представляю весь этот процесс.

Поэтапно буду освещать его здесь. Первые 3 вопроса будут:
1. Работа с MySQL в Delphi
2. Парсинг базы аватаров
3. Наполнение базы юзеров

Остальные пункты допишу потом.

пятница, 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, чтобы посещать все страницы только один раз.

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

вторник, 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-агрегаторы можно найти на блоге ЖПО - Жизнь Поисковой Оптимизации. Было бы здорово, если бы кто-нибудь добавил все ссылки и выложил получившийся файл. Может, у меня дойдут руки на этой неделе... Но пока не до этого. Ждите продолжения данной статьи. Там я остановлюсь на некоторых технических моментах написания программы и приведу кое-какие листинги.

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

Поделиться