Facility: 003661
Century Square Self Storage
- Facility ID
- 003661
- Name
- Century Square Self Storage
- URL
- http://www.soundselfstorage.com/century-square-self-storage/
- Address
- 1120 South 324th St, Federal Way, WA 98003, USA, Federal Way, Washington 98003
- Platform
- custom_facility_003661
- Parser File
- src/parsers/custom/facility_003661_parser.py
- Last Scraped
- 2026-03-27 13:56:56.572329
- Created
- 2026-03-14 16:21:53.706708
- Updated
- 2026-03-27 13:56:56.602940
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_003661_parser.py)
"""Parser for KJ Storage (storewithkjstorage.com)."""
from __future__ import annotations
import json
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility003661Parser(BaseParser):
"""Extract storage units from KJ Storage via embedded JSON in <pre> tags."""
platform = "custom_facility_003661"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Unit data is embedded as JSON inside <pre> tags within tables.
# Each <pre> contains a JSON object with dcWidth, dcLength, dcPreferredRate, sTypeName.
# The first <pre> is a summary record (fields are dicts with min/max); skip those.
for pre in soup.find_all("pre"):
text = pre.get_text(strip=True)
if not text.startswith("{"):
continue
try:
data = json.loads(text)
except (json.JSONDecodeError, ValueError):
continue
width = data.get("dcWidth")
length = data.get("dcLength")
rate = data.get("dcPreferredRate")
type_name = data.get("sTypeName", "")
# Skip summary records where fields are dicts (not scalar values)
if isinstance(width, dict) or isinstance(length, dict):
continue
if not (width and length):
continue
size_str = f"{int(width)}x{int(length)}"
size = self.normalize_size(size_str)
price = self.normalize_price(str(rate)) if rate is not None else None
climate = data.get("bClimate", False)
type_name_str = type_name if isinstance(type_name, str) else ""
desc_parts = [type_name_str] if type_name_str else []
if climate:
desc_parts.append("Climate Controlled")
if "parking" in type_name_str.lower() or "vehicle" in type_name_str.lower():
desc_parts.append("Parking")
vacant = data.get("iTotalVacant", 1)
scarcity = None if (vacant and int(vacant) > 0) else "sold out"
result.units.append(
UnitResult(
size=size,
price=price,
description=" | ".join(desc_parts) if desc_parts else None,
scarcity=scarcity,
)
)
if not result.units:
result.warnings.append("No units found")
return result
Scrape Runs (5)
-
exported Run #19452026-03-27 13:56:45.242430 | 14 units | Facility003661Parser | View Data →
-
exported Run #19442026-03-27 13:56:45.238307 | 14 units | Facility003661Parser | View Data →
-
exported Run #12352026-03-23 02:58:07.963749 | 14 units | Facility003661Parser | View Data →
-
exported Run #7422026-03-21 18:49:47.516161 | 14 units | Facility003661Parser | View Data →
-
exported Run #2912026-03-14 16:31:11.324797 | 9 units | Facility003661Parser | View Data →
Run #291 Details
- Status
- exported
- Parser Used
- Facility003661Parser
- Platform Detected
- storageunitsoftware
- Units Found
- 9
- Stage Reached
- exported
- Timestamp
- 2026-03-14 16:31:11.324797
Timing
| Stage | Duration |
|---|---|
| Fetch | 4344ms |
| Detect | 63ms |
| Parse | 36ms |
| Export | 17ms |
Snapshot: 003661_20260314T163115Z.html · Show Snapshot · Open in New Tab
Parsed Units (9)
3x3
No price
5x5
No price
5x10
No price
5x15
No price
10x10
No price
10x15
No price
10x20
No price
10x25
No price
10x30
No price