Определение географического положения по IP-адресу
Задался я недавно вопросом, как определить страну, город и координаты юзера по его IP-адресу, погуглил немного и вот хочу поделиться результатами моих научных изысканий.
Первым мне на глаза попался сайт http://ipgeobase.ru/ . Скачал я их скрипт и БД. Протестировал и обнаружил:
- Скрипт http://ipgeobase.ru/files/soft/search_cgi.tar.gz при поиске по базе для начала загружает всю базу в память а потом уже производит по ней поиск, что очень плохо по вполне очевидной причине большого и неразумного потребления ресурсов памяти. На моем хостинге этот скрипт вообще отваливался, так и не дорабатывая до конца загрузку базы в память.
- Сама база данных создана совершенно нерациональным образом, обладая неразумной избыточностью. Вы и сами можете оценить этот факт, скачав базу по адресу http://ipgeobase.ru/files/db/Main/db_files.tar.gz
Так что лучше вообще не обращать внимания на предлагаемые алгоритм и базу, а сразу перейти к более корректным методам.
Вот здесь http://www.maxmind.com/ я обнаружил действительно быстрый и эффективный алгоритм определения географического положения пользователя по его IP-адресу.
Причем, эта компания предоставляет как платные, так и бесплатные версии ПО. Платная версия стоит $370, плюс по $90 в месяц за обновления. Бесплатная версия покрывает 99.5% территории, а платная – 99.8% на уровне стран, и 79% и 83% соответственно на уровне городов. Как видим, разница в точности несущественная и для наших скромных некоммерческих целей подойдет и бесплатная версия ПО. Обновления баз для обеих версий производятся раз в месяц, поэтому надо периодически скачивать новую версию базы данных.
Использовать ПО достаточно просто.
- Качаем здесь саму библиотеку.
- Качаем здесь последнюю версию базы данных. В распакованном виде база занимает около 28 мегабайт.
- Заливаем на хостинг файлы GeoLiteCity.dat, geoipcity.inc, geoip.inc, geoipregionvars.php. Пусть они лежат в одной директории на сервере.
- В той же директории создаете файл 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 »
RSS-лента комментариев. Адрес для трекбека


