feat: AR-House initial commit
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user