generated from mirzaev/pot
работа завершена
This commit is contained in:
parent
d1a6e3f914
commit
cabdfb8cc7
|
@ -2,7 +2,7 @@
|
||||||
"name": "mirzaev/parser_from_rossko",
|
"name": "mirzaev/parser_from_rossko",
|
||||||
"description": "",
|
"description": "",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"keywords": [],
|
"keywords": ["parser", "rossko"],
|
||||||
"type": "site",
|
"type": "site",
|
||||||
"homepage": "https://git.mirzaev.sexy/mirzaev/parser_from_rossko",
|
"homepage": "https://git.mirzaev.sexy/mirzaev/parser_from_rossko",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
|
@ -22,10 +22,10 @@
|
||||||
"php": "~8.3",
|
"php": "~8.3",
|
||||||
"ext-sodium": "~8.3",
|
"ext-sodium": "~8.3",
|
||||||
"mirzaev/minimal": "^2.2.0",
|
"mirzaev/minimal": "^2.2.0",
|
||||||
"mirzaev/accounts": "~1.2.x-dev",
|
"guzzlehttp/guzzle": "^7.8",
|
||||||
"mirzaev/arangodb": "^1.0.0",
|
"php-imap/php-imap": "^5.0",
|
||||||
"triagens/arangodb": "~3.9.x-dev",
|
"avadim/fast-excel-reader": "^2.17",
|
||||||
"twig/twig": "^3.4"
|
"avadim/fast-excel-writer": "^5.7"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "~9.5"
|
"phpunit/phpunit": "~9.5"
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Edit this file to introduce tasks to be run by cron.
|
||||||
|
#
|
||||||
|
# Each task to run has to be defined through a single line
|
||||||
|
# indicating with different fields when the task will be run
|
||||||
|
# and what command to run for the task
|
||||||
|
#
|
||||||
|
# To define the time you can provide concrete values for
|
||||||
|
# minute (m), hour (h), day of month (dom), month (mon),
|
||||||
|
# and day of week (dow) or use '*' in these fields (for 'any').
|
||||||
|
#
|
||||||
|
# Notice that tasks will be started based on the cron's system
|
||||||
|
# daemon's notion of time and timezones.
|
||||||
|
#
|
||||||
|
# Output of the crontab jobs (including errors) is sent through
|
||||||
|
# email to the user the crontab file belongs to (unless redirected).
|
||||||
|
#
|
||||||
|
# For example, you can run a backup of all your user accounts
|
||||||
|
# at 5 a.m every week with:
|
||||||
|
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
|
||||||
|
#
|
||||||
|
# For more information see the manual pages of crontab(5) and cron(8)
|
||||||
|
#
|
||||||
|
# m h dom mon dow command
|
||||||
|
0 0 * * * php /var/www/parser_from_rossko/mirzaev/parser_from_rossko/system/public/mail.php
|
||||||
|
# 30 0 * * * php /var/www/parser_from_rossko/mirzaev/parser_from_rossko/system/public/import.php
|
||||||
|
0 20 * * * php /var/www/parser_from_rossko/mirzaev/parser_from_rossko/system/public/avito.php
|
||||||
|
0 21 * * * php /var/www/parser_from_rossko/mirzaev/parser_from_rossko/system/public/drom.php
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\parser_from_rossko;
|
||||||
|
|
||||||
|
use avadim\FastExcelWriter\Excel as excel;
|
||||||
|
|
||||||
|
use ZipArchive as zip,
|
||||||
|
DirectoryIterator as directory;
|
||||||
|
|
||||||
|
use SQLite3 as db;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client as client,
|
||||||
|
GuzzleHttp\Psr7\Request as request,
|
||||||
|
GuzzleHttp\Psr7\Utils as utils;
|
||||||
|
|
||||||
|
ini_set('error_reporting', E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
|
||||||
|
define('VIEWS', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views'));
|
||||||
|
define('STORAGE', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||||
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация инстанции абстрактной базы данных
|
||||||
|
$db = new db(STORAGE . DIRECTORY_SEPARATOR . 'products.db');
|
||||||
|
|
||||||
|
// Инициализация товаров
|
||||||
|
$products = $db->query('SELECT * FROM product;');
|
||||||
|
|
||||||
|
// Инициализация директории с документом avito
|
||||||
|
define('AVITO', STORAGE . DIRECTORY_SEPARATOR . 'avito');
|
||||||
|
|
||||||
|
// Инициализация хранилища фотографий товаров
|
||||||
|
define('IMAGES', STORAGE . DIRECTORY_SEPARATOR . 'images');
|
||||||
|
|
||||||
|
// Инициализация таблицы
|
||||||
|
$spreadsheet = excel::create(['export']);
|
||||||
|
|
||||||
|
// Инициализация рабочего листа
|
||||||
|
$worksheet = $spreadsheet->sheet();
|
||||||
|
|
||||||
|
// Инициализация заголовков
|
||||||
|
$worksheet->writeHeader([
|
||||||
|
'id',
|
||||||
|
'ListingFee',
|
||||||
|
'Category',
|
||||||
|
'GoodsType',
|
||||||
|
'ProductType',
|
||||||
|
'SparePartType',
|
||||||
|
'Title',
|
||||||
|
'Description',
|
||||||
|
'Price',
|
||||||
|
'ImageUrls',
|
||||||
|
'Condition',
|
||||||
|
'Originality',
|
||||||
|
'Address',
|
||||||
|
'Availability',
|
||||||
|
'Brand',
|
||||||
|
'OEM',
|
||||||
|
'EMail',
|
||||||
|
'ContactPhone',
|
||||||
|
'AdType',
|
||||||
|
'CompanyName',
|
||||||
|
]);
|
||||||
|
|
||||||
|
while ($product = $products->fetchArray()) {
|
||||||
|
// Перебор товаров
|
||||||
|
|
||||||
|
// Пропустить товары с отсутствием названия
|
||||||
|
if (empty($product['description'])) continue;
|
||||||
|
|
||||||
|
// Инициализация списка изображений товара
|
||||||
|
$images = [];
|
||||||
|
|
||||||
|
// Поиск изображений
|
||||||
|
if (file_exists($file = IMAGES . DIRECTORY_SEPARATOR . $product['nomenclature']))
|
||||||
|
foreach (new directory($file) as $file)
|
||||||
|
if (strtolower($file->getExtension()) === 'jpg')
|
||||||
|
$images[] = "https://vinauto.store/{$product['nomenclature']}/{$file->getFilename()}";
|
||||||
|
|
||||||
|
// Около 40% товаров из Rossko без изображений и drom их не принимает! Временное решение
|
||||||
|
if (empty($images)) continue;
|
||||||
|
|
||||||
|
// Преобразование типов
|
||||||
|
$product['cost'] = (float) $product['cost'];
|
||||||
|
|
||||||
|
// Инициализация списков
|
||||||
|
$brands = $models = $engines = [];
|
||||||
|
|
||||||
|
|
||||||
|
foreach (json_decode((string) $product['applicability'], true) ?? [] as $brand) {
|
||||||
|
// Перебор марок автомобилей
|
||||||
|
|
||||||
|
// Запись в список марок
|
||||||
|
$brands[] = $brand['name'];
|
||||||
|
|
||||||
|
foreach ($brand['carModels'] as $model) {
|
||||||
|
// Перебор моделей автомобилей
|
||||||
|
|
||||||
|
// Запись в список моделей
|
||||||
|
$models[] = $model['name'];
|
||||||
|
|
||||||
|
foreach ($model['engines'] as $engine) {
|
||||||
|
// Перебор двигателей автомобилей
|
||||||
|
|
||||||
|
// Запись в список двигателей
|
||||||
|
$engines[] = $engine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ограничение по максимальному количеству
|
||||||
|
$brands = array_slice($brands, 0, 5);
|
||||||
|
$models = array_slice($models, 0, 10);
|
||||||
|
$engines = array_slice($engines, 0, 30);
|
||||||
|
|
||||||
|
// Форматирование названия
|
||||||
|
$product['description'] = trim(preg_replace('/\|.*/', '', $product['description']), ' ');
|
||||||
|
|
||||||
|
// Инициализация категорий Avito связанных с ключевыми словами
|
||||||
|
$categories = [
|
||||||
|
'Автосвет' => ['фара', 'свет'],
|
||||||
|
'Аккумуляторы' => ['аккумулятор', 'батарея', 'батарейки', 'зарядное'],
|
||||||
|
'Двигатель' => ['двигатель', 'патрубок', 'подушка', 'гидрокомпенсатор', 'датчик', 'детонации', 'заслонка', 'клапан', 'колпачок', 'кольца', 'порншневых', 'поршень', 'лямбда-зонд', 'лямбда', 'зонд', 'маховик', 'гбц', 'распылитель', 'расходомер', 'поликлиновой', 'натяжной', 'ролик', 'сальник', 'распред', 'распредвал', 'стакан', 'термостат', 'толкатель', 'фильтр', 'форсунка', 'шатун', 'шестерня', 'шкиф', 'цилиндр'],
|
||||||
|
'Кузов' => ['кузов', 'ролик', 'рычаг', 'дверь', 'стеклоочиститель', 'трос', 'капот'],
|
||||||
|
'Подвеска' => ['рычаг', 'стойка', 'сайлентблок', 'ремкомплект', 'опора', 'втулка', 'амортизатор', 'крестовина', 'опора', 'пневмобаллон', 'подушка', 'пружина', 'пыльник'],
|
||||||
|
'Рулевое управление' => ['наконечник', 'тяга', 'кулак', 'подшипник', 'ступица', 'полуось', 'пыльник', 'шрус', 'шруса', 'рулевой', 'рулевая', 'рейка', 'сошка', 'ступица', 'гидроусилитель', 'шланг', 'шлейф', 'насос'],
|
||||||
|
'Салон' => ['салон'],
|
||||||
|
'Стёкла' => ['стекло', 'стёкла'],
|
||||||
|
'Топливная и выхлопная системы' => ['выхлопная', 'выхлопной', 'топливный', 'топливо', 'топливная', 'виброкомпенсатор', 'пламягаситель', 'выхлоп'],
|
||||||
|
'Тормозная система' => ['тормоз', 'тормозной', 'барабан', 'тормозные', 'abs', 'диск', 'трос', 'колодка', 'колодки', 'ремкомплект', 'шланг'],
|
||||||
|
'Трансмиссия и привод' => ['привода', 'трансмиссии', 'диск', 'сцеп', 'сцепления', 'акпп'],
|
||||||
|
'Электрооборудование' => ['электро', 'электрический', 'бендикс', 'бензонасос', 'втягивающее', 'генератор', 'катушка', 'диодный', 'диод', 'реле', 'рычаг', 'свеча', 'зажигания', 'стартер', 'электровентилятор', 'вентилятор'],
|
||||||
|
'Система охлаждения' => ['охлаждение', 'бак', 'бачок', 'водяной', 'радиатор', 'термостат'],
|
||||||
|
'Автомобиль на запчасти' => ['запчасти']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Инициализация списка слов из названия
|
||||||
|
$words = explode(' ', $product['description']);
|
||||||
|
|
||||||
|
// Инициализация реестра найденных категорий Avito по совпадению слов из названия с ключевыми словами категорий Avito
|
||||||
|
$sorted = [];
|
||||||
|
|
||||||
|
foreach ($words as $word) {
|
||||||
|
// Перебор слов в названии
|
||||||
|
|
||||||
|
foreach ($categories as $avito => $keywords) {
|
||||||
|
// Перебор категорий Avito
|
||||||
|
|
||||||
|
// Инициализация буфера кратчайшего расстояния между словом из названия и ключевым словом категории Avito
|
||||||
|
$shortest = null;
|
||||||
|
|
||||||
|
// Объявление буфера ближайшего слова из названия к ключевому слову категории Avito
|
||||||
|
$closest = null;
|
||||||
|
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
// Перебор ключевых слов категории Avito
|
||||||
|
|
||||||
|
// Инициализация расстояния по технологии Левенштейна между словом из названия и ключевым словом категории Avito
|
||||||
|
$levenshtein = levenshtein($word, $keyword);
|
||||||
|
|
||||||
|
if ($levenshtein === 0) {
|
||||||
|
// Полное совпадение слова из названия с ключевым словом
|
||||||
|
|
||||||
|
// Запись крайтчайшего расстояния между ключевым словом и словом из названия
|
||||||
|
$shortest = 0;
|
||||||
|
|
||||||
|
// Запись ближайшей категории Avito к слову из названия
|
||||||
|
$closest = $avito;
|
||||||
|
|
||||||
|
// Переход к следующей итерации (успех)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($levenshtein <= 2 && ($levenshtein <= $shortest || $shortest === null)) {
|
||||||
|
// Расстояние по технологии Левенштейна менее или равно 2
|
||||||
|
// И Расстояние по технологии Левенштейна между словом из названия и ключевым словом категории Avito меньше чем в предыдущей итерации
|
||||||
|
// ИЛИ расстояние между словом из названия и ключевым словом категории Avito ещё не инициализировано (первая итерация)
|
||||||
|
|
||||||
|
// Запись крайтчайшего расстояния между ключевым словом и словом из названия
|
||||||
|
$shortest = $levenshtein;
|
||||||
|
|
||||||
|
// Запись ближайшей категории Avito к слову из названия
|
||||||
|
$closest = $avito;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($closest)) {
|
||||||
|
// Вычислено вхождение в реестр подобранных категорий Avito по ключевым словам относительно слова из названия
|
||||||
|
|
||||||
|
// Запись в реестр подобранных категорий Avito по ключевым словам относительно слова из названия
|
||||||
|
$sorted[$closest] ??= [];
|
||||||
|
$sorted[$closest][] = $word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация буфера максимального количества вхождений в категорию Avito по ключевым словам относительно всех слов из названия
|
||||||
|
$maximum = null;
|
||||||
|
|
||||||
|
// Объявление буфера категории с максимальным количеством вхождений в реестре подобранных категорий Avito по ключевым словам относительно всех слов из названия
|
||||||
|
$category = null;
|
||||||
|
|
||||||
|
foreach ($sorted as $avito => $entries) {
|
||||||
|
// Перебор реестра подобранных категорий Avito по ключевым словам относительно всех слов из названия
|
||||||
|
|
||||||
|
// Инициализация количества вхождений в категорию Avito по ключевым словам относительно всех слов из названия
|
||||||
|
$amount = count($entries);
|
||||||
|
|
||||||
|
if ($maximum <= $amount || $maximum === null) {
|
||||||
|
// Число вхождений в категорию Avito по ключевым словам относительно всех слов из названия больше чем в предыдущей итерации
|
||||||
|
// ИЛИ число вхождений в категорию Avito по ключевым словам относительно всех слов из названия ещё не инициализировано
|
||||||
|
|
||||||
|
// Запись максимального значения вхождений в категории Avito по ключевым словам относительно всех слов из названия
|
||||||
|
$maximum = $amount;
|
||||||
|
|
||||||
|
// Запись категории с максимальным количеством вхождений в реестре подобранных категорий Avito по ключевым словам относительно всех слов из названия
|
||||||
|
$category = $avito;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись строки
|
||||||
|
$worksheet->writeRow([
|
||||||
|
$product['code'],
|
||||||
|
'Package',
|
||||||
|
'Запчасти и аксессуары',
|
||||||
|
'Запчасти',
|
||||||
|
'Для автомобилей',
|
||||||
|
$category,
|
||||||
|
$product['description'],
|
||||||
|
<<<TXT
|
||||||
|
Компании VIN AUTO
|
||||||
|
Специализируемся на всех видах автозапчастей, как для легковых, так и грузовых автомобилей.
|
||||||
|
Очень быстро ответим и привезем нужную деталь
|
||||||
|
Уточняйте цену и наличие у менеджера.
|
||||||
|
Пункт выдачи находится в г.Красноярске по адресу Дудинская 2Б
|
||||||
|
Часы работы: 10:00-19:00
|
||||||
|
Отправка запчастей и деталей по всей России.
|
||||||
|
TXT,
|
||||||
|
(int) ($product['cost_rossko'] + ($product['cost_rossko'] * 0.23)),
|
||||||
|
implode(' ', $images),
|
||||||
|
'Новое',
|
||||||
|
'Оригинал',
|
||||||
|
'Красноярск, Дудинская улица, 2Б',
|
||||||
|
'В наличии',
|
||||||
|
$product['brand'],
|
||||||
|
$product['code'],
|
||||||
|
'vinauto555@gmail.com',
|
||||||
|
'79950760655',
|
||||||
|
'Товар от производителя',
|
||||||
|
'VIN-auto -АВТОЗАПЧАСТИ',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение файла
|
||||||
|
$spreadsheet->save(AVITO . DIRECTORY_SEPARATOR . 'export.xlsx');
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\parser_from_rossko;
|
||||||
|
|
||||||
|
use avadim\FastExcelWriter\Excel as excel;
|
||||||
|
|
||||||
|
use ZipArchive as zip,
|
||||||
|
DirectoryIterator as directory;
|
||||||
|
|
||||||
|
use SQLite3 as db;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client as client,
|
||||||
|
GuzzleHttp\Psr7\Request as request,
|
||||||
|
GuzzleHttp\Psr7\Utils as utils;
|
||||||
|
|
||||||
|
ini_set('error_reporting', E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
|
||||||
|
define('VIEWS', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views'));
|
||||||
|
define('STORAGE', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||||
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация инстанции абстрактной базы данных
|
||||||
|
$db = new db(STORAGE . DIRECTORY_SEPARATOR . 'products.db');
|
||||||
|
|
||||||
|
// Инициализация товаров
|
||||||
|
$products = $db->query('SELECT * FROM product;');
|
||||||
|
|
||||||
|
// Инициализация директории с документом Drom
|
||||||
|
define('DROM', STORAGE . DIRECTORY_SEPARATOR . 'drom');
|
||||||
|
|
||||||
|
// Инициализация хранилища фотографий товаров
|
||||||
|
define('IMAGES', STORAGE . DIRECTORY_SEPARATOR . 'images');
|
||||||
|
|
||||||
|
// Инициализация таблицы
|
||||||
|
$spreadsheet = excel::create(['export']);
|
||||||
|
|
||||||
|
// Инициализация рабочего листа
|
||||||
|
$worksheet = $spreadsheet->sheet();
|
||||||
|
|
||||||
|
// Инициализация заголовков
|
||||||
|
$worksheet->writeHeader([
|
||||||
|
'Артикул',
|
||||||
|
'Наименование товара',
|
||||||
|
'Новый или б/у',
|
||||||
|
'Марка',
|
||||||
|
'Модель',
|
||||||
|
'Кузов',
|
||||||
|
'Номер',
|
||||||
|
'Производитель',
|
||||||
|
'Двигатель',
|
||||||
|
'Год',
|
||||||
|
'L-R',
|
||||||
|
'F-R',
|
||||||
|
'U-D',
|
||||||
|
'Цвет',
|
||||||
|
'Номера замен',
|
||||||
|
'Применимость',
|
||||||
|
'Примечание',
|
||||||
|
'Количество',
|
||||||
|
'Цена',
|
||||||
|
'Наличие',
|
||||||
|
'Сроки доставки',
|
||||||
|
'Фотография'
|
||||||
|
]);
|
||||||
|
|
||||||
|
while ($product = $products->fetchArray()) {
|
||||||
|
// Перебор товаров
|
||||||
|
|
||||||
|
// Пропустить товары с отсутствием названия
|
||||||
|
if (empty($product['description'])) continue;
|
||||||
|
|
||||||
|
// Инициализация списка изображений товара
|
||||||
|
$images = [];
|
||||||
|
|
||||||
|
// Поиск изображений
|
||||||
|
if (file_exists($file = IMAGES . DIRECTORY_SEPARATOR . $product['nomenclature']))
|
||||||
|
foreach (new directory($file) as $file)
|
||||||
|
if (strtolower($file->getExtension()) === 'jpg')
|
||||||
|
$images[] = "https://vinauto.store/{$product['nomenclature']}/{$file->getFilename()}";
|
||||||
|
|
||||||
|
// Около 40% товаров из Rossko без изображений и drom их не принимает! Временное решение
|
||||||
|
if (empty($images)) continue;
|
||||||
|
|
||||||
|
// Преобразование типов
|
||||||
|
$product['cost'] = (float) $product['cost'];
|
||||||
|
|
||||||
|
// Инициализация списков
|
||||||
|
$brands = $models = $engines = [];
|
||||||
|
|
||||||
|
foreach (json_decode((string) $product['applicability'], true) ?? [] as $brand) {
|
||||||
|
// Перебор марок автомобилей
|
||||||
|
|
||||||
|
// Запись в список марок
|
||||||
|
$brands[] = $brand['name'];
|
||||||
|
|
||||||
|
foreach ($brand['carModels'] as $model) {
|
||||||
|
// Перебор моделей автомобилей
|
||||||
|
|
||||||
|
// Запись в список моделей
|
||||||
|
$models[] = $model['name'];
|
||||||
|
|
||||||
|
foreach ($model['engines'] as $engine) {
|
||||||
|
// Перебор двигателей автомобилей
|
||||||
|
|
||||||
|
// Запись в список двигателей
|
||||||
|
$engines[] = $engine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ограничение по максимальному количеству
|
||||||
|
$brands = array_slice($brands, 0, 5);
|
||||||
|
$models = array_slice($models, 0, 10);
|
||||||
|
$engines = array_slice($engines, 0, 30);
|
||||||
|
|
||||||
|
// Форматирование названия
|
||||||
|
$product['description'] = trim(preg_replace('/\|.*/', '', $product['description']), ' ');
|
||||||
|
|
||||||
|
// Запись строки
|
||||||
|
$worksheet->writeRow([
|
||||||
|
$product['code'],
|
||||||
|
$product['description'],
|
||||||
|
'Новый',
|
||||||
|
implode(', ', $brands),
|
||||||
|
implode(', ', $models),
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
$product['brand'],
|
||||||
|
implode(', ', $engines),
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'',
|
||||||
|
'По запчастям уточняйте наличие по телефону или через кнопку "спросить", ТАК КАК ОН МОЖЕТ БЫТЬ ПРОДАН В ТЕЧЕНИИ ДНЯ ИЛИ НАХОДИТЬСЯ В РЕЗЕРВЕ, часть запчастей находится на складе, на перемещение может потребоваться от 2 часов',
|
||||||
|
rand(1, 6),
|
||||||
|
(int) ($product['cost_rossko'] + ($product['cost_rossko'] * 0.23)),
|
||||||
|
'В наличии',
|
||||||
|
1,
|
||||||
|
implode(' ', $images)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохранение файла
|
||||||
|
$spreadsheet->save(DROM . DIRECTORY_SEPARATOR . 'export.xlsx');
|
|
@ -0,0 +1,292 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\parser_from_rossko;
|
||||||
|
|
||||||
|
use avadim\FastExcelReader\Excel as excel;
|
||||||
|
|
||||||
|
use ZipArchive as zip,
|
||||||
|
DirectoryIterator as directory;
|
||||||
|
|
||||||
|
use SQLite3 as db;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client as client,
|
||||||
|
GuzzleHttp\Psr7\Request as request,
|
||||||
|
GuzzleHttp\Psr7\Utils as utils;
|
||||||
|
|
||||||
|
use DateTimeImmutable as datetimeimmutable;
|
||||||
|
|
||||||
|
ini_set('error_reporting', E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
|
||||||
|
define('VIEWS', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views'));
|
||||||
|
define('STORAGE', realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||||
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require INDEX . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация инстанции абстрактной базы данных
|
||||||
|
$db = new db(STORAGE . DIRECTORY_SEPARATOR . 'products.db');
|
||||||
|
|
||||||
|
// Инициализация таблицы
|
||||||
|
$db->exec('CREATE TABLE IF NOT EXISTS product (id INTEGER PRIMARY KEY AUTOINCREMENT, avito INTEGER UNIQUE, nomenclature TEXT, brand TEXT, code TEXT, description TEXT, weight INTEGER, multiplicity REAL, cost_rossko REAL, cost REAL, amount TEXT, delivery INTEGER, code_rossko TEXT UNIQUE, oem TEXT, applicability TEXT, code_vendor TEXT, warehouse TEXT, updated DATETIME DEFAULT CURRENT_TIMESTAMP);');
|
||||||
|
|
||||||
|
// Инициализация директории с документом Rossko
|
||||||
|
define('ROSSKO', STORAGE . DIRECTORY_SEPARATOR . 'rossko');
|
||||||
|
|
||||||
|
// Инициализация хранилища фотографий товаров
|
||||||
|
define('IMAGES', STORAGE . DIRECTORY_SEPARATOR . 'images');
|
||||||
|
|
||||||
|
// Объявление документа
|
||||||
|
$document;
|
||||||
|
|
||||||
|
foreach (new directory(ROSSKO) as $file)
|
||||||
|
if ($file->getExtension() === 'xlsx') {
|
||||||
|
// Инициализация документа
|
||||||
|
$document = ROSSKO . DIRECTORY_SEPARATOR . $file->getFilename();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (empty($document)) {
|
||||||
|
// Инициализация архива
|
||||||
|
$archive = ROSSKO . DIRECTORY_SEPARATOR . 'rossko_price.zip';
|
||||||
|
|
||||||
|
if (($zip = new zip)->open($archive)) {
|
||||||
|
// Открыт архив
|
||||||
|
|
||||||
|
// Разархивирование документа
|
||||||
|
$zip->extractTo(ROSSKO);
|
||||||
|
$zip->close();
|
||||||
|
|
||||||
|
// Удаление архива (необязательно)
|
||||||
|
unlink($archive);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (new directory(ROSSKO) as $file)
|
||||||
|
if ($file->getExtension() === 'xlsx') {
|
||||||
|
// Инициализация документа
|
||||||
|
$document = ROSSKO . DIRECTORY_SEPARATOR . $file->getFilename();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($document) ?? file_exists($document)) {
|
||||||
|
// Инициализирован документ
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скачать изображение товара
|
||||||
|
*/
|
||||||
|
function image(string $nomenclature)
|
||||||
|
{
|
||||||
|
// Если существуют файлы (подразумеваются изображения), то пропустить проверку и скачивание изображений
|
||||||
|
if (file_exists($image = IMAGES . DIRECTORY_SEPARATOR . $nomenclature))
|
||||||
|
foreach (new directory($image) as $file) return;
|
||||||
|
|
||||||
|
// Инициализация клиента Guzzle
|
||||||
|
$client = new client(['cookies' => true]);
|
||||||
|
|
||||||
|
// Инициализация сессии (cookie)
|
||||||
|
/* $cookies = ($client->send(new request('GET', 'https://krsk.rossko.ru/')))->getHeader('Set-Cookie');
|
||||||
|
$_auth = '';
|
||||||
|
foreach ($cookies as $cookie)
|
||||||
|
if (str_starts_with($cookie, 'auth')) {
|
||||||
|
preg_match('/auth=(.*);/', $cookie, $matches);
|
||||||
|
$_auth = $matches[1];
|
||||||
|
}
|
||||||
|
if (empty($_auth)) return false; */
|
||||||
|
|
||||||
|
// Инициализация запроса
|
||||||
|
/* $request = new request(
|
||||||
|
'GET',
|
||||||
|
"https://productcard.rossko.ru/api/Product/Card/$nomenclature",
|
||||||
|
[ */
|
||||||
|
// 'Accept' => '*/*',
|
||||||
|
/* 'Accept-Encoding' => 'gzip, deflate, br, zstd',
|
||||||
|
'Accept-Language' => 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
|
||||||
|
'Access-Control-Allow-Origin' => '*',
|
||||||
|
'Authorization-Domain' => 'https://krsk.rossko.ru',
|
||||||
|
'Authorization-Session' => $_auth,
|
||||||
|
'Cache-Control' => 'no-cache',
|
||||||
|
'Connection' => 'keep-alive',
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'DNT' => '1',
|
||||||
|
'Host' => 'productcard.rossko.ru',
|
||||||
|
'Origin' => 'https://krsk.rossko.ru',
|
||||||
|
'Pragma' => 'no-cache',
|
||||||
|
'Priority' => 'u=4',
|
||||||
|
'Referer' => 'https://krsk.rossko.ru/',
|
||||||
|
'Sec-Fetch-Dest' => 'empty',
|
||||||
|
'Sec-Fetch-Mode' => 'no-cors',
|
||||||
|
'Sec-Fetch-Site' => 'same-site',
|
||||||
|
'Sec-GPC' => '1',
|
||||||
|
'source' => 'frontend',
|
||||||
|
'TE' => 'trailers',
|
||||||
|
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0',
|
||||||
|
]
|
||||||
|
); */
|
||||||
|
|
||||||
|
// ОНИ НЕ ПРОВЕРЯЮТ ПОДЛИННОСТЬ СЕССИИ, ХА
|
||||||
|
// Запрос данных карточки товара в разделе "Описание"
|
||||||
|
$request = new request(
|
||||||
|
'GET',
|
||||||
|
"https://productcard.rossko.ru/api/Product/Card/$nomenclature",
|
||||||
|
[
|
||||||
|
'Authorization-Domain' => 'https://krsk.rossko.ru',
|
||||||
|
'Authorization-Session' => 'ya_tvoi_kartinki_spizdil',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Отправка запроса и инициализация ответа
|
||||||
|
$response = $client->send($request);
|
||||||
|
|
||||||
|
// Инициализация изображений
|
||||||
|
$images = json_decode((string) $response->getBody(), true)['mainPart']['images'];
|
||||||
|
|
||||||
|
foreach ($images ?? [] as $image) {
|
||||||
|
// Перебор изображений
|
||||||
|
|
||||||
|
// Инициализация пути до директории изображения
|
||||||
|
$path_directory = IMAGES . DIRECTORY_SEPARATOR . $nomenclature;
|
||||||
|
|
||||||
|
// Инициализация пути до изображения
|
||||||
|
$path_image = $path_directory . DIRECTORY_SEPARATOR . preg_replace('/.*\//', '', $image);
|
||||||
|
|
||||||
|
// Инициализация директории изображения
|
||||||
|
if (!file_exists($path_directory)) mkdir($path_directory, 0775, true);
|
||||||
|
|
||||||
|
/* var_dump("Проверка: $path_image"); */
|
||||||
|
|
||||||
|
// Инициализация изображения (скачивание)
|
||||||
|
if (!file_exists($path_image)) {
|
||||||
|
/* var_dump("Загрузка: $path_image"); */
|
||||||
|
$client->request('GET', $image, ['sink' => $path_image]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Скачать список автомобилей для которых продукт подходит
|
||||||
|
*/
|
||||||
|
function applicability(string $nomenclature)
|
||||||
|
{
|
||||||
|
// Инициализация клиента Guzzle
|
||||||
|
$client = new client(['cookies' => true]);
|
||||||
|
|
||||||
|
// Запрос данных карточки товара в разделе "Применимость"
|
||||||
|
$request = new request(
|
||||||
|
'GET',
|
||||||
|
"https://productcard.rossko.ru/api/Product/CarTypes/$nomenclature",
|
||||||
|
[
|
||||||
|
'Authorization-Domain' => 'https://krsk.rossko.ru',
|
||||||
|
'Authorization-Session' => 'ya_tvoi_applicabilities_spizdil',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Отправка запроса и инициализация ответа
|
||||||
|
$response = $client->send($request);
|
||||||
|
|
||||||
|
// Инициализация данных автомобилей и возврат (успех)
|
||||||
|
return (string) $response->getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация таблицы
|
||||||
|
$spreadsheet = excel::open($document);
|
||||||
|
|
||||||
|
// Инициализация рабочего листа
|
||||||
|
$worksheet = $spreadsheet->sheet();
|
||||||
|
|
||||||
|
foreach (
|
||||||
|
$worksheet->nextRow(
|
||||||
|
[
|
||||||
|
'A' => 'nomenclature',
|
||||||
|
'B' => 'brand',
|
||||||
|
'C' => 'code',
|
||||||
|
'D' => 'description',
|
||||||
|
'E' => 'weight',
|
||||||
|
'F' => 'multiplicity',
|
||||||
|
'G' => 'cost_rossko',
|
||||||
|
'H' => 'cost',
|
||||||
|
'I' => 'amount',
|
||||||
|
'J' => 'delivery',
|
||||||
|
'K' => 'code_rossko',
|
||||||
|
'L' => 'oem',
|
||||||
|
'M' => 'applicability',
|
||||||
|
'N' => 'code_vendor',
|
||||||
|
'O' => 'warehouse'
|
||||||
|
],
|
||||||
|
excel::KEYS_FIRST_ROW
|
||||||
|
) as $number => $row
|
||||||
|
) {
|
||||||
|
// Перебор строк
|
||||||
|
|
||||||
|
if (!empty($row['nomenclature'])) {
|
||||||
|
// Инициализирован "nomenclature" (номенклатура)
|
||||||
|
|
||||||
|
// Инициализация запроса: "SELECT" (по номенклатуре получить applicability и updated)
|
||||||
|
$request = $db->prepare('SELECT applicability, updated FROM product WHERE nomenclature=?;');
|
||||||
|
|
||||||
|
// Инициализация параметров запроса
|
||||||
|
$request->bindValue(1, $row['nomenclature'] ?? null, SQLITE3_TEXT);
|
||||||
|
|
||||||
|
// Выполнение запроса
|
||||||
|
$response = $request->execute()?->fetchArray();
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($response['updated'])
|
||||||
|
|| (!empty($_current = datetimeimmutable::createFromFormat(DATE_W3C, date(DATE_W3C)))
|
||||||
|
&& !empty($_updated = datetimeimmutable::createFromFormat(DATE_W3C, $response['updated']))
|
||||||
|
&& $_current->diff($_updated)->h > 24)
|
||||||
|
) {
|
||||||
|
// Записи не существует (может быть не инициализирована) ИЛИ запись обновлялась более чем 24 часа назад
|
||||||
|
|
||||||
|
// Объявление переменной для значения "applicability" ()применимость)
|
||||||
|
$applicability;
|
||||||
|
|
||||||
|
if (empty($response['applicability'])) {
|
||||||
|
// Не найдено значение: "applicability" (применимость)
|
||||||
|
|
||||||
|
// Инициализация значения: "applicability" (применимость)
|
||||||
|
$applicability = applicability($row['nomenclature']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация запроса: "REPLACE" (INSERT или UPDATE)
|
||||||
|
$request = $db->prepare('REPLACE INTO product (nomenclature, brand, code, description, weight, multiplicity, cost_rossko, cost, amount, delivery, code_rossko, oem, applicability, code_vendor, warehouse, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);');
|
||||||
|
|
||||||
|
// Инициализация параметров запроса
|
||||||
|
$request->bindValue(1, $row['nomenclature'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(2, $row['brand'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(3, $row['code'], SQLITE3_TEXT);
|
||||||
|
$request->bindValue(4, $row['description'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(5, $row['weight'] ?? null, SQLITE3_INTEGER);
|
||||||
|
$request->bindValue(6, $row['multiplicity'] ?? null, SQLITE3_FLOAT);
|
||||||
|
$request->bindValue(7, $row['cost_rossko'] ?? null, SQLITE3_FLOAT);
|
||||||
|
$request->bindValue(8, $row['cost'] ?? null, SQLITE3_FLOAT);
|
||||||
|
$request->bindValue(9, $row['amount'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(10, $row['delivery'] ?? null, SQLITE3_INTEGER);
|
||||||
|
$request->bindValue(11, $row['code_rossko'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(12, $row['oem'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(13, $applicability ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(14, $row['code_vendor'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(15, $row['warehouse'] ?? null, SQLITE3_TEXT);
|
||||||
|
$request->bindValue(16, date(DATE_W3C), SQLITE3_TEXT);
|
||||||
|
|
||||||
|
// Выполнение запроса
|
||||||
|
$request->execute();
|
||||||
|
|
||||||
|
if (!file_exists(IMAGES . DIRECTORY_SEPARATOR . $row['nomenclature'] . '1.jpg')) {
|
||||||
|
// Не найдено изображение товара
|
||||||
|
|
||||||
|
// Ожидание перед загрузкой изображения
|
||||||
|
/* sleep(10); */
|
||||||
|
|
||||||
|
// Загрузка изображения товара
|
||||||
|
image($row['nomenclature']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Удаление документа
|
||||||
|
unlink($document);
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\parser_from_rossko;
|
||||||
|
|
||||||
|
use PhpImap\Mailbox as mail,
|
||||||
|
PhpImap\Exceptions\ConnectionException as _exception;
|
||||||
|
|
||||||
|
ini_set('error_reporting', E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
ini_set('display_startup_errors', 1);
|
||||||
|
|
||||||
|
define('INDEX', __DIR__);
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта почты
|
||||||
|
$account = require(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'account.php');
|
||||||
|
|
||||||
|
// Create PhpImap\Mailbox instance for all further actions
|
||||||
|
$mailbox = new mail(
|
||||||
|
$account['imap'], // IMAP server and mailbox folder
|
||||||
|
$account['mail'], // Username for the before configured mailbox
|
||||||
|
$account['password'], // Password for the before configured username
|
||||||
|
__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'rossko', // Directory, where attachments will be saved (optional)
|
||||||
|
'UTF-8', // Server encoding (optional)
|
||||||
|
true, // Trim leading/ending whitespaces of IMAP path (optional)
|
||||||
|
true // Attachment filename mode (optional; false = random filename; true = original filename)
|
||||||
|
);
|
||||||
|
|
||||||
|
// set some connection arguments (if appropriate)
|
||||||
|
$mailbox->setConnectionArgs(
|
||||||
|
CL_EXPUNGE // expunge deleted mails upon mailbox close
|
||||||
|
//| OP_SECURE // don't do non-secure authentication
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all emails (messages)
|
||||||
|
|
||||||
|
// PHP.net imap_search criteria: http://php.net/manual/en/function.imap-search.php
|
||||||
|
$mailsIds = $mailbox->searchMailbox('UNSEEN FROM "price@rossko.ru" SINCE "' . date('j F Y', strtotime('-1 day')) . '"');
|
||||||
|
} catch(_exception $ex) {
|
||||||
|
echo "IMAP connection failed: " . implode(",", $ex->getErrors('all'));
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the first message (автоматически скачает файл и пометит прочитанным письмо, чтобы не обработать повторно)
|
||||||
|
if (isset($mailsIds[0])) $mailbox->getMail($mailsIds[0]);
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'imap' => '{imap.gmail.com:993/imap/ssl}ROSSKO',
|
||||||
|
'mail' => '@gmail.com',
|
||||||
|
'password' => ''
|
||||||
|
];
|
|
@ -0,0 +1 @@
|
||||||
|
images
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,14 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Parser from Rossko(import)
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/var/www/parser_from_rossko/mirzaev/parser_from_rossko/system/public
|
||||||
|
ExecStart=/usr/bin/php import.php
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3600s
|
||||||
|
RuntimeMaxSec=86400s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
Loading…
Reference in New Issue