MongoDB и Python: работа с NoSQL базой данных через pymongo

MongoDB и Python: работа с NoSQL базой данных через pymongo

Сегодня познакомимся с интересным инструментом - NoSQL базой данных. Ранее мы мучали MySQL и подобные базы, которые требуют много внимания к SQL-запросам и структуре таблицы. Теперь мы познакомимся с идеальной базой данных для не структурированной информации. То есть мы избавимся от строгого синтаксиса и сможем легко дополнять записи новой информацией. В этом огромный плюс NoSQL-базы для написания различных ботов, которые в дальнейшем будут масштабироваться.

Что такое MongoDB и NoSQL простыми словами

Перед созданием кода разберёмся с терминами. MongoDB - популярная NoSQL база данных, которая хранит данные не в таблицах, а в документах. Формат документов похож на JSON.

Чем NoSQL база данных отличается от привычных SQL (MySQL, PostgreSQL):

  • В SQL данные лежат в таблицах со строгими столбцами. Добавить новое поле - надо менять схему всей таблицы.
  • В NoSQL базе данных MongoDB данные - это документы, и у каждого может быть свой набор полей. Захотели добавить поле одному пользователю - просто добавили, остальных не трогаем.

Основные понятия MongoDB:

  • База данных (database) - контейнер для коллекций, как и в SQL.
  • Коллекция (collection) - аналог таблицы в SQL, но без жёсткой структуры.
  • Документ (document) - одна запись, похожая на словарь Python или JSON-объект.
  • Поле (field) - пара ключ-значение внутри документа.

Главный плюс MongoDB - гибкость. Не нужно заранее проектировать жёсткую схему. Это удобно для проектов где структура данных меняется: боты, логи, пользовательские профили, каталоги товаров с разными характеристиками.

MongoDB или SQL: когда что выбирать

Частый вопрос новичков - брать MongoDB или классический SQL. Однозначного ответа нет, зависит от задачи.

Критерий MongoDB (NoSQL) SQL (PostgreSQL, MySQL)
Структура данных Гибкая, документы Жёсткие таблицы и схемы
Связи между данными Слабые, вложенность Сильные, JOIN-ы
Масштабирование Горизонтальное (шардинг) Вертикальное
Транзакции Есть, но проще ACID из коробки
Когда брать Логи, профили, каталоги, боты Финансы, заказы, строгие связи

Когда MongoDB - хороший выбор:

  • Структура данных заранее неизвестна или часто меняется.
  • Нужно быстро прототипировать без проектирования схемы.
  • Данные естественно ложатся в документы (JSON из API, профили, события).
  • Боты и приложения которые планируется масштабировать.

Когда лучше остаться на SQL:

  • Данные сильно связаны (заказы, платежи, пользователи с ролями).
  • Нужны сложные JOIN-ы и строгая целостность.
  • Важны полноценные ACID-транзакции.

Многие проекты используют обе базы: SQL для строгих данных, MongoDB для гибких. Это нормальная практика.

Установка MongoDB и подготовка окружения

Сначала установим саму MongoBD: переходим на официальный сайт Mongo по ссылке и скачиваем сервер версии 6.0.3 под вашу операционную систему.

Для корректной установки следуйте примерам на скриншотах:

Важно тут выбрать пункт “Complete”. Этот пункт поставит все стандартные настройки и модули, что для начала нам подойдет. Потом, если будете изучать эту БД, сможете настраивать под себя:

В этом пункте важно поставить галочку на “Install MongoDB Compass” - на компьютер установится графический интерфейс для взаимодействия с СУБД.

После установки нам открывается программа MongoDB Compass, где уже вставлены данные для подключения к установленному серверу. Нажимаем кнопку "connect", и мы в нем:

Ниже пример как можно создать базу данных и коллекцию. Коллекции – это аналог таблиц в SQL-базах. В них мы храним информацию, которая нам нужна.

Подключение к MongoDB на Python через pymongo

Для Windows:

pip install pymongo faker

Для MacOS:

python pip install pymongo faker

Для знакомства с СУБД MongoDB нам хватит сделать несколько методов, таких как: добавление пользователя, получить всю коллекцию, изменить какое-то значение, поиск, по заданным критериям и на этом все. Если вам будет интересна эта СУБД,  вы сможете самостоятельно ознакомиться с другими её возможностями. 

Анонсы всех видео, статей и полезностей - в нашем Telegram🔥
Присоединяйтесь, обсуждайте и автоматизируйте!

У нас будет два файла - pymongoAPI.py и main.py (призываю вас всегда разделять проект на логические модули – это правильный тон в мире программистов Python). Первый файл будет отвечать за взаимодействие с БД, а во втором мы будем пытаться добавлять новые значения и что-то менять. 

