Facility: 109474
401 Storage
- Facility ID
- 109474
- Name
- 401 Storage
- URL
- https://www.401storage.com/storage-units/rhode-island/providence/401-providence-3501857/
- Address
- 840 Manton Ave, Providence, RI 02909, USA, Providence, Rhode Island 02909
- Platform
- custom_facility_109474
- Parser File
- src/parsers/custom/facility_109474_parser.py
- Last Scraped
- 2026-03-27 13:43:23.782995
- Created
- 2026-03-20 23:23:10.660695
- Updated
- 2026-03-27 13:43:23.812843
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_109474_parser.py)
"""Parser for 401 Storage - Providence."""
from __future__ import annotations
import json
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility109474Parser(BaseParser):
"""Extract storage units from 401 Storage using JSON-LD offer catalog."""
platform = "custom_facility_109474"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Extract units from JSON-LD SelfStorage schema with hasOfferCatalog
scripts = soup.find_all("script", type="application/ld+json")
for script in scripts:
try:
data = json.loads(script.string or "")
if not isinstance(data, dict):
continue
catalog = data.get("hasOfferCatalog", {})
if not catalog:
continue
items = catalog.get("itemListElement", [])
seen_sizes = set()
for item in items:
if item.get("@type") != "Product":
continue
size_str = item.get("size", "").strip()
if not size_str or size_str in seen_sizes:
continue
seen_sizes.add(size_str)
offers = item.get("offers", {})
low_price = offers.get("lowPrice")
high_price = offers.get("highPrice")
price_val = low_price if low_price is not None else high_price
unit = UnitResult()
unit.size = size_str
w, ln, sq = self.normalize_size(size_str)
if w is not None:
unit.metadata = {"width": w, "length": ln, "sqft": sq}
if price_val is not None:
unit.price = float(price_val)
if unit.size or unit.price:
result.units.append(unit)
except (json.JSONDecodeError, AttributeError, TypeError):
continue
if not result.units:
result.warnings.append("No units found on page")
return result
Scrape Runs (4)
-
exported Run #16152026-03-27 13:43:21.507729 | 8 units | Facility109474Parser | View Data →
-
exported Run #16142026-03-27 13:43:19.920349 | 8 units | Facility109474Parser | View Data →
-
exported Run #10692026-03-23 02:43:34.276304 | 7 units | Facility109474Parser | View Data →
-
exported Run #5742026-03-21 18:33:52.785147 | 6 units | Facility109474Parser | View Data →
Run #574 Details
- Status
- exported
- Parser Used
- Facility109474Parser
- Platform Detected
- table_layout
- Units Found
- 6
- Stage Reached
- exported
- Timestamp
- 2026-03-21 18:33:52.785147
Timing
| Stage | Duration |
|---|---|
| Fetch | 2320ms |
| Detect | 105ms |
| Parse | 45ms |
| Export | 9ms |
Snapshot: 109474_20260321T183355Z.html · Show Snapshot · Open in New Tab
Parsed Units (6)
5' x 5'
$22.00/mo
5' x 10'
$30.00/mo
10' x 5'
$30.00/mo
10' x 10'
$53.00/mo
10' x 15'
$77.00/mo
20' x 10'
$96.00/mo