← back to Rentv Ad Engine
Integrate RENTV podcast (SoundCloud rentvnews) + The REview authoritative source: harvest-rentvreview.py maps 7 episodes with official sponsors/guest/date; dossier shows 'Verified via The REview' block; header podcast+REview links
750f51fe653974b1b22d36d11aa64e8373f7054c · 2026-08-07 09:43:25 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A data/rentvreview.jsonM public/index.htmlA scripts/harvest-rentvreview.pyM server.js
Diff
commit 750f51fe653974b1b22d36d11aa64e8373f7054c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 7 09:43:25 2026 -0700
Integrate RENTV podcast (SoundCloud rentvnews) + The REview authoritative source: harvest-rentvreview.py maps 7 episodes with official sponsors/guest/date; dossier shows 'Verified via The REview' block; header podcast+REview links
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
data/rentvreview.json | 78 ++++++++++++++++++++++++++++++++++
public/index.html | 14 +++++-
scripts/harvest-rentvreview.py | 96 ++++++++++++++++++++++++++++++++++++++++++
server.js | 1 +
4 files changed, 188 insertions(+), 1 deletion(-)
diff --git a/data/rentvreview.json b/data/rentvreview.json
new file mode 100644
index 0000000..92e303a
--- /dev/null
+++ b/data/rentvreview.json
@@ -0,0 +1,78 @@
+{
+ "771348410": {
+ "rv_id": "1909",
+ "url": "https://www.rentvreview.com/Videos/View/1909",
+ "vimeo": "771348410",
+ "title": "Commercial Real Estate Talk, Episode #1 - Bill Shopoff 9677 views Video added by Commercia",
+ "sponsors": [],
+ "date": "November 14, 2022",
+ "guest_note": "Bill Shopoff \u2014 President and CEO of Shopoff Realty Investments, which is celebrating its 30th Anniversar"
+ },
+ "795355693": {
+ "rv_id": "1910",
+ "url": "https://www.rentvreview.com/Videos/View/1910",
+ "vimeo": "795355693",
+ "title": "Commercial Real Estate Talk, Episode #2 - Sandy Sigal 7118 views Video added by Commercial",
+ "sponsors": [],
+ "date": "January 30, 2023",
+ "guest_note": "Sandy SIgal \u2014 President and CEO of NewMark Merrill Companies"
+ },
+ "953281926": {
+ "rv_id": "2493",
+ "url": "https://www.rentvreview.com/Videos/View/2493",
+ "vimeo": "953281926",
+ "title": "RENTV CRE Talk Episode #11 - Michael Rademaker 9942 views Video added by Commercial Real E",
+ "sponsors": [
+ "Paramount Property Tax Appeal",
+ "Commercial Real Estate Inspectors",
+ "Fidelity Mortgage Lenders"
+ ],
+ "date": "June 03, 2024",
+ "guest_note": "Michael Rademaker \u2014 Founder and CEO of MGR Real Estate, his Ontario-based company the has a residential and"
+ },
+ "1039101858": {
+ "rv_id": "2642",
+ "url": "https://www.rentvreview.com/Videos/View/2642",
+ "vimeo": "1039101858",
+ "title": "RENTV CRE Talk Episode #13 - Ethan Penner 4876 views Video added by Commercial Real Estate",
+ "sponsors": [
+ "Chase Partners",
+ "Commercial Real Estate Inspectors",
+ "Fidelity Mortgage Lenders"
+ ],
+ "date": "December 13, 2024",
+ "guest_note": "Ethan Penner \u2014 Founder & CEO of Mosaic Real Estate Investors, an iconic figure in the real estate fina"
+ },
+ "1056513923": {
+ "rv_id": "2726",
+ "url": "https://www.rentvreview.com/Videos/View/2726",
+ "vimeo": "1056513923",
+ "title": "RENTV CRE Talk Episode #14 - John McNellis 3573 views Video added by Commercial Real Estat",
+ "sponsors": [],
+ "date": "February 12, 2025",
+ "guest_note": "John McNellis \u2014 Partner at McNellis Partners, an owner, investor and developer of shopping centers, mai"
+ },
+ "1200839794": {
+ "rv_id": "3255",
+ "url": "https://www.rentvreview.com/Videos/View/3255",
+ "vimeo": "1200839794",
+ "title": "CRE Talk Episode #22 with Bill Shopoff 2034 views Video added by Commercial Real Estate Ta",
+ "sponsors": [
+ "Rockefeller Group",
+ "Chase Partners",
+ "Provident Savings Bank",
+ "Fidelity Mortgage Lenders"
+ ],
+ "date": "June 10, 2026",
+ "guest_note": "Bill Shopoff \u2014 President and CEO of Shopoff Realty Investments, a very active developer of major long-te"
+ },
+ "852462696": {
+ "rv_id": "2033",
+ "url": "https://www.rentvreview.com/Videos/View/2033",
+ "vimeo": "852462696",
+ "title": "RENTV CRE Talk Episode #5 Barry DiRaimondo 14679 views Video added by Commercial Real Esta",
+ "sponsors": [],
+ "date": "August 09, 2023",
+ "guest_note": ""
+ }
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index ab7910b..4d1e23d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -67,6 +67,10 @@
<div class="tab" data-view="coverage">Coverage</div>
</div>
<div class="stat" id="stat"></div>
+ <div style="margin-left:auto;display:flex;gap:10px;font-size:12px">
+ <a href="https://soundcloud.com/rentvnews" target="_blank" rel="noopener" title="RENTV podcast on SoundCloud">🎧 Podcast</a>
+ <a href="https://www.rentvreview.com" target="_blank" rel="noopener" title="The REview — RENTV's authoritative episode archive">📺 The REview</a>
+ </div>
</header>
<div class="toolbar">
@@ -96,7 +100,7 @@ const setLS=(k,v)=>localStorage.setItem("rae_"+k,JSON.stringify(v));
const siteURL=s=>/^https?:/.test(s)?s:"https://"+s;
const telURL=p=>"tel:"+String(p).replace(/[^\d+]/g,"");
-let DATA={advertisers:[],guests:[],videos:[],emails:[]}, FRAMES={}, SYN={};
+let DATA={advertisers:[],guests:[],videos:[],emails:[]}, FRAMES={}, SYN={}, RV={};
let view=LS("view","advertisers"), mode=LS("mode","cards");
const SORTS={
@@ -124,6 +128,7 @@ async function boot(){
(fr.frames||[]).forEach(f=>FRAMES[f.id]=f);
try{SYN=await fetch("/api/synopses").then(r=>r.json())}catch{SYN={}}
try{DATA.emails=(await fetch("/api/emails").then(r=>r.json())).emails||[]}catch{DATA.emails=[]}
+ try{RV=await fetch("/api/rentvreview").then(r=>r.json())}catch{RV={}}
$("#stat").innerHTML=`<b>${s.advertisers}</b> advertisers · <b>${s.guests}</b> guests · <b>${s.videos_rich}</b>/${s.videos_total} videos transcribed`;
$("#dens").value=LS("dens",4); document.documentElement.style.setProperty("--cols",$("#dens").value);
document.querySelectorAll(".tab").forEach(t=>t.classList.toggle("on",t.dataset.view===view));
@@ -341,6 +346,13 @@ window.openEpisode=id=>{
<h3>Synopsis</h3>
<p>${esc(syn.synopsis||"(synopsis still generating — refresh shortly)")}</p>
${(syn.highlights||[]).length?`<ul style="color:var(--dim)">${syn.highlights.map(h=>`<li>${esc(h)}</li>`).join("")}</ul>`:""}
+ ${RV[id]?`<div class="mailpanel" style="border-color:var(--gold)">
+ <div class="row" style="justify-content:space-between"><b style="color:var(--gold)">✓ Verified via The REview (RENTV official)</b>
+ <a href="${esc(RV[id].url)}" target="_blank" rel="noopener">open ↗</a></div>
+ ${RV[id].date?`<div class="kv">📅 ${esc(RV[id].date)}</div>`:""}
+ ${RV[id].guest_note?`<div class="kv">🎤 ${esc(RV[id].guest_note)}</div>`:""}
+ ${(RV[id].sponsors||[]).length?`<div class="row"><span class="kv">Official sponsors:</span>${RV[id].sponsors.map(s=>`<span class="chip" style="color:var(--gold);border-color:#3a3320">${esc(s)}</span>`).join("")}</div>`:""}
+ </div>`:""}
<h3>Advertisers in this episode (${ads.length})</h3>
${ads.length?ads.map(a=>`<div class="row" style="margin:4px 0"><b>${esc(a.name)}</b> <span class="badge cat">${esc(a.category)}</span>
${(a.websites||[]).map(w=>`<a href="${siteURL(esc(w))}" target="_blank" rel="noopener">${esc(w)}</a>`).join(" ")}
diff --git a/scripts/harvest-rentvreview.py b/scripts/harvest-rentvreview.py
new file mode 100644
index 0000000..b17990c
--- /dev/null
+++ b/scripts/harvest-rentvreview.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Harvest AUTHORITATIVE episode data from rentvreview.com ("The REview").
+
+Each /Videos/View/<id> page is server-rendered and carries, in plain HTML:
+ - data-video-external-id="<vimeoId>" (maps REview episode -> our Vimeo video)
+ - "... Sponsored by: A; B; and C. To be a part ..." (authoritative sponsor list)
+ - the guest/synopsis prose + "Date Created: <date>"
+This is cleaner than transcript inference and covers episodes predating the Vimeo feed.
+
+We BFS from known CRE Talk pages, following the 6 related-video links per page, keeping
+pages whose Vimeo id matches our library OR whose title is a CRE Talk episode. Free,
+bounded (MAX_FETCH), no API/DRM.
+
+Output: data/rentvreview.json { "<vimeoId>": {rv_id,url,sponsors[],guest_note,date,title} }
+Env: MAX_FETCH (default 120), SEEDS (comma ids)
+"""
+import json, os, re, time, urllib.request, html as _html
+from collections import deque
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+MANIFEST = os.path.join(ROOT, "data", "manifest.json")
+OUT = os.path.join(ROOT, "data", "rentvreview.json")
+UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1"
+MAX_FETCH = int(os.environ.get("MAX_FETCH", "120"))
+SEEDS = [s for s in os.environ.get("SEEDS", "1909,1910,2493,2642,2726,3255,1034,1650,2033,2298,2898,3273").split(",") if s]
+
+VIMEO_IDS = {str(v["id"]) for v in json.load(open(MANIFEST))["videos"]}
+
+
+def fetch(rv_id):
+ try:
+ return urllib.request.urlopen(urllib.request.Request(
+ f"https://www.rentvreview.com/Videos/View/{rv_id}", headers={"User-Agent": UA}), timeout=20
+ ).read().decode("utf-8", "ignore")
+ except Exception:
+ return ""
+
+
+def parse(h):
+ ext = re.search(r'data-video-external-id="(\d+)"', h)
+ vimeo = ext.group(1) if ext else ""
+ text = _html.unescape(re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", h)))
+ title = ""
+ mt = re.search(r"(RENTV CRE Talk Episode #?\d+[^|<]*|CRE Talk,? Episode #?\d+[^|<]*|Commercial Real Estate Talk,? Episode #?\d+[^|<]*)", text)
+ if mt:
+ title = mt.group(1).strip()[:90]
+ sponsors = []
+ ms = re.search(r"Sponsored by[:\s]+(.+?)\.\s*(?:To be a part|If you|Thank you|$)", text, re.I)
+ if ms:
+ raw = ms.group(1)
+ parts = re.split(r";|,|\band\b", raw)
+ sponsors = [re.sub(r"\s+", " ", p).strip().title() for p in parts if p.strip() and len(p.strip()) > 2]
+ date = ""
+ md = re.search(r"Date Created:\s*([A-Za-z]+ \d{1,2}, \d{4})", text)
+ if md:
+ date = md.group(1)
+ guest = ""
+ mg = re.search(r"interview\s+([A-Z][a-zA-Z.\-]+ [A-Z][a-zA-Z.\-]+)[,]?\s+((?:President|CEO|Founder|Partner|Managing|Chief|Head|Owner|Chairman)[^.]{0,80})", text)
+ if mg:
+ guest = f"{mg.group(1)} — {mg.group(2).strip()}"[:120]
+ related = re.findall(r'href="/Videos/View/(\d+)"', h)
+ return {"vimeo": vimeo, "title": title, "sponsors": sponsors, "date": date, "guest_note": guest}, related
+
+
+def main():
+ out = json.load(open(OUT)) if os.path.exists(OUT) else {}
+ seen, q, fetched = set(), deque(SEEDS), 0
+ print(f"[rentvreview] BFS from {len(SEEDS)} seeds, cap {MAX_FETCH}, matching {len(VIMEO_IDS)} vimeo ids")
+ while q and fetched < MAX_FETCH:
+ rv = q.popleft()
+ if rv in seen:
+ continue
+ seen.add(rv)
+ h = fetch(rv)
+ fetched += 1
+ if not h:
+ continue
+ rec, related = parse(h)
+ is_cre = ("Commercial Real Estate Talk" in h or "CRE Talk" in h)
+ if rec["vimeo"] and (rec["vimeo"] in VIMEO_IDS or is_cre):
+ key = rec["vimeo"]
+ out[key] = {"rv_id": rv, "url": f"https://www.rentvreview.com/Videos/View/{rv}", **rec}
+ # follow related to find sibling episodes
+ for r in related:
+ if r not in seen:
+ q.append(r)
+ if fetched % 15 == 0:
+ print(f" fetched={fetched} matched={len(out)} queue={len(q)}")
+ time.sleep(0.3)
+ json.dump(out, open(OUT, "w"), indent=1)
+ matched_lib = sum(1 for k in out if k in VIMEO_IDS)
+ print(f"[rentvreview] DONE fetched={fetched} matched={len(out)} (of which {matched_lib} in our Vimeo library) -> {OUT}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/server.js b/server.js
index c6ded5f..c5120d6 100644
--- a/server.js
+++ b/server.js
@@ -40,6 +40,7 @@ const server = http.createServer((req, res) => {
if (p === "/api/frames") return send(res, 200, readJSON("frames.json", { frames: [] }));
if (p === "/api/synopses") return send(res, 200, readJSON("synopses.json", {}));
if (p === "/api/emails") return send(res, 200, readJSON("emails.json", { emails: [] }));
+ if (p === "/api/rentvreview") return send(res, 200, readJSON("rentvreview.json", {}));
if (p === "/api/email" && req.method === "POST") return draftEmail(req, res);
if (p === "/api/health") return send(res, 200, { ok: true, ts: Date.now() });
if (p === "/api/stats") {
← 6eeb40a Phase 2b: web-search address enrichment — 24/29 advertisers
·
back to Rentv Ad Engine
·
Full REview reconciliation: producer-page harvest maps 20/22 c3eae5f →