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

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

03-.jpg


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


Сохранение полученных данных в Word

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

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

Python:
def save_data(list_res):
    for num, user in enumerate(list_res):
        print(f'\r[+] Сохраняю данные: {num}', end="")
        user_id = str(user['user_id']).replace("-", "")
        if not os.path.isfile(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx')):
            doc = docx.Document()
            style = doc.styles['Normal']
            style.font.name = 'Arial'
            style.font.size = Pt(12)
            try:
                req = requests.get(user['user_info']['photo']).content
                with open(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'), 'wb') as ph:
                    ph.write(req)
                doc.add_picture(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'),
                                width=docx.shared.Cm(5))
                os.remove(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'))
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Ф.И.О.: {user['user_info']['first_name']} {user['user_info']['last_name']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Дата рождения: {user['user_info']['bdate']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Страна: {user['user_info']['country']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Город: {user['user_info']['city']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Профиль ВК: {str(user['user_info']['vk_prof']).replace('-', '')}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Фото профиля: {user['user_info']['photo']}")
            except KeyError:
                pass
            doc.add_paragraph(" ")
            doc.add_paragraph("-" * 80)
            doc.add_paragraph("Найденные локации")
            doc.add_paragraph("-" * 80)

            for usr in list_res:
                if str(user_id) == str(usr['user_id']).replace("-", ""):
                    try:
                        doc.add_paragraph(f"Фото: {usr['url']}")
                    except KeyError:
                        pass
                    try:
                        date = dt.datetime.utcfromtimestamp(usr["date"]).strftime('%d.%m.%Y %H:%M:%S')
                        doc.add_paragraph(f'Дата фото: {date}')
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph(f"Координаты: {usr['lat']}, {usr['long']}")
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph(f"Адрес: {usr['address']}")
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph("-" * 80)
                    except KeyError:
                        pass
            doc.save(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx'))
    print(f'\n[+] Данные сохранены в папку: {os.path.join(os.getcwd(), "users_link")}')

Здесь используется похожий принцип распарсивания списка словарей, который был описан в функции добавления меток на карту. Для начала запускается цикл по списку. Получается user_id. Затем делается проверка, есть ли файл с названием user_id. Если нет, двигаемся дальше. Если есть, переходим к следующему user_id.

Python:
    for num, user in enumerate(list_res):
        print(f'\r[+] Сохраняю данные: {num}', end="")
        user_id = str(user['user_id']).replace("-", "")
        if not os.path.isfile(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx')):

Если файла нет, создаем документ docx. Устанавливаем стиль для текста, шрифт и его размер. Затем скачиваем фото по ссылке в списке. Сохраняем его. Добавляем в документ, а затем, после того, как фото добавлено, удаляем его из папки пользователя. Для добавления фото используем функцию doc.add_picture, в которой надо указать путь к картинке и здесь устанавливается ширина, чтобы фото не добавлялось в его истинном размере. Ширина устанавливается в сантиметрах. Также есть возможность использовать пиксели и дюймы, но так проще и понятнее. Здесь также обрабатываем ошибку отсутствия ключа.

Python:
doc = docx.Document()
            style = doc.styles['Normal']
            style.font.name = 'Arial'
            style.font.size = Pt(12)
            try:
                req = requests.get(user['user_info']['photo']).content
                with open(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'), 'wb') as ph:
                    ph.write(req)
                doc.add_picture(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'),
                                width=docx.shared.Cm(5))
                os.remove(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'))
            except KeyError:
                pass

Затем добавляем параграф, в качестве параметра передаем текст, сформированный из значений списка. В данном случае добавляется Ф.И.О., или то, что пользователь указал вместо них. И обрабатывается отсутствие ключа.

Python:
 try:
                doc.add_paragraph(f"Ф.И.О.: {user['user_info']['first_name']} {user['user_info']['last_name']}")
            except KeyError:
                pass

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

Python:
doc.add_paragraph(" ")
doc.add_paragraph("-" * 80)
doc.add_paragraph("Найденные локации")
doc.add_paragraph("-" * 80)

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

Python:
            for usr in list_res:
                if str(user_id) == str(usr['user_id']).replace("-", ""):
                    try:
                        doc.add_paragraph(f"Фото: {usr['url']}")
                    except KeyError:
                        pass

Здесь добавляется ссылка на найденное фото, его координаты и адрес. После того, как данные будут добавлены, файл сохраняется с именем user_id, в его персональной папке.

Python:
doc.save(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx'))

Так как эта функция последняя, то по окончании ее работы выводиться сообщение, что данные были успешно сохранены и скрипт завершает свою работу.

Python:
print(f'\n[+] Данные сохранены в папку: {os.path.join(os.getcwd(), "users_link")}')

Вот в принципе и все. Наконец-то я добрался до последней функции. Ниже полный код скрипта.

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


# сохраняем данные в word
# скачивание фото пользователя и добавление в word
def save_data(list_res):
    for num, user in enumerate(list_res):
        print(f'\r[+] Сохраняю данные: {num}', end="")
        user_id = str(user['user_id']).replace("-", "")
        if not os.path.isfile(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx')):
            doc = docx.Document()
            style = doc.styles['Normal']
            style.font.name = 'Arial'
            style.font.size = Pt(12)
            try:
                req = requests.get(user['user_info']['photo']).content
                with open(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'), 'wb') as ph:
                    ph.write(req)
                doc.add_picture(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'),
                                width=docx.shared.Cm(5))
                os.remove(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.jpg'))
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Ф.И.О.: {user['user_info']['first_name']} {user['user_info']['last_name']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Дата рождения: {user['user_info']['bdate']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Страна: {user['user_info']['country']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Город: {user['user_info']['city']}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Профиль ВК: {str(user['user_info']['vk_prof']).replace('-', '')}")
            except KeyError:
                pass
            try:
                doc.add_paragraph(f"Фото профиля: {user['user_info']['photo']}")
            except KeyError:
                pass
            doc.add_paragraph(" ")
            doc.add_paragraph("-" * 80)
            doc.add_paragraph("Найденные локации")
            doc.add_paragraph("-" * 80)

            for usr in list_res:
                if str(user_id) == str(usr['user_id']).replace("-", ""):
                    try:
                        doc.add_paragraph(f"Фото: {usr['url']}")
                    except KeyError:
                        pass
                    try:
                        date = dt.datetime.utcfromtimestamp(usr["date"]).strftime('%d.%m.%Y %H:%M:%S')
                        doc.add_paragraph(f'Дата фото: {date}')
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph(f"Координаты: {usr['lat']}, {usr['long']}")
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph(f"Адрес: {usr['address']}")
                    except KeyError:
                        pass
                    try:
                        doc.add_paragraph("-" * 80)
                    except KeyError:
                        pass
            doc.save(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.docx'))
    print(f'\n[+] Данные сохранены в папку: {os.path.join(os.getcwd(), "users_link")}')


# проставляем метки на персонализированых картах
# и все найденные метки на общей
def get_map(location, lat, long, radius):
    area_all = folium.Map(location=(float(lat), float(long)), zoom_start=8)
    for loc in location:
        user_id = str(loc['user_id']).replace("-", "")
        lat_long = (float(loc["lat"]), float(loc["long"]))
        try:
            date = dt.datetime.utcfromtimestamp(loc["date"]).strftime('%d.%m.%Y %H:%M:%S')
            popup = f'<a target="_blank" href="{loc["url"]}">{lat_long}<br><br>{date}<br><br>{loc["address"]}</a>'
            folium.Marker(location=lat_long, popup=popup, icon=folium.Icon(color='red')).add_to(area_all)
        except KeyError:
            popup = f'<a target="_blank" href="{loc["url"]}">{lat_long}<br><br>{loc["address"]}</a>'
            folium.Marker(location=lat_long, popup=popup, icon=folium.Icon(color='red')).add_to(area_all)

        if not os.path.isfile(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.html')):
            area = folium.Map(location=(float(loc['lat']), float(loc['long'])), zoom_start=10)
            for usr in location:
                if user_id == str(usr['user_id']).replace("-", ""):
                    lat_long = (float(usr["lat"]), float(usr["long"]))
                    try:
                        date = dt.datetime.utcfromtimestamp(usr["date"]).strftime('%d.%m.%Y %H:%M:%S')
                        popup = f'<a target="_blank" href="{usr["url"]}">{lat_long}<br><br>{date}<br><br>' \
                                f'{usr["address"]}</a>'
                        folium.Marker(location=lat_long, popup=popup, icon=folium.Icon(color='red')).add_to(area)
                    except KeyError:
                        popup = f'<a target="_blank" href="{usr["url"]}">{lat_long}<br><br>{usr["address"]}</a>'
                        folium.Marker(location=lat_long, popup=popup, icon=folium.Icon(color='red')).add_to(area)

            if not os.path.isdir(os.path.join(os.getcwd(), 'users_link')):
                os.mkdir(os.path.join(os.getcwd(), 'users_link'))
            if not os.path.isdir(os.path.join(os.getcwd(), 'users_link', user_id)):
                os.mkdir(os.path.join(os.getcwd(), 'users_link', user_id))
            area.save(os.path.join(os.getcwd(), 'users_link', user_id, f'{user_id}.html'))
    area_all.save(os.path.join(os.getcwd(), 'users_link', f'result_(({lat},{long})_{radius}).html'))


# получаем адрес по координатам
def get_addr(location):
    try:
        geoloc = Nominatim(user_agent="GetLoc")
        locname = geoloc.reverse(location)
    except GeocoderUnavailable:
        locname = 'Unknown'
    return locname


# получение информации о пользователе и добавление в список для последующей обработки
def get_user_info(user_ids):
    session = VkApi(token=token)
    vk = session.get_api()
    user_info = vk.users.get(user_id=user_ids, fields='bdate, city, country, photo_max_orig')
    data = {}
    try:
        data['first_name'] = user_info[0]['first_name']
    except (KeyError, IndexError):
        pass
    try:
        data['last_name'] = user_info[0]['last_name']
    except (KeyError, IndexError):
        pass
    try:
        data['bdate'] = user_info[0]['bdate']
    except (KeyError, IndexError):
        pass
    try:
        data['country'] = user_info[0]['country']['title']
    except (KeyError, IndexError):
        pass
    try:
        data['city'] = user_info[0]['city']['title']
    except (KeyError, IndexError):
        pass
    try:
        data['vk_prof'] = f'https://vk.com/id{user_ids}'
    except (KeyError, IndexError):
        pass
    try:
        data['photo'] = user_info[0]['photo_max_orig']
    except (KeyError, IndexError):
        pass

    return data


# поиск информации по запрошенным координатам, радиусу и времени
# передача в запросившую функцию для обработки
def get_photo(latitude, longitude, start_time, end_time, count, radius):
    params = {'access_token': token, 'lat': latitude, 'long': longitude, 'start_time': start_time,
              'end_time': end_time, 'offset': 0, 'count': count, 'radius': radius, 'v': 5.131}
    vk_session = VkApi(token=token)
    try:
        search_result = vk_session.method('photos.search', params)
    except ApiError:
        print('\n[+] Введены неверные параметры для поиска! Повторите ввод!\n')
        return

    list_res = []

    for num, res in enumerate(search_result['items']):
        print(f'\rОбработка локации: {num}', end='')
        url = ""
        height = 0
        for size in res['sizes']:
            if size['height'] > height:
                height = size['height']
                url = size['url']
        try:
            lat = res['lat']
        except KeyError:
            lat = latitude
        try:
            long = res['long']
        except KeyError:
            long = f"{longitude[0:6]}{random.randrange(100, 1000, 50)}"

        data = {'user_id': res['owner_id'], 'date': res['date'], 'lat': lat, 'long': long, 'url': url}

        user_info = get_user_info(res['owner_id'])
        data['user_info'] = user_info
        time.sleep(0.1)
        addr = get_addr((float(lat), float(long)))
        try:
            data['address'] = addr.address
        except AttributeError:
            data['address'] = "No address"

        list_res.append(data)
    return list_res


# запрос координат и обработка
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()

На самом деле, не знаю уж почему, но я заметил в работе не скрипта, а API ВК некоторую странность. Какой бы диапазон дат я не вводил, возвращаются данные из диапазона, но преимущественно за одну, какую-то дату. С чем это связано, даже не знаю. Могу только предположить, что тут все дело в параметре offset и ограничении контакта на получение количества результатов. Тут, как бы вы не меняли смещение, больше 3000 результатов получить не удастся. А потому, я использовал его в основном для поиска фото в определенную дату.

И напоследок, пример работы скрипта. Так это выглядит в терминале:

screenshot9.png

Все найденные геометки, сохраненные на карте:

screenshot6.png

А так выглядят метки при приближении:

screenshot7.png

Данные пользователя вместе с найденными фото с геометками в файле Word:

screenshot8.png

А на этом, пожалуй, все.

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

Вложения

  • vk_photo_search.zip
    3,7 КБ · Просмотры: 132
  • Нравится
Реакции: ROP
Мы в соцсетях:

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