Table of contents

How can I scrape data from APIs using PHP?

API scraping with PHP is a fundamental skill for developers who need to extract data from web services programmatically. Unlike traditional web scraping that parses HTML content, API scraping involves making HTTP requests to structured endpoints that return data in formats like JSON or XML. PHP provides several built-in and third-party tools to accomplish this efficiently.

Understanding API Scraping vs Web Scraping

API scraping differs from traditional web scraping in several key ways:

  • Structure: APIs return structured data (JSON, XML) rather than HTML
  • Reliability: APIs are designed for programmatic access with stable endpoints
  • Authentication: Most APIs require authentication tokens or keys
  • Rate Limiting: APIs often implement strict rate limits
  • Documentation: APIs typically provide comprehensive documentation

Method 1: Using cURL

cURL is PHP's most versatile tool for making HTTP requests. It's built into most PHP installations and provides extensive options for customizing requests.

Basic GET Request with cURL

<?php
function fetchApiData($url, $headers = []) {
    $ch = curl_init();

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTPHEADER => $headers
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);

    curl_close($ch);

    if ($error) {
        throw new Exception("cURL Error: " . $error);
    }

    if ($httpCode !== 200) {
        throw new Exception("HTTP Error: " . $httpCode);
    }

    return json_decode($response, true);
}

