Определение географического положения по IP-адресу

Автор: Вячеслав Гринин | веб-программирование | 10 Мар 2009 6:34 пп

Задался я недавно вопросом, как определить страну, город и координаты юзера по его IP-адресу, погуглил немного и вот хочу поделиться результатами моих научных изысканий.

Первым мне на глаза попался сайт http://ipgeobase.ru/ . Скачал я их скрипт и БД. Протестировал и обнаружил:

  1. Скрипт http://ipgeobase.ru/files/soft/search_cgi.tar.gz при поиске по базе для начала загружает всю базу в память а потом уже производит по ней поиск, что очень плохо по вполне очевидной причине большого и неразумного потребления ресурсов памяти. На моем хостинге этот скрипт вообще отваливался, так и не дорабатывая до конца загрузку базы в память.
  2. Сама база данных создана совершенно нерациональным образом, обладая неразумной избыточностью. Вы и сами можете оценить этот факт, скачав базу по адресу http://ipgeobase.ru/files/db/Main/db_files.tar.gz

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

Вот здесь http://www.maxmind.com/ я обнаружил действительно быстрый и эффективный алгоритм определения географического положения пользователя по его IP-адресу.

Причем, эта компания предоставляет как платные, так и бесплатные версии ПО. Платная версия стоит $370, плюс по $90 в месяц за обновления. Бесплатная версия покрывает 99.5% территории, а платная – 99.8%  на уровне стран, и 79% и 83% соответственно на уровне городов. Как видим, разница в точности несущественная и для наших скромных некоммерческих целей подойдет и бесплатная версия ПО. Обновления баз для обеих версий производятся раз в месяц, поэтому надо периодически скачивать новую версию базы данных.

Использовать ПО достаточно просто.

  1. Качаем здесь саму библиотеку.
  2. Качаем здесь последнюю версию базы данных. В распакованном виде база занимает около 28 мегабайт.
  3. Заливаем на хостинг файлы GeoLiteCity.dat, geoipcity.inc, geoip.inc, geoipregionvars.php. Пусть они лежат в одной директории на сервере.
  4. В той же директории создаете файл whereami.php имеющий следующее содержимое:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Где я?</title>
</head>
<body>
<?php
include "geoipcity.inc";
$gi = geoip_open("GeoLiteCity.dat", GEOIP_STANDARD);
$res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
geoip_close($gi);
foreach ($res as $key => $val)
{
    print "$key = $val<br>\n";
}
?>
</body>
</html>

Как можно видеть, в строке 8 подключается сама библиотека, в строке 9 создается объект GeoIP, которому в качестве параметра передается имя файла, содержащего базу данных, в строке 10 происходит сам поиск по базе данных. Функции GeoIP_record_by_addr в качестве параметров передаются объект GeoIP и IP-адрес (в данном случае это адрес хоста, вызвавшего страницу, то есть адрес пользователя, открывшего в бразуере вашу страницу). В строке 11 происходит корректное освобождение ресурсов, занятых под объект GeoIP. Следующий цикл foreach выводит на экран все поля объекта, который вернула функция GeoIP_record_by_addr.

Структура результата имеет вид:

country_code = RU
country_code3 = RUS
country_name = Russian Federation
region = 48
city = Moscow
postal_code =
latitude = 55.7522
longitude = 37.6156
area_code =
dma_code =

Вот и все. Не боги горшки обжигают.

Прочтите еще:

