Apollo.io gives you 10,000 free email credits monthly. That sounds great until you hit their export limits.

Free accounts get just 120 exports yearly. Paid plans start at $49/month. For agencies scraping thousands of leads, that cost adds up fast.

Building your own Apollo scraper changes this equation completely. You bypass export restrictions and scale your lead generation without subscription fees.

In this guide, I'll show you how to build an Apollo scraper using Python and Selenium, including proxy setup, rate limiting, and data export workflows that keep your account safe.

What is an Apollo Scraper and Why Build One?

An Apollo scraper automates data extraction from Apollo.io by programmatically accessing search results, contact profiles, and company pages. Instead of manually copying emails and phone numbers, the scraper collects names, job titles, verified emails, and company data across thousands of records in minutes.

Apollo charges based on export volume. Their Basic plan ($49/month) caps exports at 900 contacts monthly.

Building your own scraper eliminates these limits entirely. Your only costs are proxy services ($10-30/month) and compute resources.

The math is simple: A $50/month Apollo subscription gets you 10,800 exports yearly. Your custom scraper handles unlimited volume for less than half that price.

Method 1: Building a Python Apollo Scraper

This approach gives you maximum control. You'll automate the entire workflow from search to export.

Prerequisites and Setup

Install Python 3.8+ and required libraries:

pip install selenium pandas openpyxl webdriver-manager --break-system-packages

You'll also need Chrome or Firefox browser installed.

Step 1: Initialize Your Selenium WebDriver

Create a new file called apollo_scraper.py:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import time
import pandas as pd
import random

class ApolloScraper:
    def __init__(self, email, password):
        self.email = email
        self.password = password
        self.driver = self.setup_driver()
        
    def setup_driver(self):
        options = webdriver.ChromeOptions()
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64)')
        driver = webdriver.Chrome(options=options)
        return driver

The disable-blink-features argument hides Selenium's automation signature. Apollo's anti-bot detection looks for this flag.

Random user agents make your scraper appear as a normal browser.

Step 2: Automate Apollo Login

Add this login method to your class:

def login(self):
    self.driver.get('https://app.apollo.io/#/login')
    time.sleep(3)
    
    email_field = self.driver.find_element(By.NAME, 'email')
    password_field = self.driver.find_element(By.NAME, 'password')
    
    email_field.send_keys(self.email)
    password_field.send_keys(self.password)
    
    login_button = self.driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
    login_button.click()
    
    time.sleep(5)
    print("Logged in successfully")

This navigates to Apollo's login page, fills credentials, and clicks submit.

The 5-second delay after login prevents triggering rate limits immediately.

Step 3: Navigate to Your Saved List

def navigate_to_list(self, list_url):
    self.driver.get(list_url)
    time.sleep(4)
    
    # Wait for contacts to load
    WebDriverWait(self.driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, 'zp_contact_row'))
    )
    print("List loaded successfully")

Replace list_url with your actual Apollo saved list URL.

The WebDriverWait ensures the page fully loads before scraping begins.

Step 4: Extract Contact Data

Here's where the real scraping happens:

def scrape_contacts(self):
    contacts = []
    
    # Find all contact rows
    rows = self.driver.find_elements(By.CLASS_NAME, 'zp_contact_row')
    
    for row in rows:
        try:
            # Extract name
            name = row.find_element(By.CLASS_NAME, 'zp_contact_name').text
            
            # Extract title
            title = row.find_element(By.CLASS_NAME, 'zp_contact_title').text
            
            # Extract company
            company = row.find_element(By.CLASS_NAME, 'zp_company_name').text
            
            # Extract email (if available)
            try:
                email = row.find_element(By.CLASS_NAME, 'zp_contact_email').text
            except:
                email = "Not available"
            
            # Extract location
            try:
                location = row.find_element(By.CLASS_NAME, 'zp_contact_location').text
            except:
                location = "Not available"
            
            contacts.append({
                'name': name,
                'title': title,
                'company': company,
                'email': email,
                'location': location
            })
            
        except Exception as e:
            print(f"Error extracting contact: {e}")
            continue
    
    return contacts

This loops through each visible contact row and extracts key fields.

The try-except blocks handle missing data gracefully. Not every contact has email or phone visible.

Step 5: Handle Pagination

Apollo limits page views to 25 contacts. You need pagination logic:

def scrape_all_pages(self, max_pages=10):
    all_contacts = []
    
    for page in range(max_pages):
        print(f"Scraping page {page + 1}")
        
        # Scrape current page
        contacts = self.scrape_contacts()
        all_contacts.extend(contacts)
        
        # Random delay between pages (2-5 seconds)
        delay = random.uniform(2, 5)
        time.sleep(delay)
        
        # Click next page button
        try:
            next_button = self.driver.find_element(By.CSS_SELECTOR, 'button[aria-label="Next page"]')
            next_button.click()
            time.sleep(3)
        except:
            print("No more pages available")
            break
    
    return all_contacts

Random delays between pages mimic human behavior. Consistent timing triggers bot detection.

The max_pages parameter prevents infinite loops if pagination breaks.

Step 6: Export to CSV

def export_to_csv(self, contacts, filename='apollo_leads.csv'):
    df = pd.DataFrame(contacts)
    df.to_csv(filename, index=False)
    print(f"Exported {len(contacts)} contacts to {filename}")

Pandas converts your contact list into a clean CSV file.

You can also export to Excel by changing the file extension to .xlsx.

Complete Usage Example

# Initialize scraper
scraper = ApolloScraper(
    email='your-email@company.com',
    password='your-password'
)

# Login
scraper.login()

