Статья Поиск в ВК фото с геометками в заданном диапазоне дат с помощью Python. Часть #01

Геопозиция, геоданные… Если раньше эти слова были чем-то далеким, ну или как минимум ассоциировались с дорогим навигатором, то теперь уже GPS, ГЛОНАСС есть в каждом смартфоне. Более того, множество приложений жаждет к ним доступа, чтобы поточнее вас идентифицировать. Не является исключением и камера в вашем телефоне. Правда, в некоторых телефонах, встраивание геоданных в фото отключено по умолчанию, но, так далеко не везде. И мы уже даже не обращаем на эту технологию должного внимания, настолько к ней привыкли. В этой статье я попробую показать, насколько просто можно получить геоданные и что можно с ними сделать на примере «многострадального» ВК.

01.jpg


Дисклеймер: Данная статья предоставлена для ознакомления, и не призывает ни к каким действиям.


На самом деле, на эту тему меня натолкнуло совершенно случайное видео, так как я искал другую тему. Тем не менее, оно меня заинтересовало. Рекламировать его здесь не буду, скажу лишь, что называется оно «Нестандартный геопоиск через API вконтакте…». Правда, учитывая, что оно аж от 2015 года, информация в нем слегка устарела. И теперь все то, что описывается в видео можно реализовать гораздо проще.

Так вот, к сути. Что будет делать данный скрипт? Ну, на самом деле не так уж и много. С помощью API будет выполнен поиск фото с геоданными за определенный промежуток времени, после чего, будут получены данные человека, который эти геоданные опубликовал, а также адрес по геокоординатам. Также я помещу найденные места съемки на карту с указанием координат, адреса и ссылкой на фото по данным координатам.

Тут надо еще сказать, что в самом ВК есть поиск по определенным координатам, но без учета даты. Если вы зайдете на страницу поиска и развернете «Параметры поиска», то справа внизу увидите параметр «Геолокация», при клике на который откроется карта, на которой можно выбрать координаты и получить данные по ним в виде ленты. Но, тут у вас не получиться получить данные за определенную дату. Скриншот прикреплен ниже:

screenshot0.png

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

Для чего использовать этот скрипт? Даже не знаю. Можно попробовать поискать понравившуюся девушку, которую вы видели на Соборной площади 17 марта 2021 года в день летнего солнцестояния, а после никак не могли найти. А потому, решили поискать ее по координатам, в надежде, что кто-то сделал ее случайное фото. Тут уж, на что хватит вашей фантазии. Что же, давайте приступим к делу.


Что понадобиться?

Установить vk-api, для работы с данными ВК. Пишем в терминале:

pip install vk-api

Еще нам понадобиться библиотека folium для работы с геодаными, а точнее, для размещения точек с координатами на карте. Устанавливаем ее с помощью команды:

pip install folium

Затем устанавливаем библиотеку geopy. Она будет нужна для реверса геоданных, а точнее для получения адреса по определенным координатам. Устанавливаем ее:

pip install geopy

На самом деле, тут возникла небольшая трудность. Изначально я хотел использовать API Google. И вроде бы все прошло гладко. Я его даже получил. Но вот когда начал использовать в коде, почему-то в терминале вылезла ошибка «access denied». Я не стал разбираться что да почему, а поискал другие способы и нашел то, что нашел.

Теперь давайте установим python-docx. С помощью данной библиотеки мы будем работать с документами Word. И хотя у нее возможностей по форматированию не особо много, по крайней мере из тех, что я нашел, для наших целей их будет достаточно. С помощью нее мы будем компоновать и сохранять информацию в вордовских документах.

pip install python-docx

Ну и, собственно, необязательный модуль. Я его не особо хотел использовать. А потом подумал, что может быть надо чуток облагородить скрипт на старте? Впрочем, устанавливать ее или нет, ввиду того, что особого значения для кода она не имеет, решать вам. Установка в терминале:

pip install pyfiglet

После того, как все установиться, нужно добавить в скрипт импорты всего этого добра. Вот как это выглядит у меня:

Python:
import datetime as dt
import os.path
import random
import requests
import time
from datetime import timedelta

import docx
import folium
import pyfiglet
from docx.shared import Pt
from geopy.exc import GeocoderUnavailable
from geopy.geocoders import Nominatim
from vk_api import VkApi
from vk_api.exceptions import ApiError

from set import token

Последний параметр, это питоновский файл, в котором сохранен мой токен API ВК, а также user id. Там все достаточно просто:

Python:
token = '<ваш токен> '
user_id = '<id пользователя>'

