Developing a Competitor Stock Availability Monitoring Bot
Monitoring competitor stock opens business opportunities: raise prices when competitors run out; signal managers about restocking; show "Limited Stock" when competitors are empty. The bot monitors availability on schedule and reacts to changes.
What Gets Tracked
- Availability/absence — is the product for sale
- Quantity — if site shows remaining stock ("3 left")
- Status — in stock / preorder / discontinued / temporarily unavailable
- Appearance dates — when product returned to sale
Stock Status Extractor
class StockStatusExtractor
{
private array $inStockPatterns = ['/in\s*stock/i', '/available/i'];
private array $outOfStockPatterns = ['/out\s*of\s*stock/i', '/unavailable/i', '/sold\s*out/i'];
public function extract(string $html, array $config = []): StockStatus
{
$crawler = new Crawler($html);
// Strategy 1: Microdata (most reliable)
if ($availability = $this->extractFromMicrodata($crawler)) {
return $availability;
}
// Strategy 2: JSON-LD
if ($availability = $this->extractFromJsonLd($crawler)) {
return $availability;
}
// Strategy 3: Custom CSS selectors from config
if (!empty($config['stock_selector'])) {
if ($availability = $this->extractWithSelector($crawler, $config)) {
return $availability;
}
}
// Strategy 4: Button state ("Buy" / "Add to Cart")
return $this->extractFromButtonState($crawler);
}
public function extractQuantity(string $html): ?int
{
$crawler = new Crawler($html);
$patterns = [
'/(\d+)\s*(?:left|remaining)/i',
'/in\s*stock[:\s]+(\d+)/i',
];
$text = $crawler->text();
foreach ($patterns as $pattern) {
if (preg_match($pattern, $text, $m)) {
return (int) $m[1];
}
}
return null;
}
}
Model and History
class CompetitorStock extends Model
{
protected $casts = ['in_stock' => 'boolean'];
protected static function booted(): void
{
static::updated(function (self $model) {
if ($model->wasChanged('in_stock')) {
CompetitorStockChanged::dispatch($model);
}
});
}
}
Reacting to Changes
class HandleCompetitorStockChanged
{
public function handle(CompetitorStockChanged $event): void
{
$stock = $event->stock;
// Competitor ran out → notify about price opportunity
if (!$stock->in_stock && $stock->getOriginal('in_stock')) {
$this->notifyPriceOpportunity($stock->product, $stock->competitor);
}
// All competitors out of stock → apply scarcity pricing
$allOutOfStock = CompetitorStock::where('product_id', $stock->product_id)
->where('in_stock', true)->doesntExist();
if ($allOutOfStock && config('repricing.raise_when_competitors_out')) {
$this->applyScarcityPricing($stock->product);
}
}
}
Development timeline: monitoring 3-5 competitors with history and events — 5-7 business days.







