Как программно извлечь HTML страницы в PHP: от базовых методов до продвинутых техник

Раздел: Веб-разработка -> Обработка ссылок

Изучение способов получения исходного кода веб страниц в PHP

Наиболее универсальное и гибкое решение для загрузки URL в PHP - использование расширения cURL.

Оно позволяет задавать таймауты, обрабатывать редиректы, отправлять пользовательские заголовки и работать с защищенными соединениями. Этот подход подходит для большинства сценариев: парсинг, проверка доступности, получение данных API.


<?php
$ch = curl_init('http://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$html = curl_exec($ch);
if (curl_errno($ch)) {
    echo 'Ошибка: ' . curl_error($ch);
} else {
    echo 'Код получен: ' . strlen($html) . ' символов';
}
curl_close($ch);
?>
  

Здесь создается дескриптор сеанса cURL, устанавливаются основные опции: CURLOPT_RETURNTRANSFER возвращает результат в переменную, CURLOPT_FOLLOWLOCATION автоматически переходит по редиректам, а CURLOPT_TIMEOUT ограничивает время выполнения. Ошибки обрабатываются через curl_errno() и curl_error().

Типичная проблема: не разрешено использование cURL (расширение отключено). Решение - установить расширение через менеджер пакетов или включить в php.ini. Другая распространенная ошибка - таймаут при долгом ответе сервера. Стоит подобрать оптимальное значение CURLOPT_TIMEOUT и CURLOPT_CONNECTTIMEOUT.

Как получить исходный код URL без установки дополнительных расширений?

Базовый встроенный способ - функция file_get_contents(). Она работает при включенной директиве allow_url_fopen.


<?php
$html = @file_get_contents('http://example.com');
if ($html === false) {
    echo 'Не удалось получить содержимое';
} else {
    echo $html;
}
?>
  

Это простейший метод, но он не дает контроля над заголовками и таймаутами. При недоступности сервера скрипт может зависнуть или вернуть предупреждение. Используйте оператор управления ошибками @ для подавления предупреждений.

Ошибка: open_basedir restriction in effect или allow_url_fopen = Off. Решение - либо изменить настройки сервера (если есть доступ), либо перейти на cURL. Если сайт требует HTTPS, необходимо убедиться, что OpenSSL включен.

Как передать пользовательские заголовки и таймаут при использовании file_get_contents?

Через функцию stream_context_create() можно создать потоковый контекст с нужными параметрами.


<?php
$opts = [
    'http' => [
        'method' => 'GET',
        'header' => "User-Agent: MyBot/1.0\r\n",
        'timeout' => 10
    ]
];
$context = stream_context_create($opts);
$html = file_get_contents('http://example.com', false, $context);
if ($html !== false) {
    echo $html;
}
?>
  

Этот подход позволяет задавать метод, заголовки и таймаут. Однако он все равно не обрабатывает редиректы так, как cURL, и не столь гибок для сложных сценариев (поддержка SSL, куки).

Если сервер требует аутентификацию или куки, придется вручную разбирать ответы и устанавливать заголовки. Ошибка может возникнуть при неправильном формате заголовков - обязательно использовать .

Как вручную отправить HTTP запрос и получить ответ с помощью сокетов?

Функция fsockopen() позволяет работать напрямую с сокетами. Это низкоуровневый способ, полезный для изучения протокола или в окружениях, где cURL отключен.


<?php
$host = 'example.com';
$port = 80;
$fp = fsockopen($host, $port, $errno, $errstr, 10);
if (!$fp) {
    echo "$errstr ($errno)";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: $host\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    $response = '';
    while (!feof($fp)) {
        $response .= fgets($fp, 1024);
    }
    fclose($fp);
    // Разделение заголовков и тела
    list($headers, $body) = explode("\r\n\r\n", $response, 2);
    echo $body;
}
?>
  

Требуется вручную формировать HTTP запрос, обрабатывать кодировку chunked и редиректы. Подходит для простых случаев или для углубленного понимания.

При работе с HTTPS нужно использовать fsockopen('ssl://host', 443). Ошибка может возникнуть из-за неправильного таймаута или блокировки порта брандмауэром.

Как использовать современные HTTP клиенты, такие как Guzzle?

Для проектов, где требуется обширная функциональность (автоматические редиректы, управление куками, асинхронные запросы), удобно применять библиотеки. Популярный выбор - GuzzleHttp.


<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'http://example.com']);
$response = $client->request('GET', '/page', [
    'timeout' => 5,
    'headers' => ['User-Agent' => 'MyApp']
]);
$html = $response->getBody()->getContents();
echo $html;
?>
  

