549 lines
16 KiB
Markdown
549 lines
16 KiB
Markdown
# Parser Zakupok (Парсер Закупок)
|
||
|
||
AdonisJS 6 приложение для автоматического парсинга аукционов с сайта icetrade.by. Система автоматически собирает данные каждые 6 часов, сохраняет в PostgreSQL и отправляет Telegram уведомления по ключевым словам.
|
||
|
||
## Возможности
|
||
|
||
- 🔄 Автоматический парсинг аукционов каждые 6 часов
|
||
- 🔍 Поиск по ключевым словам (заголовок, описание, организация)
|
||
- 📱 Telegram уведомления пользователям
|
||
- 📊 Логирование всех операций парсинга
|
||
- 🗄️ PostgreSQL база данных с полной историей
|
||
- ⚡ Пагинация и rate limiting для стабильности
|
||
- 🔧 REST API для управления
|
||
|
||
## Технологический стек
|
||
|
||
- **Framework**: AdonisJS 6 (TypeScript)
|
||
- **Database**: PostgreSQL + Lucid ORM
|
||
- **Scheduler**: adonisjs-scheduler
|
||
- **HTML Parser**: Cheerio
|
||
- **Validation**: Zod
|
||
- **Telegram Bot**: Grammy
|
||
- **Testing**: Japa
|
||
|
||
## Установка
|
||
|
||
### Требования
|
||
|
||
- Node.js >= 20.6
|
||
- PostgreSQL >= 14
|
||
- npm >= 9
|
||
|
||
### Шаги установки
|
||
|
||
```bash
|
||
# 1. Клонировать репозиторий
|
||
git clone <repository-url>
|
||
cd parser-zakupok
|
||
|
||
# 2. Установить зависимости
|
||
npm install
|
||
|
||
# 3. Скопировать .env файл
|
||
cp .env.example .env
|
||
|
||
# 4. Настроить переменные окружения
|
||
nano .env # или любой редактор
|
||
|
||
# 5. Запустить миграции
|
||
node ace migration:run
|
||
|
||
# 6. Запустить приложение
|
||
npm run dev
|
||
```
|
||
|
||
### Настройка .env
|
||
|
||
```env
|
||
# Сервер
|
||
PORT=3333
|
||
HOST=localhost
|
||
NODE_ENV=development
|
||
APP_KEY=<generate-with-node-ace-generate:key>
|
||
|
||
# База данных PostgreSQL
|
||
DB_HOST=127.0.0.1
|
||
DB_PORT=5432
|
||
DB_USER=postgres
|
||
DB_PASSWORD=your_password
|
||
DB_DATABASE=parser_zakupok
|
||
|
||
# Telegram Bot
|
||
TELEGRAM_BOT_TOKEN=your_bot_token_from_@BotFather
|
||
|
||
# Опционально
|
||
LOG_LEVEL=info
|
||
```
|
||
|
||
## Команды запуска
|
||
|
||
### Режим разработки
|
||
|
||
```bash
|
||
# Запуск с hot reload (рекомендуется)
|
||
npm run dev
|
||
|
||
# Альтернативный вариант
|
||
node ace serve --hmr
|
||
|
||
# С watch режимом
|
||
node ace serve --watch
|
||
```
|
||
|
||
### Production режим
|
||
|
||
```bash
|
||
# 1. Собрать проект
|
||
npm run build
|
||
|
||
# 2. Запустить миграции
|
||
node ace migration:run --force
|
||
|
||
# 3. Запустить сервер
|
||
npm start
|
||
# или
|
||
node build/bin/server.js
|
||
```
|
||
|
||
## Команды базы данных
|
||
|
||
### Миграции
|
||
|
||
```bash
|
||
# Выполнить все pending миграции
|
||
node ace migration:run
|
||
|
||
# Откатить последний batch
|
||
node ace migration:rollback
|
||
|
||
# Откатить все миграции
|
||
node ace migration:rollback --batch=0
|
||
|
||
# Пересоздать базу (drop + migrate)
|
||
node ace migration:fresh
|
||
|
||
# Fresh + seed данные
|
||
node ace migration:fresh --seed
|
||
```
|
||
|
||
### Создание новых сущностей
|
||
|
||
```bash
|
||
# Создать миграцию
|
||
node ace make:migration create_table_name
|
||
node ace make:migration add_column_to_table
|
||
|
||
# Создать модель
|
||
node ace make:model ModelName
|
||
|
||
# Создать модель + миграцию
|
||
node ace make:model ModelName -m
|
||
|
||
# Создать контроллер
|
||
node ace make:controller ControllerName
|
||
```
|
||
|
||
### Database утилиты
|
||
|
||
```bash
|
||
# Открыть REPL с доступом к моделям
|
||
node ace repl
|
||
|
||
# Примеры в REPL:
|
||
# > await loadModels()
|
||
# > const Auction = await import('#models/auction')
|
||
# > await Auction.default.all()
|
||
```
|
||
|
||
## Команды парсинга
|
||
|
||
### Основная команда parse:auctions
|
||
|
||
```bash
|
||
# Парсинг с дефолтными настройками (до 10 страниц)
|
||
node ace parse:auctions
|
||
|
||
# Парсинг конкретного количества страниц
|
||
node ace parse:auctions --pages=5
|
||
|
||
# Парсинг без отправки уведомлений (только сохранение)
|
||
node ace parse:auctions --skip-notifications
|
||
|
||
# Парсинг 1 страницы без уведомлений (для тестирования)
|
||
node ace parse:auctions --pages=1 --skip-notifications
|
||
|
||
# Комбинация опций
|
||
node ace parse:auctions --pages=20 --skip-notifications
|
||
```
|
||
|
||
**Что делает команда:**
|
||
1. Создает запись в `parse_logs` (статус: running)
|
||
2. Парсит страницы с icetrade.by с rate limiting (1 сек между запросами)
|
||
3. Валидирует данные через Zod схемы
|
||
4. Upsert аукционов по `auction_num` (обновляет существующие, создает новые)
|
||
5. Ищет совпадения по ключевым словам
|
||
6. Создает записи уведомлений для пользователей
|
||
7. Обновляет parse_log (статус: completed/failed, статистика)
|
||
|
||
**Выводит:**
|
||
- Количество найденных аукционов
|
||
- Количество новых аукционов
|
||
- Количество обновленных аукционов
|
||
- Количество созданных уведомлений
|
||
- Время выполнения
|
||
- Ошибки (если есть)
|
||
|
||
## Команды Scheduler
|
||
|
||
### Управление планировщиком
|
||
|
||
```bash
|
||
# Запустить scheduler (production)
|
||
node ace scheduler:work
|
||
|
||
# Запустить с watch режимом (development)
|
||
node ace scheduler:work --watch
|
||
|
||
# Показать список всех запланированных задач
|
||
node ace scheduler:list
|
||
|
||
# Вывод scheduler:list:
|
||
# ┌────────────────┬──────────────────┬─────────────────┐
|
||
# │ Name │ Cron Expression │ Next Run │
|
||
# ├────────────────┼──────────────────┼─────────────────┤
|
||
# │ parse:auctions │ 0 0 */6 * * * │ 2024-01-15 18:00│
|
||
# └────────────────┴──────────────────┴─────────────────┘
|
||
```
|
||
|
||
### Настройка расписания
|
||
|
||
Расписание настроено в `start/scheduler.ts`:
|
||
|
||
```typescript
|
||
// Текущее: каждые 6 часов (00:00, 06:00, 12:00, 18:00)
|
||
scheduler.call(async () => {
|
||
await ace.exec('parse:auctions', [])
|
||
}).cron('0 0 */6 * * *')
|
||
|
||
// Другие варианты расписания:
|
||
// .cron('0 0 * * * *') // Каждый час
|
||
// .cron('0 */30 * * * *') // Каждые 30 минут
|
||
// .everyFourHours() // Каждые 4 часа
|
||
// .dailyAt('09:00') // Ежедневно в 9:00
|
||
// .twiceDaily(9, 18) // Дважды в день (9:00, 18:00)
|
||
```
|
||
|
||
### Production deployment scheduler
|
||
|
||
```bash
|
||
# Вариант 1: Использовать встроенный scheduler
|
||
npm start & # Запустить сервер
|
||
node ace scheduler:work # Запустить scheduler в отдельном процессе
|
||
|
||
# Вариант 2: Использовать PM2 (рекомендуется)
|
||
pm2 start ecosystem.config.js
|
||
|
||
# Вариант 3: Systemd services
|
||
# Создать два сервиса: parser-app.service и parser-scheduler.service
|
||
```
|
||
|
||
## Команды тестирования
|
||
|
||
```bash
|
||
# Запустить все тесты
|
||
npm test
|
||
# или
|
||
node ace test
|
||
|
||
# Запустить конкретный файл теста
|
||
node ace test --files=tests/unit/models/auction.spec.ts
|
||
|
||
# Запустить тесты по паттерну (grep)
|
||
node ace test --grep="keyword matching"
|
||
node ace test --grep="scraper"
|
||
|
||
# Запустить только unit тесты
|
||
node ace test tests/unit
|
||
|
||
# Запустить только functional тесты
|
||
node ace test tests/functional
|
||
```
|
||
|
||
## Code Quality команды
|
||
|
||
```bash
|
||
# Проверка типов TypeScript
|
||
npm run typecheck
|
||
|
||
# Линтинг (ESLint)
|
||
npm run lint
|
||
|
||
# Автофикс линтинга
|
||
npm run lint -- --fix
|
||
|
||
# Форматирование (Prettier)
|
||
npm run format
|
||
|
||
# Проверить форматирование без изменений
|
||
npm run format -- --check
|
||
```
|
||
|
||
## Telegram Bot команды
|
||
|
||
```bash
|
||
# Запустить Telegram бота (когда будет реализовано в Phase 5)
|
||
node ace telegram:start
|
||
|
||
# С watch режимом
|
||
node ace telegram:start --watch
|
||
```
|
||
|
||
### Команды бота для пользователей
|
||
|
||
После запуска бота, пользователи могут использовать:
|
||
|
||
- `/start` - Регистрация в системе
|
||
- `/addkeyword <слово>` - Добавить ключевое слово
|
||
- `/keywords` или `/listkeywords` - Список ваших ключевых слов
|
||
- `/deletekeyword <id>` - Удалить ключевое слово
|
||
- `/help` - Справка по командам
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
parser-zakupok/
|
||
├── app/
|
||
│ ├── controllers/ # HTTP контроллеры
|
||
│ ├── models/ # Lucid ORM модели
|
||
│ │ ├── auction.ts
|
||
│ │ ├── keyword.ts
|
||
│ │ ├── user.ts
|
||
│ │ ├── notification.ts
|
||
│ │ └── parse_log.ts
|
||
│ ├── services/ # Бизнес-логика
|
||
│ │ ├── scraper_service.ts # Парсинг icetrade.by
|
||
│ │ └── notification_service.ts # Обработка уведомлений
|
||
│ ├── middleware/ # HTTP middleware
|
||
│ └── validators/ # VineJS валидаторы
|
||
├── commands/ # Ace CLI команды
|
||
│ └── parse_auctions.ts # Команда парсинга
|
||
├── config/ # Конфигурация приложения
|
||
│ ├── app.ts
|
||
│ ├── database.ts
|
||
│ └── logger.ts
|
||
├── database/
|
||
│ └── migrations/ # Database миграции
|
||
├── start/
|
||
│ ├── routes.ts # Маршруты
|
||
│ ├── kernel.ts # Middleware регистрация
|
||
│ ├── scheduler.ts # Настройка scheduler
|
||
│ └── env.ts # Env validation
|
||
├── tests/ # Тесты (Japa)
|
||
│ ├── unit/
|
||
│ └── functional/
|
||
├── docs/ # Документация
|
||
├── .env # Environment переменные
|
||
├── adonisrc.ts # AdonisJS конфигурация
|
||
└── package.json
|
||
```
|
||
|
||
## Import Aliases
|
||
|
||
В проекте настроены удобные алиасы для импортов:
|
||
|
||
```typescript
|
||
#controllers/* → ./app/controllers/*.js
|
||
#models/* → ./app/models/*.js
|
||
#services/* → ./app/services/*.js
|
||
#validators/* → ./app/validators/*.js
|
||
#middleware/* → ./app/middleware/*.js
|
||
#config/* → ./config/*.js
|
||
#database/* → ./database/*.js
|
||
#start/* → ./start/*.js
|
||
|
||
// Пример использования:
|
||
import Auction from '#models/auction'
|
||
import ScraperService from '#services/scraper_service'
|
||
import { DatabaseConfig } from '#config/database'
|
||
```
|
||
|
||
## Логирование
|
||
|
||
Все операции логируются через Pino logger:
|
||
|
||
```typescript
|
||
import logger from '@adonisjs/core/services/logger'
|
||
|
||
// В коде:
|
||
logger.info('Scraping started')
|
||
logger.error({ err }, 'Failed to parse page')
|
||
logger.debug({ count: auctions.length }, 'Auctions scraped')
|
||
```
|
||
|
||
Логи сохраняются в:
|
||
- `tmp/logs/app.log` (production)
|
||
- Console output (development)
|
||
|
||
## Parse Logs в БД
|
||
|
||
Каждый запуск парсинга создает запись в таблице `parse_logs`:
|
||
|
||
```sql
|
||
SELECT * FROM parse_logs ORDER BY started_at DESC LIMIT 5;
|
||
|
||
-- Поля:
|
||
-- id, parse_type, status, started_at, completed_at,
|
||
-- items_found, items_new, items_updated, items_failed,
|
||
-- error_message, error_details
|
||
```
|
||
|
||
Полезные запросы:
|
||
|
||
```sql
|
||
-- Последние успешные парсинги
|
||
SELECT * FROM parse_logs
|
||
WHERE status = 'completed'
|
||
ORDER BY started_at DESC;
|
||
|
||
-- Статистика по парсингам
|
||
SELECT
|
||
COUNT(*) as total_runs,
|
||
AVG(items_found) as avg_found,
|
||
AVG(items_new) as avg_new,
|
||
SUM(items_failed) as total_failed
|
||
FROM parse_logs
|
||
WHERE parse_type = 'auction';
|
||
|
||
-- Неудачные парсинги с ошибками
|
||
SELECT started_at, error_message
|
||
FROM parse_logs
|
||
WHERE status = 'failed'
|
||
ORDER BY started_at DESC;
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### База данных не подключается
|
||
|
||
```bash
|
||
# Проверить что PostgreSQL запущен
|
||
# Linux/Mac:
|
||
sudo systemctl status postgresql
|
||
|
||
# Windows:
|
||
# Проверить через Services (services.msc)
|
||
|
||
# Проверить подключение
|
||
psql -U postgres -h localhost
|
||
|
||
# Создать базу вручную
|
||
psql -U postgres
|
||
CREATE DATABASE parser_zakupok;
|
||
\q
|
||
```
|
||
|
||
### Ошибки при парсинге
|
||
|
||
```bash
|
||
# Проверить логи
|
||
tail -f tmp/logs/app.log
|
||
|
||
# Проверить parse_logs в БД
|
||
node ace repl
|
||
> await loadModels()
|
||
> const ParseLog = await import('#models/parse_log')
|
||
> await ParseLog.default.query().orderBy('started_at', 'desc').first()
|
||
|
||
# Запустить парсинг одной страницы для отладки
|
||
node ace parse:auctions --pages=1 --skip-notifications
|
||
```
|
||
|
||
### Scheduler не запускается
|
||
|
||
```bash
|
||
# Проверить что scheduler провайдер добавлен в adonisrc.ts
|
||
cat adonisrc.ts | grep scheduler
|
||
|
||
# Проверить список задач
|
||
node ace scheduler:list
|
||
|
||
# Запустить вручную для проверки
|
||
node ace parse:auctions
|
||
```
|
||
|
||
### TypeScript ошибки
|
||
|
||
```bash
|
||
# Очистить build и пересобрать
|
||
rm -rf build/
|
||
npm run build
|
||
|
||
# Проверить типы
|
||
npm run typecheck
|
||
|
||
# Обновить зависимости
|
||
npm update
|
||
```
|
||
|
||
## API Endpoints (будут реализованы в Phase 7)
|
||
|
||
```
|
||
GET /api/auctions # Список аукционов (пагинация, фильтры)
|
||
GET /api/auctions/:id # Детали аукциона
|
||
GET /api/keywords # Ключевые слова пользователя
|
||
POST /api/keywords # Добавить ключевое слово
|
||
DELETE /api/keywords/:id # Удалить ключевое слово
|
||
GET /api/notifications # История уведомлений
|
||
```
|
||
|
||
## Docker (будет реализовано в Phase 8)
|
||
|
||
```bash
|
||
# Сборка и запуск
|
||
docker-compose up -d
|
||
|
||
# Просмотр логов
|
||
docker-compose logs -f app
|
||
|
||
# Остановка
|
||
docker-compose down
|
||
```
|
||
|
||
## Roadmap
|
||
|
||
- [x] Phase 1: Project Setup
|
||
- [x] Phase 2: Database Models & Migrations
|
||
- [x] Phase 3: ScraperService Implementation
|
||
- [x] Phase 4: Scheduler Command
|
||
- [ ] Phase 5: Telegram Bot Integration
|
||
- [ ] Phase 6: NotificationService Enhancement
|
||
- [ ] Phase 7: REST API Endpoints
|
||
- [ ] Phase 8: Docker Deployment
|
||
|
||
## Contributing
|
||
|
||
1. Fork репозиторий
|
||
2. Создать feature branch (`git checkout -b feature/AmazingFeature`)
|
||
3. Commit изменения (`git commit -m 'Add some AmazingFeature'`)
|
||
4. Push в branch (`git push origin feature/AmazingFeature`)
|
||
5. Открыть Pull Request
|
||
|
||
## License
|
||
|
||
[MIT License](LICENSE)
|
||
|
||
## Поддержка
|
||
|
||
При возникновении проблем:
|
||
1. Проверьте [документацию](docs/)
|
||
2. Создайте [Issue](../../issues)
|
||
3. Проверьте существующие Issues
|
||
|
||
## Полезные ссылки
|
||
|
||
- [AdonisJS Documentation](https://docs.adonisjs.com/)
|
||
- [Lucid ORM](https://lucid.adonisjs.com/)
|
||
- [Grammy Telegram Bot](https://grammy.dev/)
|
||
- [PostgreSQL Documentation](https://www.postgresql.org/docs/)
|