Где его взять я могу рассказать вкратце.

Для получения токена и доступа из вашего скрипта к vk_api нужно создать Standalone-приложение. Для этого заходим на эту страницу « ». И нажать кнопку «Создать приложение» в пункте меню «Мои приложения».

screenshot1.png

Затем выбираем «Standalone-приложение» и нажимаем кнопку «Подключить приложение».

screenshot2.png

На следующей станице нужно будет ввести код, который придет на номер телефона указанный вами при регистрации.

screenshot3.png

После переходите в настройки приложения и копируете оттуда «ID приложения». Он вам понадобиться для получения прав доступа.

screenshot4.png

Теперь идем на страницу получения и нажимаем кнопку «Настройки».

screenshot8.png

Выбираем кнопку «Пользователь». Отмечаем нужные права доступа, не забудьте отметить «Доступ в любое время» и жмете кнопку «Получить».

screenshot5.png

Затем жмете кнопку «Разрешить» на странице подтверждения прав.

screenshot6.png

После этого вас перебросит на почти пустую страницу, в адресной строке которой, будет содержаться токен, между «access_token=»

screenshot4.png

и «&expires_in=»

screenshot7.png

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


Начало. Запрос необходимых данных

Начнем пожалуй с самого начала, а именно с функции main(), которую прикреплю чуть ниже, а затем уже подробно опишу.

Python:
def main():
    print(pyfiglet.figlet_format("VK Search geodata", font="digital"))
    lat_long = lat_long_input()
    lat = lat_long[0]
    long = lat_long[1]
    start_end_time = start_end_time_input()
    start_time = int(start_end_time[0])
    end_time = int(start_end_time[1])

    count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"
    while not count.isdigit():
        count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"

    radius = input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                   '(по умолчанию: 1000)\n~# ') or "1000"
    while not radius.isdigit():
        radius = int(input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                           '(по умолчанию: 1000)\n~# ') or "1000")

    start_pril = time.monotonic()
    list_res = get_photo(lat, long, start_time, end_time, int(count), int(radius))
    get_map(list_res, lat, long, radius)
    save_data(list_res)

    print(f'\nВремя поиска {count} результатов в радиусе {radius} м. = {time.monotonic() - start_pril}')


if __name__ == "__main__":
    main()

Для начала наводим красоту и с помощью print(pyfiglet.figlet_format("VK Search geodata", font="digital")) выводим название программы. Ну или то, что влазит в терминал. ) Затем вызывается функция запроса у пользователя координат для поиска. Полученные данные разделяются между переменными, чтобы можно было использовать по отдельности.

Python:
lat_long = lat_long_input()
lat = lat_long[0]
long = lat_long[1]

Затем запускается функция запроса интервала даты у пользователя для поиска, полученные даты из которой также разделяются между переменными:

Python:
start_end_time = start_end_time_input()
start_time = int(start_end_time[0])
end_time = int(start_end_time[1])

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

Python:
count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"
    while not count.isdigit():
        count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"

    radius = input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                   '(по умолчанию: 1000)\n~# ') or "1000"
    while not radius.isdigit():
        radius = int(input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                           '(по умолчанию: 1000)\n~# ') or "1000")

Получаем время старта скрипта, запускаем функцию поиска фото, в которую передаем полученные данные из предыдущих функций. А именно: координаты, дата начала поиска, дата окончания, количество результатов поиска и его радиус. После запускаем функцию нанесения меток на карту и ее сохранения, в которую передаем сформированный функцией поиска json, координаты, которые нужны для того, чтобы отметить стартовую точку на карте, и радиус поиска. Последний нужен только лишь для того, чтобы сохранить файл с определенным названием. И далее запускаем функцию сохранения полученных данных в Word, куда передаем только полученный из функции поиска json.

Python:
start_pril = time.monotonic()
list_res = get_photo(lat, long, start_time, end_time, int(count), int(radius))
get_map(list_res, lat, long, radius)
save_data(list_res)

Что ж, с функцией main() вроде бы все. Теперь перейдем к функции получения координат для поиска.


Функция получения координат

Назову ее lat_long_input(). Сюда мы не передаем никаких данных, а только лишь получаем результаты.

Python:
def lat_long_input():
    coord = input('> Введите координаты для поиска:\n> (пример: 55.754255, 37.620251)\n~#: ')

    while True:
        try:
            lat = coord.split(",")[0].strip()
            long = coord.split(",")[1].strip()
            float(lat)
            float(long)
            return lat, long
        except (IndexError, ValueError):
            print('[-] Неверный ввод.')
            coord = input('> Введите координаты для поиска:\n> (пример: 55.754255, 37.620251)\n~#: ')

