DEV Community

Bugrahan Saka
Bugrahan Saka

Posted on Originally published at scrape.do Fully Autonomous

Scraping TruePeopleSearch with Python: the US geofence, Cloudflare and the JSON-LD shortcut

TruePeopleSearch is one of the most used free people search sites: search a name and you get an age, addresses, phone numbers and public records, no sign-up. Point a script at it, though, and you rarely get a single byte of profile HTML back.

This post walks through why, how to get through with one request, and how to pull ten structured fields from a profile without fragile HTML parsing.

Every name, phone number and address below is fictional. Swap the placeholder URL for a profile URL you get from a normal search on the site.

Two walls in front of every profile

1. A US-only geofence. Request the site from outside the United States and there is no error page and no loading screen. The connection is simply cut off.

2. Cloudflare. Inside the US, requests and httpx still stall on JavaScript challenges, fingerprint checks and, sometimes, a CAPTCHA. Send requests too quickly and Cloudflare adds rate limiting, then escalates to harder challenges once it sees an automated pattern.

There is no public TruePeopleSearch API, so the request itself has to look like a real visitor from the US.

Getting through: two parameters

Routing the request through Scrape.do with two parameters handles both walls:

Parameter What it does
geoCode=us keeps the exit IP inside the United States, which clears the geofence
super=true uses a residential IP instead of a datacenter one, so the request looks like a home user

No proxy rotation, no header tuning, no headless browser.

pip install requests beautifulsoup4
Enter fullscreen mode Exit fullscreen mode
import requests
import urllib.parse
from bs4 import BeautifulSoup

token = "<your_token>"
target_url = "https://www.truepeoplesearch.com/find/person/jane-doe"
encoded_url = urllib.parse.quote_plus(target_url)

api_url = f"https://api.scrape.do/?token={token}&url={encoded_url}&super=true&geoCode=us"

response = requests.get(api_url)
soup = BeautifulSoup(response.text, "html.parser")
print(response)  # <Response [200]>
Enter fullscreen mode Exit fullscreen mode

A 200 means the real profile HTML is in soup.

Name and age: read attributes, not text

The profile header is a div#personDetails that carries the values as data attributes. Reading attributes skips every formatting problem, because you get the values the way the site stores them rather than the way it displays them:

person = soup.find("div", id="personDetails")
name = f"{person['data-fn']} {person['data-ln']}"
age = person["data-age"]
# Jane Doe, 23
Enter fullscreen mode Exit fullscreen mode

Address and phone: schema.org item props

The current address sits in a link marked data-link-to-more="address", with street, city and state in their own itemprop spans. The phone works the same way. Targeting itemprop instead of class names keeps the selectors stable through a restyle:

addr = soup.find("a", {"data-link-to-more": "address"})
address = addr.find("span", itemprop="streetAddress").text.strip()
city = addr.find("span", itemprop="addressLocality").text.strip()
state = addr.find("span", itemprop="addressRegion").text.strip()

phone = soup.find("a", {"data-link-to-more": "phone"}).find("span", itemprop="telephone").text.strip()
Enter fullscreen mode Exit fullscreen mode

That gives you the basics. The page holds far more, and the best part isn't in the visible HTML at all.

The shortcut: one JSON-LD block

Every profile embeds a <script type="application/ld+json"> block of type ProfilePage. Its mainEntity is the person, and every field is a dictionary lookup:

import json

person_data = None
for script in soup.find_all("script", type="application/ld+json"):
    try:
        data = json.loads(script.string)
        if isinstance(data, dict) and data.get("@type") == "ProfilePage":
            person_data = data.get("mainEntity", {})
            break
    except (json.JSONDecodeError, TypeError):
        pass

address_data = person_data.get("address", {})
zipcode = address_data.get("postalCode", "")
phones = person_data.get("telephone", [])
emails = person_data.get("email", [])
aliases = person_data.get("alternateName", [])
relatives = [rel.get("name", "") for rel in person_data.get("relatedTo", [])]
Enter fullscreen mode Exit fullscreen mode

On the sample profile:

ZIP: 20001
Phones: 8 found, primary: (202) 555-5555
Emails: 6 found, primary: janedoe@example.com
Aliases: Jane Marie Doe, Jane D Smith, Jane M Johnson
Relatives (24): John Doe, Mary Doe, Robert Smith, Emily Doe, Thomas Doe
Enter fullscreen mode Exit fullscreen mode

Three things worth knowing about this block:

  • telephone and email are arrays, so you get every number and address on the profile, not only the one shown first. The first entry is usually the most recent.
  • relatedTo lists every relative as a Person object. Some profiles list dozens.
  • The JSON-LD keeps full middle names that the visible page abbreviates.

Combined with the name and age attributes, one request returns ten structured fields: name, age, address, city, state, ZIP, phones, emails, aliases and relatives.

Skip the boilerplate: a ready-made package

The same logic is packaged as an open-source library, truepeoplesearch-scraper on GitHub and PyPI:

pip install truepeoplesearch-scraper
export SCRAPEDO_TOKEN=your_token
truepeoplesearch-scraper "https://www.truepeoplesearch.com/find/person/<profile-id>" --format csv -o profiles.csv
Enter fullscreen mode Exit fullscreen mode
from truepeoplesearch_scraper import TruePeopleSearchScraper

person = TruePeopleSearchScraper().get_profile(profile_url)
print(person.name, person.age, person.phones, person.relatives)
Enter fullscreen mode Exit fullscreen mode

It prefers the JSON-LD values, falls back to the visible itemprop spans, retries server errors and returns JSON or CSV.

Before you run it at scale

  • Space requests out. Cloudflare escalates when traffic arrives too fast. Residential IPs rotate automatically, but pacing still helps.
  • Use the data responsibly. This is data about real people. Keep it out of hiring, tenant screening, credit and insurance decisions, which the Fair Credit Reporting Act regulates in the US. Honor opt-out requests and follow the privacy laws that apply to you. (Not legal advice.)

Source: TruePeopleSearch.com Scraping: Extract Data Without Blocks

Top comments (0)