HimeraSearchDB
Carding_EbayThief
triada
CrackerTuch
JustinSun

НОВОСТИ Начни зарабатывать на софте: Создание mini-digital-business

BDFpromo
Оффлайн

BDFpromo

.
.
Регистрация
23.09.18
Сообщения
12.347
Реакции
176
Репутация
0
hndot78f5tlzjqujos6-i7j8mnu.gif


Сидя на freelance видел много раз задачи по сбору БД. Чаще всего просят собрать информацию о компаниях или специфические запросы на Google, Yandex картах.
Есть спрос, давайте создавать предложения, но обо всём по порядку.


В данной статье предлагаю разработать Telegram bot, который будет принимать название города (в котором будет производиться поиск) и запрос (по которому будет производиться поиск. Например: Бар, Кофе, Ресторан и т.д.). Реализуем возможность делать donation, оплачивать услугу по сбору БД и отправлять клиентам на почту БД.

Используемые технологии:


Данные технологии или инструменты выбраны для удобства использования, возможности быстрой реализации и автоматизации задачи.
Эта программа не является коммерческой. Реализована для примера, т.к. запрещено сохранять данные
О правилах можно сказать то же, что и о законах: их многочисленность доказывает не столько соблюдение, сколько нарушение их. ©Дюбэ​

Подготовка.

Для удобства разделим проект на 3 файла. Создадим bot.py, yandex.py, send_email.py.

Реализация.


Yandex.py
  • Импортируем библиотеки:

    # -*- coding: utf-8 -*-
    import requests
    import xlwt, xlrd
    from xlutils.copy import copy as xlcopy

  • Для обращение к Yandex API нужно ключ, по которому будут Вас идентифицировать. Ключи для использования maps api и location api разные.
    Задаем api ключи:

    apikey = '*******-***-****-****-************'
    apikey_location = '********-****-****-****-************'

  • Получение общего кол-во найденных объектов:

    def sum_taken_object(all_informations):
    try:
    found = str(all_informations['properties']['ResponseMetaData']['SearchResponse']['found'])
    except KeyError:
    found = '-'
    return found

  • Получение всей информации:

    def get_all_infomations(text, city):
    value_low_upp, point = get_location(city)
    low = value_low_upp[0].split(',')
    upp = value_low_upp[1].split(',')
    informations = requests.get(' '
    'apikey='+apikey+'&'
    'text='+text+'&'
    'lang=ru_RU&'
    'll='+point[0]+'&'
    'bbox='+low[1]+','+low[0]+'~'+upp[1]+','+upp[0]+'&'
    'results=500')
    return informations.json()
    В запросе requests.get параметр bbox указывает на координаты области поиска, а ll на центр области поиска. Можно указать только параметр ll без bbox, если нужны результаты только в центре города. О дополнительных возможностях можете почитать .
    Обратите внимание, что больше 500 объектов запрос не вернет.
    Если будет интересна реализация сбора всех объектов превышающих кол-во 500, отпишитесь. Сделаю отдельный пост.​

  • Получение долготы и широты:

    def get_location(city):
    location = requests.get(' '
    'apikey='+apikey_location+'&'
    'geocode='+city.title()+'&'
    'format=json')
    loc = location.json()
    loc = loc['response']['GeoObjectCollection']['featureMember']
    point = loc[0]['GeoObject']['Point']['pos'].split()
    value_lower = loc[0]['GeoObject']['boundedBy']['Envelope']['lowerCorner'].split()
    value_upper = loc[0]['GeoObject']['boundedBy']['Envelope']['upperCorner'].split()
    value_low_upp = [value_lower[1]+','+value_lower[0], value_upper[1]+','+value_upper[0]]
    return value_low_upp, point
    Возвращает долготу и ширину координаты области и центра.
  • Получаем ограниченное кол-во объектов:

    def get_information_limit(text,city):
    value_low_upp, point = get_location(city)
    low = value_low_upp[0].split(',')
    upp = value_low_upp[1].split(',')
    info = requests.get(' '
    'apikey='+apikey+'&'
    'text='+text+'&'
    'lang=ru_RU&'
    'll='+point[0]+'&'
    'bbox='+low[1]+','+low[0]+'~'+upp[1]+','+upp[0]+'&'
    'results=5&'
    'skip=5')
    information = info.json()
    information = information['features']
    list = {}
    i = 1
    for key in information:
    try:
    coordinates = str(key['geometry']['coordinates'])
    except KeyError:
    coordinates = '-'
    try:
    name = str(key['properties']['CompanyMetaData']['name'])
    except KeyError:
    name = '-'
    try:
    address = str(key['properties']['CompanyMetaData']['address'])
    except KeyError:
    address = '-'
    try:
    url = str(key['properties']['CompanyMetaData']['url'])
    except KeyError:
    url = '-'
    try:
    phones = key['properties']['CompanyMetaData']['Phones']
    except KeyError:
    phones = '-'
    try:
    hours = str(key['properties']['CompanyMetaData']['Hours']['text'])
    except KeyError:
    hours = '-'

    for k in phones:
    try:
    phones = k['formatted']
    except TypeError:
    pass

    list['object'+str(i)] = {'coordinates':coordinates,'name':name,'address':address,'url':url,'phones':phones,'hours':hours}
    i += 1
    return list
    Возвращает словарь с информацией по 5 объектам. Если хотите изменить кол-во объектов, тогда измените значение двух переменных resul, skip в запросе requests.get.
  • Запись базы данных в Excel файл:

    def write_exl(text, city_name):
    try:
    name_excel_BD = creat_excel_file(name='{}_{}'.format(text, city_name))
    read_book = xlrd.open_workbook(name_excel_BD) # Открываем исходный документ
    write_book = xlcopy(read_book) # Копируем таблицу в память, в неё мы ниже будем записывать
    write_sheet = write_book.get_sheet(0) # Будем записывать в первый лист
    # index = read_book.sheet_by_index(0).nrows # Номер последней строки
    url_maps = ' '
    info = get_all_infomations(text=text, city=city_name)

    for number, inf in enumerate(info['features']):
    try:
    name_object = inf['properties']['CompanyMetaData']['name']
    except KeyError:
    name_object = '-'
    try:
    address = inf['properties']['CompanyMetaData']['address']
    except KeyError:
    address = '-'
    try:
    time_work = inf['properties']['CompanyMetaData']['Hours']['text']
    except KeyError:
    time_work = '-'
    try:
    id_organization = inf['properties']['CompanyMetaData']['id']
    except KeyError:
    id_organization = '-'

    write_sheet.write(int(number), 0, city_name) # Город
    write_sheet.write(int(number), 1, name_object) # Название объекта
    write_sheet.write(int(number), 2, address) # Адрес
    write_sheet.write(int(number), 3, time_work) # Время работы
    write_sheet.write(int(number), 4, url_maps + str(id_organization)) # Url на карте
    write_book.save(name_excel_BD) # Сохраняем таблицу
    return True, name_excel_BD
    except TypeError or KeyError as err:
    return False

  • Создание файла Excel:

    def creat_excel_file(name):
    book = xlwt.Workbook('utf8')
    book.add_sheet('База_{}'.format(name))
    book.save('BD_{}.xls'.format(name))
    return 'BD_{}.xls'.format(name)