Импорты:

from pymongo import MongoClient

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

Класс взаимодействия с MongoDB:

class MongoDB(object):

    def __init__(self, host: str = 'localhost',
                 port: int = 27017,
                 db_name: str = None,
                 collection: str = None):
        self._client = MongoClient(f'mongodb://{host}:{port}')
        self._collection = self._client[db_name][collection]

    def create_user(self, user: dict):
        try:
            if self._collection.find_one({"username": user.get('username')}) == None:
                self._collection.insert_one(user)
                print(f"Added New user: {user.get('username')}")
            else:
                print(f"User: {user.get('username')} in collection")
        except Exception as ex:
            print("[create_user] Some problem...")
            print(ex)

    def get_all_users(self):
        try:
            data = self._collection.find()
            print("Get all users")
            return data
        except Exception as ex:
            print("[get_all] Some problem...")
            print(ex)

    def find_by_username(self, username: str):
        try:
            data = self._collection.find_one({"username": username})
            print("Get user by username")
            return data
        except Exception as ex:
            print("[find_by_username] Some problem...")
            print(ex)

    def change_user(self, username: str, key: str, value: str):
        try:
            if self._collection.find_one({"username": user.get('username')}) is not None:
                self._collection.update_one({"username": username}, {"$set": {key: value}})
            else:
                print(f'User: {username} not find')
        except Exception as ex:
            print("[change_user] Some problem...")
            print(ex)

Разберёмся что за чем идет.

__init__ — это конструктор, он принимает в себя ip адрес сервера, порт, название базы данных и название коллекции. Он создает подключение к серверу и записывает в отдельную внутреннюю переменную. Дальше отдельно вытягивает в переменную нашу базу данных. Часто спрашивают зачем ставить нижней прочерк перед полем – это нужно, чтобы показать другим разработчикам, который показывает, что его трогать не надо (так принято показывать в Python приватные переменные).

Create_user – «кушает» словарь, это связанно с тем, что мы будем сразу передавать нового пользователя. Можно формировать его и внутри метода, но так проще. Сразу скажу, что все запросы к базам данных следует оборачивать в try… except, потому что в случае ошибки у нас не будет «падать» программа. В реальной жизни если из-за этой ошибки пользователи не смогу воспользоваться функциями, у вас могут быть проблемы. 

Теперь переходим к сути. Обращаемся к методу find_one для проверки, существует ли пользователь в коллекции. Внутрь мы передаем словарь ключ (искомое поле) и значение. Если нам в ответ вернется None, то значит такого значения не существует, ну и если словарь, то значит пользователь существует (сразу отмечу, что username у нас будут индивидуальным значением, то есть не будет повторяться). 

Дальше обращаемся к insert_one и передаем словарь пользователя. Чтобы видеть ошибки, мы делаем выводы в консоль о выполненном действии и в случае ошибки показываем модуль с ошибкой.

Get_all_users – с помощью этого метода будем выводить все записи в коллекции. Обращаемся к объекту коллекции и применяем метод find, без каких-либо значений. В ответ мы получаем массив значений и возвращаем его из метода.

Find_by_username – теперь будем искать по уникальному значению в виде username. Принимаем искомого пользователя и обращаемся к методу коллекции find_one. Он будет выводить первое совпадение и возвращать его словарем, если пользователя не существует, то вернется None.

Change_user – последний метод на сегодня, он будет менять значение. Принимает в себя username, key (изменяемое значение) и value (новое значение). В начале идет проверка на наложение пользователя в коллекции, потом используется update_one

Что же это такое, метод, предоставляемый объектом коллекции для обновления записи? Первым значением идет словарь для поиска по значение, потом второй с служебным оператором $set с вложенным вторым словарем, внутри которого находится ключ и его новое значение (подробнее об служебных операторах по ссылке).

Работа с базой данных MongoDB: запись и чтение

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

from mongoAPI import MongoDB
import faker

Дальше создаем объект dbase на основе нашего класса, передаем в него название БД и коллекции с которой будем работать:

dbase = MongoDB(db_name='article', collection='user')

Так же не стоит забывать про объект для создание «фейковых» пользователей:

faker_obj = faker.Faker()

Создаем наш фейковый профиль и посмотрим вывод в консоль:

user_profile = faker_obj.simple_profile()
user_profile['birthdate'] = user_profile['birthdate'].strftime('%d/%m/%Y')
print(user_profile)

Словарь состоит из логина, имени, пола, адреса, почты и даты рождения:

{'username': 'dparker', 'name': 'Megan Morris', 'sex': 'F', 'address': '15074 Conner Lane\nDavidshire, ME 67230', 'mail': 'leslie72@gmail.com', 'birthdate': '24/04/2017'}

