Facility: 003533

Bulldog Storage

Stale Data Warning: This facility has not been successfully scraped in 26 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
003533
Name
Bulldog Storage
URL
https://bulldogstoragekearney.com/storage-chart/
Address
1115 Couchman Dr, Kearney, MO 64060, USA, Kearney, Missouri 64060
Platform
custom_facility_003533
Parser File
src/parsers/custom/facility_003533_parser.py
Last Scraped
2026-03-27 13:56:25.643977
Created
2026-03-14 16:21:53.706708
Updated
2026-03-27 13:56:25.677517
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_003533_parser.py)
"""Parser for Bulldog Storage (Kearney, MO).

The storage chart page has a table with unit sizes, equivalents, and
descriptions but no pricing information.  We extract sizes only.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

from src.parsers.base import BaseParser, ParseResult, UnitResult


class Facility003533Parser(BaseParser):
    """Extract storage units from Bulldog Storage's storage chart table."""

    platform = "custom_facility_003533"

    _SIZE_RE = re.compile(
        r"(\d+)\s*[×xX]\s*(\d+)"
    )

    def parse(self, html: str, url: str = "") -> ParseResult:
        soup = BeautifulSoup(html, "lxml")
        result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)

        for tag in soup.find_all(["script", "style"]):
            tag.decompose()

        # Look for the storage chart table
        tables = soup.find_all("table")
        for table in tables:
            rows = table.find_all("tr")
            for row in rows:
                cells = row.find_all("td")
                if len(cells) < 2:
                    continue

                cell_text = cells[0].get_text(strip=True)
                m = self._SIZE_RE.search(cell_text)
                if not m:
                    continue

                # Skip header rows
                if "unit" in cell_text.lower() and "size" in cell_text.lower():
                    continue

                size_text = f"{m.group(1)}x{m.group(2)}"
                w, ln, sq = self.normalize_size(size_text)
                if w is None or w < 3 or ln < 3:
                    continue

                unit = UnitResult()
                unit.size = size_text

                # Extract sqft if present in parentheses
                sqft_match = re.search(r"\((\d+)\s*sq\.?\s*ft\.?\)", cell_text, re.IGNORECASE)
                if sqft_match:
                    sq = float(sqft_match.group(1))

                unit.metadata = {"width": w, "length": ln, "sqft": sq}

                # Use the "equivalent to" and "what unit will hold" as description
                desc_parts = []
                if len(cells) >= 2:
                    equiv = cells[1].get_text(strip=True)
                    if equiv:
                        desc_parts.append(equiv)
                if len(cells) >= 3:
                    holds = cells[2].get_text(strip=True)
                    if holds:
                        desc_parts.append(holds)
                if desc_parts:
                    unit.description = " - ".join(desc_parts)[:200]

                result.units.append(unit)

        if not result.units:
            result.warnings.append("No units found in storage chart table")

        return result

Scrape Runs (5)

Run #1933 Details

Status
exported
Parser Used
Facility003533Parser
Platform Detected
table_layout
Units Found
5
Stage Reached
exported
Timestamp
2026-03-27 13:56:22.396855
Timing
Stage Duration
Fetch3167ms
Detect15ms
Parse5ms
Export19ms

Snapshot: 003533_20260327T135625Z.html · Show Snapshot · Open in New Tab

Parsed Units (5)

5x10

No price

10x10

No price

10x15

No price

10x20

No price

10x30

No price

← Back to dashboard