Send_email.py
  • Импортируем библиотеки:

    #!/usr/bin/env python
    # coding: utf8

    from smtplib import SMTP_SSL
    from email.mime.multipart import MIMEMultipart
    from email.mime.base import MIMEBase
    from email import encoders
    import os

  • Функция для отправки писем с вложением:

    def send_mail(name_file, to_address):
    filepath = name_file
    address_from = "********@gmail.com"
    address_to = to_address
    password = '************'
    mail_adr = 'smtp.gmail.com'
    mail_port = 465

    # Compose attachment
    part = MIMEBase('application', "octet-stream")
    part.set_payload(open(filepath, "rb").read())
    encoders.encode_base64(part)
    part.add_header('Content-Disposition', "attachment", filename="%s" % os.path.basename(filepath))

    # Compose message
    msg = MIMEMultipart()
    msg['From'] = address_from
    msg['To'] = address_to
    msg.attach(part)

    # Send mail
    smtp = SMTP_SSL(mail_adr)
    smtp.set_debuglevel(1)
    smtp.connect(host=mail_adr, port=mail_port)
    smtp.login(address_from, password)
    smtp.sendmail(address_from, address_to, msg.as_string())
    smtp.quit()
    Если будете реализовывать через Gmail, Вам нужно зайти в личный аккаунт на Google – Безопасность и в «Ненадежные приложения, у которых есть доступ к аккаунту» нужно отключить, иначе отправка писем будет блокироваться на стороне сервера Gmail.
    kczwqy_gvegqyv85jjvak_ekrx4.png



