Для журнала "Мой компьютер". В журнале публиковалось под названием "Perl'ы для веб-мастера".

Perl'ы для веб-мастера

Евгений Гривастов (tw@tv-agent.net)

(...продолжение…)

"И тут Остапа понесло…" :-) Это я о том, что усложнять и усовершенствовать любой скрипт можно до бесконечности. Поэтому давайте для начала ограничимся основными функциями. Цель ведь - на практических примерах показать основные операторы языка и правила программирования. И сделать это за возможно короткий срок. Поэтому пока не будем лезть в дебри и оставим сложности и навороты на потом. Если вас интересуют конкретные вопросы, способы решения конкретных задач - пишите мне. А я постараюсь ответить на них или лично или (если ответ будет полезен многим) на страницах журнала.

Глава 9. Просмотр собранной статистики.

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

Прежде всего нам надо получить имя файла статистики для интересующей нас страницы. Храниться оно, если помните, в файле counters.txt. Но прежде всего получим из параметров вызова скрипта кодовое имя интересующей нас страницы. А после этого откроем этот файл для чтения и будем считывать подряд строки, пока не найдем нужную (мы это уже делали в последней версии нашего счетчика):

$pageid=$ENV{'QUERY_STRING'};
$countersfile="/home2/your_domen/public_html/mystat/counters.txt";
open (IPFILE, "$countersfile");
METKA: while ($stroka=<IPFILE>) {
chomp($stroka);
($countid, $url, $countfile, $countipfile, $countstatfile)=split(/|/,$stroka);
last METKA if ($countid eq $pageid);
}
close(IPFILE);

Теперь в переменной $countstatfile у нас храниться имя файла статистики, который мы и будем изучать. Прежде всего составим полный путь на этот файл:

$namestatfile="/home2/your_domen/public_html/mystat/".$countstatfile;

Вы ведь помните, что знак "." (точка) служит для соединения строковых переменных.
Теперь, просматривая файл статистики, заполним хэш:

open (STATFILE, "$namestatfile");
while ($stroka=<STATFILE>) {
chomp($stroka);
($host, $useragent)=split(/|/,$stroka);
$hosts{$host}=$hosts{$host}+1;
$useragents{$useragent}=$useragents{$useragent}+1;
}
close(STATFILE);

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

$useragents{$useragent}=$useragents{$useragent}+1;

значение соответствующей записи станет равно единице. При следующем появлении броузера с таким же названием - это значение увеличится еще на единицу. Таким образом мы подсчитаем сколько раз в нашем файле статистики встречаются одинаковые названия броузеров. То же самое относится и к хэшу %hosts и именам хостов.

Однако, если одинаковые названия броузеров будут встречаться достаточно часто, то одинаковые хосты - достаточно редко. Поэтому давайте будем считать не полные названия хостов, а только последнюю часть домена (т.е. ".com", ".net", ".ua", ".ru" и т.д.). Тут нам понадобятся небольшие знания правил обращения с регулярными выражениями.

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

$host=~/(\.\D+$)/;
$host=$1;

и поместим мы их перед строкой

$hosts{$host}=$hosts{$host}+1;

А теперь разберем, что же мы делаем. Операция "=~" является операцией сопоставления. Слева от нее помещается строковая переменная, с которой будет сопоставляться регулярное выражение, помещенное справа. Само регулярное выражение заключено в пару слэшей (косых черточек). Знак "\." (обратный слэш и точка) обозначает просто точку. Так как у точки в регулярных выражениях имеется собственное служебное предназначение, то, чтобы указать собственно точку, необходимо поставить перед ней обратный слэш. Знак "\D" означает "один любой нецифровой символ". Знак "+" означает "один или более одного символов, стоящих непосредственно перед ним". Ну и знак "$" означает конец строки. То есть полностью наше регулярное выражение можно прочитать как "найти точку, после которой расположен один или более нецифровых символов, стоящих в конце строки". Осталось пояснить зачем регулярное выражение заключено в круглые скобки и что означает вторая строка. Круглые скобки применяются для запоминания найденной части строки. Часть эта запоминается в переменной $1. Таким образом в результате выполнения наших двух строк из всего доменного имени в переменной $host останется только его окончание (т.е. ".com", ".net", ".ua", ".ru" и т.д.). Значит в хэше %hosts мы получим количество посетителей из корневых доменов и сможем как то понять, из каких стран к нам на страницу ходит больше народа.

Весь скрипт просмотра статистики будет выглядеть так:

#!/usr/bin/perl

$pageid=$ENV{'QUERY_STRING'};
$countersfile="/home2/your_domen/public_html/mystat/counters.txt";
open (IPFILE, "$countersfile");
METKA: while ($stroka=<IPFILE>) {
chomp($stroka);
($countid, $url, $countfile, $countipfile, $countstatfile)=split(/|/,$stroka);
last METKA if ($countid eq $pageid);
}
close(IPFILE);

open (STATFILE, "$namestatfile");
while ($stroka=<STATFILE>) {
chomp($stroka);
($host, $useragent)=split(/|/,$stroka);
$host=~/(\.\D+$)/;
$host=$1;
$hosts{$host}=$hosts{$host}+1;
$useragents{$useragent}=$useragents{$useragent}+1;
}
close(STATFILE);

# Ну и далее - выведем содержимое наших хэшей в виде html страницы

print "Content-Type: text/html\n\n";
print "Статистика по броузерам: <br>";
while (($name, $count)=each(%useragents)) {
print "$name => $count<br>";
}
print "<br>Статистика по хостам: <br>";
while (($host, $count)=each(%hosts)) {
print "$host => $count<br>";
}

