Facility: 090738
ABC Storage LLC
- Facility ID
- 090738
- Name
- ABC Storage LLC
- URL
- https://www.abcstoragellc.com/
- Address
- N/A
- Platform
- custom_facility_090738
- Parser File
- src/parsers/custom/facility_090738_parser.py
- Last Scraped
- 2026-03-27 14:04:21.579533
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-27 14:04:21.579533
- Parser Status
- ⚠ Needs Fix
- Status Reason
- Parser returned 0 units
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_090738_parser.py)
"""Parser for ABC Mini-Storage (facility 090738).
This site (abcstoragellc.com) is a brochure-style WordPress/ThemeSuite site
that does not publish pricing online. Unit sizes are listed on the
/unit-sizing/ page inside <article> elements grouped by category
(Small / Medium / Large). Each category has an <h2 class="tsI_sh"> heading
and <li> items containing dimension strings like "5' x 5'".
Because no prices are available the parser records size and category
description only; price fields are left as None.
"""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
# Matches dimension strings such as "5' x 5'", "7'x 10'", "10' x 20'"
_SIZE_RE = re.compile(
r"(\d+(?:\.\d+)?)\s*['\u2019\u2032]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)\s*['\u2019\u2032]?",
)
# Extracts the square-footage bracket from headings like "Small (0-50 sq. ft.)"
_CATEGORY_RE = re.compile(r"^(Small|Medium|Large)\s*\(([^)]+)\)", re.IGNORECASE)
class Facility090738Parser(BaseParser):
"""Extract storage unit sizes from ABC Mini-Storage unit-sizing page.
No pricing data is published on this site; price fields will be None.
Sizes and category descriptions are extracted from the /unit-sizing/ page.
"""
platform = "custom_facility_090738"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Find all article containers that hold size groups
articles = soup.find_all("article", class_=re.compile(r"\btsI\b"))
if not articles:
# Fallback: search the whole page for li items with dimension text
articles = [soup]
for article in articles:
# Extract category heading e.g. "Small (0-50 sq. ft.)"
heading_tag = article.find(class_="tsI_sh")
category = heading_tag.get_text(strip=True) if heading_tag else ""
# Extract category description from the paragraph inside tsI_dsc
desc_tag = article.find(class_="tsI_dsc")
category_desc = ""
if desc_tag:
p = desc_tag.find("p")
if p:
category_desc = p.get_text(separator=" ", strip=True)
# Extract each <li> containing a dimension string
li_items = article.find_all("li")
for li in li_items:
li_text = li.get_text(strip=True)
m = _SIZE_RE.search(li_text)
if not m:
continue
width = float(m.group(1))
length = float(m.group(2))
sqft = width * length
size_label = f"{int(width)}' x {int(length)}'"
unit = UnitResult(
size=size_label,
description=category_desc if category_desc else None,
price=None,
metadata={
"width": width,
"length": length,
"sqft": sqft,
"category": category,
},
)
result.units.append(unit)
if not result.units:
result.warnings.append(
"No unit size entries found. "
"Check that the snapshot is from the /unit-sizing/ page."
)
else:
result.warnings.append(
"No pricing data available on this site. "
"Prices must be obtained by calling 307-266-2080."
)
return result
Scrape Runs (7)
-
exported Run #21312026-03-27 14:04:16.665930 | Facility090738Parser
-
exported Run #21302026-03-27 14:04:16.428640 | Facility090738Parser
-
exported Run #13312026-03-23 03:06:35.546697 | Facility090738Parser
-
exported Run #8382026-03-21 18:58:35.689754 | Facility090738Parser
-
exported Run #3872026-03-14 16:38:15.110189 | Facility090738Parser
-
failed Run #222026-03-07 01:42:37.206086 | 1 failure(s)
-
started Run #122026-03-07 01:05:47.916473
Run #387 Details
- Status
- exported
- Parser Used
- Facility090738Parser
- Platform Detected
- table_layout
- Units Found
- 0
- Stage Reached
- exported
- Timestamp
- 2026-03-14 16:38:15.110189
Timing
| Stage | Duration |
|---|---|
| Fetch | 4133ms |
| Detect | 17ms |
| Parse | 9ms |
| Export | 10ms |
Snapshot: 090738_20260314T163819Z.html · Show Snapshot · Open in New Tab
No units found in this run.
All Failures for this Facility (6)
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-27 14:04:21.559923
No units extracted for 090738
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 090738
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-27 14:04:21.553830
No units extracted for 090738
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 090738
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-23 03:06:40.060588
No units extracted for 090738
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 090738
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-21 18:58:40.165066
No units extracted for 090738
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 090738
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 16:38:19.315337
No units extracted for 090738
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 090738
fetch
DatatypeMismatch
unknown
unknown
permanent
Run #22 | 2026-03-07 01:42:41.097915
column "success" is of type boolean but expression is of type integer LINE 3: ... VALUES ('090738', 22, '090738_20260307T014241Z.html', 0) ^ HINT: You will need to rewrite or cast the expression.
Stack trace
Traceback (most recent call last):
File "/app/src/pipeline.py", line 329, in _process_facility
manifest_id = storage.insert_snapshot_manifest(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/src/db/pg_backend.py", line 615, in insert_snapshot_manifest
row = self._execute_returning(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/src/db/pg_backend.py", line 54, in _execute_returning
cur.execute(sql, params)
File "/app/.venv/lib/python3.11/site-packages/psycopg2/extras.py", line 236, in execute
return super().execute(query, vars)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.DatatypeMismatch: column "success" is of type boolean but expression is of type integer
LINE 3: ... VALUES ('090738', 22, '090738_20260307T014241Z.html', 0)
^
HINT: You will need to rewrite or cast the expression.