# Navigate to saved list
list_url = 'https://app.apollo.io/#/people?savedListId=YOUR_LIST_ID'
scraper.navigate_to_list(list_url)

# Scrape 10 pages (250 contacts)
contacts = scraper.scrape_all_pages(max_pages=10)

# Export results
scraper.export_to_csv(contacts)

# Cleanup
scraper.driver.quit()

This workflow logs in, scrapes your target list, and exports everything to CSV.

For 1,000 contacts, set max_pages=40 (1,000 / 25 per page).

Rate Limiting and Avoiding Account Bans

Apollo monitors scraping patterns. Follow these rules to stay under the radar:

Limit daily volume: Scrape no more than 1,000 contacts per day. Spread this across 8-10 hours.

Randomize delays: Use random.uniform(2, 10) for delays between actions. Never use fixed intervals.

Rotate user agents: Change browser fingerprints every session:

user_agents = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]

options.add_argument(f'--user-agent={random.choice(user_agents)}')

Use residential proxies: Datacenter IPs get flagged instantly. Services like BrightData or Smartproxy cost $10-30/month but keep your scraper alive.

Add proxy support to your driver setup:

def setup_driver_with_proxy(self, proxy):
    options = webdriver.ChromeOptions()
    options.add_argument(f'--proxy-server={proxy}')
    return webdriver.Chrome(options=options)

Cost Comparison: Custom Scraper vs Apollo Subscription

Here's the real savings breakdown:

Apollo Basic Plan ($49/month):

  • 900 exports monthly = 10,800 yearly
  • Cost per lead: $0.054

Custom Apollo Scraper:

  • Proxy service: $20/month = $240/year
  • Unlimited exports
  • Cost per lead: $0.00024 (assuming 100,000 leads/year)

Your scraper pays for itself after extracting just 5,000 contacts.

For agencies running multiple campaigns, the savings exceed $10,000 annually.

Alternative Method: Chrome Extension Approach

If coding isn't your thing, Chrome extensions offer a simpler path.

Tools like "Apollo Easy Scrape" automate extraction through the browser UI.

Installation Steps:

  1. Download the extension folder (search GitHub for "apollo scraper chrome extension")
  2. Open Chrome → Extensions → Enable Developer Mode
  3. Click "Load Unpacked" and select the folder
  4. Pin the extension to your toolbar

Usage:

  1. Navigate to your Apollo saved list
  2. Click the extension icon
  3. Set delay interval (5 seconds recommended)
  4. Click "Scrape List"
  5. Download CSV when complete

Extensions work great for small volumes (under 500 contacts). They break with UI updates and lack advanced features like proxy support.

Optimizing Your Apollo Scraper for Performance

Headless mode speeds up scraping by removing the browser UI:

options.add_argument('--headless')
options.add_argument('--disable-gpu')

This runs 2-3x faster but makes debugging harder.

Parallel scraping across multiple accounts scales throughput. Spin up 5 instances with different proxies and credentials.

Never run parallel scrapers on the same Apollo account. That's an instant ban.

Database storage beats CSV for large volumes. Use SQLite or PostgreSQL:

import sqlite3

def save_to_database(contacts):
    conn = sqlite3.connect('apollo_leads.db')
    cursor = conn.cursor()
    
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS contacts (
            name TEXT,
            title TEXT,
            company TEXT,
            email TEXT,
            location TEXT
        )
    ''')
    
    for contact in contacts:
        cursor.execute('''
            INSERT INTO contacts VALUES (?, ?, ?, ?, ?)
        ''', (contact['name'], contact['title'], contact['company'], 
              contact['email'], contact['location']))
    
    conn.commit()
    conn.close()

Databases handle millions of records without performance degradation.

Common Errors and Troubleshooting

"Element not found" errors: Apollo updated their HTML structure. Inspect the page and update your CSS selectors.

Login fails repeatedly: Apollo detected automation. Switch to cookie-based authentication instead:

def load_cookies(self):
    with open('apollo_cookies.json', 'r') as f:
        cookies = json.load(f)
        for cookie in cookies:
            self.driver.add_cookie(cookie)

Scraper stops mid-execution: You hit rate limits. Reduce volume and increase delays.

No email addresses appear: Apollo restricts email reveals to verified accounts. Use a business email for signup.

FAQ

How many contacts can I scrape from Apollo per day?

Limit yourself to 1,000 contacts daily spread across 8-10 hours. This mimics normal user behavior and avoids triggering Apollo's anti-bot systems.

Can I scrape Apollo without a paid account?

Yes. Free Apollo accounts provide 10,000 email credits monthly. Your scraper bypasses export limits, so you can extract all 10,000 without upgrading.

What happens if Apollo detects my scraper?

First offense usually results in temporary account suspension (24-48 hours). Repeated violations lead to permanent bans. Always use proxies and reasonable rate limits.

How do I handle Apollo's CAPTCHA challenges?

Residential proxies and randomized delays prevent most CAPTCHAs. If they appear, services like 2Captcha ($3 per 1,000 solves) automate solutions.

Is building an Apollo scraper legal?

Scraping violates Apollo's Terms of Service but isn't illegal in most jurisdictions. Data privacy laws (GDPR, CCPA) restrict how you use scraped data, not collection itself. Consult a lawyer before commercial use.

Can I integrate my Apollo scraper with my CRM?

Absolutely. Most CRMs accept CSV imports. For real-time integration, use their APIs. Salesforce, HubSpot, and Pipedrive all offer Python libraries for programmatic data insertion.

Apify Apollo Scraper Alternative?

Yes. This is an alternative to the well known Apify scraper but totally free