Здравствуйте уважаемые Хабравчане! Создав свой сайт, я добавил на него счетчик посещений с какого-то сервиса (что-то типа qoo...), он работал с JS. И частенько обновлялся. Я не мог посмотреть, сколько в действительности было посетителей на сайте. Также я хотел знать, с каких IP-адресов ко мне заходят, а также к какой стране этот IP относится. Чтобы не создавать велосипед решил погулять на форумах. Нашел много разной информации в том числе и про SxGeo. Мне понравился данный вариант, так как там можно сразу узнать и страну и город. Так как я только начинаю изучать PHP и таких людей наверно много, я решил выложить данную статью. Только не кусочек кода, отвечающий за определение страны и города, а полностью рабочий код. Итак, начнем.



Для начала необходимо создать таблицу в БД куда будут заноситься данные о посетителях. У меня все поля кроме id, идут с типом varchar 255 и сравнением utf8_general_ci, для id тип int 10 атрибут UNSIGNED и конечно же A/I. Теперь создадим конфигурационный файл в котором запишем параметры подключения к нашей БД. И наконец скачиваем SxGeo.php и SxGeoCity.dat с сайта sypexgeo.net ссылки на оба файла: раз и два.


В SxGeo.php на 55 строке при обращении к библиотеке необходимо заменить название файла с SxGeo.dat на SxGeoCity.dat. Все, подготовительные работы окончены.


Создаем файл stats.php (название файла на ваш выбор) и в нем устанавливаем соединение с нашей БД:


<?php
include_once ('config.php');
include_once ('SxGeo.php'); 
$mysqli = new mysqli($server, $user, $pswd, $db);
if ($mysqli->connect_errno) {
printf("Не удалось подключиться: %s\n", $mysqli->connect_error);
exit();}
$mysqli -> select_db($db) or die ("Невозможно открыть $db");
if (!$mysqli->set_charset("utf8")) {
    printf("Ошибка при загрузке набора символов utf8: %s\n", $mysqli->error);
    exit();}

Теперь через функцию getRealIpAddr() получаем ip-клиента:


public function getRealIpAddr() {
    if (!empty($_SERVER['HTTP_CLIENT_IP']))        // Определяем IP
    { $ip=$_SERVER['HTTP_CLIENT_IP']; }
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    { $ip=$_SERVER['HTTP_X_FORWARDED_FOR']; }
    else { $ip=$_SERVER['REMOTE_ADDR']; }
    return $ip;}

Следом проверяем, не бот ли к нам зашел в гости:


if (strstr($_SERVER['HTTP_USER_AGENT'], 'YandexBot')) {$visitor='YandexBot';} //Выявляем поисковых ботов
elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Googlebot')) {$visitor='Googlebot';}
else { $visitor=$_SERVER['HTTP_USER_AGENT']; }

и передаем в $ip значение функции getRealIpAddr().


$ip = getRealIpAddr();

Теперь создаем объект SxGeo и определяем город, страну и регион:


$SxGeo = new SxGeo('SxGeoCity.dat', SXGEO_BATCH | SXGEO_MEMORY);
$result = $SxGeo->getCityFull($ip); 

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


$city = $result["city"]["name_ru"];
$region = $result["region"]["name_ru"];
$country = $result["country"]["name_ru"];
unset($SxGeo);

Теперь определяем дату, время и страницу сайта, на которую заходили.


$date = date("H:i:s d.m.Y");        // определяем дату и время события
$host = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];    // определяем страницу сайта

Экранируем специальные символы в строке для использования в SQL выражении и заносим наши данные в таблицу.


$ip   = $mysqli->real_escape_string($ip);
$date = $mysqli->real_escape_string($date);
$host  = $mysqli->real_escape_string($host);
$region = $mysqli->real_escape_string($region);
$country = $mysqli->real_escape_string($country);
$city = $mysqli->real_escape_string($city);
$visitor = $mysqli->real_escape_string($visitor);
if ($query = $mysqli -> query("INSERT INTO `pre_visitors` (date, visitor, ip, country, region, city, host) VALUES ('".$date."', '".$visitor."', '".$ip."', '".$country."', '".$region."', '".$city."', '".$host."')")){
    //printf("%d строк вставлено.\n", $mysqli->affected_rows);
}
else{
printf("Errorcode: %d\n", $mysqli->errno);
};

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


1. Для тех, кто зашел впервые или с другого ip-адреса:


