СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Граббер 2GIS в семь строчек кода, или почему важно контролировать лимиты запросов на сервер в черновиках Из песочницы

Наверное любому из тех, кто хоть как-то причастен к области анализа данных хотя-бы раз приходилось сталкиваться с поиском сторонних источников получения этих самых данных. Сегодня я хотел бы поделиться с Вами одним из самых неожиданных для меня мест, где эти данные лежат почти что на поверхности, да еще и в огромных количествах. Знакомьтесь — это 2GIS.


Image


Как ты это сделал?


Итак, первым делом заходим на сайт 2GIS, вводим случайный адрес и открываем режим разработчика, работа с сетью. Нас интересует вкладка XHR(Он же XMLHttpRequest). Данный запрос предоставляет клиенту функциональность для обмена данными между клиентом и сервером. Более подробна его работа описана здесь.


image


Видим, что есть запросы нескольких типов:


  • get — Запрос на получение информации об объекте по его id;
  • items — Запрос на получение списка объектов по строке поиска;
  • markers — Запрос на получение информации о значках и их расположении на карте;
  • count — Запрос на получение ссылок на фотографии с данного места (могу ошибаться);
  • bss — Запрос на построение отдельных полигонов карты (могу ошибаться);
  • poi — Запрос на получение информации об отдельных полигонах на карте.

Нас интересует первые два запроса, а именно — запрос items, и запрос get. Недолго думая, полностью копируем первый, вставляем его в браузерную строку, и получаем тот самый JSON ответ, в котором хранится вся информация по запросу "офис компании 2gis". Делаем однозначный вывод: Если можно напрямую отправлять запросы на сервер и получать от него ответ, то это действие можно автоматизировать. Но, давайте для начала разберем, из чего состоит сам запрос:


items запрос
https://catalog.api.2gis.ru/3.0/items?
viewpoint1=37.28485099218749%2C55.77155201664903
&viewpoint2=37.95501700781249%2C55.73561570631377
&type=street%2Cadm_div.city%2Ccrossroad%2Cadm_div.settlement%2Cstation%2Cbuilding%2Cadm_div.district%2Croad%2Cadm_div.division%2Cadm_div.region%2Cadm_div.living_area%2Cattraction%2Cadm_div.place%2Cadm_div.district_area%2Cbranch%2Cparking%2Cgate%2Croute
&page=1
&page_size=12
&q=офис%20компании%202gis
&locale=ru_RU
&fields=request_type%2Citems.adm_div%2Citems.context%2Citems.attribute_groups%2Citems.contact_groups%2Citems.flags%2Citems.address%2Citems.rubrics%2Citems.name_ex%2Citems.point%2Citems.geometry.centroid%2Citems.region_id%2Citems.segment_id%2Citems.external_content%2Citems.org%2Citems.group%2Citems.schedule%2Citems.timezone_offset%2Citems.ads.options%2Citems.stat%2Citems.reviews%2Citems.purpose%2Csearch_type%2Ccontext_rubrics%2Csearch_attributes%2Cwidgets%2Cfilters
&stat%5Bsid%5D=91e1c495-9e55-4ca9-8712-e15073071f6e
&stat%5Buser%5D=a8e546d0-291f-4778-bc72-1f84d55dcdfc
&key=ruoedw9225
&r=1831242903

Перед нами самый обычный GET запрос. Для удобства я предварительно разделил его на части. Взглянем на него и разберемся в деталях:


  • viewpoint1, viewpoint2 — это непосредственные координаты нашего окна карты;
  • type — тип запроса. Изменяя этот параметр можно осуществлять поиск, к примеру, только только по городам, либо только по "жилым зонам", либо же устроить поиск везде, как в нашем примере.
  • page, page_size — номер страницы и количество отображаемых запросов на странице. Бывает так, что по одному запросу может быть несколько ответов. К примеру, на запрос: "банкоматы". Здесь данный параметр очень пригодится.
  • locale — Выбранная локаль для запроса.
  • q — поле нашего запроса. Как видим, пробелы заменены знаками %20, запятые — на знак %2С. При составлении запроса необходимо будет это учитывать.
  • fields — поля возвращаемых значений. В данном поле, по сути, хранится вся информация, которую мы хотим получить в нашем запросе.
  • stat, key, r — поля идентификации пользователя.

Попробуем скорректировать наш запрос и посмотреть, какие поля имеют значения, а какие — нет. Забегая вперед скажу, что запрос прекрасно будет работать и без viewpoint,page и прочих подобных. А вот если изменить поля идентификации — непременно получим error 400. Значит по этим ключам и id любая информация должна быть нам доступна.