Bot.py
  • Импортируем библиотеки:

    # -*- coding: utf-8 -*-
    import telebot
    from telebot.types import LabeledPrice
    import re

    import yandex
    from send_email import send_mail

  • token и добавляем в переменную:

    token = '*********:**********************************'
    bot = telebot.TeleBot(token)

  • Создаем обработку команд:

    @bot.message_handler(commands=['start', 'donation'])
    def send_welcom(message):
    if message.text == '/start':
    keyboard = telebot.types.InlineKeyboardMarkup()
    keyboard.row(telebot.types.InlineKeyboardButton('Получить базу', callback_data='bd_get'))
    bot.send_message(message.chat.id, 'Выберите действие: ', reply_markup=keyboard)

    if message.text == '/donation':
    bot.send_invoice(message.chat.id,
    title='Donation',
    description='Можешь отправить финансовую благодарность.',
    invoice_payload='donation',
    provider_token='*********:TEST:*******',
    currency='RUB',
    prices=[LabeledPrice(label='Donation', amount=10000)],
    start_parameter='pay_start',
    photo_url=' '
    'fundraising-justgiving-charitable-organization-donation-'
    '5Yehm9UecF2cRWrqtms4e6emn.jpg',
    photo_height=512, # !=0/None or picture won't be shown
    photo_width=512,
    photo_size=512,
    is_flexible=False)
    Первый IF обрабатывает запрос /start, второй IF обрабатывает запрос /donation.
    В параметре invoice_payload задаем название, по которому будем определяться подтверждение оплаты.
    В bot.send_invoice нужно указать token используемого провайдера платежей. Как его получить, написано .
    Для разработки советую использовать тестовый token провайдера. При его использовании функционал сохраняется, но деньги не снимаются. В дальнейшем Вам потребуется только заменить на реальный token.
    Тестовый: 123:TEST:XXXX
    Реальный: 123:LIVE:XXXX

  • Данный декоратор необходим для подтверждения наличия заказа:

    @bot.pre_checkout_query_handler(func=lambda query: True)
    def checkout(message):
    bot.answer_pre_checkout_query(message.id, ok=True,
    error_message="Инопланетяне пытались украсть CVV вашей карты, "
    "но мы успешно защитили ваши учетные данные. "
    "Попробуй расплатиться через несколько минут, "
    "нам нужен небольшой отдых.")
    Т.к. у нас digital услуга, она всегда в наличии по этому, параметр ok всегда True. Без данного подтверждения оплата не будет проходить у клиента.
    Данный декоратор полезен если у Вас, например, магазин обуви. Клиент заходит оформить заказ, нажимает оплатить и в этот момент Вам на сервер приходит запрос, который обрабатывается этой функцией. Если на складе имеется данный товар, то параметр ok=True иначе задаете значение False и клиент видит всплывающее окно с текстом, который указан в параметре error_message.
  • Декоратор подтверждает оплату заказа:

    @bot.message_handler(content_types=['successful_payment'])
    def got_payment(message):
    if message.json['successful_payment']['invoice_payload'].split(',')[0] == 'buy':
    email = message.json['successful_payment']['order_info']['email']
    bot.send_message(message.chat.id,
    'Ураааа! Спасибо за оплату на сумму: `{} {}`.\n'
    'Вам на почту (`{}`) отправленно письмо с базой данных.\n\n' \
    'По всем возникшим вопросам обращайтесь @имя_админа'\
    .format(message.successful_payment.total_amount / 100,
    message.successful_payment.currency,
    email),
    parse_mode='Markdown')
    request_text = message.json['successful_payment']['invoice_payload'].split(',')[1]
    city = message.json['successful_payment']['invoice_payload'].split(',')[2]
    write_in_BD, name_excel_BD = yandex.write_exl(text=request_text, city_name=city)
    if write_in_BD == True:
    send_mail(name_file=name_excel_BD, to_address=email)

    elif message.json['successful_payment']['invoice_payload'] == 'donation':
    bot.send_video(message.chat.id, ' ')

  • Декоратор обрабатывает нажатые кнопки InlineKeyboardMarkup:

    @bot.callback_query_handler(func=lambda call: True)
    def callback_key(message):
    if message.data == 'bd_get':
    input_city(message)
    elif re.search(r'bd_yes/',message.data):
    location = re.sub('bd_yes/','',message.data)
    bot.answer_callback_query(message.id, text=location, show_alert=False)
    input_text(message, city=location)
    elif message.data == 'bd_no':
    input_city(message)
    elif re.search(r'pay', message.data):
    info_get_bd_limit = re.split(r'/',message.data)
    sent_text = info_get_bd_limit[1]
    sent_city = info_get_bd_limit[2]
    found = info_get_bd_limit[3]
    bot.answer_callback_query(message.id, text='Оплата', show_alert=False)
    pay(message, text=sent_text, city=sent_city, found=found)

  • Функция отправляет счет на оплату:

    def pay(message, text, city, found):
    bot.send_invoice(message.from_user.id,
    title='База данных',
    description='Оплата базы данных по запросу.\n'
    'Текст: '+text+'\nГород: '+city+'\nКол-во объектов: '+found,
    invoice_payload='buy,{},{}'.format(text, city),
    provider_token='*********:TEST:*******',
    currency='RUB',
    prices=[LabeledPrice(label='База данных', amount=20000)],
    start_parameter='pay_start',
    photo_url=' :'
    'ANd9GcRVUs3eGt4U9YSXZrsbOkJoNEdpcYUdq0vEzM-ci_oIxEWs1FK0',
    photo_height=300,
    photo_width=300,
    photo_size=300,
    need_email=True,
    is_flexible=False)
    Параметр need_email необходим, так как нужно запросить у клиента почту, на которую будем отправлять БД.
  • Функция запрашивает название города, по которому будет производиться поиск:

    def input_city(message):
    bot.send_message(message.from_user.id, 'Введите город:')
    bot.register_next_step_handler_by_chat_id(message.from_user.id, get_city)
    bot.answer_callback_query(message.id, text='Введите город', show_alert=False)

  • Функция отправляет клиенту геолокацию, для уточнения найденного города:

    def get_city(message):
    get_location, get_location_point = yandex.get_location(message.text)
    keyboard = telebot.types.InlineKeyboardMarkup()
    keyboard.row(telebot.types.InlineKeyboardButton('Да', callback_data='bd_yes/'+message.text),
    telebot.types.InlineKeyboardButton('Нет', callback_data='bd_no'))
    bot.send_location(message.chat.id, get_location_point[1], get_location_point[0], reply_markup=keyboard)

  • Функция запрашивает ввод объекта, который хочет искать клиент:

    def input_text(message, city=None):
    global location_city
    location_city = city
    info_message_id = bot.send_message(message.from_user.id, 'Введите объект (например бар): ')
    bot.register_next_step_handler_by_chat_id(message.from_user.id, get_bd_limit)

  • Функция отправляет ограниченное кол-во информации о найденных объектах и предлагает оплатить полную БД:

    def get_bd_limit(message):
    information = yandex.get_information_limit(message.text, location_city)
    if information:
    found = yandex.sum_taken_object(yandex.get_all_infomations(message.text, location_city))
    bot.send_message(message.from_user.id, 'Бот работает в бесплатном режиме.\n'
    'Будет выведено только 5 первых попавшихся результатов.')
    i = 1
    text = ''
    for key, value in information.items():
    name = value['name']
    address = value['address']
    url = value['url']
    phones = value['phones']
    hours = value['hours']
    text = text + '' + str(i) + ') Название: '+name+'\nАдрес: '+address+'\nСайт: '+url+\
    '\nТелефон: '+phones+'\nВремя работы: '+hours+'\n----------\n'
    i += 1
    bot.send_message(message.from_user.id, text, disable_web_page_preview=True)

    keyboard = telebot.types.InlineKeyboardMarkup()
    keyboard.row(telebot.types.InlineKeyboardButton('Оплатить 200 руб.',
    callback_data='pay/' + message.text+'/'+location_city+'/'+found))
    bot.send_message(message.from_user.id, 'Всего найдено объектов по вашему запросу: ' + found+''
    '\n\nВсю базу вы можете получить на почту.', reply_markup=keyboard)
    else:
    bot.send_message(message.from_user.id, 'Ничего не найдено по данному запросу!')


  • В конце кода добавляем по Telegram:

    while True:
    try:
    bot.polling(none_stop=True)
    except Exception as e:
    time.sleep(15)



Сейчас Telegram поддерживает 8 платежных систем. Для российского рынка самые ходовые это Яндекс Касса и Сбербанк. Советую использовать Tranzzo, т.к. с помощью него можно сохранять данные карты, использовать отпечаток пальца для оплат в течение 5 часов после подтверждения паролем.
При использовании тестового token от Сбербанк процесс выставление счет зависал, а при использовании Яндекс Кассы все хорошо. Не знаю с чем это связанно, но факт.​


a43901d62e70eb4940f739419d0588e8.gif


1a4eb1cd12296b3abe1f19d7d42f8290.gif



Заключение.


Данный пост предназначен не только для того, чтобы показать возможности реализации mini-digital-business, но и для того чтобы расшевелить в тебе it предпринимателя, начать генерировать новые идеи где можно было бы применить данный функционал.
Что заработал, то и получил: ударник — хлеб, а лодырь — ничего.​
 
Сверху Снизу