Compare commits

...

3 Commits

28 changed files with 931 additions and 13 deletions

View File

@ -1,8 +0,0 @@
<?php
return [
'endpoint' => 'unix:///var/run/arangodb3/arango.sock',
'database' => 'parser_from_rossko',
'name' => 'parser_from_rossko',
'password' => ''
];

View File

@ -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"

28
crontab Normal file
View File

@ -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

View File

@ -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');

View File

@ -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');

View File

@ -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);
}

View File

@ -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]);

View File

@ -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); */

View File

@ -0,0 +1,7 @@
<?php
return [
'imap' => '{imap.gmail.com:993/imap/ssl}ROSSKO',
'mail' => '@gmail.com',
'password' => ''
];

View File

@ -0,0 +1,6 @@
<?php
return [
'mail' => '@gmail.com',
'password' => ''
];

View File

@ -0,0 +1,2 @@
!.gitignore
products.db

View File

@ -0,0 +1,3 @@
!.gitignore
/avaiable/*
/order/*

14
rossko.service Normal file
View File

@ -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