Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
xob0t committed Apr 12, 2024
1 parent 113948e commit 501329e
Show file tree
Hide file tree
Showing 11 changed files with 1,708 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

.vscode/settings.json
.vscode/launch.json
/core/__pycache__
*.sqlite
*.json
98 changes: 98 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# mmparser
## Купить
Парсер открыт, но не бесплатен :)
Купить - [Yoomoney](https://yoomoney.ru/fundraise/122C5TB8IKI.240412)

## Особенности
* Работа через api
* Парсинг карточек товаров при парсинге каталога/поиска
* Сохраниние результатов в sqlite БД
* Запуск с конфигом и/или аргументами
* Интерактивное создание конфигов
* Поддержка прокси строкой или списком из файла
* Поддержка ссылок каталога, поиска, или карточек товара
* Парсиг одной ссылки в многопотоке, по потоку на прокси/соединение
* Импорт cookies экспортированних в формате Json с помошью [Cookie-Editor](https://chrome.google.com/webstore/detail/hlkenndednhfkekhgcdicdfddnkalmdm)
* Блеклист продавцов
* Regex фильтр по именам товаров
* Уведомления в телеграм по заданным параметрам
* Позволяет выставить время, через которое подходящий по параметрам уведомлений товар будет повторно отправлен в TG

## Установка:
1. Уставновить [Python](https://www.python.org/downloads/), перед установкой поставить галочку "Добавить в PATH"
2. [Скачать парсер](https://github.com/xob0t/mmparser/releases/latest/download/mmparser.zip)
3. Устновить парсер: `pip install mmparser.zip`

## Пример использования
### <span style="color:yellow">Кавычки обязательны!</span>
### Просто парсинг url
`mmparser "https://megamarket.ru/catalog/?q=%D0%BD%D0%BE%D1%83%D1%82%D0%B1%D1%83%D0%BA&suggestionType=frequent_query#?filters=%7B%2288C83F68482F447C9F4E401955196697%22%3A%7B%22min%22%3A229028%2C%22max%22%3A307480%7D%2C%22A03364050801C25CD0A856C734F74FE9%22%3A%5B%221%22%5D%7D&sort=1"`
### Без аргументов, создание конфига
`mmparser`
### Запуск с конфигом
`mmparser -cfg "config.json"`
### Запуск с конфигом и аргументами
`mmparser -cfg "config.json" --allow-direct`

## Чтение результатов
При запуске парсер создаст в рабочей директории файл storage.sqlite

Это sqlite база данных, очень удобно читается в [DB Browser for SQLite](https://sqlitebrowser.org/)

## Запуск по расписанию на windows:
[Планировщик заданий Windows для начинающих](https://remontka.pro/windows-task-scheduler/)

#

```
usage: main.py [-h] [-cfg CONFIG] [-i INCLUDE] [-e EXCLUDE] [-b BLACKLIST] [-ac] [-nc] [-c COOKIES] [-aa ACCOUNT_ALERT] [-a ADDRESS] [-p PROXY] [-pl PROXY_LIST] [-ad] [-tc TG_CONFIG] [-pva PRICE_VALUE_ALERT]
[-pbva PRICE_BONUS_VALUE_ALERT] [-bva BONUS_VALUE_ALERT] [-bpa BONUS_PERCENT_ALERT] [-art ALERT_REPEAT_TIMEOUT] [-t THREADS] [-d DELAY] [-ed ERROR_DELAY] [-log {DEBUG,INFO,WARNING,ERROR,CRITICAL}]
[url]
positional arguments:
url URL для парсинга
options:
-h, --help show this help message and exit
-cfg CONFIG, --config CONFIG
Путь к конфигу парсера
-i INCLUDE, --include INCLUDE
Парсить только товары, название которых совпадает с выражением
-e EXCLUDE, --exclude EXCLUDE
Пропускать товары, название которых совпадает с выражением
-b BLACKLIST, --blacklist BLACKLIST
Путь к файлу со списком игнорируемых продавцов
-ac, --all-cards Всегда парсить карточки товаров
-nc, --no-cards Не парсить карточки товаров
-c COOKIES, --cookies COOKIES
Путь к файлу с cookies в формате JSON (Cookie-Editor - Export Json)
-aa ACCOUNT_ALERT, --account-alert ACCOUNT_ALERT
Если вы используйте cookie, и вход в аккаунт не выполнен, присылать уведомление в TG
-a ADDRESS, --address ADDRESS
Адрес, будет использовано первое сопадение
-p PROXY, --proxy PROXY
Строка прокси в формате protocol://username:password@ip:port
-pl PROXY_LIST, --proxy-list PROXY_LIST
Путь к файлу с прокси в формате protocol://username:password@ip:port
-ad, --allow-direct Использовать прямое соединение параллельно с прокси для ускорения работы в многопотоке
-tc TG_CONFIG, --tg-config TG_CONFIG
Telegram Bot Token и Telegram Chat Id в формате token$id
-pva PRICE_VALUE_ALERT, --price-value-alert PRICE_VALUE_ALERT
Если цена товара равна или ниже данного значения, уведомлять в TG
-pbva PRICE_BONUS_VALUE_ALERT, --price-bonus-value-alert PRICE_BONUS_VALUE_ALERT
Если цена-бонусы товара равна или ниже данного значения, уведомлять в TG
-bva BONUS_VALUE_ALERT, --bonus-value-alert BONUS_VALUE_ALERT
Если количество бонусов товара равно или выше данного значения, уведомлять в TG
-bpa BONUS_PERCENT_ALERT, --bonus-percent-alert BONUS_PERCENT_ALERT
Если процент бонусов товара равно или выше данного значения, уведомлять в TG
-art ALERT_REPEAT_TIMEOUT, --alert-repeat-timeout ALERT_REPEAT_TIMEOUT
Если походящий по параметрам товар уже был отправлен в TG, повторно уведомлять по истечении заданного времени, в часах
-t THREADS, --threads THREADS
Количество потоков. По умолчанию: 1 на каждое соединиение
-d DELAY, --delay DELAY
Задержка между запросами в секундах при работе в одном потоке. По умолчанию: 2
-ed ERROR_DELAY, --error-delay ERROR_DELAY
Задержка между запосами в секундах в случае ошибки при работе в одном потоке. По умолчанию: 5
-log {DEBUG,INFO,WARNING,ERROR,CRITICAL}, --log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
Уровень лога. По умолчанию: INFO
```
Empty file added core/__init__.py
Empty file.
147 changes: 147 additions & 0 deletions core/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import sqlite3
import datetime

FILENAME = "storage.sqlite"


def create_db():
sqlite_connection = sqlite3.connect(FILENAME)
cursor = sqlite_connection.cursor()

cursor.execute("""
CREATE TABLE "jobs" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"name" TEXT,
"started" DATETIME,
"completed" DATETIME
);
""")

sqlite_connection.commit()
cursor.close()


def new_job(job_name):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sqlite_connection = sqlite3.connect(FILENAME)
cursor = sqlite_connection.cursor()
cursor.execute(
"""INSERT INTO
jobs
(name,started) VALUES (?,?)""",
(job_name,
now)
)
job_id = cursor.lastrowid
cursor.execute(f"""
CREATE TABLE "{job_name}_{job_id}" (
"goodsId" TEXT,
"merchantId" TEXT,
"url" TEXT,
"title" TEXT,
"finalPrice" INTEGER,
"finalPriceBonus" INTEGER,
"bonusAmount" INTEGER,
"bonusPercent" INTEGER,
"availableQuantity" INTEGER,
"deliveryPossibilities" TEXT,
"merchantName" TEXT,
"scraped" DATETIME,
"notified" BOOL
);
""")
sqlite_connection.commit()
return job_id


def add_to_db(
job_id,
job_name,
goodsId,
merchantId,
url,
title,
finalPrice,
finalPriceBonus,
bonusAmount,
bonusPercent,
availableQuantity,
deliveryPossibilities,
merchantName,
notified,
):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sqlite_connection = sqlite3.connect(FILENAME)
cursor = sqlite_connection.cursor()
cursor.execute(
f"""INSERT INTO
"{job_name}_{job_id}"
(goodsId,merchantId,url,title,finalPrice,finalPriceBonus,bonusAmount,
bonusPercent,availableQuantity,deliveryPossibilities,
merchantName,scraped,notified)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
goodsId,
merchantId,
url,
title,
finalPrice,
finalPriceBonus,
bonusAmount,
bonusPercent,
availableQuantity,
deliveryPossibilities,
merchantName,
now,
notified,
),
)
sqlite_connection.commit()


def get_last_notified(goodsId, merchantId, finalPrice, bonusAmount):
sqlite_connection = sqlite3.connect(FILENAME)
cursor = sqlite_connection.cursor()
last_notified_row = None

# Get a list of all job tables
# cursor.execute("SELECT id, name FROM jobs")
# job_tables = cursor.fetchall()

cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name != 'jobs' AND name != 'sqlite_sequence'")
job_tables = [table[0] for table in cursor.fetchall()]

# Construct a union query to select from all job tables at once
union_query = " UNION ".join([f"SELECT scraped FROM {table} WHERE notified = 1 AND goodsId = ? AND merchantId = ? AND finalPrice = ? AND bonusAmount = ?" for table in job_tables])

union_query+="ORDER BY scraped DESC LIMIT 1"

# Concatenate all parameters to be passed into the execute function
parameters = tuple([goodsId, merchantId, finalPrice, bonusAmount] * len(job_tables))

cursor.execute(union_query, parameters)
row = cursor.fetchone()
if row:
last_notified_row = row[0]

cursor.close()
return last_notified_row

def finish_job(job_id):
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sqlite_connection = sqlite3.connect(FILENAME)
cursor = sqlite_connection.cursor()
cursor.execute(
"""UPDATE
"jobs"
SET completed = ?
WHERE id = ?
""",
(now, job_id),
)
sqlite_connection.commit()
cursor.close()
if cursor.rowcount == 0:
return False
return True
Loading

0 comments on commit 501329e

Please sign in to comment.