PHP cURL 프록시 스크래핑: CURLOPT_PROXY, 인증 및 Guzzle

대부분의 PHP 프록시 코드 조각은 여전히 2011년의 동일한 세 가지 버그를 가지고 있습니다. 여기에 프로덕션 환경에서도 유효한 깨끗한 CURLOPT_PROXY 설정 — 인증, SOCKS5, curl_multi 회전, Guzzle 및 타임아웃 — 이 있습니다.

PHP cURL 프록시 코드는 독특한 문제를 가지고 있습니다: 상위 랭킹 코드 조각이 10년 이상 되었고, 많은 코드가 실제 버그를 포함하고 있습니다 — 모든 요청을 두 번 실행하는 이중 curl_exec() 호출, "HTTPS 작동을 위해" 비활성화된 CURLOPT_SSL_VERIFYPEER, 이미 기본값으로 설정된 옵션들. 이 가이드는 현재, 올바른 버전입니다: 인증이 포함된 CURLOPT_PROXY, SOCKS5, curl_multi와의 병렬 회전, Guzzle에서의 동일한 설정, 밤새 멈추지 않도록 하는 타임아웃.

최소한의 올바른 PHP cURL 프록시 예제

<?php
function fetch(string $url): string|false {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_PROXY          => 'http://gate.quantumproxies.io:8000',
        CURLOPT_PROXYUSERPWD   => 'USER:PASS',
        CURLOPT_RETURNTRANSFER => true,  // return the body, don't echo it
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_CONNECTTIMEOUT => 10,    // seconds to reach the proxy
        CURLOPT_TIMEOUT        => 30,    // seconds for the whole transfer
        CURLOPT_ENCODING       => '',    // accept gzip/br, decode automatically
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        error_log('cURL error ' . curl_errno($ch) . ': ' . curl_error($ch));
    }
    curl_close($ch);
    return $body;
}

echo fetch('https://httpbin.org/ip'); // -> the proxy exit IP

자격 증명은 CURLOPT_PROXYUSERPWD에 표시된 대로 존재할 수 있으며, 프록시 URL에 인라인으로 포함될 수도 있습니다 (http://USER:PASS@host:port) — 둘 다 작동합니다. CURLOPT_RETURNTRANSFER는 초보자가 놓치는 부분입니다: 이것이 없으면 curl_exec()가 응답을 직접 출력에 에코하고 true를 반환합니다. 이는 스크래퍼가 원하는 것이 아닙니다. PHP 8에서는 핸들이 CurlHandle 객체이며 리소스가 아닙니다; 옵션 호출은 변경되지 않았습니다.

사람들이 무작정 따라하는 옵션들 (그리고 하나의 실제 보안 실수)

설정해야 할 PHP cURL 프록시 옵션과 피해야 할 옵션의 체크리스트, SSL_VERIFYPEER 실수 포함
여섯 가지 옵션이 전체 작업을 수행합니다; 오른쪽 열은 2011년 튜토리얼에서 복사-붙여넣기한 부채입니다.

PHP에서 SOCKS5 프록시

<?php
// scheme style: socks5h = DNS resolved by the proxy (use for scraping)
curl_setopt($ch, CURLOPT_PROXY, 'socks5h://USER:PASS@gate.quantumproxies.io:1080');

// split style: same result via CURLOPT_PROXYTYPE
curl_setopt($ch, CURLOPT_PROXY, 'gate.quantumproxies.io:1080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'USER:PASS');

스킴은 DNS가 어디에서 발생하는지를 결정합니다: socks5:// (및 CURLPROXY_SOCKS5)는 호스트 이름을 서버에서 해석하여 모든 대상 도메인을 로컬 리졸버에 노출합니다; socks5h:// (CURLPROXY_SOCKS5_HOSTNAME)는 프록시 종료 지점에서 해석합니다. 후자를 선호하십시오. QuantumProxies는 모든 플랜에서 HTTP 및 SOCKS5를 제공하므로 프로토콜 전환은 한 줄의 변경으로 가능합니다, 새로운 구독이 필요하지 않습니다. 동일한 DNS 규칙은 명령줄에서도 적용됩니다 — 이 페이지의 모든 것에 대한 셸 등가물은 curl 프록시 레시피를 참조하십시오.

curl_multi를 사용한 회전 및 병렬 요청

PHP에는 async/await가 없지만 병렬 스크래핑에는 필요하지 않습니다 — curl_multi는 단일 프로세스에서 수십 개의 전송을 동시에 실행합니다. 모든 핸들을 회전 게이트웨이에 지정하면 각 요청이 자동으로 다른 주거 IP에서 종료됩니다, 코드에 프록시 목록 관리가 필요 없습니다:

<?php
$urls = ['https://example.com/p/1', 'https://example.com/p/2', 'https://example.com/p/3'];

$mh = curl_multi_init();
$handles = [];

foreach ($urls as $url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        // one gateway, a fresh residential exit per request
        CURLOPT_PROXY          => 'http://USER:PASS@rotating.quantumproxies.io:8000',
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_ENCODING       => '',
    ]);
    curl_multi_add_handle($mh, $ch);
    $handles[$url] = $ch;
}