if ($result1 = $mysqli -> query("SELECT * FROM `pre_visitors` WHERE (visitor NOT RLIKE  'bot') GROUP BY ip ORDER BY 'id'")){
//printf("%d строк вставлено.\n", $result->affected_rows);
}
$res1 = $result1 -> num_rows;

2. Для всего количества просмотров:


if ($result = $mysqli -> query("SELECT  MAX(id) AS id  FROM `pre_visitors` ORDER BY id")){
//printf("%d строк вставлено.\n", $result->affected_rows);
}
$res = $result -> fetch_array();

Ну и закрываем соединение с БД:


$mysqli -> close();
?>

Кстати, не забываем подключить наш файл stats.php к index.php. Теперь визуализируем нашу таблицу. Создаем файл seestats.php и подключаемся к нашей БД:


<?php
include_once ('core/config_class.php');
$mysqli = new mysqli($server, $user, $pswd, $db) or die("Не могу соединиться с MySQL.");
$mysqli -> select_db($db) or die("Не могу подключиться к базе.");
?>

Теперь немного HTML, чтобы вывести все это на экран. Я еще добавил выбор количества выводимых строк.


<html>
    <head>
        <style type='text/css'>
            td.zz {PADDING-LEFT: 3px; FONT-SIZE: 9pt; PADDING-TOP: 2px; FONT-FAMILY: Arial; }
        </style>
    </head>

<body>
<center>
    <form action="seestats.php" method="get">
        <select name="visualcount">
            <option value="10" selected></option>
            <option value="50">50</option>
            <option value="100">100</option>
            <option value="250">250</option>
            <option value="500">500</option>
            <option value="1000">1000</option>
        </select>
        <input type="button" value="Запросить">
    </form>

Далее обрабатываем нашу форму, запрашиваем все поля с нашей таблицы исключая ботов и повторяющиеся ip-адреса, также запрашиваем MAX id для концовки нашей таблицы.