Пробуем добавить его в коллекцию:

dbase.create_user(user_profile)

 Выводы в консоль:

{'username': 'ronald44', 'name': 'Carrie Armstrong', 'sex': 'F', 'address': '0829 Cook Key Apt. 678\nKatherinestad, CA 73382', 'mail': 'tammy73@hotmail.com', 'birthdate': '24/08/1998'}

Added New user: ronald44

Как выглядит все в коллекции:

Тут может возникнуть вопрос, что за значение _id? Это уникальный ID, который автоматически создает MongoDB. Можно провести аналогию с Primary Key в MySQL.

Давайте добавим пару случайных пользователей с помощью библиотеки faker и цикла for. Выглядит все так:

for _ in range(3):
    user_profile = faker_obj.simple_profile()
    user_profile['birthdate'] = user_profile['birthdate'].strftime('%d/%m/%Y') 
    dbase.create_user(user_profile)

Добавили пользователей: xwilliams, kylewilliams и obrowning. Так это выглядит в коллекции:

Чтобы посмотреть полную сводку из коллекции надо будет воспользоваться циклом for. Это связано с тем, что в ответ нам идет объект Cursor:

result = dbase.get_all_users()
for i in result:
    print(i)

 Вывод в консоль:

Get all users

{'_id': ObjectId('63790f4edc6a4ce30ed44398'), 'username': 'ronald44', 'name': 'Carrie Armstrong', 'sex': 'F', 'address': '0829 Cook Key Apt. 678\nKatherinestad, CA 73382', 'mail': 'tammy73@hotmail.com', 'birthdate': '24/08/1998'}

{'_id': ObjectId('637910f07d126aae084a4310'), 'username': 'xwilliams', 'name': 'Erin Simmons', 'sex': 'F', 'address': 'PSC 0729, Box 2069\nAPO AP 14417', 'mail': 'suzanne49@hotmail.com', 'birthdate': '05/04/1918'}

{'_id': ObjectId('637910f07d126aae084a4311'), 'username': 'kylewilliams', 'name': 'Jeffrey Miller', 'sex': 'M', 'address': '977 Santos Turnpike Apt. 039\nNew Cynthia, FM 81301', 'mail': 'christopher50@gmail.com', 'birthdate': '16/06/2016'}

{'_id': ObjectId('637910f07d126aae084a4312'), 'username': 'obrowning', 'name': 'Amy Thompson', 'sex': 'F', 'address': '041 Leah Locks Suite 280\nReedport, MI 56259', 'mail': 'vargassarah@hotmail.com', 'birthdate': '25/08/1915'}

Поиск человека по логину:

print(dbase.find_by_username('username'))

 Вывод в консоль:

Get user by username

{'_id': ObjectId('63790f4edc6a4ce30ed44398'), 'username': 'ronald44', 'name': 'Carrie Armstrong', 'sex': 'F', 'address': '0829 Cook Key Apt. 678\nKatherinestad, CA 73382', 'mail': 'tammy73@hotmail.com', 'birthdate': '24/08/1998'}

Теперь изменим пользователю имя на "тест":

dbase.change_user('ronald44', 'name', 'test')

После запуска мы увидим в коллекции следующее:

CRUD-операции в MongoDB: полный набор

CRUD - четыре базовые операции с данными: Create (создать), Read (читать), Update (обновить), Delete (удалить). В нашем классе уже есть часть, разберём весь набор для MongoDB через pymongo.

Create - вставка документов:

# Один документ
collection.insert_one({"name": "Иван", "age": 30})
# Несколько сразу
collection.insert_many([
    {"name": "Пётр", "age": 25},
    {"name": "Мария", "age": 28},
])

Read - поиск документов:

# Найти один
user = collection.find_one({"name": "Иван"})
# Найти все подходящие
adults = collection.find({"age": {"$gte": 18}})
for u in adults:
    print(u)
# С условиями: age больше 25 И name начинается на "П"
collection.find({"age": {"$gt": 25}, "name": {"$regex": "^П"}})

Update - обновление:

# Обновить одно поле
collection.update_one({"name": "Иван"}, {"$set": {"age": 31}})
# Обновить несколько документов
collection.update_many({"age": {"$lt": 18}}, {"$set": {"status": "несовершеннолетний"}})

Delete - удаление:

collection.delete_one({"name": "Иван"})
collection.delete_many({"status": "неактивный"})

Операторы запросов MongoDB которые пригодятся постоянно: $gt (больше), $gte (больше или равно), $lt (меньше), $in (входит в список), $regex (по регулярке), $set (установить значение), $exists (поле существует).

Индексы в MongoDB: ускоряем запросы

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

Индекс - это структура которая позволяет находить нужные документы быстро, не перебирая всё.

