Недавно дали мне задачку — сделать скрипт, который будет вытаскивать данные из базы данных Notion по API и загружать их в хранилище S3 в формате Parquet (автоматическая работа скрипта была на заказчике), при этом:
-
нужен был определенный порядок выгрузки столбцов, которые будет задавать заказчик самостоятельно
-
нужно было ограничить выгрузку данных типами столбцов, которые заранее оговорили с заказчиком
Полную версию проекта можно посмотреть у меня на GitHub. Ну а в статье речь пойдет только про выгрузку из Notion.
Итак, первым делом надо создать интеграцию, получить токен и подключить эту интеграцию на странице с базой данных в Notion:
-
Создаем интеграцию → идем сюда https://www.notion.com/my-integrations → нажимаем + New integration → даем имя (например my_token_bd) → сохраняем токен
-
Идем на страницу Notion с базой данных, к которой нужен доступ по API → жмем “…” правом верхнем углу
→ крутим вниз до + Add Connections → выбираем созданную интеграцию (по имени)
Если нужно подробнее → смотри доку по Api Notion.
Запрашиваем данные из базы Notion:
def fetch_database_items(notion, database_id: str) -> List[Dict]: """ Запросить элементы базы данных из Notion. :param database_id: Идентификатор базы данных в Notion. :param notion: Экземпляр клиента для взаимодействия с API Notion. :return: Список элементов базы данных. """ response = notion.databases.query(database_id=database_id) return response['results']
Чтобы посмотреть что внутри я закинула их в json и пошла лицезреть, что там выгрузилось.
Я думала, что сразу выгрузится то, что мне нужно, то, что я вижу в самой базе в Notion.
Но нет…там была гигантская json — ина, в которой были указаны все параметры базы…и даже цвет текста…

