WooCommerce — настройка обмена по API
Настроим обмен цен и остатков по API WooCommerce с помощью скрипта на Python.
План такой:
- Просим разработчиков настроить регулярную выгрузку цен и остатков из 1С к нам на сервер.
- Получаем доступ к API WooCommerce нашего интернет-магазина
- Пишем скрипт обновления
- Настраиваем сервер на регулярное выполнение скрипта
Настраиваем выгрузку из 1С на сервер
Эту работу мы поручаем специалистам 1С заказчика. В этой статье не будем рассматривать, как делается выгрузка из 1С. Нам важно в результате получить обычный текстовый файл с данными о товарах.
Для этого мы создаём на сервере папку, в которую будет этой файл выгружаться из 1С по FTP. Соответственно настраиваем отдельный FTP-доступ к этой папке.
Пишем письмо разработчику 1С:
Прошу настроить выгрузку данных о товарах из 1С на сервер. Содержание: - Артикул - Цена - Остаток Формат - txt или csv Папка на сервере: https://site.ru/exchange/ Доступ по FTP: Хост: site.beget.tech Логин: LOGIN Пароль: PASSWORD
Получаем доступы к API WooCommerce
Чтобы получить доступы к API, переходим в Консоль -> WooCommerce -> Настройки -> Вкладка “Дополнительно” -> REST API и нажимаем кнопку “Создать ключ API”
Инструкция: API WooCommerce — как настроить подключение для Python
С сайтом закончили. Переходим к скрипту.
Пишем скрипт обновления
Обновления состоит из 4 этапов:
- Скачиваем файл экспорта товаров из 1С
- Экспортируем товары с сайта, чтобы получить цены, остатки и ID товаров
- Сверяем товары из двух файлов
- Обновляем информацию о товарах на сайте
Внимание: выгрузку цен и остатков из 1С нужно будет привести в нормальный вид. В ценах могут быть пробелы между разрядами, остатки отрицательными и другие неожиданные ошибки.
Предварительно мы создаём файл конфигурации – config.py. В нем будем хранить ключ API и настройки модуля WooCommerce API
from woocommerce import API
exchange_file = "export_products_1C.csv"
export_file = "export_products_woo.csv"
exchange_file_url = "https://site.ru/exchange/price.csv"
url = "https://site.ru/"
consumer_key = "ck_89cc7c2150907bjljgfjl4f63797d3821eddf73"
consumer_secret = "cs_6653433b374a6c8d62072454fdhgsdh62711f4fe"
wcapi = API(
url=url,
consumer_key=consumer_key,
consumer_secret=consumer_secret,
wp_api=True,
version="wc/v3"
)
1 этап. Скачиваем файл экспорта из 1С
Используем всего одну функцию, в которую передаем url – адрес нашей выгрузки из 1С и имя файла, под которым будем его сохранять на сервере.
def download_file(filename, url):
with open(filename, 'wb') as file:
response = requests.get(url=url, stream=True)
for chunk in response.iter_content(4096):
file.write(chunk)
2 этап. Получаем товары с сайта
Первая функция будет получать товары с сайта порциями по 20 позиции. Можно по 100, но так будет дольше и не все интернет-магазины выдерживают такие большие запросы.
Для отправки запросов я создал дополнительную функцию get_response, которая будет обращаться к сайту и в случае проблем с соединением, делать повторную попытку. Проблемы с соединением бывают редко, но приготовиться к ним нужно.
Функция get_products запускает цикл для получения информации о товарах. Обратите внимание на функции get_product_simple и get_product_variable – они нужны для получения простых и вариативных товаров соответственно. Про них чуть позже.
def get_products(filename, fieldnames):
page = 0
offset = 0
per_page = 20
while True:
page += 1
print(page, offset)
params = (
("per_page", per_page),
("offset", offset),
("order", "asc"),
("orderby", "id")
)
response = get_response(metod="get", endpoint="products", params=params, data=None)
products = response.json()
if not products:
break
for product in products:
product_type = product['type']
if 'simple' in product_type:
get_product_simple(product, filename, fieldnames)
if 'variable' in product_type:
get_product_variable(product, filename, fieldnames)
offset += per_page
Функция get_product_simple получает данные о простых товарах. Функция write_csv записывает данные в файл экспорта товаров с сайта. Мы мотом эти два файла будем сверять.
def get_product_simple(product, filename, fieldnames):
data = {
'id': product['id'],
'name': product['name'],
'sku': product["sku"],
'price': product["price"],
'stock': product["stock_quantity"]
}
write_csv(filename=filename, fieldnames=fieldnames, data=data)
А вот функция get_product_variable для получения ID вариативного товара требует дополнительного запроса к сайту.
def get_product_variable(product, filename, fieldnames):
product_id = product['id']
name = product['name']
endpoint = f"products/{product_id}/variations"
variables = get_response(metod='get', endpoint=endpoint, params=None, data=None)
for variable in variables:
data = {
'id': variable['id'],
'name': name,
'sku': variable["sku"],
'price': variable["price"],
'stock': variable["stock_quantity"]
}
write_csv(filename=filename, fieldnames=fieldnames, data=data)
Экспорт товаров с сайта закончен. Переходим к сопоставлению товаров из 1С и товаров с сайта.
3. Сверяем товары из двух файлов
Чтобы обновить товар в WooCommerce, нам нужно знать его ID. В нашем примере мы будем сравнивать товары по артикулу.
Получаем список товаров из файла выгрузки с 1С. Обратите внимание – пришлось убрать пробел '\xa0'
в цене и заменить запятые на точки, чтобы привести цены и остатки из формата строки в число. Кстати, отрицательные остатки заменяем на ноль. Отрицательные остатки в 1С – не редкость, часто товары отгружаются ещё до оприходования, чтобы получить предоплату от покупателя. У нас и не такое бывает )
def get_products_1c(filename):
products_1c = {}
with open(filename, 'r', encoding='utf-8') as file:
for line in csv.DictReader(file, delimiter=';'):
sku = line['\ufeffArticle']
price = float(line['Retail_Price'].replace('\xa0', '').replace(',', '.'))
stock = float(line['Stock_balance'].replace(',', '.'))
if stock < 0:
stock = 0
products_1c[sku] = {"price": price, "stock": stock}
return products_1c
Получаем список товаров из выгрузки с сайта:
def get_products_woo(filename):
products_woo = []
with open(filename, 'r', encoding='utf-8') as file:
for line in csv.DictReader(file):
products_woo.append(line)
return products_woo
Сверяем товары. Если у товара цены и остатки не изменились, то такие товары обновлять не будем. Зачем на них тратить время, верно? А вот если товар есть на сайте, а в выгрузке из 1С его нет, то устанавливаем ему цену и остаток по нолям. Потому что товар может быть снят с продажи.
def match_products(products_1c, products_woo):
update_products = []
for product in products_woo:
product_id = product["id"]
price_woo = product["price"]
stock_woo = product["stock"]
product_1c = products_1c.get(product["sku"])
update = {
'id': product_id,
'price': 0,
'regular_price': 0,
'manage_stock': 'True',
'stock_quantity': 0
}
if product_1c:
price_1c = product_1c["price"]
stock_1c = product_1c["stock"]
if price_woo == price_1c and stock_woo == stock_1c:
pass
else:
update['price'] = price_1c
update['regular_price'] = price_1c
update['stock_quantity'] = stock_1c
update_products.append(update)
return update_products
На выходе мы получили список товаров для обновления. Теперь осталось только отправить эту информацию на сайт.
4. Обновляем информацию о товарах на сайте
Используем функцию batch_update_products, которая будет обновлять товары пакетами по 20 позиций. Лимит на обновление пачкой, до 100 позиций. Но как я уже говорил, не все сайты готовы к обработке больших запросов. В общем количество товаров, отправляемых на сайт за один раз, подбираем опытным путём.
def batch_update_products(update_products):
start = 0
step = 20
while True:
finish = start + step
update_slice = update_products[start:finish]
count = len(update_slice)
if count != 0:
data = {"update": update_slice}
get_response(metod='post', endpoint="products/batch", params=None, data=data)
start += step
print(f'[+] {start}')
else:
break
Итак скрипты написаны, теперь всё это надо поженить в один исполняемый скрипт и настроить запуск этого скрипта на сервере по расписанию.
Настраиваем сервер на регулярное выполнение скрипта
Как настроить сервер – это отдельная история. О ней будет в другой статье. Но чтобы завершить эту инструкцию, читайте пост запускаем Python-скрипт с помощью Cron на Linux.
Спасибо, что прочитали до конца. Если есть вопросы, готов ответить в комментариях или пишите на почту lukin@usota.ru.