Тут все просто. Запрашиваем у пользователя координаты в определенном формате. Так их удобнее разделять, да и забирать удобнее. Сначала я снова полез в Google Карты и какое-то время забирал координаты оттуда. Но, потом открыл карты от Яндекс и понял, что пользоваться ими удобнее. Координаты можно получить при клике правой кнопкой мыши по нужной области и выборе пункта меню «Что здесь?». А дальше слева вверху отобразятся координаты места, которые можно скопировать одним кликом по ним.

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

Хочу сказать, что использовал разные функции не только из-за того, что нужно много проверок, но и еще и потому, что если все делать в одной функции, то при возникновении ошибки придется вводить все данные заново. А так, только те, где ошибка и возникла.

Ну и затем возвращаем полученные от пользователя и распиленные по запятой значения.


Получение диапазона дат для поиска

Даты в запрос ВК API передаются в формате Unix. В этой системе отсчета времени за дату начала отсчета принято считать количество секунд прошедших с даты 01 января 1970 года. То есть, с 00 часов, 00 минут 00 секунд и далее. И в общем-то сам по себе перевод в данный формат трудностей не вызвал. Но, сначала функция, я затем пояснения по ней.

Python:
def start_end_time_input():
    start_end = input('> Введите дату или диапазон дат для поиска:\n> (пример: 05.02.2020-07.02.2020)\n!# ')
    while True:
        try:
            start_time = start_end.split("-")[0].strip()
            end_time = start_end.split("-")[1].strip()
        except IndexError:
            start_time = start_end
            end_time = start_end

        try:
            start = f'{start_time} 00:01'
            start_t = dt.datetime.strptime(start, "%d.%m.%Y %H:%M")
            timezone_s = dt.datetime.strptime(start, "%d.%m.%Y %H:%M").astimezone()
            offset_s = int(str(timezone_s).split("+")[-1].split(":")[0])
            start_d = start_t + timedelta(hours=offset_s)
            start_unx_s = start_d.timestamp()

            end = f'{end_time} 23:29'
            end_t = dt.datetime.strptime(end, "%d.%m.%Y %H:%M")
            timezone_e = dt.datetime.strptime(end, "%d.%m.%Y %H:%M").astimezone()
            offset_e = int(str(timezone_e).split("+")[-1].split(":")[0])
            end_d = end_t + timedelta(hours=offset_e)
            end_unx_s = end_d.timestamp()
            return int(start_unx_s), int(end_unx_s)

        except ValueError:
            print('[-] Неверный ввод.')
            start_end = input('> Введите дату или диапазон дат для поиска:\n> (пример: 05.02.2020-07.02.2020)\n!# ')

А дело было вот в чем. Я столкнулся с тем, что время переводится питоном с учетом смещения по GMT. То есть, к примеру, если у вас смещение +3, то и время будет переведено именно с учетом этого смещения. И для вас это будет именно та дата, которая нужна. А вот для ВК смещение нужно убирать. А заметил я это случайно. Когда одна из дат оказалась на день меньше, чем я запрашивал. Ну, или вернее, на несколько часов. Что и было предыдущим днем. То есть, для примера, запрашивал я пятое число, а получил четвертое.

Выход тут оказался достаточно простой. Нужно передавать время без учета смещения, то есть именно в GMT или переводить его в Unix-формат уже с учетом смещения. Тогда я накидал небольшой «велосипед», который учитывает текущую временную зону по GMT и переводит уже с ее учетом.

Для начала получаем время от пользователя. Пытаемся его распилить по «-». Если не получается, мы понимаем, что пользователь ввел одну дату и в ошибке присваиваем диапазону введенное значение.

Python:
start_end = input('> Введите дату или диапазон дат для поиска:\n> (пример: 05.02.2020-07.02.2020)\n!# ')
    while True:
        try:
            start_time = start_end.split("-")[0].strip()
            end_time = start_end.split("-")[1].strip()
        except IndexError:
            start_time = start_end
            end_time = start_end

Затем добавляем к введенной дате время. К первой 00:01, ко второй 23:59, чтобы получить полный охват. После добавления времени переводим строку в объект datetime. Затем получаем текущий часовой пояс с помощью функции .astimezone(). Получаем offset, то есть нужное нам смещение и прибавляем к дате начала или окончания поиска полученное смещение. А затем уже переводим в Unix-формат.

