Skip to content

Высокопроизводительный API wrapper для Питона для быстрого массового обмена данными с Битрикс24 через REST API

License

Notifications You must be signed in to change notification settings

Zidcov/fast_bitrix24

 
 

Repository files navigation

fast_bitrix24

API wrapper для Питона для быстрого получения данных от Битрикс24 через REST API.

Статистика тестов Статистика загрузок Sourcery

Основная функциональность

Высокая скорость обмена данными

Тест скорости

  • На больших списках скорость обмена данными с сервером достигает тысяч элементов в секунду.
  • Использование асинхронных запросов через asyncio и aiohttp позволяет экономить время, делая несколько запросов к серверу параллельно.
  • Автоматическая упаковка запросов в батчи сокращает количество требуемых запросов к серверу и ускоряет обмен данными.

Избежание отказов сервера

  • Скорость отправки запросов к серверу Битрикс контролируется для избежания ошибки HTTP 503 Service unavailable.
  • Если сервер для сложных запросов начинает возвращать 500 Internal Server Error, можно в одну строку понизить скорость запроосов.

Удобство кода

  • Высокоуровневые списочные методы для сокращения количества необходимого кода. Большинство операций занимают только одну строку кода. Обработка параллельных запросов, упаковка запросов в батчи и многое другое убрано "под капот".
  • Позволяет задавать параметры запроса именно в таком виде, как они приведены в документации к Bitrix24 REST API. Параметры проверяются на корректность для облегчения отладки.
  • Выполнение запросов автоматически сопровождается прогресс-баром из пакета tqdm, иллюстрирующим не только количество обработанных элементов, но и прошедшее и оставшееся время выполнения запроса.

Начало

Установите модуль через pip:

pip install fast_bitrix24

Далее в python:

from fast_bitrix24 import *

# замените на ваш вебхук для доступа к Bitrix24
webhook = "https://your_domain.bitrix24.ru/rest/1/your_code/"
b = Bitrix(webhook)

Методы полученного объекта b в дальнейшем используются для взаимодействия с сервером Битрикс24.

Использование

get_all()

Чтобы получить полностью список сущностей, используйте метод get_all():

# список пользователей
users = b.get_all('user.get')

Метод get_all() возвращает список, где каждый элемент списка является словарем, описывающим одну сущность из запрошенного списка.

Вы также можете использовать параметр params, чтобы кастомизировать запрос:

# список сделок в работе, включая пользовательские поля
deals = b.get_all('crm.deal.list', params={
    'select': ['*', 'UF_*'],
    'filter': {'CLOSED': 'N'}
})

get_by_ID()

Если у вас есть список ID сущностей, то вы можете получить их свойства при помощи метода get_by_ID() и использовании методов вида *.get:

'''
получим список всех контактов, привязанных к сделкам, в виде
[
    (ID сделки1, [контакт1, контакт2, ...]),
    (ID сделки2, [контакт1, контакт2, ...]),
    ...
]
'''

contacts = b.get_by_ID('crm.deal.contact.items.get',
    [d['ID'] for d in deals])

Метод get_by_ID() возвращает список кортежей вида (ID, result), где result - ответ сервера относительно этого ID.

call()

Чтобы создавать, изменять или удалять список сущностей, используйте метод call():

# вставим в начало названия всех сделок их ID
tasks = [
    {
        'ID': d['ID'],
        fields: {
            'TITLE': f'{d["ID"]} - {d["TITLE"]}'
        }
    }
    for d in deals
]

b.call('crm.deal.update', tasks)

Метод call() возвращает список ответов сервера по каждому элементу переданного списка.

call_batch()

Если вы хотите вызвать пакетный метод, используйте call_batch():

results = b.call_batch ({
    'halt': 0,
    'cmd': {
        'deals': 'crm.deal.list', # берем список сделок
        # и берем список дел по первой из них
        'activities': 'crm.activity.list?filter[ENTITY_TYPE]=3&filter[ENTITY_ID]=$result[deals][0][ID]'
    }
})