Проверим. Попробуем заменить поле нашего запроса на любой случайный адрес, с замененными пробелами и запятыми. Страница обновилась, в окне появились данные о постройке по вновь введенном адресу. Значит, запрос корректен. Можно автоматизировать!


Напишем наш Python скрипт, который будет получать JSON ответ с информацией об адресе. Для этого импортируем модуль requests, добавим в headers заголовки браузера ноутбука, предобработаем адрес, и просто отправим запрос на сервер.


код Python для получения информации по адресу
import requests 

headers = {'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:41.0) Gecko/20100101 Firefox/41.0'}

#Создаем функцию получения данных по адресу
def getDataFromAddress(address):
    address = '%20'.join('%2C'.join(address.split(',')).split(' ')) #Получаем адрес, заменяем запятые на %2C, а пробелы на %20
    link = 'https://catalog.api.2gis.ru/3.0/items?type=street%2Cadm_div.city%2Ccrossroad%2Cadm_div.settlement%2Cstation%2Cbuilding%2Cadm_div.district%2Croad%2Cadm_div.division%2Cadm_div.region%2Cadm_div.living_area%2Cattraction%2Cadm_div.place%2Cadm_div.district_area%2Cbranch%2Cparking%2Cgate%2Croute&page=1&page_size=12&q='+address+'&locale=ru_RU&fields=request_type%2Citems.adm_div%2Citems.context%2Citems.attribute_groups%2Citems.contact_groups%2Citems.flags%2Citems.address%2Citems.rubrics%2Citems.name_ex%2Citems.point%2Citems.geometry.centroid%2Citems.region_id%2Citems.segment_id%2Citems.external_content%2Citems.org%2Citems.group%2Citems.schedule%2Citems.timezone_offset%2Citems.ads.options%2Citems.stat%2Citems.reviews%2Citems.purpose%2Csearch_type%2Ccontext_rubrics%2Csearch_attributes%2Cwidgets%2Cfilters&stat%5Bsid%5D=91e1c495-9e55-4ca9-8712-e15073071f6e&stat%5Buser%5D=a8e546d0-291f-4778-bc72-1f84d55dcdfc&key=ruoedw9225&r=1831242903' # передаем предобработанный адрес в запрос
    answer = requests.get(link, headers=headers) # делаем запрос
    return json.loads(answer.content.decode('utf-8')) # возвращаем ответ в виде json

Вот и все! 7 строчек кода, и поиск по адресу готов. Введя город, улицу, и дом, наша функция вернет JSON с достаточно неплохой информацией об объекте: его id, широту, долготу, тип, район города, и так далее. И это уже впечатляет!


Больше, больше данных!


Еще больше информации можно получить по запросу get. Правда вместо адреса он использует id постройки, но мы без труда получаем его из предыдущего запроса:


код Python для получения информации по строению
def getDataFromBuildings(building_id):
    link = 'https://catalog.api.2gis.ru/2.0/catalog/branch/list?building_id='+str(building_id)+'&locale=ru_RU&fields=items.region_id%2Citems.segment_id%2Citems.reviews%2Citems.adm_div%2Citems.contact_groups%2Citems.flags%2Citems.address%2Citems.rubrics%2Citems.name_ex%2Citems.point%2Citems.external_content%2Citems.schedule%2Citems.timezone_offset%2Citems.org%2Citems.stat%2Citems.ads.options%2Citems.attribute_groups%2Crequest_type%2Csearch_attributes&stat%5Bsid%5D=91e1c495-9e55-4ca9-8712-e15073071f6e&stat%5Buser%5D=c8109e98-e546-455d-b6ed-fcfd7cb4ffe0&key=ruoedw9225&r=3862084826' #передаем id постройки в запрос
    answer = requests.get(link ,headers=headers) #делаем запрос
    return json.loads(answer.content.decode('utf-8'))# возвращаем ответ в виде json

building_id = getDataFromAddress('Арма,Нижний Сусальный переулок, 5 ст16,Басманный район, Москва')['result']['items'][0]['address']['building_id'] #получаем id постройки по данному адресу
getDataFromBuildings(building_id) #получаем json ответ с организациями в здании

Еще 7 строчек кода, и теперь мы имеем доступ не только к данным о строении, но также и об организациях в этом здании. А именно — время работы, способы оплаты, тип организации, и даже номера телефонов.


А теперь распаралеллить!


