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

Сидя на freelance видел много раз задачи по сбору БД. Чаще всего просят собрать информацию о компаниях или специфические запросы на Google, Yandex картах.
Есть спрос, давайте создавать предложения, но обо всём по порядку.
В данной статье предлагаю разработать Telegram bot, который будет принимать название города (в котором будет производиться поиск) и запрос (по которому будет производиться поиск. Например: Бар, Кофе, Ресторан и т.д.). Реализуем возможность делать donation, оплачивать услугу по сбору БД и отправлять клиентам на почту БД.
Используемые технологии:
-
You must be registered for see links
-
You must be registered for see links
-
You must be registered for see links
Данные технологии или инструменты выбраны для удобства использования, возможности быстрой реализации и автоматизации задачи.
Эта программа не является коммерческой. Реализована для примера, т.к.
You must be registered for see links
запрещено сохранять данныеО правилах можно сказать то же, что и о законах: их многочисленность доказывает не столько соблюдение, сколько нарушение их. ©Дюбэ
Подготовка.
Для удобства разделим проект на 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 нужно
You must be registered for see linksключ, по которому будут Вас идентифицировать. Ключи для использования 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('You must be registered for see links'
'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, если нужны результаты только в центре города. О дополнительных возможностях можете почитатьYou must be registered for see links.
Обратите внимание, что больше 500 объектов запрос не вернет.
Если будет интересна реализация сбора всех объектов превышающих кол-во 500, отпишитесь. Сделаю отдельный пост.
- Получение долготы и широты:
def get_location(city):
location = requests.get('You must be registered for see links'
'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('You must be registered for see links'
'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'hones,'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 = 'You must be registered for see links'
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.
Bot.py
- Импортируем библиотеки:
# -*- coding: utf-8 -*-
import telebot
from telebot.types import LabeledPrice
import re
import yandex
from send_email import send_mail
-
You must be registered for see linkstoken и добавляем в переменную:
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='You must be registered for see links'
'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 используемого провайдера платежей. Как его получить, написаноYou must be registered for see links.
Для разработки советую использовать тестовый 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, 'You must be registered for see links')
- Декоратор обрабатывает нажатые кнопки 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='You must be registered for see links:'
'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, 'Ничего не найдено по данному запросу!')
- В конце кода добавляем по
You must be registered for see linksTelegram:
while True:
try:
bot.polling(none_stop=True)
except Exception as e:
time.sleep(15)
Сейчас Telegram поддерживает 8 платежных систем. Для российского рынка самые ходовые это Яндекс Касса и Сбербанк. Советую использовать Tranzzo, т.к. с помощью него можно сохранять данные карты, использовать отпечаток пальца для оплат в течение 5 часов после подтверждения паролем.
При использовании тестового token от Сбербанк процесс выставление счет зависал, а при использовании Яндекс Кассы все хорошо. Не знаю с чем это связанно, но факт.
![]() | ![]() |
Заключение.
Данный пост предназначен не только для того, чтобы показать возможности реализации mini-digital-business, но и для того чтобы расшевелить в тебе it предпринимателя, начать генерировать новые идеи где можно было бы применить данный функционал.
Что заработал, то и получил: ударник — хлеб, а лодырь — ничего.