# FindLaw Attorney Directory Scraper (`moving_beacon-owner1/findlaw-attorney-directory-scraper`) Actor

Scrapes individual attorneys from FindLaw by state, city, and practice area, providing names, contact details, addresses, practice areas, reviews, ratings, badges, and profile URLs. Filters out firm listings and supports multi-city searc

- **URL**: https://apify.com/moving\_beacon-owner1/findlaw-attorney-directory-scraper.md
- **Developed by:** [Jamshaid Arif](https://apify.com/moving_beacon-owner1) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 45.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $9.99 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## FindLaw Attorney Directory Scraper

Extract individual attorney profiles from [FindLaw's lawyer directory](https://lawyers.findlaw.com/) by state, city and practice area. Returns name, email, phone, office address, practice areas, review counts and badges, one row per attorney.

FindLaw mixes law-firm listings into its attorney search results. This actor identifies firm pages from the search card *before* fetching them, so you get individual attorneys without paying for requests to firm profiles you don't want.

### What you get

Each record in the dataset:

| Field | Notes |
|---|---|
| `name` | As shown on the profile |
| `profile_type` | `attorney`, `firm`, or `unknown` (see below) |
| `email` | Publisher-declared where possible; see `email_source` |
| `email_source` | `mailto`, `jsonld`, `form_field`, `base64`, `page_text`, or `contact:…` if it came from the contact page. The first two are strongest. |
| `email_is_generic` | `true` for shared inboxes like `info@`, `intake@`, `leads@` — deliverable, but not a person |
| `phone` | Normalised to `(305) 555-0100` |
| `street`, `city`, `state`, `zip` | Office address |
| `website` | Firm site, when listed |
| `practice_areas` | Cleaned list |
| `practice_area_shares` | `{ "Divorce": 60, ... }` when the profile gives percentages |
| `review_count`, `rating` | From the search card or profile schema |
| `is_super_lawyers`, `has_free_consultation`, `badges` | Search-card badges |
| `attorneys_at_firm` | Roster, when the page is a firm |
| `found_in_city` | Which city listing surfaced this profile |
| `profile_url`, `profile_id` | Stable FindLaw identifiers |

### Input

| Field | Default | Notes |
|---|---|---|
| **Practice area** | `criminal-law` | The slug from the FindLaw URL. Use *Custom practice area* for anything not in the list — copy it from `lawyers.findlaw.com/<slug>/...` |
| **State** | `florida` | |
| **Cities** | *(empty)* | **Empty scrapes every city in the state.** Otherwise one name per entry. A `[texas]` entry switches state for the entries after it, so one list can span the country. |
| **Profile type** | `attorneys` | `firms` or `both` also available |
| **Fetch contact page** | on | Most emails live only on the contact form page. Off halves requests, finds far fewer emails. |
| **Max results** | — | Stop after N records. Good for a test run. |
| **Max cities** | — | Scrape the first N of the list |
| **Max pages per city** | 50 | Safety cap |
| **Stale pages** | 4 | Move to the next city after this many consecutive pages with no unseen profiles. Neighbouring cities list the same firms, so this saves a lot of requests. `0` walks to the end. |
| **Requests per second** | 1.5 | Shared across workers, capped at 3 |
| **Concurrency** | 3 | Hides latency; does not raise the request rate. Capped at 5. |
| **Proxy** | off | One sticky session per worker |

#### Example

```json
{
  "practiceArea": "personal-injury-plaintiff",
  "state": "texas",
  "cities": ["Houston", "Dallas", "Austin", "[florida]", "Miami", "Tampa"],
  "profileType": "attorneys",
  "maxResults": 500
}
```

### How firm filtering works

Two layers. The search card title is checked first — a corporate form (`PLLC`, `LLP`, `Law Offices of`, a bare trailing `Law` or `Firm`) drops the card before any request is spent. Anything that reaches the profile page is classified again from its structured data, name shape, and whether it lists a roster of attorneys.

Profiles the classifier cannot call confidently are **kept and labelled `unknown`**, never dropped silently. Filter on `profile_type` if you want only the confident ones.

### Behaviour worth knowing

**Duplicates are by profile ID.** FindLaw sometimes carries two profiles for the same person (a personal one and a firm-attached one). Both are returned; dedupe on `name` + `phone` if that matters to you.

**Some phone numbers are FindLaw call-tracking lines**, not the firm's published number. They forward correctly, but they're rented numbers assigned per listing. Spot-check against the firm's own site if you need the real line.

**Migrations resume cleanly.** City progress and seen-profile IDs are kept in actor state, so a run moved between servers picks up at the interrupted city without re-scraping or duplicating.

**Aborts are clean.** An aborted run finishes its current page, pushes what it has, and exits. The interrupted city is left unmarked so a rerun with the same state resumes there.

**Rate limiting is deliberate.** The request rate is a shared token bucket across all workers and is capped at 3 requests/second. Raising concurrency does not raise the rate. This keeps runs predictable and avoids the 429 back-offs that make a fast run slower than a steady one.

### Pay-per-event

When run under pay-per-event pricing, the actor charges one `attorney-scraped` event per record pushed and stops when your spend limit is reached. Records are pushed in batches of 100, so the overshoot is at most one batch.

### Compliance — read before running

This actor collects **publicly listed professional contact information** from a directory where attorneys publish it in order to be contacted. It does not log in, bypass paywalls, or access anything not visible to an anonymous visitor.

That said, the following are **your responsibility as the user**, not the actor's:

- **FindLaw's Terms of Use.** FindLaw (Thomson Reuters) restricts automated access in its terms. Review them before running at scale and decide for yourself whether your use is acceptable.
- **How you use the contact data.** Attorney emails and phones are subject to anti-spam and privacy law where the recipient is located and where you are — CAN-SPAM in the US, GDPR/UK GDPR if any subject is in Europe, CCPA for California residents, and state bar rules on solicitation if you are yourself a lawyer. Unsolicited bulk email to addresses gathered this way is regulated in most jurisdictions.
- **Apify's Terms of Service**, which govern what may be run on the platform.

The defaults (1.5 requests/second, 3 workers) are conservative on purpose. The actor is not built to evade blocking beyond ordinary browser-like request headers; if FindLaw rate-limits or blocks a run, it backs off and retries a few times, then stops.

### Known gaps

- Practice-area slugs other than `criminal-law` and `personal-injury-plaintiff` in the dropdown have not all been verified against live FindLaw URLs. A wrong slug returns no cities. Use *Custom practice area* with a slug copied from a real URL to be sure.
- City name → URL conversion follows FindLaw's convention (`St Petersburg` → `st-petersburg`, `Castleton-on-Hudson` → `castleton-on-hudson`). A city with no directory page returns 404, is logged, and the run continues.
- Review and rating fields depend on what the search card and profile schema expose; many profiles have neither.

# Actor input Schema

## `practiceArea` (type: `string`):

The FindLaw practice-area slug that appears in the directory URL, e.g. <code>criminal-law</code> in <code>lawyers.findlaw.com/criminal-law/florida/miami/</code>. Pick from the list, or use <b>Custom practice area</b> below for any other.

## `customPracticeArea` (type: `string`):

Copy the slug straight from a lawyers.findlaw.com URL. Overrides <b>Practice area</b> when set.

## `state` (type: `string`):

US state to scrape. Leave blank only if your <b>Cities</b> list uses <code>\[state]</code> headers or full URLs.

## `cities` (type: `array`):

City names, one per entry. <b>Leave empty to discover and scrape every city in the state.</b> A <code>\[texas]</code> entry switches state for the entries after it, so one list can span states. Full URLs are used as-is.

## `profileType` (type: `string`):

FindLaw mixes law-firm listings into attorney search results. <b>Attorneys</b> keeps individuals only and skips firm pages before fetching them.

## `fetchContactPage` (type: `boolean`):

Most emails are only on the profile's contact form page. Turning this off halves requests but finds far fewer emails.

## `maxResults` (type: `integer`):

Stop after this many records. Leave blank for no limit. Useful for a quick test run.

## `maxCities` (type: `integer`):

Only scrape the first N cities of the list (or of the discovered list).

## `maxPagesPerCity` (type: `integer`):

Safety cap on result pages walked per city.

## `stalePages` (type: `integer`):

After this many consecutive pages with no unseen profiles, move to the next city. Neighbouring cities list the same firms, so this saves a lot of requests. 0 walks every page to the end.

## `requestsPerSecond` (type: `number`):

Shared across all workers. Capped at 3. Higher values risk rate-limiting, which slows the whole run.

## `concurrency` (type: `integer`):

Worker threads. Does not raise the request rate (that is governed above); it hides latency. Capped at 5.

## `proxyConfiguration` (type: `object`):

Optional. Each worker gets one sticky proxy session for the whole run.

## Actor input object example

```json
{
  "practiceArea": "criminal-law",
  "customPracticeArea": "employment-law-employee",
  "state": "florida",
  "cities": [
    "Miami",
    "Tampa",
    "Orlando"
  ],
  "profileType": "attorneys",
  "fetchContactPage": true,
  "maxCities": 1,
  "maxPagesPerCity": 5,
  "stalePages": 4,
  "requestsPerSecond": 1.5,
  "concurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cities": [
        "Miami",
        "Tampa",
        "Orlando"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("moving_beacon-owner1/findlaw-attorney-directory-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "cities": [
        "Miami",
        "Tampa",
        "Orlando",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("moving_beacon-owner1/findlaw-attorney-directory-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cities": [
    "Miami",
    "Tampa",
    "Orlando"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call moving_beacon-owner1/findlaw-attorney-directory-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moving_beacon-owner1/findlaw-attorney-directory-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nJHd3VUcYjYC4FhSy/builds/hhXA2z4GptmPFp1N1/openapi.json