Отзывов: 23 »

  1. Отзыв Cаня — Июнь 14, 2009 в 4:36 пп

    Ваш сайт суперовый!!! Очень интересная и нужная инфа, которая редко где встречается. Все доступно написано и главное: действительно РАБОЧИЕ примеры. Спасибо! Продолжайте в том-же духе! И порекламируйте себя, вас много людей ищет!

  2. Отзыв Juri — Октябрь 7, 2009 в 12:25 пп

    Очень понравился этот метод опр. Geo IP. Мы раньше использовали метод реализующий подключение к некой базе данных на удалённом сервере. Попробовали 2 какие то бесплатные, работают не стабильно. Мы зависимы от их сервера. А в данном примере мы работаем с базой лежащей у нас же на сервере. Обновляйся только раз в месяц и всё. Очень удобно.
    Но. Немного не разобрался я. Нам бы нужен конечный результат в другом виде. Во первых нам достаточно только кода страны. Во вторых нам нужна именно переменная $country_code, чтобы использовать её дальше в своих скриптах. Что то не сообразил я где взять эту переменную. Не подскажете ли ?

  3. Отзыв Вячеслав Гринин — Октябрь 8, 2009 в 7:09 пп

    Попробуйте после строки:
    $res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
    взять из результата нужный Вам код:
    $res['country_code'];

  4. Отзыв Juri — Октябрь 9, 2009 в 4:35 пп

    Здравствуйте, к сожалению так не получилось.
    Но, поразмыслив, я пришёл к изящному решению своей проблемы.
    Я просто прерываю исполнение цикла foreach сразу после первой строки массива (а это по любому и есть код страны), и дальше использу просто переменную $val.

    $val)
    {
    if ($key==country_code) break;
    }

    $my_code = $val;
    echo ” $val”;

    ?>

  5. Отзыв Juri — Октябрь 9, 2009 в 4:41 пп

    Извиняюсь, немного не точно изложил свою идею.
    Я прерываю цикл не после первой строки, а после того как значение $key будет равно country_code.
    Вот полный рабочий код.

    $val)
    {
    if ($key==country_code) break;
    }

    $my_code = $val;
    echo ” $my_code”;
    ?>

  6. Отзыв Вячеслав Гринин — Октябрь 9, 2009 в 6:20 пп

    Да, я ошибся. Переменная $res это не ассоциированный массив, а объект. А потому обращение к его полям производится при помощи оператора “->”, таким образом нужно делать так:
    $res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
    echo $res->country_code;

  7. Отзыв Vermut — Октябрь 11, 2009 в 4:02 пп

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

  8. Отзыв Вячеслав Гринин — Октябрь 11, 2009 в 5:51 пп

    Нужно понять, где именно происходит ошибка. У меня, например, с первого раза тоже ничего не заработало :) .
    Попробуйте в папке, где лежит скрипт, создать файл .htaccess с таким содержимым:
    php_flag display_errors 1
    php_flag display_startup_errors 1
    чтобы PHP Отдавал вам ошибки прямо в браузер.
    В моем случае в файле geoip.inc мне пришлось переименовать функции: geoip_country_name_by_name и geoip_country_code_by_name, я просто приписал им суффиксы 2. Просто эти функции уже где-то ранее были объявлены у хостера и происходла попытка переопределения имен.

  9. Отзыв Vermut — Октябрь 12, 2009 в 6:09 дп

    Ошибка происходит, с возращением аргумента функции foreach(),
    В браузере выглядит следующим образом:
    “Warning: Invalid argument supplied for foreach()”

  10. Отзыв Вячеслав Гринин — Октябрь 12, 2009 в 8:34 дп

    Значит у вас установлен PHP версии ниже пятой. Дело в том, что именно в PHP5 встроены итераторы для свойств классов, годные для использования в циклах foreach. Для решения вашей проблемы вам нужно либо перейти на PHP5, либо вместо цикла foreach использовать доступ к нужным полям сразу по имени, вот так:

    $gi = geoip_open(”GeoLiteCity.dat”, GEOIP_STANDARD);
    $res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
    geoip_close($gi);

    echo(”country_code=”.$res->country_code);
    echo(”country_code3=”.$res->country_code3);
    echo(”country_name=”.$res->country_name);
    echo(”region=”.$res->region);
    echo(”city=”.$res->city);

  11. Отзыв Vermut — Октябрь 12, 2009 в 4:20 пп

    Спасибо за советы; рискну предположить, что ошибка вызываемая в моем случае, связана с отсутствием информации по моему ip в GeoLiteCity.dat
    в результате чего функция возращает нулевое значение.

  12. Отзыв Вячеслав Гринин — Октябрь 12, 2009 в 6:00 пп

    Такой вариант тоже возможен. Мой тестовый пример на то и тестовый, что не проверяет возвращаемый результат на допустимость. Действительно, если айпишник в базе не существует, то функция GeoIP_record_by_addr возвращает нулевое значение и цикл foreach выдает Warning: Invalid argument supplied for foreach(). Поэтому предлагаю немного модифицировать пример:
    include “geoipcity.inc”;
    $gi = geoip_open(”GeoLiteCity.dat”, GEOIP_STANDARD);
    $res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);geoip_close($gi);
    if(!$res)
    {
    echo(”IP в базе не обнаружен”);
    return;
    }
    foreach ($res as $key => $val)
    {
    print “$key = $val
    \n”;
    }

  13. Отзыв Vermut — Октябрь 13, 2009 в 6:25 дп

    Вячеслав, извините за беспокойство. Прекрасная статья, спасибо! Очень помогла! Все доходчиво написано. На счет ошибки все верно, именно с вышеперечисленной проблемой я и столкнулся.(И еще раз мои извинения, в примере выше не ровно легли “кавычки”)
    P.S Сайту нужен форум!

  14. Отзыв Вячеслав Гринин — Октябрь 13, 2009 в 9:18 дп

    Мы прислушаемся к Вашему совету и сделаем форум. :)
    В комментариях действительно wordpress зачем-то заменяет кавычки так что тексты потом перед вставкой в PHP-документы редактировать надо.
    Кстати, в предпоследней статье я написал про создание easyAPI с реализацией функций GeoIP, как считаете, насколько полезным может быть подобное начинание? И какие еще полезные сервисы можно реализовать в рамках GeoIP?

  15. Отзыв Vermut — Октябрь 13, 2009 в 11:09 дп

    Из сервисов задействующих GeoIP, могу порекомендовать разобраться с блогом Андрея Цюпко: http://blog.zupko.info/?p=221&cpage=1#comment-3161
    Но он основан на flash если быть точнее на Action Script 3, Flex, программировании. Вобщем, с flex-ом я разобрался, а вот что касается серверной части, есть вопросы…
    По поводу создания easyAPI продолжайте трудится,ведь проиграл не тот, у кого не получилось, а тот, кто опустил руки :)

  16. Отзыв wins — Октябрь 22, 2009 в 5:57 пп

    Неплохо описано.
    Название области можно показать так:
    echo $GEOIP_REGION_NAME[$res->country_code][$res->region];
    Видимо, файл geoipregionvars.php для этого и предназначен.

  17. Отзыв Вячеслав Гринин — Октябрь 25, 2009 в 4:41 пп

    Да, вы совершенно правы. :)

  18. Отзыв mitas — Февраль 19, 2010 в 8:00 дп

    Спасибо. Очень хорошая статья. Я воспользовался с небольшой правкой под свои задачи. Подружил GeoIP и Google Maps API. Как раз то что мне нужно было получилось. Если интересно вот определитель местоположения http://mitasych.com/speed-test-ip, а вот описание http://mitasych.com/kak-opredelit-geograficheskoe-polozhenie-po-ip-i-svyazat-ego-s-google-maps Не сочтите за рекламу. Это я по теме “какие еще полезные сервисы можно реализовать в рамках GeoIP”.

  19. Отзыв admin — Февраль 19, 2010 в 9:23 дп

    Если реклама в тему, то мы ее с радостью публикуем. :) Особенно, когда она – взаимная… Ваш блог, кстати, по духу сходен с нашим, потому что мы пишем только о том, что попробовали сами.

  20. Отзыв Олег — Май 7, 2010 в 7:18 дп

    Статья действительно очень полезная – сейчас такое редкость.
    Из отзывов видно, что у всех всё получилось.
    Но мне чёт легко эта тема не даётся.
    А именно: если в апаче подключен модуль геоайпи, то при выполнении кода возникает ошибка 500.
    Её действительно можно избежать двумя путями: либо менять названия функций, либо убирать подключение модуля.
    Но дальше этого я не смог уйти.
    У меня определяется только часть данных, а именно:
    country_code = RU
    country_code3 = RUS
    country_name = Russian Federation
    region =
    city =
    postal_code =
    latitude = 60
    longitude = 100
    area_code =
    dma_code =
    При этом, на данному сайти, и на сайте http://mitasych.com/speed-test-ip мой айпи определяется, и показыает координаты адекватно.
    Думал, что бд устарела. Скачал новую. Бесполезно. Может права не те- установил 777.
    Одна ерунда – не работает.
    Очевидно, существует ещё что-то, что помогает работать данному коду (моды, модули, хитрые настройки апача).
    Кто-нибудь встречался с такой ошибкой?
    Буду признателен за любые мысли по теме, т.к. мои мысли закончились.

  21. Отзыв admin — Май 7, 2010 в 8:58 дп

    И все таки очень похоже на то, что вашего IP в базе нет. Чаще всего в этом случае БД возвращает очень круглые координаты (60, 100). Ваш IP моим блогом определился, как 217.114.4.178. Попробуйте, зайдите на мой сервис EasyAPI вот по этой ссылке http://easyapi.ru/geoip/get.php и посмотрите, выдаст ли он название города. А потом еще попробуйте вызвать его задав жестко Ваш IP http://easyapi.ru/geoip/get.php?ip=217.114.4.178 и посмотрите что выйдет. Сравните, кстати IP которые выдаются моим скриптом и скриптом на http://mitasych.com/speed-test-ip. У меня например EasyAPI про Ваш IP адрес сказал следующее: country_code=RU;country_code3=RUS;country_name=Russian Federation;region=71;city=Yekaterinburg;latitude=56.85;longitude=60.6;

    Собираюсь, кстати в ближайшем будущем модифицировать http://easyapi.ru/geoip/get.php чтобы он выдавал еще и русские названия городов(по возможности), а также русские названия области и страны.

    Можете в своих целях пользоваться сервисом http://easyapi.ru/geoip/get.php, его описание вот здесь http://easy4web.ru/?p=607

  22. Отзыв Олег — Май 7, 2010 в 9:14 пп

    Ваш скрипт выдал вот что:

    country_code=RU;country_code3=RUS;country_name=Russian Federation;region=71;city=Yekaterinburg;latitude=56.85;longitude=60.6;

    По ходу, дело в настройках самого сервера.
    Айпи определился правильно.
    Текущая база весит 30 метров. Именно она и не работает.
    Дело в скорее в настройках PHP.

  23. Отзыв admin — Май 10, 2010 в 9:13 пп

    Вам стоит попробовать установить уровень отображения ошибок таким образом чтобы он отображал Errors, Warnings & Notices, возможно это даст дополнительную информацию к размышлению. Проверьте еще также совпадает ли размер базы с точностью до байта.

RSS-лента комментариев. Адрес для трекбека

Ваш отзыв