Перейти к содержимому

Отслеживание цены и наличия товара

Ждёте, пока подешевеет видеокарта или билет, ловите момент, когда товар снова завезут? Вместо того чтобы обновлять вкладку по десять раз в день, повесим проверку на cron: скрипт сам скачает страницу, вытащит цену и пришлёт push, когда что-то изменится.

Скрипт скачивает страницу, вытаскивает цену (тут — через grep, но чаще удобнее CSS-селектором в pup или JSON через jq), сравнивает с сохранённой:

#!/usr/bin/env bash
# ~/bin/notifly-price — `notifly-price <url> <желаемая_цена>`
set -eu
set -a; source ~/.notifly.env; set +a
URL="${1:?url}"
TARGET="${2:-0}" # порог: push, если цена не выше этого значения
UA="Mozilla/5.0 (compatible; notifly-price/1.0)"
STATE="/tmp/notifly-price-$(echo -n "$URL" | md5sum | cut -c1-8).state"
PREV=$(cat "$STATE" 2>/dev/null || echo 0)
HTML=$(curl -fsSL --compressed -A "$UA" "$URL")
# Вытаскиваем первое число из тега с ценой. Селектор подгоните под сайт:
# у многих магазинов цена лежит в JSON-LD ("price":"12990") — тогда через jq.
PRICE=$(echo "$HTML" | grep -oE '"price":"[0-9]+' | head -1 | grep -oE '[0-9]+')
[[ -z "$PRICE" ]] && { echo "Цена не найдена — поправьте селектор" >&2; exit 1; }
# Цена упала относительно прошлой проверки?
if (( PREV > 0 )) && (( PRICE < PREV )); then
notifly-send "📉 Цена упала: $PRICE" "Было $PREV ₽\n$URL" 6
fi
# Достигли желаемого порога?
if (( TARGET > 0 )) && (( PRICE <= TARGET )) && (( PREV > TARGET || PREV == 0 )); then
notifly-send "🎯 Цена ≤ $TARGET" "Сейчас $PRICE ₽\n$URL" 8
fi
echo "$PRICE" > "$STATE"

Ставим в cron раз в два часа (уважаем сайт — не чаще):

# ~/bin/notifly-price <url> <порог>
17 */2 * * * ~/bin/notifly-price 'https://shop.example.com/gpu-4080' 89990

Иногда важна не цена, а сам факт появления. Ловим по признаку в разметке («В корзину» вместо «Нет в наличии»):

#!/usr/bin/env bash
# ~/bin/notifly-stock — `notifly-stock <url>`
set -eu
set -a; source ~/.notifly.env; set +a
URL="${1:?url}"
STATE="/tmp/notifly-stock-$(echo -n "$URL" | md5sum | cut -c1-8).state"
PREV=$(cat "$STATE" 2>/dev/null || echo OUT)
HTML=$(curl -fsSL --compressed -A "Mozilla/5.0 (notifly-stock)" "$URL")
# Признак наличия. Подгоните строку под конкретный магазин.
if echo "$HTML" | grep -qi 'в корзину\|add to cart'; then
NOW=IN
else
NOW=OUT
fi
if [[ "$NOW" == "IN" && "$PREV" == "OUT" ]]; then
notifly-send "🛒 Товар в наличии!" "$URL" 8
fi
echo "$NOW" > "$STATE"

Многие сайты кладут цену в JSON-LD <script type="application/ld+json">. Это стабильнее, чем парсить HTML:

Окно терминала
PRICE=$(echo "$HTML" \
| grep -oP '(?<=<script type="application/ld\+json">).*?(?=</script>)' \
| jq -r '..|.price? // empty' | head -1)

Аналог на PowerShell. Предполагается настроенная функция Send-Notifly.

C:\scripts\Notifly-Price.ps1
param([Parameter(Mandatory)][string]$Url, [int]$Target = 0)
. $env:USERPROFILE\Documents\WindowsPowerShell\Notifly.ps1
$hash = ([System.BitConverter]::ToString(
(New-Object Security.Cryptography.MD5CryptoServiceProvider).ComputeHash(
[Text.Encoding]::UTF8.GetBytes($Url))) -replace '-').Substring(0, 8)
$State = "$env:LOCALAPPDATA\notifly-price-$hash.txt"
$prev = if (Test-Path $State) { [int](Get-Content $State) } else { 0 }
$html = Invoke-WebRequest -Uri $Url -UserAgent "Mozilla/5.0 (notifly-price)" -UseBasicParsing
# Первое число из "price":"12990". Селектор подгоните под сайт.
$price = [int]([regex]::Match($html.Content, '"price":"(\d+)').Groups[1].Value)
if ($price -eq 0) { throw "Цена не найдена — поправьте селектор" }
if ($prev -gt 0 -and $price -lt $prev) {
Send-Notifly -Title "📉 Цена упала: $price" -Message "Было $prev`n$Url" -Priority 6
}
if ($Target -gt 0 -and $price -le $Target -and ($prev -gt $Target -or $prev -eq 0)) {
Send-Notifly -Title "🎯 Цена ≤ $Target" -Message "Сейчас $price`n$Url" -Priority 8
}
$price | Set-Content $State

Расписание раз в два часа:

Окно терминала
$Action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File C:\scripts\Notifly-Price.ps1 -Url 'https://shop.example.com/gpu' -Target 89990"
$Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Hours 2)
Register-ScheduledTask -TaskName "Notifly Price Watch" -Action $Action -Trigger $Trigger
  • Ловите скидку, не залипая в браузере. Cron проверяет за вас круглосуточно.
  • Порог + падение. Отдельно «стало дешевле» (приоритет 6) и «достигло цели» (приоритет 8, чтобы точно не проспать).
  • Не только цены. Тот же приём — любое изменение на странице: статус заявки, расписание, наличие записи к врачу.