do {
    curl_multi_exec($mh, $running);
    curl_multi_select($mh);        // wait for activity instead of spinning
} while ($running > 0);

$results = [];
foreach ($handles as $url => $ch) {
    $results[$url] = curl_multi_getcontent($ch);
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);

배치를 적당히 유지하십시오 — 대부분의 대상에 대해 8에서 16개의 동시 핸들이 충분하며, 속도보다 페이싱이 더 중요합니다: 배치 사이에 몇 백 밀리초의 usleep()은 거의 비용이 들지 않으며 요청 속도를 429를 트리거하는 임계값 아래로 유지합니다. 각 핸들에 대해 curl_getinfo($ch, CURLINFO_HTTP_CODE)를 확인하고 실패를 인라인으로 다시 가져오는 대신 재시도 큐로 라우팅하십시오. 흐름이 로그인, 장바구니, 쿠키가 있는 페이지 매김과 같은 하나의 ID를 공유해야 하는 여러 요청에 걸쳐 있을 때, 사용자 이름에 접미사를 붙여 그 핸들을 고정 세션으로 전환하십시오: USER-session-a1b2c3는 회전 대신 지속 시간 동안 동일한 종료 IP를 유지합니다.

회전 주거 프록시 게이트웨이를 통해 curl_multi 배치를 보내는 PHP 스크래핑 파이프라인의 다이어그램
작업자는 단순하게 유지되고, 게이트웨이는 스마트하게 유지됩니다: 회전, 고정성 및 지리적 타겟팅은 모두 프록시 URL에 존재합니다.

헤더와 쿠키: 주장하는 브라우저처럼 보이기

기본 cURL 헤더가 있는 깨끗한 종료 IP는 여전히 봇으로 읽힙니다 — PHP는 요청을 꾸미지 않으면 스스로를 발표합니다. 세 가지 옵션이 대부분의 차이를 메웁니다: 현재 브라우저 문자열이 있는 CURLOPT_USERAGENT, Accept-Language 및 관련 항목에 대한 CURLOPT_HTTPHEADER, 그리고 동일한 파일을 가리키는 쿠키 쌍 CURLOPT_COOKIEJAR / CURLOPT_COOKIEFILE, 이는 브라우저가 하는 방식으로 핸들 간에 세션 쿠키를 지속시킵니다. 쿠키 지속성은 사람들이 예상하는 것보다 더 중요합니다: 많은 사이트가 첫 번째 히트에 토큰을 설정하고 이를 반환하지 않는 클라이언트를 조용히 도전합니다. 주장하는 ID를 일관되게 유지하십시오 — Accept-Language가 없는 Chrome User-Agent와 기본 헤더 세트는 자체적으로 지문이며, 일관성 검사는 중간 수준의 안티봇 공급업체가 두 번째 요청에서 PHP 스크래퍼를 잡는 방법입니다.

Guzzle: 동일한 프록시, 더 나은 사용성

<?php
use GuzzleHttp\Client;

$client = new Client([
    'proxy'           => 'http://USER:PASS@gate.quantumproxies.io:8000',
    'timeout'         => 30,
    'connect_timeout' => 10,
]);

$res = $client->get('https://httpbin.org/ip');
echo $res->getBody();

// per-request override, e.g. a sticky session for a login flow
$res = $client->get('https://example.com/account', [
    'proxy' => 'http://USER-session-a1b2c3:PASS@gate.quantumproxies.io:8000',
]);

