Возникла задача, нужно определять город, в котором находится пользователь, пройдемся по таким сервисам:
API Яндекс Локатор - не подходит, отдает не город:
curl -H "Content-Type: application/json" \
-X POST "https://locator.api.maps.yandex.ru/v1/locate?apikey=YOUR_API_KEY" \
-d '{"ip":[{"address":"1.2.3.4"}]}'
результат: {"location":{"point":{"lon":37.6177,"lat":55.755863},"accuracy":100000}}не знаю что не так, но например зная координаты (latitude и longitude) API Геокодер Яндекса отдает информацию.
dadata.ru/api/iplocate/ - не подходит, о некоторых не знает:
curl -X GET -H "Accept: application/json" -H "Authorization: Token XXX" http://suggestions.dadata.ru/suggestions/api/4_1/rs/iplocate/address?ip=102.222.79.255
результат: {"location":null}тоже самое, dadata.ru/api/geolocate/ может рассказать про город, если ему скормить координаты.
Есть ряд крупных сервисов, которые предоставляют базы данных IP адресов/сетей с привязкой к Гео.
Один из них является maxmind.com (попробовать онлайн).
Данный сервис разрешает скачивать базу только тем, кто зарегистрировался.
Он предоставляет 2 версии - бесплатную и платную (вот отличие Lite от не Lite).
Бесплатная версия бывает двух видов:
Самый простой способ испробуем.
1. скачаем mmdb файл по ссылке:
curl -O -J -L -u YOUR_ACCOUNT_ID:YOUR_LICENSE_KEY "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=YOUR_LICENSE_KEY&suffix=tar.gz"2. устанавливаем php-класс работы с mmdb - файлом:
composer require geoip2/geoip23. тестируем:
$reader = new Reader('/app/GeoLite2-City.mmdb');
$record = $reader->city('72.14.201.24');
echo $record->city->names['ru'] ?? '-';Итог:
$result = [];
$faker = Factory::create();
$i = 0;
while($i < 10_000) {
$i++;
try {
$record = $reader->city($faker->ipv4());
$result[] = ($record->city->names['ru'] ?? '-'). PHP_EOL;
} catch (AddressNotFoundException $exception) {
continue; // странные IP-адреса, надо исследовать, база плохая или подсунут нереальный IP-адрес
}
}Кстати, geonameid это ID с сайта geonames.org представляющий ИД-локации (может быть континентом, страной, городом, регионом и т.д. - детальнее.
Через файл csv:
curl -O -J -L -u YOUR_ACCOUNT_ID:YOUR_LICENSE_KEY "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City-CSV&license_key=YOUR_LICENSE_KEY&suffix=zip"О нем пока ничего не скажу, кроме того, что наверняка точность определения у него такая же, как у первого бесплатного варианта.
А еще есть:
Почти тоже самое, и не смотря на то, что обновляется 1 раз в месяц, немного плюсы:
Cделать на своем сайте страницу определения Гео по IP с использованием Symfony можно так:
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class ApiController extends AbstractController
{
#[Route('/api/ip')]
public function ip(Request $request): Response
{
$form = $this->createFormBuilder()
->add('ip', TextType::class, [
'attr' => [
'class' => 'form-control'
]
])
->add('submit', SubmitType::class, [
'attr' => [
'class' => 'btn btn-primary'
]
])
->getForm();
$ip = trim($_POST['form']['ip'] ?? '');
if (!empty($ip)) {
$db = new \IP2Location\Database ('/app/IP2LOCATION-LITE-DB5.BIN', \IP2Location\Database::FILE_IO);
$records = $db->lookup($ip, \IP2Location\Database::ALL);
return $this->render('common/ip.html.twig', [
'form' => $form->createView(),
'records' => empty($records) ? null : $records,
]);
}
return $this->render('common/ip.html.twig', [
'form' => $form->createView()
]);
}
}и шаблон:
{% extends 'base.html.twig' %}
{% block body %}
<style>
h3, p, form {
padding-left: 25px;
}
</style>
<h3>IP2Location Symfony 4 Demo</h3>
<p>This demo uses <a href="/api/redirect?key=c2405108a3c42aa9105c7b77f4631eb3a52af17d&to=https%3A%2F%2Flite.ip2location.com%2Fdatabase%2Fip-country-region-city-latitude-longitude-zipcode-timezone" target="_blank">IP2Location DB11 LITE BIN Data</a> for geolocation lookup. You may download the BIN Data at <a href="/api/redirect?key=9f173c4979a35b2c634389bc7f5e55eb44f897fe&to=https%3A%2F%2Flite.ip2location.com" target="_blank">https://lite.ip2location.com</a> (Free edition) or <a href="/api/redirect?key=025c7beceb1999c58cafe2c144c25a717a257259&to=https%3A%2F%2Fwww.ip2location.com" target="_blank">https://www.ip2location.com</a> (Commercial edition)</p>
{% if form is defined %}
<form id="form" action="" method="POST">
<table>
<tr>
<td>IP Address: </td>
<td style="padding-left:10px">{{ form_widget(form.ip) }}</td>
<td style="padding-left:10px"><input class="btn btn-primary" type="submit" value="Submit" name="submit"/></td>
</tr>
</table>
</form>
{% endif %}
<br/>
{% if records is defined %}
<div>
<table class="table table-bordered table-striped">
<tbody>
<tr>
<td>IP Number: </td>
<td>{{ records.ipNumber }}</td>
</tr>
<tr>
<td>IP Version: </td>
<td>{{ records.ipVersion }}</td>
</tr>
<tr>
<td>IP Address: </td>
<td>{{ records.ipAddress }}</td>
</tr>
<tr>
<td>Country Code: </td>
<td>{{ records.countryCode }}</td>
</tr>
<tr>
<td>Country Name: </td>
<td>{{ records.countryName }}</td>
</tr>
<tr>
<td>Region Name: </td>
<td>{{ records.regionName }}</td>
</tr>
<tr>
<td>City Name: </td>
<td>{{ records.cityName }}</td>
</tr>
<tr>
<td>Time Zone: </td>
<td>{{ records.timeZone }}</td>
</tr>
<tr>
<td>ZIP Code: </td>
<td>{{ records.zipCode }}</td>
</tr>
<tr>
<td>Latitude: </td>
<td>{{ records.latitude }}</td>
</tr>
<tr>
<td>Longitude: </td>
<td>{{ records.longitude }}</td>
</tr>
<tr>
<td>Area Code: </td>
<td>{{ records.areaCode }}</td>
</tr>
<tr>
<td>IDD Code: </td>
<td>{{ records.iddCode }}</td>
</tr>
<tr>
<td>Weather Station Code: </td>
<td>{{ records.weatherStationCode }}</td>
</tr>
<tr>
<td>Weather Station Name: </td>
<td>{{ records.weatherStationName }}</td>
</tr>
<tr>
<td>MCC: </td>
<td>{{ records.mcc }}</td>
</tr>
<tr>
<td>MNC: </td>
<td>{{ records.mnc }}</td>
</tr>
<tr>
<td>Mobile Carrier: </td>
<td>{{ records.mobileCarrierName }}</td>
</tr>
<tr>
<td>Usage Type: </td>
<td>{{ records.usageType }}</td>
</tr>
<tr>
<td>Elevation: </td>
<td>{{ records.elevation }}</td>
</tr>
<tr>
<td>Net Speed: </td>
<td>{{ records.netSpeed }}</td>
</tr>
<tr>
<td>Domain Name: </td>
<td>{{ records.domainName }}</td>
</tr>
<tr>
<td>ISP Name: </td>
<td>{{ records.isp }}</td>
</tr>
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
Как мы видим, видимо из-за того, что координаты городов меняются редко, определение по IP и координатам разделяют.
Сервис download.geonames.org предоставляет нам список городов каждой страны с их координатами, в котором:
Все эти поля расписаны внизу страницы https://download.geonames.org/export/dump/
База geonames.org показала, удивительную вещь - город может быть представлен не один, а несколько раз.
Город Москва может иметь несколько geonameid в файле RU.txt из архива https://download.geonames.org/export/dump/RU.zip по нескольким причинам, связанным с тем, как база данных GeoNames структурирует и классифицирует географические объекты. Вот основные причины:
В GeoNames один и тот же топоним (например, "Москва") может быть привязан к разным географическим объектам, каждый из которых имеет свой уникальный geonameid. Например:
Пример:
Кроме города, в России есть река Москва (Москва-река), которая протекает через город и имеет своё собственное название. В RU.txt она может быть представлена отдельной записью с уникальным geonameid, так как это объект другого типа (H — гидрографический объект, например, река).
Пример:
В России есть несколько населённых пунктов с названием "Москва", помимо столицы. Например, деревни, посёлки или другие небольшие объекты в разных областях могут называться "Москва" в честь столицы или по историческим причинам. Каждый из них получает свой geonameid.
Пример:
В RU.txt поле alternatenames содержит список альтернативных названий для одного объекта, но если в базе данных GeoNames разные записи имеют "Москва" в name или alternatenames, это может привести к путанице при поиске. Однако основная причина нескольких geonameid — это всё же разные объекты, а не просто вариации названия одного и того же объекта.
GeoNames иногда создаёт отдельные записи для разных аспектов одного объекта:
Эти записи могут пересекаться по названию, но различаться по координатам, типу объекта (feature code) или другим параметрам.
Пример записей:
Столица: PPLC — столица, координаты центра Москвы.
524901 Москва Moskva Moscow,Москва́,Мoskva 55.75222 37.61556 P PPLC RU ...
Река: STM — река, немного другие координаты.
524925 Москва Moskva Moskva River,Москва-река 55.75 37.61667 H STM RU ...
Деревня: PPL — населённый пункт, координаты где-то вне столицы.
524930 Москва Moskva Moskva 56.12345 38.98765 P PPL RU ...
Поля feature class и feature code очень занятная вещь, но в своей задаче я не буду брать их в расчет и если база мне будет возвращать Москву, то я просто буду считать, что это столица Москва, а не деревня или река (ведь у меня задача простая, определить место того, кто зашел на сайт и в 99% процентов точность будет достаточной, ведь с деревни Москва на мой сайт скорее всего никто не зайдет, а купаясь в реке точно никто не зайдет).
Оказалось, что база сервиса geonames имеет недостоверные данных, а выяснил это так - с помощью базы ip2location для IP= получил координаты 57.4977, 39.7458, в базе geonames смотрю близлежащие и вижу:
6643231 Ust’yanovo Ust'yanovo 57.17892 40.36569проверяю 57.17892 40.36569 в Яндексе и он говорит, что это:
Советская улица, 84, рабочий посёлок Некрасовское, Ярославская область, 152260ладно, думаю, а вдруг Устьяново это какой-то поселок в в Ярославской области, проверяю в интернете, но нет, такого нет.
База сервиса geonames не подходит по двум причинам:
Итоговое решение
IP2LOCATION-LITE-DB5.BIN с сервиса ip2location.comВсякие полезности: 1 - 2 - 3 - 4
Комментарии
Комментариев пока нет.