Facility: 044960
Anchor Ontario Storage
- Facility ID
- 044960
- Name
- Anchor Ontario Storage
- URL
- http://www.anchorontario.com/
- Address
- N/A
- Platform
- custom_facility_044960
- Parser File
- src/parsers/custom/facility_044960_parser.py
- Last Scraped
- 2026-03-23 03:17:28.006959
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-23 03:17:28.006959
- Parser Status
- ⚠ Needs Fix
- Status Reason
- Parser returned 0 units
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_044960_parser.py)
"""Parser for Anchor Mini Storage Ontario (facility 044960).
The website (anchorontario.com) is a simple WordPress site with a Unit Size
Guide page listing available unit sizes as headings, but no pricing is
published anywhere on the site. The reservation flow uses an embedded contact
form that only lets visitors select a size.
This parser extracts available unit sizes from the Size Guide page (fetched as
part of the snapshot or from the main snapshot) so the facility is represented
in the database. All price fields are left as ``None`` and a warning is
recorded.
Unit sizes advertised: 5x10, 10x10, 10x15, 10x20, 10x25.
"""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility044960Parser(BaseParser):
"""Extract storage unit sizes from Anchor Mini Storage Ontario.
The Unit Size Guide page contains headings like:
"5 x 10 Unit"
"10 x 10 Unit"
"10 x 15 Unit"
"10 x 20 Unit"
"10 x 25 Unit"
No pricing is published on the site, so ``price`` and ``sale_price`` are
left as ``None`` and a warning is recorded.
"""
platform = "custom_facility_044960"
# Matches patterns like "5 x 10", "10x15", "10 X 25"
_SIZE_RE = re.compile(
r"(\d+(?:\.\d+)?)\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)",
)
# Description text associated with each size (keyed by "WxL" string)
_DESCRIPTIONS: dict[str, str] = {
"5x10": "Great for small furniture, bikes, mattresses, tools, etc. About two pickup loads",
"10x10": "About half the size of a one car garage or a one bedroom apartment",
"10x15": "About the size of a small car or a two bedroom apartment",
"10x20": "About the size of a two to three bedroom house",
"10x25": "About the size of a three to four bedroom house",
}
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# The sizes appear as <h2> headings of the form "N x N Unit"
# Also handle the case where sizes appear in select/option elements
# (the reservation form embed lists them as <option> values).
sizes_found: list[tuple[float, float, str]] = []
for heading in soup.find_all(["h2", "h3", "h4", "option", "strong"]):
text = heading.get_text(strip=True)
match = self._SIZE_RE.search(text)
if match:
width = float(match.group(1))
length = float(match.group(2))
# Avoid duplicates
key = (width, length)
if not any(w == key[0] and l == key[1] for w, l, _ in sizes_found):
desc_key = f"{int(width)}x{int(length)}"
description = self._DESCRIPTIONS.get(desc_key, f"Unit size {int(width)}' x {int(length)}'")
sizes_found.append((width, length, description))
if not sizes_found:
# Fallback: scan all text nodes for size patterns
for element in soup.find_all(string=self._SIZE_RE):
parent = element.parent
if not parent or parent.name in ("script", "style"):
continue
text = element.strip()
match = self._SIZE_RE.search(text)
if match:
width = float(match.group(1))
length = float(match.group(2))
key = (width, length)
if not any(w == key[0] and l == key[1] for w, l, _ in sizes_found):
desc_key = f"{int(width)}x{int(length)}"
description = self._DESCRIPTIONS.get(desc_key, f"Unit size {int(width)}' x {int(length)}'")
sizes_found.append((width, length, description))
for width, length, description in sizes_found:
size_label = self.normalize_size(f"{int(width)}x{int(length)}")
unit = UnitResult(
size=size_label,
description=f"{description} — no pricing published on site",
price=None,
sale_price=None,
metadata={
"width": width,
"length": length,
"sqft": width * length,
"no_pricing": True,
},
url=url or None,
)
result.units.append(unit)
if result.units:
result.warnings.append(
"No pricing information is published on this facility's website; "
"unit sizes were extracted but all price fields are None."
)
else:
result.warnings.append("No unit size patterns found on page")
return result
Scrape Runs (4)
Run #962 Details
- Status
- exported
- Parser Used
- Facility044960Parser
- Platform Detected
- unknown
- Units Found
- 0
- Stage Reached
- exported
- Timestamp
- 2026-03-21 19:10:07.183642
Timing
| Stage | Duration |
|---|---|
| Fetch | 9546ms |
| Detect | 36ms |
| Parse | 24ms |
| Export | 4ms |
Snapshot: 044960_20260321T191016Z.html · Show Snapshot · Open in New Tab
No units found in this run.
All Failures for this Facility (4)
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-23 03:17:28.002787
No units extracted for 044960
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 044960
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-21 19:10:16.814940
No units extracted for 044960
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 044960
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 16:53:09.598749
No units extracted for 044960
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 044960
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 01:04:34.799007
No units extracted for 044960
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 044960