exit;

Поясню работу с хэшами в последней части скрипта. Функция each извлекает из хэша пары ключ/значение которые мы списком присваеваем переменным. Делаем мы это в цикле while и таким образом извлекаем из хэша по порядку все имеющиеся в нем значения (при каждом вызове функции each извлекается следующее значение). Все это посредством функции print выводится на стандартный вывод. Строка же

print "Content-Type: text/html\n\n";

указывает, что выходные данные со скрипта будут в формате html.

Назовите файл скрипта например countstat.cgi, залейте на сервер в cgi-bin директорию (не забудьте - в режиме txt) и присвойте права доступа 755. Теперь вызовите его через броузер, указав в качестве параметра (после знака "?") кодовое название одной из страниц, которые обсчитывает наш счетчик. Например это может выглядеть так:

http://www.your_domen.com/cgi-bin/countstat.cgi?first

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

На этом отложим на некоторое время наш счетчик в сторону и займемся созданием гостевой книги.

Часть 2. Делаем гостевую книгу.

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

Глава 10. Простейшая гостевая книга.

Что такое гостевая книга по своей сути? Это html страница, на которой посетители могут оставлять свои записи. Значит в составе html страницы гостевой книги надо предусмотреть место для собственно сообщений и форму ввода новых сообщений. Создайте файл с расширением html (например myfirstgb.html) и следующим содержанием:

<html>
<head>
</head>
<body>
<p><b>Моя гостевая книга</b></p>
<!-- new message -->
<form action="http://your_domen.com/cgi-bin/guestbook.cgi" method="post">
Ник: <input type="text" name="nik" size="20"><br>
E-mail: <input type="text" name="email" size="20"><br>
Сообщение: <input type="text" name="message" size="20"><br>
<input type="submit" value="Записать">
</form>
</body>
</html>

Еще давайте создадим на сервере директорию, например, "gb" и поместим в нее этот файл. Файл заливайте на сайт в режиме txt и после заливки присвойте ему права доступа 666. Это и будет собственно файл гостевой книги. Теперь скрипт. Чтобы не повторять код два раза, я просто буду комментировать строки скрипта. В Perl строка, начинающаяся с символа "#", является комментарием и не воспринимается интерпретатором (кроме первой строки скрипта, мы об этом уже писали). Поэтому вы можете набирать скрипт со всеми комментариями, если так будет понятней потом с ним работать.

#!/usr/bin/perl

# В следующей строке мы задаем полный путь на файл myfirstgb.html
$gbfile="/home2/your_domen/public_html/gb/myfirstgb.html";

# Преобразуем данные полученные от формы в нормальный вид и поместим в хэш %field
# Я уже писал, что эта подпрограмма будет одинаковая для всех, создаваемых нами скриптов
# Именно в таком виде она генериться Perl Builder. Не рекомендую в ней что либо менять.
&GetFormInput;

# Присвоим данные полей html формы переменным $nik, $email и $message
$nik=$field{"nik"};
$email=$field{"email"};
$message=$field{"message"};

# Откроем файл гостевой книги для чтения и записи
open (GBFILE, "+<$gbfile");

# Заблокируем от доступа из других копий этого скрипта
flock (GBFILE,2);

# Поместим все содержимое файла в массив @gb
@gb =<GBFILE>;

# Переместим указатель позиции в файле на его начало
seek (GBFILE,0,0);

# Усечем файл по указатель позиции (т.е. в нашем случае - очистим)
truncate(GBFILE,0);

# У всех строк, содержащихся в массиве @gb, удалим символ конца строки (если такой имеется)
chomp (@gb);

# Перебираем в цикле все записи, содержащиеся в массиве @gb, присваивая их значение переменной $stroka
foreach $stroka (@gb) {

# Запишем строку обратно в файл
print GBFILE "$stroka\n";

# Если найдем строку "<!-- new message -->", то после нее добавим новую запись
if ($stroka eq '<!-- new message -->') {
print GBFILE '<p><b>Ник: '.$nik.</b>'<br>';
print GBFILE 'E-mail: '.$email.'<br>';
print GBFILE 'Сообщение: '.$message.'<hr></p>'."\n";
}
}

# Закроем файл, сняв с него блокировку
close(GBFILE);

# Перенаправим броузер на отредактированный файл нашей гостевой книги.
# Пропишите здесь правильный полный URL файла гостевой книги на вашем сайте.
print "Location: http://www.your_domen.com/gb/myfirstgb.html\n\n";

# Завершим работу скрипта. Это не обязательно, но лучше не привыкать к небрежности... ;-)
exit;

# Подпрограмма, разбирающая полученные от формы данные и помещающая их в хэш %field
sub GetFormInput {

(*fval) = @_ if @_ ;

local ($buf);
if ($ENV{'REQUEST_METHOD'} eq 'POST') {
read(STDIN,$buf,$ENV{'CONTENT_LENGTH'});
}
else {
$buf=$ENV{'QUERY_STRING'};
}
if ($buf eq "") {
return 0 ;
}
else {
@fval=split(/&/,$buf);
foreach $i (0 .. $#fval){
($name,$val)=split (/=/,$fval[$i],2);
$val=~tr/+/ /;
$val=~ s/%(..)/pack("c",hex($1))/ge;
$name=~tr/+/ /;
$name=~ s/%(..)/pack("c",hex($1))/ge;

if (!defined($field{$name})) {
$field{$name}=$val;
}
else {
$field{$name} .= ",$val";
}


}
}
return 1;
}

Все, что касается тегов html, я объяснять не буду.

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

(…продолжение следует…)