Python:
start = f'{start_time} 00:01'
start_t = dt.datetime.strptime(start, "%d.%m.%Y %H:%M")
timezone_s = dt.datetime.strptime(start, "%d.%m.%Y %H:%M").astimezone()
offset_s = int(str(timezone_s).split("+")[-1].split(":")[0])
start_d = start_t + timedelta(hours=offset_s)
start_unx_s = start_d.timestamp()

Заключаем данный код в блок try-except, чтобы при возникновении исключения, если пользователь умудрился ввести что-то неправильно, оно обработалось, и был снова выполнен запрос диапазона дат. Затем возвращаем полученные значения в функцию main().

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

Python:
# pip install vk-api
# pip install folium
# pip install geopy
# pip install python-docx
# pip install pyfiglet

import datetime as dt
import os.path
import random
import requests
import time
from datetime import timedelta

import docx
import folium
import pyfiglet
from docx.shared import Pt
from geopy.exc import GeocoderUnavailable
from geopy.geocoders import Nominatim
from vk_api import VkApi
from vk_api.exceptions import ApiError

from set import token


# запрос координат и обработка
def lat_long_input():
    coord = input('> Введите координаты для поиска:\n> (пример: 55.754255, 37.620251)\n~#: ')

    while True:
        try:
            lat = coord.split(",")[0].strip()
            long = coord.split(",")[1].strip()
            float(lat)
            float(long)
            return lat, long
        except (IndexError, ValueError):
            print('[-] Неверный ввод.')
            coord = input('> Введите координаты для поиска:\n> (пример: 55.754255, 37.620251)\n~#: ')


# запрос и обработка даты для поиска
def start_end_time_input():
    start_end = input('> Введите дату или диапазон дат для поиска:\n> (пример: 05.02.2020-07.02.2020)\n!# ')
    while True:
        try:
            start_time = start_end.split("-")[0].strip()
            end_time = start_end.split("-")[1].strip()
        except IndexError:
            start_time = start_end
            end_time = start_end

        try:
            start = f'{start_time} 00:01'
            start_t = dt.datetime.strptime(start, "%d.%m.%Y %H:%M")
            timezone_s = dt.datetime.strptime(start, "%d.%m.%Y %H:%M").astimezone()
            offset_s = int(str(timezone_s).split("+")[-1].split(":")[0])
            start_d = start_t + timedelta(hours=offset_s)
            start_unx_s = start_d.timestamp()

            end = f'{end_time} 23:29'
            end_t = dt.datetime.strptime(end, "%d.%m.%Y %H:%M")
            timezone_e = dt.datetime.strptime(end, "%d.%m.%Y %H:%M").astimezone()
            offset_e = int(str(timezone_e).split("+")[-1].split(":")[0])
            end_d = end_t + timedelta(hours=offset_e)
            end_unx_s = end_d.timestamp()
            return int(start_unx_s), int(end_unx_s)

        except ValueError:
            print('[-] Неверный ввод.')
            start_end = input('> Введите дату или диапазон дат для поиска:\n> (пример: 05.02.2020-07.02.2020)\n!# ')


# запуск функций получения данных
# запрос кол-ва результатов и радиуса поиска
# запуск функций получения фото по координатам
# запуск функции добавления меток на карту
# запуск функции сохранения данных
def main():
    print(pyfiglet.figlet_format("VK Search geodata", font="digital"))
    lat_long = lat_long_input()
    lat = lat_long[0]
    long = lat_long[1]
    start_end_time = start_end_time_input()
    start_time = int(start_end_time[0])
    end_time = int(start_end_time[1])

    count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"
    while not count.isdigit():
        count = input('> Введите количество результатов поиска:\n(по умолчанию: 50)\n~# ') or "50"

    radius = input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                   '(по умолчанию: 1000)\n~# ') or "1000"
    while not radius.isdigit():
        radius = int(input('> Введите радиус поиска в метрах:\n(пример: 5000 | 1 км. = 1000 м.)\n'
                           '(по умолчанию: 1000)\n~# ') or "1000")

    start_pril = time.monotonic()
    list_res = get_photo(lat, long, start_time, end_time, int(count), int(radius))
    get_map(list_res, lat, long, radius)
    save_data(list_res)

    print(f'\nВремя поиска {count} результатов в радиусе {radius} м. = {time.monotonic() - start_pril}')


if __name__ == "__main__":
    main()

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
 
Последнее редактирование:

debolg

Green Team
06.04.2021
24
106
BIT
83
Большое спасибо за статью. Она сэкономила мне кучу времени на вопрос о том, как получать токен и вообще, как начать работать с ВК апи. Ну и в целом интересно!
 
  • Нравится
Реакции: Johan Van
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!