Для меня было одновременно и шоком и удивлением то, что все это дело без особых проблем параллелится, а количество запросов на сервер никак не контролируется (намек вспомнить название темы).


код Python для получения информации по адресу, многопоточный
from multiprocessing import Pool 
from multiprocessing.dummy import Pool as ThreadPool

pool = ThreadPool(32) #создаем пул, указываем количество потоков

def getFullData(address):
    addressData = getDataFromAddress(address) #получаем данные об адресе
    building_id = addressData['result']['items'][0]['address']['building_id'] #получаем id здания
    buildingData = getDataFromBuildings(building_id) #получаем данные об организациях в здании
    return buildingData, addressData #возвращаем кортеж с данными об адресе и организациях по этому адресу

addressAr = ['Арма,Нижний Сусальный переулок, 5 ст16, Басманный район, Москва', 'Щербанёва, 25, Омск'] #массив адресов
fullData = pool.map(getFullData, addressAr) #Применяем нашу функцию к массиву с адресами

#Закрываем пул
pool.close()
pool.join()

Таким образом 2GIS позволяет получать любые данные о любых организациях достаточно быстро и просто. При этом не нужно регистрироваться, оставлять заявку или же изучать API.


Итог


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


Решается это вроде бы тоже не так сложно — необходимо лишь наладить лимит запросов на сервер (наврядли человек с одним уникальным stat user & key сможет отправлять больше чем 10 запросов в секунду) и никакой, даже самый хитрый охотник за данными, не сможет их украсть.


P.S — такие возможности были открыты в конце декабря, после чего я сразу отписался в техподдержку 2GIS (при чем ни один раз). На дворе 15 января, ответа до сих пор не поступило, из чего можно сделать вывод что "Это не баг, а фича!". Надеюсь, так оно и задумано. Спасибо!

+7
~4700

комментарии (9)

+1
0x12ee705 ,   * (был изменён)
если вы про key, то этой «фиче» уже N лет, на хабре даже парочка статей вроде где-то была)))
0
+4 –4
Piskov ,  
На дворе 15 января

На 4-й рабочий день в январе? :-) Стоило подождать, если действительно хотели соблюсти приличия.

0
rzerda ,  

Вы сильно недооцениваете хитрость охотников. Предлагаю (в рамках умственного эксперимента, разумеется) поиграть дальше и за «снаряд», и за «броню», и посмотреть, как можно этот лимит обойти в разных начальных условиях (есть у атакующего ещё компьютеры или нет, какой объём данных надо получить, когда получить). Отдельно прикинуть, как этот лимит вообще можно реализовать на стороне «брони».

+9
+11 –2
vedenin1980 ,   * (был изменён)
Для меня было одновременно и шоком и удивлением то, что все это дело без особых проблем параллелится, а количество запросов на сервер никак не контролируется (намек вспомнить название темы). Таким образом 2GIS позволяет получать любые данные о любых организациях достаточно быстро и просто. При этом не нужно регистрироваться, оставлять заявку или же изучать API.

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

Конкурент заливает себе базу, а у него адреса части организаций не там где нужно, часы работу врут и телефоны неправильные, его клиенты плются и уходят. А юристы сервиса сточат иски по воровству данных, так искаженные данные отлично видны.
0
+2 –2
tuxi ,  
Причем это не теория, а вполне себе практика. Мы такой прием используем в продакшене.
+7
nomadmoon ,  
Я уверен, компания 2GIS просто добрая и позволяет брать информацию всем желающим.
+7
jehy ,  
1. То, что вы написали — не парсер. Впрочем, если у вас удивление вызвали странные символы "%20"…
2. Ну и целом хочется поздравить вас с открытием интернета и того, что веб приложения работают через API. Сколько подобных " уязвимостей" вам ещё предстоит открыть…
+3
gecube ,   * (был изменён)
Вообще-то 2ГИС платен.

partner.api.2gis.ru
Чтобы использовать данные 2ГИС в своём проекте, подключите платный API. Первый шаг — анкета. Расскажите о себе и опишите проект. Когда анкета придёт, мы свяжемся и обсудим детали.

и еще law.2gis.ru
Никакой автоматизации не подразумевается.

Можно прикинуться «пользователем» карт, но, во-первых, это противоречит ToS, во-вторых, есть шанс получить невалидные данные. В третьих, оператор данных (2ГИС) легко может понять, что Вы его скрейпите (юзер-агент, ip и пр.) и после какого-то лимита попросту заблокировать доступ.
+3
alekciy ,  
Какой объём данных удалось скачать? Рекомендую попробовать выкачать город милионник, узнаете много интересного.