Facility: 001228
Copley Mini Storage
- Facility ID
- 001228
- Name
- Copley Mini Storage
- URL
- http://www.copleystorageunits.com/copy-of-home
- Address
- 1020 Jacoby Rd, Copley, OH 44321, USA, Copley, Ohio 44321
- Platform
- custom_facility_001228
- Parser File
- src/parsers/custom/facility_001228_parser.py
- Last Scraped
- 2026-03-27 13:49:53.399410
- Created
- 2026-03-14 16:21:53.706708
- Updated
- 2026-03-27 13:49:53.430938
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_001228_parser.py)
"""Parser for Copley Mini Storage."""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility001228Parser(BaseParser):
"""Extract storage units from Copley Mini Storage.
Units are displayed in a photo gallery on the /copy-of-home page.
Each gallery item has a caption-title (size like '5x5') and
caption-text (description with sq ft). No prices are listed online.
"""
platform = "custom_facility_001228"
_SQFT_RE = re.compile(r"(\d+)\s*sq\s*ft", re.IGNORECASE)
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Find the "Unit Sizes and Description" photo gallery
# It uses the older gallery format with <ul> lists
gallery = None
for g in soup.find_all("div", class_="dmPhotoGallery"):
# The unit gallery has caption-titles like "5x5", "10x10"
titles = g.find_all("h3", class_="caption-title")
for t in titles:
text = t.get_text(strip=True)
if re.match(r"\d+[xX]\d+", text):
gallery = g
break
if gallery:
break
if not gallery:
result.warnings.append("No unit gallery found")
return result
seen: set[str] = set()
for thumb in gallery.find_all("li", class_="photoGalleryThumbs"):
title_tag = thumb.find("h3", class_="caption-title")
if not title_tag:
continue
size_text = title_tag.get_text(strip=True)
if not re.match(r"\d+[xX×]\d+", size_text):
continue
if size_text.upper() in seen:
continue
seen.add(size_text.upper())
# Normalize size format (e.g., "15X30" -> "15x30")
normalized_size = re.sub(r"[xX×]", "x", size_text)
unit = UnitResult()
unit.size = normalized_size
w, ln, sq = self.normalize_size(normalized_size)
if w is not None:
unit.metadata = {"width": w, "length": ln, "sqft": sq}
# Extract description from caption-text
caption_div = thumb.find("div", class_="caption-text")
if caption_div:
desc = caption_div.get_text(strip=True)
unit.description = desc
# Try to extract sq ft from description
sqft_match = self._SQFT_RE.search(desc)
if sqft_match and unit.metadata:
unit.metadata["sqft_listed"] = int(sqft_match.group(1))
result.units.append(unit)
if not result.units:
result.warnings.append("No units found in gallery")
return result
Scrape Runs (5)
-
exported Run #17672026-03-27 13:49:47.401197 | 8 units | Facility001228Parser | View Data →
-
exported Run #17662026-03-27 13:49:47.375140 | 8 units | Facility001228Parser | View Data →
-
exported Run #11462026-03-23 02:51:27.413038 | 8 units | Facility001228Parser | View Data →
-
exported Run #6532026-03-21 18:42:32.315927 | 8 units | Facility001228Parser | View Data →
-
exported Run #2022026-03-14 16:24:54.586964 | 8 units | Facility001228Parser | View Data →
Run #202 Details
- Status
- exported
- Parser Used
- Facility001228Parser
- Platform Detected
- table_layout
- Units Found
- 8
- Stage Reached
- exported
- Timestamp
- 2026-03-14 16:24:54.586964
Timing
| Stage | Duration |
|---|---|
| Fetch | 6168ms |
| Detect | 22ms |
| Parse | 10ms |
| Export | 15ms |
Snapshot: 001228_20260314T162500Z.html · Show Snapshot · Open in New Tab
Parsed Units (8)
5x5
No price
10x15
No price
15x30
No price
5x10
No price
10x20
No price
20x30
No price
10x10
No price
10x30
No price