Пример (из-за размера показан не весь):
[ { "object": "page", "id": "a1b2c3d4-e5f6-7g8h-9i0j-1234567890ab", "created_time": "2024-01-01T09:00:00.000Z", "last_edited_time": "2024-01-05T09:00:00.000Z", "created_by": { "object": "user", "id": "1a2b3c4d-5e6f-7g8h-9i0j-1234567890ab" }, "last_edited_by": { "object": "user", "id": "1a2b3c4d-5e6f-7g8h-9i0j-1234567890ab" }, "cover": null, "icon": null, "parent": { "type": "database_id", "database_id": "abcdef12-3456-7890-abcd-ef1234567890" }, "archived": false, "in_trash": false, "properties": { "Last edited by": { "id": "last_editor", "type": "last_edited_by", "last_edited_by": { "object": "user", "id": "1a2b3c4d-5e6f-7g8h-9i0j-1234567890ab" } }, "Multi-select": { "id": "multi_select", "type": "multi_select", "multi_select": [ { "id": "initiation", "name": "Initiation", "color": "gray" }, { "id": "planning", "name": "Planning", "color": "brown" } ] }, "Phone Number": { "id": "phone_number", "type": "phone_number", "phone_number": "1234567890" }, "Select": { "id": "select", "type": "select", "select": { "id": "design", "name": "Design", "color": "blue" } }, "Number": { "id": "number", "type": "number", "number": 1 }, "Last edited time": { "id": "last_edited_time", "type": "last_edited_time", "last_edited_time": "2024-01-05T09:00:00.000Z" }, "Date": { "id": "date", "type": "date", "date": { "start": "2024-01-01", "end": "2024-01-10", "time_zone": null } }, "Created time": { "id": "created_time", "type": "created_time", "created_time": "2024-01-01T09:00:00.000Z" }, "Status": { "id": "status", "type": "status", "status": { "id": "not_started", "name": "Not started", "color": "default" } }, "Text": { "id": "rich_text", "type": "rich_text", "rich_text": [ { "type": "text", "text": { "content": "Overview of Project Alpha", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "Overview of Project Alpha", "href": null } ] }, "Email": { "id": "email", "type": "email", "email": "example@domain.com" }, "Name": { "id": "title", "type": "title", "title": [ { "type": "text", "text": { "content": "Project Alpha", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "Project Alpha", "href": null } ] } }, "url": "https://www.notion.so/Project-Alpha-a1b2c3d4e5f67g8h9i0j1234567890ab", "public_url": null }, { "object": "page", "id": "b2c3d4e5-f6g7-h8i9-j0k1-2345678901bc", "created_time": "2024-02-01T09:00:00.000Z", "last_edited_time": "2024-02-05T09:00:00.000Z", "created_by": { "object": "user", "id": "2b3c4d5e-6f7g-8h9i-0j1k-2345678901bc" }, "last_edited_by": { "object": "user", "id": "2b3c4d5e-6f7g-8h9i-0j1k-2345678901bc" }, "cover": null, "icon": null, "parent": { "type": "database_id", "database_id": "abcdef12-3456-7890-abcd-ef1234567890" }, "archived": false, "in_trash": false, "properties": { "Last edited by": { "id": "last_editor", "type": "last_edited_by", "last_edited_by": { "object": "user", "id": "2b3c4d5e-6f7g-8h9i-0j1k-2345678901bc" } }, "Multi-select": { "id": "multi_select", "type": "multi_select", "multi_select": [ { "id": "execution", "name": "Execution", "color": "green" }, и т.д.
Пошла искать, как можно просто вытащить нужное. Надеялась найти готовое решение, но увы. Писала в слак в сообщество разработчиков, которые в теме по Api Notion, ответ один:
«You need to format it yourself based on the different types of properties. «.
Получилось сделать так:
REQUIRED_PROPERTIES = [ 'title', 'rich_text', 'date', 'select', 'multi_select', 'number', 'checkbox', 'url' ] def extract_properties(item: Dict) -> Dict: """ Извлечь свойства элемента базы данных Notion. :param item: Объект элемента базы данных Notion. :return: Словарь с извлеченными свойствами элемента. """ properties = item['properties'] extracted_data = {'id': item['id']} property_types = {} for prop_name, prop_values in properties.items(): prop_type = prop_values['type'] if prop_type in REQUIRED_PROPERTIES: if prop_type == 'title' or prop_type == 'rich_text': extracted_data[prop_name] = get_text(prop_values[prop_type]) property_types[prop_name] = 'string' elif prop_type == 'date': start_date, end_date = get_date(prop_values.get('date', {})) extracted_data[f'{prop_name}_start'] = start_date extracted_data[f'{prop_name}_end'] = end_date property_types[f'{prop_name}_start'] = 'string' property_types[f'{prop_name}_end'] = 'string' elif prop_type == 'select': extracted_data[prop_name] = get_select(prop_values['select']) property_types[prop_name] = 'string' elif prop_type == 'multi_select': extracted_data[prop_name] = get_multi_select( prop_values['multi_select'] ) property_types[prop_name] = 'array' elif prop_type == 'url': extracted_data[prop_name] = prop_values.get(prop_type) property_types[prop_name] = 'string' elif prop_type == 'number': extracted_data[prop_name] = prop_values.get(prop_type) property_types[prop_name] = 'int64' elif prop_type == 'checkbox': extracted_data[prop_name] = prop_values.get(prop_type) property_types[prop_name] = 'bool' with open('property_types.json', 'w+') as f: json.dump(property_types, f) return extracted_data
* параметры начальной и конечной даты выгружаются отдельно, не в кортеж, такой был запрос заказчика
* property_types — словарь с типами свойств, нужен для создания схемы с нужным порядком
Ну и сами функции:
def get_text(text_object: List[Dict]) -> str: """ Получить текст из объекта текста базы данных Notion. :param text_object: Объект текста из базы данных Notion. :return: Объединенный текст. """ text = '' for rt in text_object: text += rt.get('plain_text') return text def get_date(date_object: Dict) -> tuple: """ Получить дату или диапазон дат из объекта даты базы данных Notion. :param date_object: Объект даты из базы данных Notion. :return: Одиночная дата или кортеж с начальной и конечной датами. """ start_date = date_object.get('start', None) if date_object else None end_date = date_object.get('end', None) if date_object else None return start_date, end_date def get_select(select_object: Dict) -> Union[str, None]: """ Получить имя выбранного элемента из объекта выбора базы данных Notion. :param select_object: Объект выбора из базы данных Notion. :return: Имя выбранного элемента или None, если объект отсутствует. """ return select_object.get('name') if select_object else None def get_multi_select(multi_select_object: List[Dict]) -> List[str]: """ Получить список строк, объединяющих элементы выбора из базы данных Notion. :param multi_select_object: Список объектов выбора из базы данных Notion. :return: Список строк, объединяющих элементы выбора. """ return [', '.join([item.get('name', '') for item in multi_select_object])]
После извлечения данные уже выглядели так (пример, выгружала в формат CSV):
id,Number,Name,Select,URL,id,Text,Multi-select,Date_start,Date_end a1b2c3d4-e5f6-7g8h-9i0j-1234567890ab,1,Project Alpha,Design,www.example.com/alpha,a1b2c3d4-e5f6-7g8h-9i0j-1234567890ab,Overview of Project Alpha,"['Initiation, Planning']",2024-01-01,2024-01-10 b2c3d4e5-f6g7-h8i9-j0k1-2345678901bc,2,Project Beta,Development,www.example.com/beta,b2c3d4e5-f6g7-h8i9-j0k1-2345678901bc,Details of Project Beta,"['Execution, Monitoring']",2024-02-15,2024-03-01 c3d4e5f6-g7h8-i9j0-k1l2-3456789012cd,3,Project Gamma,Testing,www.example.com/gamma,c3d4e5f6-g7h8-i9j0-k1l2-3456789012cd,Testing Phase of Project Gamma,"['Testing, Validation']",2024-03-10,2024-03-20 d4e5f6g7-h8i9-j0k1-l2m3-4567890123de,4,Project Delta,Launch,www.example.com/delta,d4e5f6g7-h8i9-j0k1-l2m3-4567890123de,Launch Phase of Project Delta,"['Launch, Review']",2024-04-01,2024-04-05 e5f6g7h8-i9j0-k1l2-m3n4-5678901234ef,5,Project Epsilon,Maintenance,www.example.com/epsilon,e5f6g7h8-i9j0-k1l2-m3n4-5678901234ef,Maintenance of Project Epsilon,"['Maintenance, Support']",2024-05-15,2024-05-20
Таким образом можно выгружать различные свойства из базы данных Notion.
Пожалуйста, не судите строго – я только начинаю свой путь в разработке. Надеюсь, мой опыт кому-нибудь поможет) И вы делитесь своим, если есть что сказать по теме статьи)
ссылка на оригинал статьи https://habr.com/ru/articles/835370/
Добавить комментарий