The email address, phone number, contact form, social accounts and postal
address each website on a list publishes, as one row per site in a CSV. Give it
a file of websites, one per line, the way they sit in a spreadsheet column; bare
hosts like rjf.uk.com work.
These scripts call Website Contact Scraper, which reads the page you give plus up to four more of that site's pages, the ones likeliest to carry contact details (the contact, about or team page).
The write-up, with a real run on 36 accountancy firms in Manchester, is How to find the email address for a list of company websites.
You need an Apify account and its API token, from Console → Settings → API & Integrations.
Node (18 or newer):
cd node
npm install
APIFY_TOKEN=your_token COUNTRY=GB node contacts.mjs ../websites.txtPython (3.11 or newer):
cd python
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
APIFY_TOKEN=your_token COUNTRY=GB python contacts.py ../websites.txtwebsites.txt holds three of the firms from the post. Put your own list in it,
or pass websites as arguments instead of the file. COUNTRY is the two-letter
code used to read phone numbers written without a country code ("0161 …");
leave it out and the scraper works it out from each site. MAX_PAGES (default
5) is how many pages of each site may be read; MAX_PAGES=1 reads only the
page you gave, which costs less and finds fewer addresses. Both scripts print
one line per site and write contacts.csv:
taylorevans.co.uk info@taylorevans.co.uk +441613021571
ac-accounts.co.uk info@ac-accounts.co.uk +441619621855
rjf.uk.com hello@rjf.uk.com +441615040629
3 of 3 site(s) read, 3 with an email -> contacts.csv
sample-output.json holds four real rows from the run:
a firm whose email was on its contact page, one whose email was on its home
page, one that publishes no email at all, and a site that answered with a bot
check. The fields you will use most:
| Field | What it holds |
|---|---|
email |
The single best address: the site's own domain first, a general mailbox (info@, hello@) before a person's |
phone |
The single best number, in E.164 (+441615040629) |
emails, phones |
Everything found, each with the page it was found on |
contactFormUrl |
The page with the site's contact form, for firms that publish no address |
social |
One URL per platform: social.linkedin, social.facebook, social.instagram... |
address |
The postal address, when the site gives a real one; address.raw is the text |
pagesRead, pagesReadCount |
Which pages were read, and how many were charged |
A site that could not be read has no emails key at all, and carries
error and errorDescription instead. A site that was read and publishes no
address has emails: []. Both scripts print the first kind and leave it out
of the CSV; the run itself stays green either way.
$0.001 per run plus $0.00075 per page read, Apify's platform costs included. A page that turns out not to exist is not charged, and neither is a site that could not be read. On the 39 Manchester addresses, reading the home page only was 37 pages, $0.029; up to five pages was 161 pages, $0.122.
- Some sites answer with a bot check; those come back as
challengeerrors. Switching the scraper'sproxyinput to residential is the documented fix. - Some firms publish no email anywhere: 4 of the 36 in the post. They usually have a phone number or a contact form instead.
- The
emailslist holds every address the pages printed, including other organisations' addresses and staff members' own. Useemailfor the one to write to, and check your local rules before contacting a named person. - It reads up to
maxPagesPerSitepages (at most 20), never a whole site.