Guzzle은 내부적으로 cURL을 구동하므로 위의 모든 것이 여전히 적용됩니다 — 단지 더 잘 읽힙니다. 한 가지 경고: Guzzle의 기본 timeout은 0이며, 이는 영원히 기다린다는 의미입니다. 전송 중간에 멈추는 프록시 종료는 PHP-FPM 작업자를 무기한 고정시킬 수 있으므로, 생성하는 모든 클라이언트에 대해 두 타임아웃을 설정하십시오. proxy 옵션은 또한 http, httpsno 키가 있는 배열을 허용하여 스킴별 라우팅이나 내부 호스트 제외가 필요할 때 사용합니다.

PHP cURL이 충분하지 않을 때

cURL은 HTML을 가져옵니다; JavaScript를 실행하지 않습니다. 응답이 빈 div가 있는 스켈레톤 페이지로 돌아오면, 데이터는 클라이언트에서 렌더링됩니다 — 빈 페이지 응답에 대한 우리의 가이드가 이를 감지하는 방법을 보여줍니다. 그리고 강화된 대상에서는, cURL의 TLS 핸드셰이크 자체가 IP가 얼마나 깨끗하든 상관없이 당신을 드러냅니다 (브라우저는 작동하지만 cURL은 403 미스터리). PHP는 요청당 헤드리스 브라우저를 합리적으로 실행할 수 없으므로, 실용적인 탈출구는 Scraper API입니다: 단일 엔드포인트로의 하나의 cURL 호출, 그리고 렌더링, 브라우저 지문, 회전 및 구문 분석이 깨끗한 markdown, JSON 또는 HTML로 돌아옵니다.

PHP에서 Scraper API 시도하기

자주 묻는 질문

PHP cURL에서 프록시를 어떻게 사용합니까?

CURLOPT_PROXY를 전체 프록시 URL로 설정하고, 자격 증명이 필요하면 CURLOPT_PROXYUSERPWD를 추가하며, 항상 CURLOPT_RETURNTRANSFER와 명시적인 타임아웃을 활성화하십시오. 이것이 필요한 전체 세트입니다 — HTTPS 대상은 자동으로 터널링되며, 나머지는 선택적 튜닝입니다.

PHP에서 프록시 사용자 이름과 비밀번호를 어떻게 전달합니까?

curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'user:pass')를 사용하거나 URL에 포함하십시오: http://user:pass@host:port. HTTP 407을 받으면, 프록시가 해당 자격 증명을 거부한 것입니다 — 오타를 확인하고 특수 문자를 URL 인코딩하십시오. IP 화이트리스트 인증을 사용하는 공급자는 서버의 IP가 승인되면 자격 증명이 전혀 필요하지 않습니다.

PHP cURL이 SOCKS5 프록시를 사용할 수 있습니까?

예. socks5h:// URL을 CURLOPT_PROXY에 전달하거나 CURLOPT_PROXYTYPECURLPROXY_SOCKS5_HOSTNAME으로 설정하십시오. 호스트 이름 변형을 일반 socks5보다 선호하여 DNS가 서버가 아닌 프록시 종료 지점에서 해석되도록 하십시오 — 그렇지 않으면 모든 대상 도메인이 로컬 리졸버를 통해 누출됩니다.

CURLOPT_HTTPPROXYTUNNEL은 무엇이며 필요합니까?

이는 cURL이 프록시를 통해 CONNECT로 터널링하도록 강제하며, 프록시에게 URL을 가져오도록 요청하는 대신입니다. https:// 대상에 대해 cURL은 자동으로 터널링하므로, 이 옵션은 거의 모든 스크래핑 코드에서 중복됩니다. 단순 HTTP 대상에 대해 터널링 의미가 필요할 때만 설정하십시오.

PHP cURL을 통해 프록시의 타임아웃을 어떻게 설정합니까?

둘 다 설정하십시오: CURLOPT_CONNECTTIMEOUT은 프록시에 도달하고 터널을 여는 것을 다루고, CURLOPT_TIMEOUT은 전체 전송을 제한합니다. 밀리초 변형 (_MS)은 더 엄격한 예산에 사용됩니다. 이들이 없으면 중단된 종료가 요청을 유지하며 — PHP-FPM에서는 전체 작업자를 유지하며 — 웹 서버의 자체 제한이 이를 종료할 때까지 유지됩니다.

첫 번째 코드 조각을 복사하고, 체크리스트를 염두에 두면, PHP는 완벽히 견고한 스크래핑 언어입니다: 병렬성을 위한 curl_multi, ID를 위한 회전 게이트웨이, 사용성을 위한 Guzzle, 그리고 반격하는 페이지를 위한 Scraper API.

QuantumProxies 주거 IP에서 PHP 스크래퍼 실행