Files
AR-House/scripts/probe_qpublic_search.py
T
2026-07-03 12:24:58 -04:00

133 lines
5.6 KiB
Python

"""Probe qPublic Indian River address search with real address from user's deal.
Address: 674 30th Ave SW Vero Beach FL 32968 (Zillow MLS deal in screenshot).
"""
from pathlib import Path
def probe():
from playwright.sync_api import sync_playwright
out_dir = Path(__file__).parent.parent / "_probe_out" / "qpublic"
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
ctx = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120",
)
page = ctx.new_page()
# Capture network POSTs
captured = []
page.on("request", lambda r: captured.append({"m": r.method, "url": r.url[:150]}) if r.method == "POST" else None)
url = "https://qpublic.schneidercorp.com/Application.aspx?App=IndianRiverCountyFL&PageType=Search"
page.goto(url, wait_until="domcontentloaded")
page.wait_for_timeout(3000)
print(f"[1] Page: {page.title()}")
# Dismiss disclaimer/cookie banner if present (the "Agree" we saw)
for txt in ["I Agree", "Agree", "Accept All", "Accept"]:
loc = page.locator(f"button:has-text('{txt}'), a:has-text('{txt}')").first
if loc.count() > 0:
try:
if loc.is_visible():
print(f"[2] Clicking '{txt}' (likely cookie/disclaimer banner)")
loc.click()
page.wait_for_timeout(1500)
break
except Exception:
pass
# Search by Location Address: txtAddress
print("[3] Filling address search...")
addr_input_id = "ctlBodyPane_ctl01_ctl01_txtAddress"
page.evaluate(f"""
const inp = document.getElementById('{addr_input_id}');
inp.focus();
inp.value = '674 30th Ave SW';
inp.dispatchEvent(new Event('input', {{ bubbles: true }}));
inp.dispatchEvent(new Event('change', {{ bubbles: true }}));
""")
page.wait_for_timeout(800)
val = page.locator(f"#{addr_input_id}").input_value()
print(f" address input value: {val!r}")
# The search button is sibling to the address input — find Search button in same section
# qPublic uses ASP.NET LinkButton. Find any submit near the address input.
# Try the button just after txtAddress in DOM
print("[4] Looking for Search button near address input...")
search_btn_id = page.evaluate(f"""
() => {{
const inp = document.getElementById('{addr_input_id}');
if (!inp) return null;
// Find the parent panel/div, then look for a submit-like element
let parent = inp.closest('.panel-body, .form-group, fieldset, div');
while (parent) {{
const btn = parent.querySelector('input[type=submit], button[type=submit], a.btn');
if (btn) return btn.id || btn.outerHTML.substring(0, 200);
parent = parent.parentElement;
}}
return null;
}}
""")
print(f" found button: {search_btn_id!r}")
# Try the button by clicking the input's parent's Search button
# Use a simpler approach: click the button labeled "Search" closest to address input
try:
# In qPublic, each search section has its own Search button. The one for address
# is typically btnSearch right after txtAddress
btn = page.locator(f"#ctlBodyPane_ctl01_ctl01_btnSearch")
if btn.count() > 0:
btn.click()
else:
# Fallback: find any Search button in the address panel
section = page.locator("div:has(#ctlBodyPane_ctl01_ctl01_txtAddress)").first
section.locator("input[type=submit], button:has-text('Search')").first.click()
page.wait_for_timeout(15000) # longer wait for Cloudflare challenge
print(f"[5] After search: URL={page.url[:100]}")
except Exception as e:
print(f" Search click error: {e}")
# Dump body for diagnosis
body_full = page.inner_text("body")
print(f"\n[5b] Full body text ({len(body_full)} chars):\n{body_full[:2000]}")
(out_dir / "02_results.html").write_text(page.content(), encoding="utf-8")
page.screenshot(path=str(out_dir / "02_results.png"), full_page=True)
# Check for results
body = page.inner_text("body")
print(f"\n[6] Body length: {len(body)}")
# Look for parcel result table or "no results"
print("\n[7] Result indicators:")
for kw in ["no results", "not found", "no matches", "search results", "parcel"]:
cnt = body.lower().count(kw)
if cnt:
print(f" '{kw}': {cnt} occurrences")
# Look for result links
result_links = page.locator("a[href*='KeyValue='], a[href*='ParcelID='], a[href*='PageID=']").all()
print(f"\n[8] Result links (first 5):")
for a in result_links[:5]:
try:
txt = (a.inner_text() or "").strip()[:80]
href = a.get_attribute("href") or ""
if txt:
print(f" {txt!r}")
print(f" href: {href[:120]}")
except Exception:
pass
# Final POSTs check
print(f"\n[9] Total POSTs during search: {len(captured)}")
for c in captured[-8:]:
print(f" {c['m']} {c['url']}")
browser.close()
if __name__ == "__main__":
probe()