// Example usage
try {
    $apiUrl = "https://jsonplaceholder.typicode.com/posts";
    $data = fetchApiData($apiUrl);

    foreach ($data as $post) {
        echo "Title: " . $post['title'] . "\n";
        echo "Body: " . substr($post['body'], 0, 100) . "...\n\n";
    }
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

POST Request with Authentication

<?php
function postApiData($url, $data, $apiKey) {
    $ch = curl_init();

    $headers = [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $apiKey,
        'User-Agent: PHP-API-Client/1.0'
    ];

    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($data),
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_TIMEOUT => 30
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    curl_close($ch);

    if ($httpCode >= 200 && $httpCode < 300) {
        return json_decode($response, true);
    } else {
        throw new Exception("API Error: HTTP " . $httpCode);
    }
}

// Example usage
$postData = [
    'title' => 'New Post',
    'body' => 'This is the content of the new post',
    'userId' => 1
];

try {
    $result = postApiData(
        'https://jsonplaceholder.typicode.com/posts',
        $postData,
        'your-api-key-here'
    );
    echo "Created post with ID: " . $result['id'];
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

Method 2: Using file_get_contents()

For simple GET requests without complex headers or authentication, file_get_contents() provides a lightweight alternative.

<?php
function simpleApiRequest($url, $context = null) {
    $response = file_get_contents($url, false, $context);

    if ($response === false) {
        throw new Exception("Failed to fetch data from API");
    }

    return json_decode($response, true);
}

// Create context for custom headers
$context = stream_context_create([
    'http' => [
        'method' => 'GET',
        'header' => [
            'Accept: application/json',
            'User-Agent: PHP-Client/1.0'
        ],
        'timeout' => 30
    ]
]);

try {
    $data = simpleApiRequest(
        'https://api.github.com/users/octocat/repos',
        $context
    );

    foreach ($data as $repo) {
        echo "Repository: " . $repo['name'] . "\n";
        echo "Language: " . $repo['language'] . "\n";
        echo "Stars: " . $repo['stargazers_count'] . "\n\n";
    }
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

Method 3: Using Guzzle HTTP Client

Guzzle is a powerful PHP HTTP client library that simplifies API interactions with features like middleware, async requests, and built-in error handling.

Installation

composer require guzzlehttp/guzzle

Basic Guzzle Implementation

<?php
require_once 'vendor/autoload.php';

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

class ApiScraper {
    private $client;
    private $baseUrl;
    private $apiKey;

    public function __construct($baseUrl, $apiKey = null) {
        $this->baseUrl = $baseUrl;
        $this->apiKey = $apiKey;

        $this->client = new Client([
            'base_uri' => $baseUrl,
            'timeout' => 30,
            'headers' => [
                'User-Agent' => 'PHP-Guzzle-Client/1.0',
                'Accept' => 'application/json'
            ]
        ]);
    }

    public function get($endpoint, $params = []) {
        try {
            $options = [];

            if ($this->apiKey) {
                $options['headers']['Authorization'] = 'Bearer ' . $this->apiKey;
            }

            if (!empty($params)) {
                $options['query'] = $params;
            }

            $response = $this->client->get($endpoint, $options);
            return json_decode($response->getBody(), true);

        } catch (RequestException $e) {
            throw new Exception("API Request failed: " . $e->getMessage());
        }
    }

    public function post($endpoint, $data) {
        try {
            $options = [
                'json' => $data
            ];

            if ($this->apiKey) {
                $options['headers']['Authorization'] = 'Bearer ' . $this->apiKey;
            }

            $response = $this->client->post($endpoint, $options);
            return json_decode($response->getBody(), true);

        } catch (RequestException $e) {
            throw new Exception("API Request failed: " . $e->getMessage());
        }
    }
}

// Example usage
$scraper = new ApiScraper('https://jsonplaceholder.typicode.com/');

try {
    // Fetch all posts
    $posts = $scraper->get('posts');
    echo "Total posts: " . count($posts) . "\n";

    // Fetch specific user's posts
    $userPosts = $scraper->get('posts', ['userId' => 1]);
    echo "User 1 posts: " . count($userPosts) . "\n";

    // Create new post
    $newPost = $scraper->post('posts', [
        'title' => 'API Scraping with PHP',
        'body' => 'Complete guide to API scraping',
        'userId' => 1
    ]);
    echo "Created post ID: " . $newPost['id'] . "\n";

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

Handling Different Authentication Methods

API Key Authentication

<?php
// Header-based API key
$headers = [
    'X-API-Key: your-api-key',
    'Content-Type: application/json'
];

// Query parameter API key
$url = 'https://api.example.com/data?api_key=your-api-key';
?>

OAuth 2.0 Authentication

<?php
class OAuth2ApiScraper {
    private $clientId;
    private $clientSecret;
    private $accessToken;

    public function __construct($clientId, $clientSecret) {
        $this->clientId = $clientId;
        $this->clientSecret = $clientSecret;
    }

    public function getAccessToken($tokenUrl) {
        $ch = curl_init();

        $postData = [
            'grant_type' => 'client_credentials',
            'client_id' => $this->clientId,
            'client_secret' => $this->clientSecret
        ];

        curl_setopt_array($ch, [
            CURLOPT_URL => $tokenUrl,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($postData),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        $tokenData = json_decode($response, true);
        $this->accessToken = $tokenData['access_token'];

        return $this->accessToken;
    }

    public function makeAuthenticatedRequest($url) {
        if (!$this->accessToken) {
            throw new Exception("Access token not set");
        }

        $ch = curl_init();

        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->accessToken,
                'Accept: application/json'
            ]
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }
}
?>

Advanced Error Handling and Retry Logic

<?php
class RobustApiScraper {
    private $maxRetries;
    private $retryDelay;

    public function __construct($maxRetries = 3, $retryDelay = 1) {
        $this->maxRetries = $maxRetries;
        $this->retryDelay = $retryDelay;
    }

    public function fetchWithRetry($url, $headers = []) {
        $attempt = 0;

        while ($attempt < $this->maxRetries) {
            try {
                $ch = curl_init();

                curl_setopt_array($ch, [
                    CURLOPT_URL => $url,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_TIMEOUT => 30,
                    CURLOPT_HTTPHEADER => $headers
                ]);

                $response = curl_exec($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                $error = curl_error($ch);

                curl_close($ch);

                if ($error) {
                    throw new Exception("cURL Error: " . $error);
                }

                // Handle rate limiting
                if ($httpCode === 429) {
                    $waitTime = pow(2, $attempt) * $this->retryDelay;
                    echo "Rate limited. Waiting {$waitTime} seconds...\n";
                    sleep($waitTime);
                    $attempt++;
                    continue;
                }

                if ($httpCode >= 200 && $httpCode < 300) {
                    return json_decode($response, true);
                }

                if ($httpCode >= 500) {
                    // Server error, retry
                    $attempt++;
                    sleep($this->retryDelay);
                    continue;
                }

                // Client error, don't retry
                throw new Exception("HTTP Error: " . $httpCode);

            } catch (Exception $e) {
                if ($attempt === $this->maxRetries - 1) {
                    throw $e;
                }
                $attempt++;
                sleep($this->retryDelay);
            }
        }

        throw new Exception("Max retries exceeded");
    }
}
?>

Rate Limiting and Best Practices

When scraping APIs, it's crucial to respect rate limits and implement proper throttling:

<?php
class RateLimitedScraper {
    private $requestTimes = [];
    private $maxRequestsPerMinute;

    public function __construct($maxRequestsPerMinute = 60) {
        $this->maxRequestsPerMinute = $maxRequestsPerMinute;
    }

    private function enforceRateLimit() {
        $now = time();

        // Remove requests older than 1 minute
        $this->requestTimes = array_filter(
            $this->requestTimes,
            function($time) use ($now) {
                return ($now - $time) < 60;
            }
        );

        if (count($this->requestTimes) >= $this->maxRequestsPerMinute) {
            $oldestRequest = min($this->requestTimes);
            $waitTime = 60 - ($now - $oldestRequest) + 1;
            echo "Rate limit reached. Waiting {$waitTime} seconds...\n";
            sleep($waitTime);
        }

        $this->requestTimes[] = $now;
    }

    public function makeRequest($url) {
        $this->enforceRateLimit();

        // Make the actual request
        $response = file_get_contents($url);
        return json_decode($response, true);
    }
}
?>

Working with Paginated APIs

Many APIs return data in pages. Here's how to handle pagination effectively:

<?php
function fetchAllPages($baseUrl, $headers = []) {
    $allData = [];
    $page = 1;
    $hasMorePages = true;

    while ($hasMorePages) {
        $url = $baseUrl . "?page=" . $page . "&per_page=100";

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers
        ]);

        $response = curl_exec($ch);
        $data = json_decode($response, true);
        curl_close($ch);

        if (empty($data) || count($data) === 0) {
            $hasMorePages = false;
        } else {
            $allData = array_merge($allData, $data);
            $page++;

            // Add delay between requests
            sleep(0.5);
        }

        echo "Fetched page {$page}, total items: " . count($allData) . "\n";
    }

    return $allData;
}
?>

Data Processing and Storage

After fetching data from APIs, you'll often need to process and store it:

<?php
class ApiDataProcessor {
    private $pdo;

    public function __construct($dbConfig) {
        $dsn = "mysql:host={$dbConfig['host']};dbname={$dbConfig['database']}";
        $this->pdo = new PDO($dsn, $dbConfig['username'], $dbConfig['password']);
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    public function processAndStore($apiData) {
        $stmt = $this->pdo->prepare(
            "INSERT INTO api_data (external_id, title, content, created_at) 
             VALUES (?, ?, ?, ?) 
             ON DUPLICATE KEY UPDATE 
             title = VALUES(title), 
             content = VALUES(content)"
        );

        foreach ($apiData as $item) {
            $stmt->execute([
                $item['id'],
                $item['title'],
                $item['body'],
                date('Y-m-d H:i:s')
            ]);
        }

        echo "Processed " . count($apiData) . " items\n";
    }

    public function validateData($item) {
        return isset($item['id']) && 
               isset($item['title']) && 
               isset($item['body']) &&
               !empty(trim($item['title']));
    }
}
?>

Conclusion

PHP offers multiple robust methods for API scraping, from the basic file_get_contents() for simple requests to sophisticated solutions using Guzzle for complex scenarios. The key to successful API scraping lies in understanding the API's authentication requirements, implementing proper error handling and retry logic, respecting rate limits, and efficiently processing the retrieved data.

When working with APIs that require more complex interactions or JavaScript execution, you might need to consider browser-based scraping solutions that can handle dynamic content loading. Additionally, for APIs that implement sophisticated anti-bot measures, understanding authentication flows becomes crucial for maintaining reliable data access.

Remember to always check the API's terms of service, implement appropriate caching mechanisms to reduce unnecessary requests, and monitor your scraping operations to ensure they remain efficient and compliant with the service provider's requirements.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon