Facility: 080714

Wynot Store It

Stale Data Warning: This facility has not been successfully scraped in 26 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
080714
Name
Wynot Store It
URL
https://www.wynotstoreit.com/
Address
N/A
Platform
custom_facility_080714
Parser File
src/parsers/custom/facility_080714_parser.py
Last Scraped
2026-03-27 14:03:32.654448
Created
2026-03-06 23:45:35.865957
Updated
2026-03-27 14:03:32.683788
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_080714_parser.py)
"""Parser for Wy Not Store It self-storage facility (Sheridan, WY).

This site uses the RNL/Storefront platform and displays unit pricing
as plain-text lines inside ``div.rnl-Rte-block--p`` spans. Each line
contains one or two size-price entries in the format:

    10x10 - $75.00                           10x35 - $185.00

Outdoor units are marked with a ``-Outdoor`` suffix immediately after
the price, e.g. ``8x20 - $50.00-Outdoor``.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

from src.parsers.base import BaseParser, ParseResult, UnitResult

# Matches one size-price entry: "10x10 - $75.00" or "8x20 - $50.00-Outdoor"
_UNIT_RE = re.compile(
    r"(\d+)\s*[xX]\s*(\d+)"   # size: WxL
    r"\s*-\s*"                  # separator
    r"\$([\d,]+(?:\.\d+)?)"    # price
    r"(-Outdoor)?",             # optional outdoor flag
    re.IGNORECASE,
)


class Facility080714Parser(BaseParser):
    """Extract storage units from wynotstoreit.com (Wy Not Store It, Sheridan WY).

    Pricing text lives inside ``div.rnl-Content > div.rnl-Rte-block--p > span``
    elements. Each span holds one or two tab-separated size/price tokens.
    """

    platform = "custom_facility_080714"

    def parse(self, html: str, url: str = "") -> ParseResult:
        soup = BeautifulSoup(html, "lxml")
        result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)

        # Locate the pricing content block
        content_div = None
        for div in soup.find_all("div", class_="rnl-Content"):
            if "$" in div.get_text():
                content_div = div
                break

        if not content_div:
            result.warnings.append("Could not find rnl-Content pricing block")
            return result

        # Each rnl-Rte-block--p holds one line that may contain 1-2 units
        for row_div in content_div.find_all("div", class_="rnl-Rte-block--p"):
            line = row_div.get_text(separator=" ", strip=True)
            for match in _UNIT_RE.finditer(line):
                width = float(match.group(1))
                length = float(match.group(2))
                price_str = match.group(3).replace(",", "")
                price = float(price_str)
                is_outdoor = bool(match.group(4))

                size_label = f"{int(width)}x{int(length)}"
                description = f"{size_label} - ${price:.2f}"
                if is_outdoor:
                    description += " (Outdoor)"

                w, ln, sq = self.normalize_size(f"{int(width)}x{int(length)}")
                metadata: dict = {}
                if w is not None:
                    metadata = {"width": w, "length": ln, "sqft": sq}
                if is_outdoor:
                    metadata["outdoor"] = True

                unit = UnitResult(
                    size=size_label,
                    price=price,
                    description=description,
                    metadata=metadata if metadata else None,
                )
                result.units.append(unit)

        if not result.units:
            result.warnings.append("No unit lines matched in rnl-Content block")

        return result

Scrape Runs (6)

Run #827 Details

Status
exported
Parser Used
Facility080714Parser
Platform Detected
storageunitsoftware
Units Found
10
Stage Reached
exported
Timestamp
2026-03-21 18:57:34.892133
Timing
Stage Duration
Fetch3415ms
Detect31ms
Parse15ms
Export7ms

Snapshot: 080714_20260321T185738Z.html · Show Snapshot · Open in New Tab

Parsed Units (10)

8x20

$50.00/mo

10x20

$110.00/mo

8x40

$85.00/mo

10x25

$135.00/mo

10x5

$60.00/mo

10x30

$160.00/mo

10x10

$75.00/mo

10x35

$185.00/mo

10x15

$90.00/mo

10x40

$210.00/mo

All Failures for this Facility (1)

fetch DatatypeMismatch unknown unknown permanent Run #30 | 2026-03-09 20:49:41.510514

column "success" is of type boolean but expression is of type integer LINE 3: ... VALUES ('080714', 30, '080714_20260309T204941Z.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 ('080714', 30, '080714_20260309T204941Z.html', 0)
                                                                     ^
HINT:  You will need to rewrite or cast the expression.

← Back to dashboard