Guzzle автоматически обрабатывает PSR-7 сообщения, поддерживает пулы соединений. Установка через Composer: composer require guzzlehttp/guzzle.

Если в окружении нет Composer или не разрешена установка внешних зависимостей, этот вариант недоступен. Также возможны конфликты версий PHP (требуется PHP 7.2+).

Дополнительные примеры с подробными пояснениями

1. Расширенный запрос cURL с обработкой редиректов и выводом заголовков

Пример

<?php
$ch = curl_init('https://httpbin.org/redirect/3');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,          // вернуть и заголовки
    CURLOPT_FOLLOWLOCATION => true,  // следовать по редиректам
    CURLOPT_MAXREDIRS => 5,          // максимальное количество редиректов
    CURLOPT_TIMEOUT => 15,
    CURLOPT_SSL_VERIFYPEER => false, // отключать только для тестов
    CURLOPT_USERAGENT => 'PhpTest/1.0'
]);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
if ($response !== false) {
    $headerSize = $info['header_size'];
    $headers = substr($response, 0, $headerSize);
    $body = substr($response, $headerSize);
    echo 'Последний URL: ' . $info['url'] . PHP_EOL;
    echo 'HTTP код: ' . $info['http_code'] . PHP_EOL;
    echo 'Размер тела: ' . strlen($body) . ' байт';
} else {
    echo 'Ошибка: ' . curl_error($ch);
}
curl_close($ch);
?>
Последний URL: https://httpbin.org/get
HTTP код: 200
Размер тела: 379 байт

В этом примере с помощью curl_getinfo извлекаются метаданные запроса. Установка CURLOPT_HEADER => true добавляет заголовки в тело ответа, затем они отделяются по размеру.

2. Отправка POST запроса с JSON данными через cURL

Пример

<?php
$data = ['name' => 'John', 'age' => 30];
$json = json_encode($data);
$ch = curl_init('https://httpbin.org/post');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $json,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Content-Length: ' . strlen($json)
    ],
    CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
{
  "args": {}, 
  "data": "{\"name\":\"John\",\"age\":30}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Content-Type": "application/json"
  }, 
  "json": {
    "age": 30, 
    "name": "John"
  }, 
  "url": "https://httpbin.org/post"
}

Передача данных в формате JSON требует явной установки заголовка Content-Type. Параметр CURLOPT_POSTFIELDS принимает строку (не массив) для отправки raw тела.

3. Загрузка только HTTP заголовков с помощью cURL

Пример

<?php
$ch = curl_init('https://www.example.com');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER => true,
    CURLOPT_NOBODY => true,   // не получать тело
    CURLOPT_TIMEOUT => 5
]);
$headers = curl_exec($ch);
curl_close($ch);
echo $headers;
?>
HTTP/2 200 
date: ...
content-type: text/html; charset=UTF-8
...

Опция CURLOPT_NOBODY отключает передачу тела ответа, что ускоряет запрос, если нужны только заголовки.

4. Работа с куками: сохранение и отправка через cURL

Пример