Как это работает

  1. Перед обращением к серверу во всех методах класса Bitrix происходит проверка корректности самых популярных параметров, передаваемых к серверу, и поднимаются исключения TypeError и ValueError при наличии ошибок.
  2. Cоздаются запросы на получение всех элементов из запрошенного списка.
  3. Созданные запросы упаковываются в батчи по 50 запросов в каждом.
  4. Полученные батчи параллельно отправляются на сервер с соблюдением установленных скоростных ограничений (см. ниже "Как Битрикс24 ограничивает скорость заросов").
  5. Ответы (содержимое поля result) собираются в единый плоский список и возвращаются пользователю.
    • Поднимаются исключения класса aiohttp.ClientError, если сервер Битрикс вернул HTTP-ошибку, и RuntimeError, если код ответа был 200, но ошибка сдержалась в теле ответа сервера.
    • Происходит сортировка ответов (кроме метода get_all()) - порядок элементов в списке результатов совпадает с порядком соответствующих запросов в списке запросов.

В случае с методом get_all() пункт 2 выше выглядит немного сложнее:

  • get_all() делает первый запрос к серверу Битрикс24 с указанным методом и параметрами.
  • Сервер возвращает первую страницу (50 элементов) и параметр total - общее количество элементов, найденных по запросу.
  • Исходя из полученного общего количества элементов, создаются запросы на каждую из страниц (всего total // 50 - 1 запросов), необходимых для получения всех запрошенных элементов.

В связи с тем, что выполнение get_all() по длинным спискам может занимать долгое время, в течение которого пользователи могут добавлять новые элементы в список, может возникнуть ситуация, когда общее полученное количество элементов может не соответствовать изначальному значению total. В таких случаях будет выдано стандартное питоновское предупреждение (warning).

Как Битрикс24 ограничивает скорость запросов

  1. Существует пул из 50 запросов, которые можно направить без ожидания.
  2. Пул пополняется со скоростью 2 запроса в секунду.
  3. При исчерпании пула и несоблюдении режима ожидания сервер выдаёт ответ 403 Too Many Requests.

Подробный справочник по классу Bitrix

Объект класса Bitrix создаётся, чтобы через него выполнять все запросы к серверу Битрикс24.

Внутри объекта ведётся учёт скорости отправки запросов к серверу, поэтому важно, чтобы все запросы приложения в отношении одного аккаунта с одного IP-адреса отправлялись из одного экземпляра Bitrix.

Метод __init__(self, webhook: str, verbose: bool = True):

Создаёт экземпляр объекта Bitrix.

Параметры

  • webhook: str - URL вебхука, полученного от сервера Битрикс.

  • verbose: bool = True - показывать прогрессбар при выполнении запроса.

Метод get_all(self, method: str, params: dict = None) -> list

Получить полный список сущностей по запросу method.

get_all() самостоятельно обрабатывает постраничные ответы сервера, чтобы вернуть полный список (подробнее см. "Как это работает" выше).

Параметры

  • method: str - метод REST API для запроса к серверу.

  • params: dict - параметры для передачи методу. Используется именно тот формат, который указан в документации к REST API Битрикс24. get_all() не поддерживает параметры start, limit и order.

Возвращает полный список сущностей, имеющихся на сервере, согласно заданным методу и параметрам.

Метод get_by_ID(self, method: str, ID_list: Sequence, ID_field_name: str = 'ID', params: dict = None) -> list

Получить список сущностей по запросу method и списку ID.

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

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

deals = b.get_all('crm.deal.list', params={
    'filter': {'CLOSED': 'N'}
})

contacts = b.get_by_ID('crm.deal.contact.item.get',
    [d['ID'] for d in deals])

Параметры

  • method: str - метод REST API для запроса к серверу.

  • ID_list: Sequence - список ID, в отношении которых будут выполняться запросы.

  • ID_field_name: str - название поля, в которое будут подставляться значения из списка ID_list. По умолчанию 'ID'.

  • params: dict - параметры для передачи методу. Используется именно тот формат, который указан в документации к REST API Битрикс24. Если среди параметров, указанных в params, указан параметр ID, то поднимается исключение ValueError.

Возвращает список кортежей вида:

[
    (ID_1, результат_выполнения_запроса_по_ID_1),
    (ID_2, результат_выполнения_запроса_по_ID_2),
    ...
]

Первым элементом каждого кортежа будет ID из списка ID_list. Вторым элементом каждого кортежа будет результат выполнения запроса относительно этого ID. Это может быть, например, список связанных сущностей или пустой список, если не найдено ни одной привязанной сущности.

get_by_ID() гарантированно возвращает список такой же длины, как и поданный на вход ID_list.

Метод call(self, method: str, items)

Вызвать метод REST API. Самый универсальный метод, применяемый, когда get_all и get_by_ID не подходят.

Параметры

  • method: str - метод REST API

  • items - параметры вызываемого метода. Может быть списком, и тогда метод будет вызван для каждого элемента списка, а может быть одним словарем параметров для единичного вызова.

call() вызывает method, последовательно подставляя в параметры запроса все элементы items, и возвращает список ответов сервера для каждого из отправленных запросов. Либо, если items - не список, а словарь с параметрами, то происходит единичный вызов и возвращается его результат.

Метод call_batch(self, params: dict) -> dict

Вызвать метод batch (см. официальную документацию по методу batch).

Поддерживается примение результатов выполнения одной команды в следующей при помощи ключевого слова $result:

results = b.call_batch ({
    'halt': 0,
    'cmd': {
        'deals': 'crm.deal.list', # берем список сделок
        # и берем список дел по первой из них
        'activities': 'crm.activity.list?filter[ENTITY_TYPE]=3&filter[ENTITY_ID]=$result[deals][0][ID]'
    }
})

Возвращает словарь вида:

{
    'имя_команды_1': результаты_выполнения_команды_1,
    'имя_команды_1': результаты_выполнения_команды_1,
    ...
}

Контекстный менеджер slow

Иногда, когда серверу Битрикса посылается запрос, отбирающий много ресурсов сервера (например, на создание 2500 лидов), то сервер не выдерживает даже стандартных темпов подачи запросов, описанных в официальной документации, возвращая 500 Internal Server Error после нескольких первых запросов.

В такой ситуации помогает замедление запросов при помощи контекстного менеджера slow:

# временно снижаем скорость до 1.2 запроса в секунду
slower_speed = 1.2
with slow(slower_speed):
    b.call('crm.lead.add', [{} for x in range(2500)])

# а теперь несемся с прежней скоростью
leads = b.get_all('crm.lead.list')
...

slow(requests_per_second: float = 0.5)

Снижает скорость запросов. По вызовам Bitrix, происходящим внутри выхова этого контекстного менеджера:

  • скорость запросов гарантированно не превысит requests_per_second
  • механика "пула запросов" отключается - считается, что размер пула равен 0, и запросы подаются равномерно

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

Параметры

  • requests_per_second: float = 0.5 - требуемая замедленная скорость запросов. По умолчанию 0.5 запросов в секунду.

Советы и подсказки

А как мне сформировать запрос к Битриксу, чтобы ...?

  1. Поищите в официальной документации по REST API.
  2. Если на ваш вопрос там нет ответа - попробуйте задать его в группе "Партнерский REST API" в Сообществе разработчиков Битрикс24.
  3. Спросите в Телеграме в группе разработчиков Битрикс24.
  4. Спросите в Телеграме в группе пользователей fast_bitrix24.
  5. Спросите на русском StackOverflow.

Я хочу добавить несколько лидов списком, но получаю ошибку сервера.

Оберните вызов call() в slow, установив скорость запросов в 1 - 1,3 в секунду:

with slow(1.2):
    results = b.call('crm.lead.add', tasks)

Я хочу вызвать call() только один раз, а не по списку.

Передавайте параметры запроса методу call(), он может делать как запросы по списку, так и единичный запрос:

method = 'crm.lead.add'
params = {'fields': {
    'TITLE': 'Чпок'
}}
b.call(method, params)

Результатом будет ответ сервера по этому одному элементу.

Однако, если такие вызовы делаются несколько раз, то более эффективно формировать из них список и вызывать call() единожды по всему списку.

Как сортируются результаты при вызове get_all()?

Пока что никак.

Все обращения к серверу происходят асинхронно и список результатов отсортирован в том порядке, в котором сервер возвращал ответы. Если вам требуется сортировка, то вам нужно делать ее самостоятельно, например:

deals = b.get_all('crm.deal.list')
deals.sort(key = lambda d: int(d['ID']))

Как связаться с автором

About

Высокопроизводительный API wrapper для Питона для быстрого массового обмена данными с Битрикс24 через REST API

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 99.4%
  • Batchfile 0.6%