Facility: 080080
LG Properties Storage
- Facility ID
- 080080
- Name
- LG Properties Storage
- URL
- https://www.storage-lgproperties.com/
- Address
- N/A
- Platform
- custom_facility_080080
- Parser File
- src/parsers/custom/facility_080080_parser.py
- Last Scraped
- 2026-03-27 14:03:28.445896
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-27 14:03:28.481063
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_080080_parser.py)
"""Parser for LG Properties, LLC - Laramie, Wyoming (facility 080080).
This is a GoDaddy Website Builder page (Starfield Technologies) that describes
a single unit type (12' x 30') with a flat monthly rate embedded in plain-text
content blocks. Unit details are inside elements with ``data-aid`` attributes
such as ``ABOUT_DESCRIPTION_RENDERED*``, not in a structured pricing table.
"""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility080080Parser(BaseParser):
"""Extract storage units from LG Properties, LLC (GoDaddy Website Builder).
The site advertises one unit type — 12' x 30' outdoor drive-up units —
at a single flat rate. Relevant data is scattered across several
``data-aid="ABOUT_DESCRIPTION_RENDERED*"`` content blocks:
* Storage Facilities block: describes the unit dimensions and RV suitability.
* Pricing block: "All units are $175 per month."
Strategy:
1. Find the "Pricing" heading via ``data-aid="ABOUT_HEADLINE_RENDERED*"``
and read the adjacent description block.
2. Extract the dollar amount with a regex.
3. Find the "Storage Facilities" block the same way and extract dimensions.
4. Emit one ``UnitResult`` combining both pieces of information.
"""
platform = "custom_facility_080080"
# "All units are $175 per month." or variations
_PRICE_RE = re.compile(
r"\$\s*([\d,]+(?:\.\d+)?)\s+per\s+month",
re.IGNORECASE,
)
# Dimension like "12' x 30'" — tolerates smart quotes and varied spacing
_DIM_RE = re.compile(
r"(\d+(?:\.\d+)?)['\u2018\u2019\u2032]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)['\u2018\u2019\u2032]?",
)
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Collect all content blocks (heading + description pairs)
# Each ContentBasic div contains one h4 heading and one description div.
content_blocks = soup.find_all("div", attrs={"data-ux": "ContentBasic"})
price: float | None = None
dimensions: list[tuple[float, float]] = []
description_text: str = ""
amenities: list[str] = []
for block in content_blocks:
heading_el = block.find(attrs={"data-ux": "ContentHeading"})
desc_el = block.find(attrs={"data-ux": "ContentText"})
if not heading_el or not desc_el:
continue
heading_text = heading_el.get_text(strip=True)
desc_text = desc_el.get_text(separator=" ", strip=True)
heading_lower = heading_text.lower()
if "pricing" in heading_lower:
m = self._PRICE_RE.search(desc_text)
if m:
price = float(m.group(1).replace(",", ""))
elif "storage facilit" in heading_lower:
description_text = desc_text
for m in self._DIM_RE.finditer(desc_text):
w = float(m.group(1))
length = float(m.group(2))
dimensions.append((w, length))
elif "safe" in heading_lower or "secure" in heading_lower:
amenities_text = desc_text.strip()
if amenities_text:
amenities.append(amenities_text)
if not dimensions:
result.warnings.append("No unit dimensions found in Storage Facilities block")
return result
if price is None:
result.warnings.append("No price found in Pricing block")
return result
# Detect drive-up access from door size description
is_drive_up = bool(re.search(r"door|RV|motorhome|drive.?up", description_text, re.IGNORECASE))
for width, length in dimensions:
size_label = f"{int(width)}' x {int(length)}'"
meta: dict = {
"width": width,
"length": length,
"sqft": width * length,
"drive_up": is_drive_up,
}
if amenities:
meta["amenities"] = amenities
unit = UnitResult(
size=size_label,
price=price,
description=description_text,
metadata=meta,
)
result.units.append(unit)
return result
Scrape Runs (7)
-
exported Run #21072026-03-27 14:03:25.406331 | 1 units | Facility080080Parser | View Data →
-
exported Run #21062026-03-27 14:03:25.255376 | 1 units | Facility080080Parser | View Data →
-
exported Run #13192026-03-23 03:05:37.979539 | 1 units | Facility080080Parser | View Data →
-
exported Run #8262026-03-21 18:57:31.335315 | 1 units | Facility080080Parser | View Data →
-
exported Run #3752026-03-14 16:37:17.547532 | 1 units | Facility080080Parser | View Data →
-
failed Run #162026-03-07 01:42:20.890582 | 1 failure(s)
-
started Run #72026-03-07 01:05:31.601528
Run #2107 Details
- Status
- exported
- Parser Used
- Facility080080Parser
- Platform Detected
- table_layout
- Units Found
- 1
- Stage Reached
- exported
- Timestamp
- 2026-03-27 14:03:25.406331
Timing
| Stage | Duration |
|---|---|
| Fetch | 2957ms |
| Detect | 19ms |
| Parse | 7ms |
| Export | 20ms |
Snapshot: 080080_20260327T140328Z.html · Show Snapshot · Open in New Tab
Parsed Units (1)
12' x 30'
$175.00/mo
All Failures for this Facility (1)
fetch
DatatypeMismatch
unknown
unknown
permanent
Run #16 | 2026-03-07 01:42:23.092739
column "success" is of type boolean but expression is of type integer LINE 3: ... VALUES ('080080', 16, '080080_20260307T014223Z.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 ('080080', 16, '080080_20260307T014223Z.html', 0)
^
HINT: You will need to rewrite or cast the expression.