All projects

Pennsylvania Court Portals Scraper

10+ PA civil + public-notice portals into a unified keyword-categorized legal-leads pipeline with BatchData enrichment.

The goal

Carson generates legal leads from probate/foreclosure/sheriff-sale/tax-sale notices across PA. Each county has a different portal. The goal was one scraper package mapping 10+ portals into a unified lead pipeline with BatchData enrichment, SQLite storage and Google Sheets export.

The solution

A multi-portal framework: config.py holds keywords-to-category mappings, county lists, proxy/captcha/ZenRows/BatchData credentials. scrapers/ has per-portal modules (civil_core.py + public_notice.py + per-county trackers with proxy / without-proxy variants). run_test.py runs any subset. Output persists to SQLite and exports to Google Sheets via a service account.

Highlights

  • 10+ PA portals unified under one lead schema
  • Keyword-to-category config beats ML for this scale
  • with-proxy/without-proxy variants tame flaky portals
  • BatchData enrichment makes leads actionable before the sheet

The challenge

10 different portals with different layouts, search forms and pagination styles.

civil_core.py implements the shared civil-portal workflow; per-county modules subclass/extend it. public_notice.py is a dedicated single-site scraper. All scrapers normalize to the same lead schema before SQLite.

Notices arrive as free text; same notice can be probate, foreclosure or tax depending on a phrase in the body.

PUBLIC_NOTICE_KEYWORDS in config.py maps phrases to categories; adding a category is a config edit, not code.

Some portals work over residential proxies but rate-limit direct; others refuse proxy IPs.

Two variants per portal — track_a_civil_with_proxy.py and track_a_civil_without_proxy.py — each tuned for its path; operator picks per-county.

Name+address from a court notice isn't actionable — need owner, phone, property metadata.

BatchData API integration enriches each lead before SQLite, so the Sheets view is immediately usable.

Some portals use CAPTCHAs; others serve different HTML to residential IPs.

2Captcha integration + ZenRows residential rendering as configured fallbacks.

What was delivered

  • scrapers/ package with per-portal modules (civil_core.py, public_notice.py, per-county trackers)
  • config.py centralizing keywords, counties, proxies, APIs
  • run_test.py master test runner
  • SQLite storage + Google Sheets export
  • BatchData + 2Captcha + ZenRows + BrightData integrations
  • With-proxy and without-proxy variants
  • requirements.txt

Results

10+

Portals

Resumable

Resilience

4 (probate/foreclosure/sheriff/tax)

Categories

BatchData

Enrichment

What it taught me

  • Keyword-to-category config is the cheapest, most maintainable classification for a legal-notices pipeline at this scale
  • Keeping with-proxy and without-proxy variants per portal turns 'sometimes breaks' into 'flip the flag'
  • A single SQLite source-of-truth + separate Sheets export keeps the analyst view decoupled from raw data