Facility: 003533
Bulldog Storage
- 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 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)
-
exported Run #19332026-03-27 13:56:22.396855 | 5 units | Facility003533Parser | View Data →
-
exported Run #19322026-03-27 13:56:22.258964 | 5 units | Facility003533Parser | View Data →
-
exported Run #12292026-03-23 02:57:41.192859 | 5 units | Facility003533Parser | View Data →
-
exported Run #7362026-03-21 18:49:19.465535 | 5 units | Facility003533Parser | View Data →
-
exported Run #2852026-03-14 16:30:47.961090 | 5 units | Facility003533Parser | View Data →
Run #1932 Details
- Status
- exported
- Parser Used
- Facility003533Parser
- Platform Detected
- table_layout
- Units Found
- 5
- Stage Reached
- exported
- Timestamp
- 2026-03-27 13:56:22.258964
Timing
| Stage | Duration |
|---|---|
| Fetch | 3304ms |
| Detect | 11ms |
| Parse | 4ms |
| Export | 24ms |
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