Формулювання завдання і можливі інструменти для її вирішення

Сьогодні на багатьох сайтах можна зустріти форми голосування. Тематика в них,
як правило, найрізноманітніша: від сексу до політики, від стійкості
операційних систем персональних комп'ютерів до рейтингу музичних альбомів.
Книжкові магазини запитують думку про книжки, стоматологічні сайти – про
ставленні до "супернових" зубній пасті … Всі ці завдання в найпростішому випадку
зводяться до однієї:

Є ПИТАННЯ.
Є кілька варіантів відповідей.
Відвідувач
повинен вибрати правильний з його точки зору відповідь (при цьому він має право
дізнатися, яка повна картина голосування в даний момент).


Насправді, якщо б все було так просто, існував би один або кілька
простих скриптів (на кшталт роздають на http://www.script.ru), які можна було
б брати за основу і переробляти під конкретні потреби. Але при уважному
розгляді виявляється, що тут існує ще маса тонких моментів і
підводних каменів. Тому я вважаю, що універсального скрипта подібного роду
просто не існує, і краще всього заглиблюватися в процес написання системи
голосування, починаючи з простих прикладів, крок за кроком відстежуючи можливі
виникаючі проблеми і при цьому чітко розуміючи, як можна навчитися їх
обходити.


Отже, якщо з вихідною формулюванням завдання все більш-менш зрозуміло, то з
методом її вирішення потрібно визначитися. У принципі, варіантів використання
голосування на сайті не так багато. Я постараюся висвітлити варіанти реалізації
деяких з них.


Розділимо для початку завдання на два класи: "простий лічильник" і "лічильник зі
статистикою ".


В якості інструментів я пропоную вибрати PHP3 (http://www.php3.net) в якості
on-line-препроцесора та Perl5 (http://www.perl.com) для
off-line-обробки.


Мова PHP3 вже достатньо поширений серед хостинг-провайдерів (webmaster.comset.net) як в Україні, так і за кордоном. Він був
створений спеціально для написання скриптів, що виконуються на стороні сервера. Для
сервера Apache він працює у вигляді додаткового модуля, забезпечуючи високу
швидкість роботи і зручність програмування.


Програма на PHP являє собою файл на мові HTML з включеними до нього
тегами PHP, які виглядають наступним чином:

<? команди?>

Таким чином, ви можете легко підготувати весь дизайн сторінки в улюбленому
редакторі HTML, а потім вставити в нього необхідні команди PHP.


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


З чого починається система опитування? Правильно, з форми. Отже:

<form method=POST action="stat.phtml">
На президентських виборах переможе: <br>
<input name=vote type=radio value="0"> мер Лужков <br>
<input name=vote type=radio value="1"> мер Петушков <br>
<input type=submit value="Go!">
</form>

Помістимо ці рядки в наш файл vote.html. Зверніть увагу на те, що
суфікс у обробника форми. phtml, а не. html або. cgi. Так зазвичай позначають
скрипт для PHP. Вам не потрібно робити цей файл виконуваним, так як його
виконує сам сервер. І, відповідно, вам не потрібно викладати його в
окремий каталог cgi-bin.


Поставимо завдання як "Зробити лічильник для кожного варіанту відповіді".


У найпростішому випадку файл stat.phtml повинен містити такі рядки:

<?
$ Errmsg = "; / / Спочатку помилок немає.
$ Fp = fopen ("vote.dat", "w +"); / / Відкриємо файл для запису,
але не очищаючи його.
if ($ fp) {/ / Якщо не відкрити, повідомимо про помилку.
/ / Прочитаємо рядок, приберемо в кінці її зайвий ""І
/ / Розділимо її за символом "" (табуляція) в масив.
$votes=split(" ",chop(fgets($fp,80)));
$ Votes [$ vote] + +; / / Збільшимо на 1 наш голос.
rewind ($ fp); / / відмотати файл в початок.
/ / Запишемо у нього масив лічильників, склеївши елементи через
знак табуляції.
fputs($fp,join(" ",$votes));
fclose ($ fp); / / Закриємо файл.
} Else $ errmsg .= "Не відкрити файл голосування";
?>

Після виконання цього коду масив $ votes буде містити результати
голосування (лічильники), а рядок $ errmsg – повідомлення про помилку (або буде
порожній, якщо помилок не було). Так як $ vote – це значення змінної vote з
форми файлу vote.html (у такому вигляді програма на PHP отримує значення полів
викликає форми) і воно може бути рівним 0 або 1 (як випливає з атрибута value
тегів input форми), то його зручно використовувати в якості індексу в масиві
лічильників.


Файл vote.dat повинен бути доступний на запис користувачу, від імені якого
працює www-server. Це питання слід уточнити в системного адміністратора
вашого сервера. Зазвичай достатньо зробити його доступним для запису (групі)
командою ftp

chmod 660

Можна вивести результати голосувань у вигляді таблиці (ці рядки можна
записати теж у stаt.phtml):

<?
/ / Запишемо у вигляді масиву відповіді.
$ Names = array ("мер Лужков", "мер Петушков");
?>
<table>
<tr>
<td> Кандидат </ td>
<td> Голосів </ td>
</tr>
<? for ($ i = 0; $ i <sizeof ($ votes); $ i + +): / / Цикл по всіх
лічильників?>
<tr>
<td><? echo $names[$i] ?></td>
<td><? echo $votes[$i] ?></td>
</tr>
<? endfor / / Кінець циклу?>
</table>

Від простого до складного


Ось і все? А ось і ні … У такої програми маса недоліків:


1. Її дуже незручно адмініструвати. Тобто щоб змінити питання або
відповідь, доведеться змінити 2 файла і не забути про файл лічильників. Це не завжди
просто і завжди не швидко …


2. Змінна $ vote отримує значення як параметр value поля input форми.
Нехороша людина може змінити форму, завантаживши її на свій комп'ютер і виконавши
її звідти з новим значенням поля, наприклад 5. Тоді в статистиці буде
виводитися не 2, а 6 рядків. Також дуже цікаве значення буде – 1.


3. Поганому людині ніщо не заважає натиснути кнопку "Go!" не один, а,
скажімо, сто разів. Більше того, зовсім погана людина навіть натискати кнопку не
буде, бо досить іcпользовать дуже просту програму для відправки форми
скільки завгодно разів … Обговорення подібних продуктів-накрутчики не входить до
рамки цієї статті, а ось захист від цього нас дуже цікавить.


Ех, ускладнювати так ускладнювати. Вб'ємо спочатку першого зайця. Давайте заведемо
окремий файл "конфігурації" опитування і перейменуємо vote.html в vote.phtml. Ось
приклад такого конфігураційні файли (назвемо його vote.cfg):

 # Файл лічильників
vote.dat
# Питання
Хто буде президентом?
# Відповіді
мер Лужков
мер Петушков

Будемо ігнорувати порожні рядки і рядки, що починаються з символу #.


Перший рядок – ім'я файлу, друга – питання, а наступні – відповіді.


Тепер, якщо написати "розумний" обробник vote.cfg, можна легко міняти анкету
на сторінці опитування, не змінюючи скриптів. Довірити зміни конфігураційні файли
можна навіть неспеціалісту (див. врізку).


Таким чином файл vote.phtml, що містить форму, буде виглядати так:

<?
$errmsg=";
/ / Завантаження конфігурації
$fp=fopen(“vote.cfg”,”r”);
if ($fp) {
/ / Читати файл, пропускаючи коментарі і порожні рядки.
while ($line=chop(fgetss($fp,200))) if (!ereg(“^( *)|(
*#.*)$”,$line)) break;
$cntFile=$line;
while ($line=chop(fgetss($fp,200))) if (!ereg(“^( *)|(
*#.*)$”,$line)) break;
$cntQuestion=$line;
$cntName=array();
while ($line=chop(fgetss($fp,200))) {
if (ereg(“^( *)|( *#.*)$”,$line)) continue;
$cntName[]=$line;
}
fclose($fp);
} Else $ errmsg .= "Не відкрити файл конфігурації голосування.";
?>

<form method=POST action="stat.phtml">
<? echo $cntQuestion ?><br>
<? for ($i=0; $i<count($cntName); $i++) : ?>
<Input type = radio name = vote value = "<? Echo $ i ?>"><?
echo $cntName[$i] ?><br>
<? endfor ?>
</form>


Як бачите, наша форма зовсім не залежить від кількості та змісту
даних.


Код завантаження файлу конфігурації можна винести в окремий файл, скажімо
vote.cfg.inc, і включати його командою include:

<? include(“vote.cfg.inc”)
?>

Тепер файл stat.phtml буде виглядати так:

<?
include(“vote.cfg.inc”);
$errmsg=";
if ($vote>=0 && $vote<count($cntName)) {
$fp=fopen(“vote.dat”,”w+”);
if ($fp) {
$votes=split(" ",chop(fgets($fp,80)));
$votes[$vote]++;
rewind($fp);
fputs($fp,join(" ",$votes));
fclose($fp);
} Else $ errmsg .= "Не відкрити файл голосування";
} / / Else це спроба хака.
?>

Виводимо результати голосувань у вигляді таблиці:

<table>
<tr>
<td> Кандидат </ td>
<td> Голосів </ td>
</tr>
<? for ($ i = 0; $ i <count ($ cntName); $ i + +): / / Цикл по всіх
лічильників?>
<tr>
<td><? echo $cntName[$i] ?></td>
<td><? echo $votes[$i] ?></td>
</tr>
<? endfor / / Кінець циклу?>
</table>

Отже, ми усунули недоліки 1 і 2 початкового варіанта. Рішення ж
завдання 3 вимагає більш складних дій. Нагадаю, мова йде про "накрутки", коли
нехороша людина намагається проголосувати не один, а багато разів. На жаль,
повністю уникнути накруток дуже важко. У загальному випадку завдання полягає в тому,
щоб не допустити повторне голосування. Для цього треба якось ідентифікувати
відвідувача. Для пошуку нових механізмів ідентифікації вам стане в нагоді корисна
функція phpinfo (), яка виводить всю доступну інформацію про відвідувача (і
багато іншого) на екран. Що нам може стати в нагоді?


1. Cookies


Можна на початку файлу stat.phtml (до виведення першого символу) перевірити наявність
змінної, наприклад, if (isset ($ reload)). Якщо вона визначена – значить,
відмовити в голосуванні, якщо немає, то використовувати функцію setcookie
("Reload", "yes") і дозволити голосування. Недолік такого методу очевидна:
накрутчики може вимкнути Cookies. Але такий метод рятує від випадкових оновлень
сторінки stat.phtml.


2. REMOTE_ADDR


Це ip-адреса відвідувача. Можна його запам'ятати в хеш-файлі. Ось так:

<?
$ MIN_TIME = 30 * 60; / / Мінімальний час для дозволу
повтору (30 хв.)
$ VoteOK = 0; / / За замовчуванням можна.
$db=dbmopen(“vote”,”w”);
if ($db) {
if (dbminsert ($ db, $ REMOTE_ADDR, time ())== 0) {/ / Новий
відвідувач
$voteOK=1;
} Else {/ / Відвідувач вже був
/ / Якщо він був давно, то можна …
$time=dbmfetch($db,$REMOTE_ADDR);
if (time()-$time>$MIN_TIME) {
/ / Вирішуючи, треба оновити час останнього доступу.
dbmreplace($db,$REMOTE_ADDR,time());
$voteOK=1;
}
}
dbmclose($db);
}
?>

Далі в програмі можна користуватися логічної змінної $ voteOK, яка
показує, чи можна відвідувачу міняти лічильник чи ні. У цього методу, звичайно,
є свої достоїнства і недоліки. До достоїнств можна віднести його достатню
параноїдальні. Вона ж і головний недолік. Через застосування цього методу,
наприклад, лукавить лічильник "Рамблера". Вся справа в тому, що $ REMOTE_ADDR – це і
ip-адресу проксі-сервера, якщо відвідувач йде через такий сервер, і ip-адреса
мосту, якщо відвідувач заходить з локальної мережі своєї фірми. Таким чином, в
СПб може виявитися всього сотня-друга хостів, що дещо не відповідає
дійсності. Користувачі, що виходять в Інтернет за допомогою модему, як
правило, отримують ip-адреса динамічно, і наступний відвідувач вашої сторінки
може бути з тим же ip, але це буде інший комп'ютер. Можна використовувати
$ MIN_TIME, як у моєму прикладі, тобто через цей інтервал часу ip вважається
не використаним. Півгодини зазвичай вистачає. Ви можете використовувати більш складний
ключ, включаючи такі змінні запиту, як HTTP_X_FORWARDED_FOR, REMOTE_PORT,
HTTP_USER_AGENT, але, на відміну від REMOTE_ADDR, перші дві з них легко
підробляються зловмисниками, а REMOTE_PORT може бути у кожного запиту
новий.


Таким чином, із застосуванням інтернет-технологій в опитуваннях (показах банерів,
лічильниках і т. п.) не уникнути спотворення результатів статистики. У ваших руках
як розробника опитування є тільки можливість досягти максимального
наближення до ідеалу. Це поле діяльності для штучного інтелекту.
Експериментуйте, діліться зі мною результатами …


Припустимо варіант з off-line-обробкою статистики. Програма stat.phtml може
просто записувати результат голосування і дані про користувача в
послідовний файл, в кінець цього файлу-журналу. Інформацію про результат
голосування можна брати з файлу результатів. Якась ж окрема процедура,
написана на Perl, буде запускатися раз на півгодини, аналізувати такий журнал
і записувати результат у файл лічильників. Якщо ви припускаєте, що за вашою
анкеті будуть голосувати як мінімум раз на пару хвилин, то краще піти по такому
шляхи:

<?
$fp=fopen(“vote.log”,”a”);
$time=time();
fputs($fp,"$vote $time $REMOTE_ADDR $HTTP_USER_AGENT $
HTTP_X_FORWARDED_FOR
");
fclose($fp);
$fp=fopen(“vote.dat”,”r”);
/ / Відкриваємо тільки на читання!
$votes=split(" ",chop(fgets($fp,80)));
?>

Така програма буде відпрацьовувати максимально швидко. А обробник журналу
не буде сильно навантажувати комп'ютер, так як буде виконуватися дуже рідко по
комп'ютерним поняттям.

#!/usr/local/bin/perl -w
#
# Простий аналізатор журналу.
#

use strict;

# Читаємо журнал
open LOG,”<vote.log” or die;
my @votes=();
while (<LOG>) {
chomp;
my ($vote,$time,$addr,$agent,$proxy)=split/ /;
$votes[$vote]++;
}
close LOG;

# Записуємо лічильники.
open CNT,”>vote.dat” or die;
print CNT join " ",@votes;
close CNT;

exit;


У принципі, off-line-обробник може не просто записувати дані у файл, а
генерувати сам html-файл статистики. Так можна вступати на дуже завантажених
системах для мінімізації навантаження на процесор сервера. Звичайно, треба б читати
і файл vote.cfg, перевіряти поля $ vote і т. д. ..


Приклади статистичної обробки результатів голосування


Як бачите, під час виконання програма отримує масу корисної інформації
користувача. Та й анкета може не обмежуватися одним питанням. Як тільки
умови роботи програми починають виходити за рамки поставленої на початку статті
завдання в бік ускладнення статистики, on-line-обробка результатів відразу
починає відходити на другий план.


У загальному завданню анкетування присутні кілька варіантів відповідей на
питання, не передбачені і просто текстові відповіді, статистика даних про
відвідувача. Ще, в принципі, можна відстежувати зміну статистики в часі в
випадку тривалих інтернет-тестів. І навряд чи для цього варто заводити базу
даних SQL для зберігання даних анкети – навіть для обробки десятків тисяч анкет
достатньо і простого текстового файлу, як у расмотренного нами прикладі.


У цій статті немає можливості розглянути навіть малу частину всіх варіантів
статистичної обробки. Найпростіше, що спадає на думку, – це динаміка
відповідей (за рівні проміжки часу) і розподіл респондентів за
географії.


Коротко розглянемо обидва випадки, а файл журналу залишимо від попереднього
прикладу.


А. Динаміка відповідей

#!/usr/local/bin/perl -w
#
# Простий аналізатор журналу зі статистикою за часом.
#
# Статистика по днях.

use strict;

# Читаємо журнал
open LOG,”<vote.log” or die;
my @slices=();
my $ slice =- 1; # Номер зрізу за часом.
my $last=0;
my @stamp=();
while (<LOG>) {
chomp;
my ($vote,$time,$addr,$agent,$proxy)=split/ /;
my $ day = floor ($ time / (24 * 60 * 60)) * (24 * 60 * 60); # Наводимо
час до початку дня.
$ Slice + + if $ last! = $ Day; # Наступний день настав.
$slices[$slice][$vote]++;
$stamp[$slice]=$day;
}
close LOG;
# Записуємо лічильники.
open CNT,”>vote.dat” or die;
my $i;
for ($ i = $ # slices; $ i; $ i-) {# За всіх днях у зворотному порядку.
print CNT $ stamp [$ i], ""; # День
print CNT join "", @ slices [$ i]; # Лічильники
print CNT "
";
}
close CNT;
exit;


В результаті виконання це програми файл vote.dat буде містити рядки
такого вигляду:

ЧАС <tab> счетчік1 <tab> счетчік2 …

За цими даними можна побудувати гарний графік зміни показань лічильників
за часом, що може характеризувати, зокрема, результати рекламних
кампаній персон з нашого самого першого прикладу.


Ясно, що після прокрутки по телебаченню вдалого кліпу кількість голосів за
даного кандидата має збільшитися, – особливо якщо рекламний ролик буде
кожні десять хвилин переривати модний бойовик. І буде особливо радісно це
побачити наочно!


Б. Статистика по географії


Про географічне становище відвідувача можна дізнатися як за його ip-адресою, так
і прямо поставивши запитання в анкеті. Не будучи соціологом за освітою, не буду
заглиблюватися в нетрі питання "Чи можна довіряти відповіді користувача?", але й
ip-адресою теж особливо не варто довіряти. Незважаючи на це якусь оцінку
географічного становища респондентів отримати цілком можливо.


За ip-адресою можна отримати інформацію з двох джерел. По-перше, це
доменне ім'я. По-друге, це дані про реєстрацію мережі. Розглянемо докладніше обидва
джерела.


Доменне ім'я ми можемо отримати з змінної $ REMOTE_HOST, якщо web-сервер
встиг його визначити, чи командою

nslookup <ip-адрес>

в іншому випадку.


Проте ні для кого вже не секрет, що сервер з ім'ям qq.spb.ru може
знаходитися в США, а www.zzz.com – у Петербурзі. Так що аналіз самого імені нам
нічого не дасть. А ось реєструючий орган (INTERNIC, RIPN, etc) нам, в першу
чергу, повідає про географічне положення власника домену. Отримати цю
інформацію можна як скориставшись командою

whois домен.ru @ whois.ripn.net

так і підключившись до сервера whois, вводячи запити послідовно, що нас
якраз влаштує в поставленому завданню. Все б добре, але не на всі домени
є інформація. Приклад – той же домен spb.ru.


Тоді можна визначити мережу відвідувача. Запишемо ip-арештом у вигляді xxx.yyy.zzz.0
і запитаємо також за протоколом whois орган, що реєструє мережі:

whois
194.105.206.0@whois.ripe.net

Залежно від сервера результат висновку може мати інший формат, але
програму можна налаштувати на всі такі сервери. Сервер whois.ripe.net, в
Зокрема, надає поля address і country у вивідний інформації,
характеризує географічне положення власника ip-мережі.


На жаль, і цей метод не дає стовідсоткової гарантії. Ви можете
виявити велику мережу, підмережі якої знаходяться в різних, дуже віддалених
один від одного, регіонах. Що ж, якщо вам потрібно зовсім точний результат, то
автоматизація тут буде лише помічником, вирішальним велику частку завдань, а
спірні питання доведеться з'ясовувати вручну.


Я не буду наводити приклад обробки журналу з аналізом географії
респондентів. Це вже велика і серйозна програма, напишіть її самі – це
гарна гімнастика для розуму …


Візуалізація результатів обробки статистики голосування


Отже, ми отримали чудову статистику по нашій анкеті. Як її вивести?


Найпростіший варіант – це таблиця з рядків виду

Відповідь – Лічильник

Саме такий висновок здійснено у нашому прикладі, розглянутому вище.


Однак хочеться мати більш гарний вигляд сторінок. Особливо в цьому вас буде
переконувати дорогою дизайнер, що здійснює розробку зовнішнього вигляду проекту.
Давайте його послухаємо … Отже, що ми можемо витиснути з таблиці? На таблицях мови
HTML ми можемо побудувати красиву столбцовую діаграму. Навіть тривимірну. Адже в
наших руках такий розумний інструмент, як PHP.


Побудуємо діаграму за останнім прикладом зі зміною лічильників у часі.
Хай по горизонталі змінюється час, а по вертикалі – величина лічильника. Для
кожного варіанта побудуємо окремий графік:

<?
include(“vote.cfg.inc”);
$ Base = 80; / / Максимальна висота смужки.
$fp=fopen(“vote.dat”,”r”);
$votes=array(); $max=0;
$ Time = floor (time () / (24 * 60 * 60)) * (24 * 60 * 60); / / Початок сьогодні.
for ($i=0; $line=chop(fgets($fp)); $i++) {
$tmp=split(" ",$line);
/ / Індекс там розраховується хитро, оскільки може бути день,
коли не було голосування.
$ Ind = floor (($ tmp [0] – $ time) / (24 * 60 * 60)); / / Кількість днів від
сьогодні.
$votes[$ind]=$tmp;
/ / Обчислює максимальне значення лічильника. Заносить в $ max.
getMaxVote($tmp);
}
/ / $ Votes містить усі голосування.
?>


<table cellpadding=3 cellspacing=o border=0>
<? for ($i=0; $i<count($cntName); $i++) : ?>
<tr>
<td><? echo $cntName[$i] ?></td>
<td><table cellpadding=0 cellspacing=0 border=0>
<tr>
<? for ($j=0; $j=count($votes); $j++ ?>
<Td width = 8 height = <? echo $ max?> align = bottom> <img
src=”http://www.ishodniki.ru/"bar.gif" width=8
height=<? echo floor($votes[$j][$i+1]*$base/$max) ?>
border=0></td>
<? endif ?>
</tr>
</table></td>
</tr>
<? endif ?>
</table>

У принципі, можна намалювати графіки окремо, під час off-line-обробки –
у вигляді зображень у форматі GIF, використовуючи такі пакети, як ImageMagic або GD,
мають інтерфейс до Perl. PHP (з підключенням модулями графіки) може теж
малювати графіки, і навіть дуже витончені, але це не завдання для
on-line-препроцесора. Ми, наприклад, навіть не включили в on-line-версію PHP
підтримку графічних бібліотек. Хоча зручність PHP спонукало мене зібрати
окремо off-line-інтерпретатор PHP, що працює з командного рядка. Ось у
нього-то і включені всі можливі функції.


Думаю, в цій статті я досить повно описав питання анкетування на
web-сторінках. Як зазвичай буває у таких випадках, почавши з простого завдання, ми
підійшли до більш загальної її постановці. Сподіваюся, що мої міркування трохи
допомогли розібратися в побудові систем голосування не тільки авторам домашніх
сторінок, але і достатньо професійним розробникам …

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*