generated from mirzaev/pot
Compare commits
3 Commits
7ae0e7431f
...
874acb8eea
Author | SHA1 | Date | |
---|---|---|---|
Arsen Mirzaev Tatyano-Muradovich | 874acb8eea | ||
Arsen Mirzaev Tatyano-Muradovich | d158f31ea8 | ||
Arsen Mirzaev Tatyano-Muradovich | 82e754d691 |
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
|
||||
'database' => 'parser_from_rossko',
|
||||
'name' => 'parser_from_rossko',
|
||||
'password' => ''
|
||||
];
|
|
@ -2,7 +2,7 @@
|
|||
"name": "mirzaev/parser_from_rossko",
|
||||
"description": "",
|
||||
"readme": "README.md",
|
||||
"keywords": [],
|
||||
"keywords": ["parser", "rossko"],
|
||||
"type": "site",
|
||||
"homepage": "https://git.mirzaev.sexy/mirzaev/parser_from_rossko",
|
||||
"license": "WTFPL",
|
||||
|
@ -22,10 +22,10 @@
|
|||
"php": "~8.3",
|
||||
"ext-sodium": "~8.3",
|
||||
"mirzaev/minimal": "^2.2.0",
|
||||
"mirzaev/accounts": "~1.2.x-dev",
|
||||
"mirzaev/arangodb": "^1.0.0",
|
||||
"triagens/arangodb": "~3.9.x-dev",
|
||||
"twig/twig": "^3.4"
|
||||
"guzzlehttp/guzzle": "^7.8",
|
||||
"php-imap/php-imap": "^5.0",
|
||||
"avadim/fast-excel-reader": "^2.17",
|
||||
"avadim/fast-excel-writer": "^5.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"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,263 @@
|
|||
<?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.21)),
|
||||
implode('|', $images),
|
||||
'Новое',
|
||||
'Оригинал',
|
||||
'Красноярск, Дудинская улица, 2Б',
|
||||
'В наличии',
|
||||
$product['brand'],
|
||||
$product['code'],
|
||||
'vinauto555@gmail.com',
|
||||
'79950760655',
|
||||
'Товар от производителя',
|
||||
'VIN-auto -АВТОЗАПЧАСТИ',
|
||||
]);
|
||||
}
|
||||
|
||||
// Сохранение файла
|
||||
$spreadsheet->save(AVITO . DIRECTORY_SEPARATOR . 'export.xlsx');
|
|
@ -0,0 +1,166 @@
|
|||
<?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_avaiable = excel::create(['export']);
|
||||
$spreadsheet_order = excel::create(['export']);
|
||||
|
||||
// Инициализация рабочих листов
|
||||
$worksheet_avaiable = $spreadsheet_avaiable->sheet();
|
||||
$worksheet_order = $spreadsheet_order->sheet();
|
||||
|
||||
// Инициализация заголовков
|
||||
$headers = [
|
||||
'Артикул',
|
||||
'Наименование товара',
|
||||
'Новый или б/у',
|
||||
'Марка',
|
||||
'Модель',
|
||||
'Кузов',
|
||||
'Номер',
|
||||
'Производитель',
|
||||
'Двигатель',
|
||||
'Год',
|
||||
'L-R',
|
||||
'F-R',
|
||||
'U-D',
|
||||
'Цвет',
|
||||
'Номера замен',
|
||||
'Применимость',
|
||||
'Примечание',
|
||||
'Количество',
|
||||
'Цена',
|
||||
'Наличие',
|
||||
'Сроки доставки',
|
||||
'Фотография'
|
||||
];
|
||||
$worksheet_avaiable->writeHeader($headers);
|
||||
$worksheet_order->writeHeader($headers);
|
||||
|
||||
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']), ' ');
|
||||
|
||||
// Товар в наличии? (доставка 2 часа)
|
||||
$avaiable = $product['delivery'] === 0 && $product['amount'] > 0;
|
||||
|
||||
// Генерация строки
|
||||
$row = [
|
||||
$product['code'],
|
||||
$product['description'],
|
||||
'Новый',
|
||||
implode(', ', $brands),
|
||||
implode(', ', $models),
|
||||
'',
|
||||
'',
|
||||
$product['brand'],
|
||||
implode(', ', $engines),
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'По запчастям уточняйте наличие и цену по телефону или через кнопку "спросить", ТАК КАК ОН МОЖЕТ БЫТЬ ПРОДАН В ТЕЧЕНИИ ДНЯ ИЛИ НАХОДИТЬСЯ В РЕЗЕРВЕ, часть запчастей находится на складе, на перемещение может потребоваться от 2 часов. Не является публичной офертой',
|
||||
$product['amount'],
|
||||
(int) ($product['cost_rossko'] + ($product['cost_rossko'] * 0.21)),
|
||||
$avaiable ? 'В наличии' : 'Под заказ',
|
||||
$product['delivery'] === 0 ? '2 часа' : $product['delivery'],
|
||||
implode(' ', $images)
|
||||
];
|
||||
|
||||
// Запись строки в документ
|
||||
if ($avaiable) $worksheet_avaiable->writeRow($row);
|
||||
else $worksheet_order->writeRow($row);
|
||||
}
|
||||
|
||||
// Сохранение файлов
|
||||
$spreadsheet_avaiable->save(DROM . DIRECTORY_SEPARATOR . 'avaiable' . DIRECTORY_SEPARATOR . 'export.xlsx');
|
||||
$spreadsheet_order->save(DROM . DIRECTORY_SEPARATOR . 'order' . 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,95 @@
|
|||
<?php
|
||||
/*
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace mirzaev\parser_from_rossko;
|
||||
|
||||
use GuzzleHttp\Client as client,
|
||||
GuzzleHttp\Psr7\Request as request;
|
||||
|
||||
use DOMDocument as dom,
|
||||
DOMXPath as xpath;
|
||||
|
||||
ini_set('error_reporting', E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
|
||||
define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage'));
|
||||
define('INDEX', __DIR__);
|
||||
|
||||
// Автозагрузка
|
||||
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||
|
||||
// Инициализация клиента Guzzle
|
||||
$client = new client(['cookies' => true]);
|
||||
|
||||
function auth(int $attempts = 5)
|
||||
{
|
||||
global $client;
|
||||
|
||||
if (--$attempts <= 0) return false;
|
||||
|
||||
$request = new request('GET', 'https://krsk.rossko.ru/');
|
||||
|
||||
return $client->sendAsync($request)
|
||||
->then(function ($response) {
|
||||
$dom = new dom;
|
||||
@$dom->loadHTML((string) $response->getBody());
|
||||
|
||||
$xpath = new xpath($dom);
|
||||
|
||||
// Кнопка "ВХОД" ('ul[class="header-top__menu-inner"] > li:nth-child(3)')
|
||||
$entries = $xpath->query('/html/body/div[1]/header/div[2]/div/div/nav/ul/li[5]/div/a/span');
|
||||
|
||||
if (!empty($entries[0])) {
|
||||
// Аутентифицирован
|
||||
|
||||
echo 'Аутентифицирован: ' . $entries[0]->textContent . "\n";
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Не аутентиффицирован
|
||||
|
||||
// Кнопка "ВХОД" ('ul[class="header-top__menu-inner"] > li:nth-child(3)')
|
||||
$entries = $xpath->query('/html/body/div[1]/header/div[2]/div[1]/div/nav/ul/li[3]/div/a/span');
|
||||
|
||||
return $entries[0]->textContent === 'Вход';
|
||||
}
|
||||
})
|
||||
->then(function ($guest) use ($attempts, $client) {
|
||||
if ($guest) {
|
||||
// Не аутентифицирован
|
||||
|
||||
echo "Не аутентифицирован\n";
|
||||
|
||||
// Инициализация данных аккаунта [mail => '', password => '']
|
||||
$account = require(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'rossko.php');
|
||||
|
||||
// Инициализация запроса для аутентификации
|
||||
$request = new request(
|
||||
'POST',
|
||||
'https://krsk.rossko.ru/utils/',
|
||||
[
|
||||
'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
],
|
||||
'auth%5Bemail%5D=' . urlencode($account['mail']) . '&auth%5Bpassword%5D=' . urlencode($account['password']) . '&action=auth&type=header'
|
||||
);
|
||||
|
||||
$promise = $client->sendAsync($request)->then(function ($response) use ($attempts) {
|
||||
auth($attempts);
|
||||
});
|
||||
|
||||
$promise->wait();
|
||||
} else {
|
||||
// Аутентифицирован (подразумевается) !!! могут быть ошибки
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Аутентификация (проверяет или аутентифицирует)
|
||||
auth()->wait();
|
||||
|
||||
echo (123); */
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'imap' => '{imap.gmail.com:993/imap/ssl}ROSSKO',
|
||||
'mail' => '@gmail.com',
|
||||
'password' => ''
|
||||
];
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'mail' => '@gmail.com',
|
||||
'password' => ''
|
||||
];
|
|
@ -0,0 +1,2 @@
|
|||
!.gitignore
|
||||
products.db
|
|
@ -0,0 +1,3 @@
|
|||
!.gitignore
|
||||
avaiable/*
|
||||
order/*
|
|
@ -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