Ваш сайт суперовый!!! Очень интересная и нужная инфа, которая редко где встречается. Все доступно написано и главное: действительно РАБОЧИЕ примеры. Спасибо! Продолжайте в том-же духе! И порекламируйте себя, вас много людей ищет!
Очень понравился этот метод опр. Geo IP. Мы раньше использовали метод реализующий подключение к некой базе данных на удалённом сервере. Попробовали 2 какие то бесплатные, работают не стабильно. Мы зависимы от их сервера. А в данном примере мы работаем с базой лежащей у нас же на сервере. Обновляйся только раз в месяц и всё. Очень удобно.
Но. Немного не разобрался я. Нам бы нужен конечный результат в другом виде. Во первых нам достаточно только кода страны. Во вторых нам нужна именно переменная $country_code, чтобы использовать её дальше в своих скриптах. Что то не сообразил я где взять эту переменную. Не подскажете ли ?
Попробуйте после строки:
$res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
взять из результата нужный Вам код:
$res['country_code'];
Здравствуйте, к сожалению так не получилось.
Но, поразмыслив, я пришёл к изящному решению своей проблемы.
Я просто прерываю исполнение цикла foreach сразу после первой строки массива (а это по любому и есть код страны), и дальше использу просто переменную $val.
$val)
{
if ($key==country_code) break;
}
$my_code = $val;
echo ” $val”;
?>
Извиняюсь, немного не точно изложил свою идею.
Я прерываю цикл не после первой строки, а после того как значение $key будет равно country_code.
Вот полный рабочий код.
$val)
{
if ($key==country_code) break;
}
$my_code = $val;
echo ” $my_code”;
?>
Да, я ошибся. Переменная $res это не ассоциированный массив, а объект. А потому обращение к его полям производится при помощи оператора “->”, таким образом нужно делать так:
$res = GeoIP_record_by_addr($gi,$_SERVER['REMOTE_ADDR']);
echo $res->country_code;
Выражаю, свою благодарность автору! Информация действительно, познавательная. Заранее приношу извинения, но собрать все это, у меня так и не получилось,вероятно все дело в настройках PHP. Подскажите, как должен быть собран PHP,чтоб все корректно отображалось?
Нужно понять, где именно происходит ошибка. У меня, например, с первого раза тоже ничего не заработало
.
Попробуйте в папке, где лежит скрипт, создать файл .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. Просто эти функции уже где-то ранее были объявлены у хостера и происходла попытка переопределения имен.
Ошибка происходит, с возращением аргумента функции foreach(),
В браузере выглядит следующим образом:
“Warning: Invalid argument supplied for foreach()”
Значит у вас установлен 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);
Спасибо за советы; рискну предположить, что ошибка вызываемая в моем случае, связана с отсутствием информации по моему ip в GeoLiteCity.dat
в результате чего функция возращает нулевое значение.
Такой вариант тоже возможен. Мой тестовый пример на то и тестовый, что не проверяет возвращаемый результат на допустимость. Действительно, если айпишник в базе не существует, то функция 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”;
}
Вячеслав, извините за беспокойство. Прекрасная статья, спасибо! Очень помогла! Все доходчиво написано. На счет ошибки все верно, именно с вышеперечисленной проблемой я и столкнулся.(И еще раз мои извинения, в примере выше не ровно легли “кавычки”)
P.S Сайту нужен форум!
Мы прислушаемся к Вашему совету и сделаем форум.
В комментариях действительно wordpress зачем-то заменяет кавычки так что тексты потом перед вставкой в PHP-документы редактировать надо.
Кстати, в предпоследней статье я написал про создание easyAPI с реализацией функций GeoIP, как считаете, насколько полезным может быть подобное начинание? И какие еще полезные сервисы можно реализовать в рамках GeoIP?
Из сервисов задействующих GeoIP, могу порекомендовать разобраться с блогом Андрея Цюпко: http://blog.zupko.info/?p=221&cpage=1#comment-3161
Но он основан на flash если быть точнее на Action Script 3, Flex, программировании. Вобщем, с flex-ом я разобрался, а вот что касается серверной части, есть вопросы…
По поводу создания easyAPI продолжайте трудится,ведь проиграл не тот, у кого не получилось, а тот, кто опустил руки
Неплохо описано.
Название области можно показать так:
echo $GEOIP_REGION_NAME[$res->country_code][$res->region];
Видимо, файл geoipregionvars.php для этого и предназначен.
Да, вы совершенно правы.
Спасибо. Очень хорошая статья. Я воспользовался с небольшой правкой под свои задачи. Подружил 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”.
Если реклама в тему, то мы ее с радостью публикуем.
Особенно, когда она – взаимная… Ваш блог, кстати, по духу сходен с нашим, потому что мы пишем только о том, что попробовали сами.
Статья действительно очень полезная – сейчас такое редкость.
Из отзывов видно, что у всех всё получилось.
Но мне чёт легко эта тема не даётся.
А именно: если в апаче подключен модуль геоайпи, то при выполнении кода возникает ошибка 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.
Одна ерунда – не работает.
Очевидно, существует ещё что-то, что помогает работать данному коду (моды, модули, хитрые настройки апача).
Кто-нибудь встречался с такой ошибкой?
Буду признателен за любые мысли по теме, т.к. мои мысли закончились.
И все таки очень похоже на то, что вашего 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
Ваш скрипт выдал вот что:
country_code=RU;country_code3=RUS;country_name=Russian Federation;region=71;city=Yekaterinburg;latitude=56.85;longitude=60.6;
По ходу, дело в настройках самого сервера.
Айпи определился правильно.
Текущая база весит 30 метров. Именно она и не работает.
Дело в скорее в настройках PHP.
Вам стоит попробовать установить уровень отображения ошибок таким образом чтобы он отображал Errors, Warnings & Notices, возможно это даст дополнительную информацию к размышлению. Проверьте еще также совпадает ли размер базы с точностью до байта.