<?php
$cookieFile = tempnam(sys_get_temp_dir(), 'cookie');
$ch = curl_init('https://httpbin.org/cookies/set?name=value');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEJAR => $cookieFile,    // куда сохранять куки
    CURLOPT_COOKIEFILE => $cookieFile,   // откуда читать куки
    CURLOPT_FOLLOWLOCATION => true
]);
$response = curl_exec($ch);
curl_close($ch);
// Второй запрос с сохраненными куками
$ch2 = curl_init('https://httpbin.org/cookies');
curl_setopt_array($ch2, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEFILE => $cookieFile
]);
$response2 = curl_exec($ch2);
curl_close($ch2);
echo $response2;
unlink($cookieFile);
?>
{
  "cookies": {
    "name": "value"
  }
}

Использование CURLOPT_COOKIEJAR и CURLOPT_COOKIEFILE позволяет автоматически управлять сессионными данными.

5. Асинхронные запросы с помощью curl_multi

Пример

<?php
$urls = ['http://example.com', 'http://httpbin.org/ip', 'https://www.google.com'];
$multiHandle = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_SSL_VERIFYPEER => false
    ]);
    curl_multi_add_handle($multiHandle, $ch);
    $handles[$i] = $ch;
}
$running = null;
do {
    curl_multi_exec($multiHandle, $running);
    curl_multi_select($multiHandle); // ждем активности
} while ($running > 0);

foreach ($handles as $ch) {
    $html = curl_multi_getcontent($ch);
    $info = curl_getinfo($ch);
    echo 'URL: ' . $info['url'] . ' - код: ' . $info['http_code'] . ' - размер: ' . strlen($html) . PHP_EOL;
    curl_multi_remove_handle($multiHandle, $ch);
}
curl_multi_close($multiHandle);
?>
URL: http://example.com - код: 200 - размер: 1256
URL: http://httpbin.org/ip - код: 200 - размер: 35
URL: https://www.google.com - код: 200 - размер: 12345

Многопоточный cURL позволяет выполнять несколько запросов параллельно, что значительно ускоряет загрузку множества страниц.

6. Простой HTTP клиент на сокетах с поддержкой chunked transfer encoding

Пример

<?php
function fetch_url($url) {
    $parts = parse_url($url);
    $host = $parts['host'];
    $port = isset($parts['port']) ? $parts['port'] : 80;
    $path = isset($parts['path']) ? $parts['path'] : '/';
    if (isset($parts['query'])) $path .= '?' . $parts['query'];

    $fp = fsockopen($host, $port, $errno, $errstr, 10);
    if (!$fp) return false;

    $request = "GET $path HTTP/1.0\r\n";
    $request .= "Host: $host\r\n";
    $request .= "Connection: close\r\n\r\n";
    fwrite($fp, $request);

    $response = '';
    while (!feof($fp)) {
        $response .= fgets($fp, 1024);
    }
    fclose($fp);

    // Извлекаем тело (без заголовков)
    $parts = explode("\r\n\r\n", $response, 2);
    $body = isset($parts[1]) ? $parts[1] : '';
    // Простейшая обработка chunked (если Transfer-Encoding: chunked)
    if (preg_match('/Transfer-Encoding:\s*chunked/i', $parts[0])) {
        $decoded = '';
        $ptr = 0;
        while ($ptr < strlen($body)) {
            $pos = strpos($body, "\r\n", $ptr);
            $chunkSize = hexdec(substr($body, $ptr, $pos - $ptr));
            if ($chunkSize == 0) break;
            $decoded .= substr($body, $pos + 2, $chunkSize);
            $ptr = $pos + 2 + $chunkSize + 2;
        }
        $body = $decoded;
    }
    return $body;
}

echo fetch_url('http://example.com');
?>
<!doctype html>
<html>
...

Этот пример демонстрирует, как вручную декодировать chunked ответ, что необходимо для полной реализации HTTP/1.1. В реальных проектах лучше использовать готовые библиотеки.

Исходный код URL в PHP - comments

En
Source php url (php)