Создаём индекс по полю:

collection.create_index("username")
# Уникальный индекс - запрещает дубли
collection.create_index("email", unique=True)
# Составной индекс по нескольким полям
collection.create_index([("age", 1), ("name", 1)])

Цифры 1 и -1 задают порядок сортировки в индексе (по возрастанию или убыванию).

Как понять что запрос тормозит и нужен индекс - метод explain():

collection.find({"username": "ronald44"}).explain()

В выводе смотрим на COLLSCAN (плохо, перебор всей коллекции) против IXSCAN (хорошо, использован индекс).

Правило простое: если по полю часто ищете - сделайте индекс. Но не плодите их без меры, каждый индекс занимает место и замедляет запись.

Агрегации в MongoDB: аналитика по данным

Когда нужно не просто достать документы, а посчитать что-то (среднее, суммы, группировки) - используется aggregation pipeline. Это конвейер этапов, через которые проходят данные.

Пример: посчитать сколько пользователей в каждом городе:

pipeline = [
    {"$group": {"_id": "$city", "count": {"$sum": 1}}},
    {"$sort": {"count": -1}},
]
result = collection.aggregate(pipeline)
for row in result:
    print(row)

Основные этапы pipeline:

  • $match - фильтрация (как find, но в конвейере).
  • $group - группировка с подсчётом.
  • $sort - сортировка результата.
  • $project - выбрать какие поля оставить.
  • $limit - ограничить количество.

Более сложный пример - средний возраст по городам, только где больше 5 человек:

pipeline = [
    {"$group": {"_id": "$city", "avg_age": {"$avg": "$age"}, "n": {"$sum": 1}}},
    {"$match": {"n": {"$gt": 5}}},
    {"$sort": {"avg_age": -1}},
]

Агрегации - мощный инструмент MongoDB, заменяющий многие SQL-запросы с GROUP BY. Для аналитики по данным в NoSQL базе это основной способ.

MongoDB Atlas: база данных в облаке

Поднимать MongoDB на своём сервере не обязательно - есть официальное облако MongoDB Atlas. Удобно когда не хочется возиться с установкой и администрированием.

Что даёт Atlas:

  • Бесплатный тариф (M0) - до 512 МБ, хватает для пет-проектов и обучения.
  • Автоматические бэкапы и репликация.
  • Не нужно настраивать сервер - база поднимается за пару минут через веб-интерфейс.
  • Подключение из кода тем же pymongo, меняется только строка подключения.

Подключение к Atlas из Python почти не отличается от локального - просто другой URI:

from pymongo import MongoClient
client = MongoClient(
    "mongodb+srv://user:password@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority"
)
db = client["article"]
collection = db["user"]

Строку подключения берёте в панели Atlas (кнопка Connect). Дальше весь код работы с базой данных MongoDB - точно такой же как для локальной. Это и есть плюс: разработали локально, задеплоили в облако без переписывания.

FAQ: MongoDB и Python

Чем MongoDB отличается от SQL?

MongoDB - NoSQL база данных, хранит данные в гибких документах (как JSON), без жёсткой схемы. SQL хранит в таблицах со строгими столбцами. MongoDB удобнее когда структура данных меняется, SQL - когда нужны строгие связи и транзакции.

Какая библиотека нужна для работы с MongoDB на Python?

pymongo - официальный драйвер MongoDB для Python. Ставится через pip install pymongo. Через него создаётся подключение MongoClient и выполняются все операции с базой.

Можно ли использовать MongoDB бесплатно?

Да. MongoDB Community Edition бесплатна для self-hosted. Облако MongoDB Atlas имеет бесплатный тариф M0 (до 512 МБ) - хватает для обучения и небольших проектов.

Что такое коллекция в MongoDB?

Коллекция - аналог таблицы в SQL. В ней хранятся документы (записи). Отличие в том что у документов в одной коллекции могут быть разные поля - жёсткой структуры нет.

Когда выбирать MongoDB вместо PostgreSQL?

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

Нужны ли индексы в MongoDB?

Да, как только данных становится много. Без индексов поиск перебирает всю коллекцию и тормозит. Индекс создаётся через collection.create_index("поле") и резко ускоряет запросы по этому полю.

Заключение

Сегодня мы тестировали СУБД MongoDB - сделали тестовое добавление коллекции и попробовали вносить в базу изменения.

Как вы видите, Mongo очень удобна и гибка, что позволяет легко масштабировать из маленького проекта в большой. Советую вам изучить её более подробно.

Полезные ссылки

Документации библиотеки pymongo: https://pymongo.readthedocs.io/en/stable/

Подробный курс на ютубе: https://www.youtube.com/playlist?list=PL6plRXMq5RABbVCM0dn23PTKO13WcXnbf