PHP: создаем собственный сервис геотаргетинга

Владимир | | HTML, MySQL, PHP, Web разработка.

Некоторое время назад я рассказывал об использовании Яндекс.Карт и сервиса IPLoc, который позволяет определить географические координаты посетителя по его IP адресу.

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

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

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

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

В результате я остановился на сервисе WIPmania.com. Он предоставляет и API для получения данных, и базу данных.

Правда полноценной заменой IPLoc его считать нельзя, т.к. он позволяет определить только страну посетителя, а географические координаты (широта и долгота) отсутствуют. Тем не менее, более полные базы я видел только в платных вариантах.

Ограничения WIPmania.

Версия базы данных для скачивания обновляется один раз в два месяца.

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

Т.к. использование API практически не отличается от IPLoc, в этой статье я расскажу только о работе с БД.

1) Загружаем базу данных с официального сайта, тут же можно скачать архив с флагами стран.

Я выбрал архив с SQL версией базы.

Нам нужны 2 файла worldip.sql и worldip.lands.ru.sql. Создаем базу данных и импортируем их.

В результате будут созданы две таблицы:
worldip_land и worldip

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

Во второй таблице – двухбуквенные коды и полные названия стран.

Я использовал таблицу с русскими названиями, но в дистрибутив также входят переводы на английский, немецкий и испанский.

Есть несколько нюансов при импортировании таблиц.

Во-первых, импортирование я выполнял с помощью команды
mysql --user=имя --password=пароль название_базы < имя_файла
Во-вторых, практически весь Файл worldip.sql занимает один SQL запрос, размер которого больше 1 МБ. И при попытке импорта возникала ошибка «MySQL server has gone away».
Чтобы её исправить добавьте в конфигурационный файл (my.ini) строку
max_allowed_packet = 4M
(в раздел [mysqld]).

2) Получение данных

Как не сложно догадаться, в большинстве случаев нам нужно будет определять название страны по известному IP. Кроме того, для того, чтобы показать флаг страны, нужен её двухбуквенный код.

Выполнить эту операцию можно с помощью следующего SQL запроса.

SELECT wl.code, wl.country FROM worldip w LEFT JOIN worldip_land wl ON (w.code=wl.code) WHERE w.start<=ip and w.end>=ip

Чтобы понять, как работает запрос нужно представлять структуру таблиц в базе.
Таблица worldip_land содержит 2 поля:
code – двухбуквенный код страны;
country – название страны.

Таблица worldip содержит 3 поля:
start – начало диапазона IP адресов;
end – конец диапазона IP адресов;
code – двухбуквенный код страны.

Думаю вы догадались, что для связи между таблицами используется поле code.

Теперь разберем запрос. Сначала будет выполнен поиск в таблице worldip диапазона адресов, в который входит заданный ip. Затем его результаты используются для поиска названия страны в таблице worldip_land.

Примечание. На этой странице вы найдете примеры наиболее распространенных запросов.

И в заключение приведу пример небольшого скрипта, который выводит IP адрес посетителя, название страны и её флаг.

<?php
//определяем IP посетителя
$visitorIP = $_SERVER['REMOTE_ADDR'];

//подключение к БД
$dbHost = 'localhost';
$dbName = 'db_name';
$dbUser = 'db_user';
$dbPass = 'db_pass';

try {
	$dbh = new PDO('mysql:host='.$dbHost.';dbname='.$dbName, $dbUser, $dbPass);
	//преобразовываем IP в целое число
	$ip = ip2long($visitorIP);
	if ($ip !== FALSE) {
		//определяем в какой стране находится посетитель и получаем
		$stmt = $dbh->prepare('SELECT wl.code, wl.country FROM worldip w LEFT JOIN worldip_land wl ON (w.code=wl.code) WHERE w.start<=? and w.end>=?');
		$stmt->bindParam(1, $ip);
		$stmt->bindParam(2, $ip);
		$stmt->execute();
		$res = $stmt->fetch(PDO::FETCH_ASSOC);
		if ($res === FALSE) {
			$errMes = 'Местоположение данного IP неизвестно';
		}
	}
	$dbh = null;
}
catch (PDOException $e) {
	$errMes = 'Ошибка: '.$e->getMessage();
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">

<head>
	<title>WIPmania</title>
	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
	<meta http-equiv="Content-Style-Type" content="text/css" />
</head>

<body>
<p>Ваш IP адрес: <?php echo $visitorIP; ?></p>
<p>
<?php
if (isset($errMes)) {
	echo $errMes;
}
else {
	//вставляем название страны и рисунок с флагом
?>
	<img src="flags/<?php echo $res['code']; ?>.png" title="флаг" />
<?php
	echo $res['country'];
}
?>
</p>
</body>
</html>

Общий алгоритм работы скрипта следующий.

1) Определяем IP адрес. И преобразуем его в целочисленную форму (с помощью функции ip2long, строка 14).

2) Отправляем запрос. В этом примере я использовал PDO, но, естественно, вы можете использовать любую другую библиотеку. О принципе работы запроса я уже рассказал.

3) Формируем страницу и показываем на ней:
— IP адрес посетителя (строка 43);
— сообщение об ошибке (если она возникла);
— флаг страны (строка 52);
— и её название (строка 54).

Обратите внимание на вставку рисунка с флагом. Я распаковал архив в папку flags. Имена файлов совпадают с двухбуквенными кодами стран. К ним нужно только добавить расширение (.png).

Если есть желание поэкспериментировать со скриптом – качайте архив с примером.

И в заключение маленькое замечание – адрес 127.0.0.1 в базе отсутствует 😉

До встречи!