<?php
$lastid =  isset ($_GET['visualcount']) ? ($_GET['visualcount']) : '10';
if ($query = $mysqli->query("SELECT * FROM `pre_visitors`
WHERE (visitor NOT RLIKE  'bot') 
GROUP BY ip 
ORDER BY 'id' 
DESC LIMIT $lastid")){
    //printf("%d строк получено.\n", $mysqli->affected_rows);
}
else{
printf("Errorcode: %d\n", $mysqli->errno);
};

if ($result = $mysqli -> query("SELECT  MAX(id) AS id  FROM `pre_visitors` ORDER BY id")){
//printf("%d строк получено.\n", $result->affected_rows);
}
$num_rows = $query -> num_rows;
$res = $result -> fetch_array();
?>

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


<table width="680" cellspacing="1" cellpadding="1" border="0"
           STYLE="table-layout:fixed">
        <tr align = "center" bgcolor="#eeeeee">
            <td class="zz" width="30"><b>№ п/п</b></td>
            <td class="zz" width="90"><b>Время и дата</b></td>
            <td class="zz" width="500"><b>Данные о посетителе</b></td>
            <td class="zz" width="100"><b>IP/прокси</b></td>
            <td class="zz" width="100"><b>Страна</b></td>
            <td class="zz" width="100"><b>Регион</b></td>
            <td class="zz" width="100"><b>Город</b></td>
            <td class="zz" width="110"><b>Посещенный URL</b></td>
        </tr>

Теперь через цикл заполняем нашу таблицу и закрываем соединение с БД:


<?php
while($row = $query -> fetch_array()){
    echo '<tr bgcolor="#eeeeee"><td class="zz">'.$row['id'].'</td>';
    echo '<td class="zz">'.$row['date'].'</td>';
    echo '<td class="zz">'.$row['visitor'].'</td>';
    echo '<td class="zz">'.$row['ip'].'</td>';
    echo '<td align = "center" class="zz">'.$row['country'].'</td>';
    echo '<td align = "center" class="zz">'.$row['region'].'</td>';
    echo '<td align = "center" class="zz">'.$row['city'].'</td>';
    echo '<td class="zz">'.$row['host'].'</td></tr>';
}

echo '<tr bgcolor="#eeeeee"><td class="zz" colspan = "3" ><b>Количество новых посетителей</b></td>';
echo '<td align = "center"><b>'.$num_rows.'</b></td>';
echo '<td class="zz" colspan = "3" ><b>Общее количество посещений</b></td>';
echo '<td align = "center"><b>'.$res['id'].'</b></td></tr>';
echo '</table>';
echo '</center>';
echo '</body></html>';
$mysqli -> close();
?>

Жаль только, что вы будете получать IP прокси-сервера, а не реального клиента.
Спасибо за внимание!

Поделиться с друзьями
-->

Комментарии (31)


  1. MaZaNaX
    26.10.2016 16:22
    +7

    Вернулся в 2007й после прочтения статьи. А как же composer, PSR-ы и прочие крутые штуки? T_T

    Стаью нужно было назвать «Как не стоит делать в 2016м»


  1. netrain
    26.10.2016 16:30
    +1

    По теме написано ровно две строчки ($SxGeo = new SxGeo и далее), да и те находятся по первой же ссылке в Гугле.

    MAX(`id`) и общее количество посещений — совершенно разные понятия, никак не связанные.
    Делать полную выборку (SELECT *) только ради подсчета строк — не самое умное решение, мягко говоря.
    Про подключение к mysql из php все написано в документации по самому php, и даже больше, чем здесь.

    К чему целая статья на Хабре?


    1. T_verdisla_V
      26.10.2016 16:41
      -1

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


      1. f0rk
        26.10.2016 17:32
        +2

        Конструктивная критика и предложения по улучшению и оптимизации данного кода приветствуются.

        Предлагаю все выкинуть и поставить код google analytics на страницу.


      1. netrain
        26.10.2016 18:57
        +2

        Так может быть, надо было искать не решение конкретной задачи со всеми ее особенностями, а решение класса подобных задач, да изучать основы?
        Банальный запрос «подсчет числа строк mysql» в Гугле первой же ссылкой дает описание COUNT(). Так что непонятно, как вы искали.
        Что касается улучшений и оптимизации, то улучшать здесь ничего не надо. Надо переписать все полностью.


  1. Nord001
    26.10.2016 16:31
    +2

     echo '<td class="zz">'.$row['visitor'].'</td>';
    


    Зря выводите так — в UA можно передать всё что угодно, JS код, например, который выполнится.


    1. T_verdisla_V
      26.10.2016 16:44
      -3

      Конструктивная критика и предложения по улучшению и оптимизации данного кода приветствуются.


  1. hamnsk
    26.10.2016 16:35
    +1

    про PDO вы наверное не в курсе))) mysql_connect это сильно)


    1. T_verdisla_V
      26.10.2016 16:36
      -3

      У меня PHP 7 подходит только mysqli_connect и иже с ними


      1. MaZaNaX
        26.10.2016 16:41
        +2

        простите, но http://php.net/manual/ru/pdo.installation.php

        PDO и драйвер PDO_SQLITE включены по умолчанию в PHP, начиная с версии 5.1.0.


    1. MaZaNaX
      26.10.2016 16:37
      +1

      все же здесь mysqli, но за PDO обидно, да


      1. T_verdisla_V
        26.10.2016 16:43
        -1

        чем вам ООП не угодило?


        1. MaZaNaX
          26.10.2016 16:45

          Если говорить в контексте статьи, здесь нет даже намека на ООП. К тому же, я не заявлял, что меня не устраивает ООП, вам показалось


          1. T_verdisla_V
            26.10.2016 16:50
            -2

            Ну нравиться мне так больше писать

            new mysqli, $misqli -> query()
            

            что ж теперь поделаешь.
            Лучше бы подсказали может это можно как то улучшить, оптимизировать и т.д…


            1. MaZaNaX
              26.10.2016 16:55
              +2

              Так о чем и речь. Я подсказываю, как можно улучшить: использовать PDO с его prepared statements и возможностью работать не только с MySQL. Конкатенация запросов ни к чему хорошему не приводит.

              К тому же, если вы такой ярый сторонник ООП, PDO вам должен только понравиться. Скажу по секрету, там тоже создаются инстансы классов, с которым потом вы работаете. Почитайте, в этом нет ничего плохого :)


            1. webmasterx
              26.10.2016 16:59
              +6

              Извините, но мы не станем. Откройте сами книжки по mysql, php и c и прочтите их. Вы только сделали первый шаг. Но это не значит что вы можете уже учить кого-то что-то делать.
              Не понимаю — почему НЛО пропускает такое?


              1. MaZaNaX
                26.10.2016 17:03
                +2

                Самое обидное, что такое чаще всего и пишут в книжках по mysql и php. Тут скорее можно порекомендовать изучать исходники проектов на github.


              1. ns5d
                26.10.2016 17:04
                -3

                чтобы повысить самоеценку кармодрочеров


  1. kesn
    26.10.2016 16:38
    +2

    //Выявляем поисковых ботов
    if (strstr($_SERVER['HTTP_USER_AGENT'], 'YandexBot')) {
        $visitor='YandexBot';
    } elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'Googlebot')) {
        $visitor='Googlebot';
    } else { 
        $visitor=$_SERVER['HTTP_USER_AGENT'];
    }

    "Капитан Очевидность выявляет ботов" :)


    $ip   = $mysqli->real_escape_string($ip);
    $date = $mysqli->real_escape_string($date);
    $host  = $mysqli->real_escape_string($host);
    $region = $mysqli->real_escape_string($region);
    $country = $mysqli->real_escape_string($country);
    $city = $mysqli->real_escape_string($city);
    $visitor = $mysqli->real_escape_string($visitor);
    if ($query = $mysqli -> query("INSERT INTO `pre_visitors` (date, visitor, ip, country, region, city, host) VALUES ('".$date."', '".$visitor."', '".$ip."', '".$country."', '".$region."', '".$city."', '".$host."')")){
        //printf("%d строк вставлено.\n", $mysqli->affected_rows);
    }

    Неблагодарная работа у программистов! И это вам повезло ещё! А представляете, если бы ещё и адрес выводился, вроде address_line1, address_line2, ..., address_line4, postal_code, district?


    <td class="zz">

    "zz" слишком длинное название для класса, попробуйте "z".


    PADDING-LEFT

    Если писать капслоком, то CSS выглядит брутальней.


    Пишите ещё, но лучше по пятницам!


    1. ns5d
      26.10.2016 17:09
      -6

      накинулсь… composer, PSR, PDO, ОПП… профессионалы млять собрались… вроде бы и правильно говорят, но когда в таком тоне, нах** всех охота послать.


      1. kesn
        26.10.2016 18:28

        Воспринимать критику тоже нужно уметь


      1. ns5d
        27.10.2016 17:26
        -1

        по количество дизлайков легко можно вычислить количество профессиАналов.


  1. cjbars
    26.10.2016 17:32
    +4

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

    Доколе чорд побери.


  1. Dreyk
    26.10.2016 18:15
    +3

    люди всю жизнь считают, что пхп — плохой язык, из-за такого кода =(


  1. smurov
    26.10.2016 18:21

    Статья смахивает на тролинг


  1. KlimovDm
    26.10.2016 21:56
    +1

    Откровенно говоря, мне печальнее читать комментарии, чем саму статью. Да, автор еще в «детском саду». Однако все там были. Да и надумано многое. Composer… А он тут нужен вообще? PDO… Ну автор использовал mysqli — не возбраняется и даже рекомендуется в документации. PSR… Неплохая рекомендация, но всего лишь рекомендация…
    А то, что НЛО пропускает… так это дело НЛО, какой уровень сайта держать.


    1. T_verdisla_V
      27.10.2016 06:45

      Вы правы я только месяц изучаю PHP.


  1. ponich
    27.10.2016 06:40

    чем метрика то не угодила?


    1. T_verdisla_V
      27.10.2016 06:40

      вы о чем?


      1. cjbars
        27.10.2016 08:55
        +2

        Ну как минимум можно было использовать вот эти сильнейшие разработки:
        https://metrika.yandex.ru
        http://www.liveinternet.ru/add
        https://www.google.com/analytics/

        Ведь там целые отделы работают над качеством измерения параметров. Люди собирают конференции, работают с огромными данными. Предоставляются совершенно невообразимые отчеты.
        Мне кажется можно было сперва просто посмотреть на эти сервисы.

        PS по теме статьи. Я прекрасно понимаю что вы учитесь, и делаете первые шаги, сталкиваясь с первыми трудностями, и испытываете первые радости решения этих трудностей. Вам хочется поделиться радотью с такими же начинающими программистами как вы. Это похвально, и так держать!
        Но есть и грустная составляющаяя этой статьи. Многие читающие хабр люди в разы продвинутее вас, они потратили многие тысячи человекочасов на решение подобных проблем. И ваша статья выглядит откровенным плевком в лицо таким людям.
        Мне кажется нужно в самом начале статьи предупреждать о том, что дальшейшее исключительно для новичков.А еще лучше не выкладывать подобные статьи, ведь их начнут копипастить все кому не лень, и потом подобные решения начнут расползаться по небольшим проектикам.


        1. KlimovDm
          27.10.2016 10:14

          >>>А еще лучше не выкладывать подобные статьи, ведь их начнут копипастить все кому не лень, и потом подобные решения начнут расползаться по небольшим проектикам.

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