Как сформировать большой файл для маркета

Как сформировать большой файл для маркета

На сайте интернет-магазина с большим количеством товаров (от 8000) возникает проблема с формированием XML-фида для Яндекс Маркета – товаров в БД так много, что лимитов хостинга по выделяемой памяти и времени не хватает для формирования файла.

Решение проблемы – разбить генерацию файла на несколько этапов и запаковать готовый файл в ZIP-архив.

О работе скрипта

При первом запуске скрипта cron.php создается файл market_status.json для хранения необходимых переменных, формируется «шапка» файла с данными о магазине и списком категорий, далее эти данные сохраняются в файл market.xml.

В последующих запусках скрипта из базы данных берутся по 1000 товаров и дописываются в конец файла market.xml. В последнем запуске цикла, в файл дописываются закрывающие теги и происходит архивация файла в market.zip.

<?php

// Локаль.
setlocale(LC_ALL, 'ru_RU.utf8');
date_default_timezone_set('Europe/Moscow');
header('Content-type: text/html; charset=utf-8');

// Интервал обновления файла market.zip (сек.)
$interval = 12000; // Обновление раз в 3,5 часа

// Количество товаров в одном шаге
$limit = 1000;

// Подключение к БД
$dbh = new PDO('mysql:dbname=db_name;host=localhost', 'логин', 'пароль');

// Загрузка файла market_status.json
if (is_file(__DIR__ . '/market_status.json')) {
	$status = json_decode(file_get_contents(__DIR__ . '/market_status.json'), true);
} else {
	$status = array(
		'date'  => 0,
		'total' => 0,
		'step'  => 0
	);
}

if ($status['total'] == $status['step'] && $status['date'] + $interval < time()) {
	// Шаг 0 - создаем новый файл market.xml
	$out = '<?xml version="1.0" encoding="UTF-8"?>
	<yml_catalog date="' . date('Y-m-d H:i') . '">
	<shop>
		<name>Название магазина</name>
		<company>Полное наименование компании</company>
		<url>https://example.com</url>
		<currencies>
			<currency id="RUR" rate="1"/>
		</currencies>';
 
	// Список категорий магазина:
	$sth = $dbh->prepare("SELECT `id`, `parent`, `name` FROM `category`");
	$sth->execute();
	$category = $sth->fetchAll(PDO::FETCH_ASSOC);

	$out .= '<categories>' . PHP_EOL;
	foreach ($category as $row) {
		$out .= '<category id="' . $row['id'] . '" parentId="' . $row['parent'] . '">' . $row['name'] . '</category>' . PHP_EOL;
	}
	$out .= '</categories>' . PHP_EOL;
	$out .= '<offers>' . PHP_EOL;
	
	// Сохраняем в файл
	file_put_contents(__DIR__ . '/market.xml', $out);
	
	// Получаем общее кол-во товаров
	$sth = $dbh->prepare("SELECT COUNT(`id`) FROM `prods`");
	$sth->execute();
	$total = $sth->fetch(PDO::FETCH_COLUMN);

	$status = array(
		'date'  => time(),
		'total' => ceil($total / $limit),
		'step'  => 0
	);

	// Обновляем данные в market_status.json
	file_put_contents(__DIR__ . '/market_status.json', json_encode($status));

	echo 'Шаг ' . $status['step'] . ' из ' . $status['total'];
} elseif ($status['total'] != $status['step']) {
	// Выполнение остальных шагов
	$start = $status['step'] * $limit;
	$sth = $dbh->prepare("SELECT * FROM `prods` LIMIT {$start}, {$limit}");
	$sth->execute();	
	$prods = $sth->fetchAll(PDO::FETCH_ASSOC);

	$out = '';
	foreach ($prods as $row) {
		$out .= '
		<offer id="'.$row['id'] . '" available="true">
			<name>' . $row['name'] . '</name>
			<url>https://example.com/prods/' . $row['id'] . '</url>
			<vendor>' . $row['brand'] . '</vendor>
			<price>' . $row['price'] . '</price>
			<currencyId>RUR</currencyId>
			<categoryId>' . $row['category'] . '</categoryId>
			<picture>https://example.com/uploads/' . $row['image'] . '</picture>
			<description><![CDATA[' . $row['text'] . ']]></description>
		</offer>';	
	}

	// Обновляем данные в market_status.json
	$status['step']++;
	file_put_contents(__DIR__ . '/market_status.json', json_encode($status));

	// Если последний шаг, то нужно закрыть теги
	if ($status['total'] == $status['step']) {
		$out .= '</offers></shop></yml_catalog>';
	}
	
	// Сохраняем данные в market.xml
	$fp = fopen(__DIR__ . '/market.xml', 'a');  
	fwrite($fp, PHP_EOL . $out);  
	fclose($fp);

	// Если последний шаг, то ZIP-архивация
	if ($status['total'] == $status['step']) {
		$zip = new ZipArchive();
		$zip->open(__DIR__ . '/market.zip', ZipArchive::CREATE|ZipArchive::OVERWRITE);
		$zip->addFile(__DIR__ . '/market.xml', 'market.xml');
		$zip->close();
	}

	echo 'Шаг ' . $status['step'] . ' из ' . $status['total'];
} else {
	// Файл недавно сформирован, пока отдыхаем
	echo 'Файл сформирован, следующее обновление в ' . date('d.m.Y H:s', $status['date'] + $interval);
}
PHP

Данный скрип должен вызываться поочередно, например по крону c интервалом раз в 15 минут:

Минута  Час  День  Месяц  День недели  Команда
*/15    *    *     *      *            /usr/local/bin/wget -O - -q "https://example.com/cron.php"

Пример работы скрипта

Первый запуск скрипта cron.php
Первый запуск скрипта cron.php
Последующие запуски скрипта
Последующие запуски скрипта
Завершение процесса, файл market.zip сформирован
Завершение процесса, файл market.zip сформирован
14.01.2021
4232
Предыдущая запись Local Storage и Session Storage в JavaScript
Следующая запись Работа с MIME-типами в PHP

Комментарии

, чтобы добавить комментарий.

Другие публикации

ZIP в PHP (ZipArchive)
Класс ZipArchive позволяет быстро и удобно работать с ZIP-архивам, рассмотрим основные возможности класса.
20927
+7
Примеры использования PDO MySQL
В статье приведены основные примеры работы с расширением PHP PDO. Такие как подключение к БД, получение, изменение и...
108931
+8
PHP-класс обертка для PDO
Класс значительно упрощает работу с PDO, сокращает код. Реализован на статических классах и не требует создание экземпляра класса.
23939
+12
Автоматическое сжатие и оптимизация картинок на сайте
Изображения нужно сжимать для ускорения скорости загрузки сайта, но как это сделать? На многих хостингах нет...
29965
+7
Формирование файла sitemap.xml
Пример создания файла карты сайта (sitemap.xml) на PHP. Интеграция его на сайт и подключение его в robots.txt
25019
+4
Получить фото из Instagram без API
Так как Instagram и Fasebook ограничили доступ к API, а фото с открытого аккаунта всё же нужно периодически получать и...
25217
+7