← back to La Dscr Lists
auto-save: 2026-06-28T10:44:13 (2 files) — .gitignore pull.py
3ec0a8f77ce3ec9f04322033d1c1ecb604352fbe · 2026-06-28 10:44:13 -0700 · Steve Abrams
Files touched
Diff
commit 3ec0a8f77ce3ec9f04322033d1c1ecb604352fbe
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jun 28 10:44:13 2026 -0700
auto-save: 2026-06-28T10:44:13 (2 files) — .gitignore pull.py
---
.gitignore | 6 ++++++
pull.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 72 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e51fc2d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+lists/*.csv
diff --git a/pull.py b/pull.py
new file mode 100644
index 0000000..62bbfff
--- /dev/null
+++ b/pull.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+"""Pull all 2-4 unit residential-income parcels in LA County (2025 roll) from the
+public LA County Assessor FeatureServer. Splits into duplex/triplex/fourplex lists
+plus an investor-owned subset (no homeowners exemption). Free, keyless, read-only."""
+import csv, json, time, urllib.parse, urllib.request, os
+
+BASE = ("https://services.arcgis.com/RmCCgQtiZLDCtblq/arcgis/rest/services/"
+ "Parcel_Data_2021_Table/FeatureServer/0/query")
+WHERE = ("RollYear='2025' AND (UseCode LIKE '020%' OR UseCode LIKE '030%' "
+ "OR UseCode LIKE '040%')")
+FIELDS = ("AIN,AssessorID,PropertyLocation,SitusHouseNo,SitusDirection,SitusStreet,"
+ "SitusUnit,SitusCity,SitusZIP5,TaxRateArea_CITY,UseCode,UseCodeDescChar2,"
+ "Units,YearBuilt,SQFTmain,Bedrooms,Bathrooms,Roll_LandValue,Roll_ImpValue,"
+ "Roll_TotalValue,Roll_HomeOwnersExemp,RecordingDate,CENTER_LAT,CENTER_LON")
+OUT = os.path.join(os.path.dirname(__file__), "lists")
+
+def fetch(offset):
+ q = {"where": WHERE, "outFields": FIELDS, "returnGeometry": "false",
+ "resultRecordCount": 2000, "resultOffset": offset, "f": "json"}
+ url = BASE + "?" + urllib.parse.urlencode(q)
+ with urllib.request.urlopen(url, timeout=60) as r:
+ return json.load(r)
+
+rows, offset = [], 0
+while True:
+ data = fetch(offset)
+ feats = data.get("features", [])
+ if not feats:
+ break
+ rows += [f["attributes"] for f in feats]
+ print(f" fetched {len(rows)} ...", flush=True)
+ if len(feats) < 2000:
+ break
+ offset += 2000
+ time.sleep(0.4)
+
+cols = FIELDS.split(",")
+def tier(uc):
+ if uc.startswith("02"): return "2-unit"
+ if uc.startswith("03"): return "3-unit"
+ return "4-unit"
+
+buckets = {"2-unit": [], "3-unit": [], "4-unit": []}
+for r in rows:
+ buckets[tier(r.get("UseCode", ""))].append(r)
+
+def write(path, recs):
+ with open(path, "w", newline="") as f:
+ w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
+ w.writeheader()
+ for r in recs:
+ w.writerow(r)
+
+os.makedirs(OUT, exist_ok=True)
+total = len(rows)
+for k, recs in buckets.items():
+ write(os.path.join(OUT, f"la-county-{k}.csv"), recs)
+# investor-owned subset (no homeowners exemption claimed)
+inv = [r for r in rows if not r.get("Roll_HomeOwnersExemp")]
+write(os.path.join(OUT, "la-county-2to4-investor-owned.csv"), inv)
+write(os.path.join(OUT, "la-county-2to4-all.csv"), rows)
+
+print(f"\nDONE total={total}")
+for k in ("2-unit", "3-unit", "4-unit"):
+ print(f" {k}: {len(buckets[k])}")
+print(f" investor-owned (no HOX): {len(inv)}")
(oldest)
·
back to La Dscr Lists
·
(newest)