Panduan ini membahas setiap bagian dari konfigurasi proxy Guzzle yang berfungsi: opsi permintaan proxy dalam bentuk string dan array, URL proxy yang diautentikasi, target HTTPS melalui terowongan CONNECT, batas waktu dan verifikasi sertifikat, serta pengecualian yang dilemparkan oleh Guzzle ketika proxy menolak permintaan. Pada akhirnya, Anda akan memiliki klien PHP yang dapat digunakan kembali yang merutekan melalui proxy residensial, mempertahankan satu alamat IP keluar selama satu sesi, memverifikasi alamat IP tersebut, dan mencoba kembali dengan sesi baru saat permintaan gagal.
Semua contoh ditujukan untuk Guzzle 7 dan PHP 8.1 atau yang lebih baru. Instal dengan composer require guzzlehttp/guzzle:^7.
Opsi proxy: string atau array
Guzzle mengonfigurasi proxy melalui opsi permintaan proxy, yang didokumentasikan di docs.guzzlephp.org. Anda dapat meneruskannya per permintaan atau dalam konstruktor klien sebagai nilai default. Bentuk paling sederhana adalah string, yang berlaku untuk semua protokol.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'proxy' => 'http://USERNAME:PASSWORD@premium-residential.geonode.com:9000',
'timeout' => 30,
]);
$response = $client->get('https://api.ipify.org?format=json');
echo $response->getBody(), PHP_EOL;
Bentuk array memungkinkan Anda memilih proxy yang berbeda per skema URI dan mengecualikan host tertentu. Terdapat tiga kunci yang dikenali: http, https, dan no.
<?php
$proxyUrl = 'http://USERNAME:PASSWORD@premium-residential.geonode.com:9000';
$client = new GuzzleHttp\Client([
'proxy' => [
'http' => $proxyUrl, // used for http:// targets
'https' => $proxyUrl, // used for https:// targets (CONNECT tunnel)
'no' => ['localhost', '.internal.example'], // never proxied
],
]);
Gunakan bentuk string jika semua lalu lintas harus melalui satu proxy. Gunakan bentuk array jika Anda memerlukan daftar no atau ingin permintaan HTTP biasa melewati kumpulan server pusat data sementara HTTPS melewati server perumahan.
Daftar no dan peringatan NO_PROXY
Guzzle membaca HTTP_PROXY, HTTPS_PROXY, dan NO_PROXY dari lingkungan saat Anda tidak menetapkan opsi proxy. Begitu Anda menetapkan salah satunya, otomatisasi tersebut berhenti, termasuk daftar pengecualian. Dokumen resmi secara eksplisit menyatakan bahwa Anda harus menetapkan no sendiri. Jika kontainer Anda menetapkan NO_PROXY untuk layanan internal, mengabaikan hal ini berarti panggilan tersebut tiba-tiba akan dialihkan melalui proxy perumahan.
$no = getenv('NO_PROXY') ? explode(',', getenv('NO_PROXY')) : [];
$client = new GuzzleHttp\Client([
'proxy' => [
'http' => $proxyUrl,
'https' => $proxyUrl,
'no' => $no,
],
]);
Otentikasi proxy Guzzle: kredensial dalam URL
Guzzle tidak memiliki opsi terpisah seperti proxy_auth. Kredensial dimasukkan ke dalam URL proxy sebagai userinfo (http://username:password@host:port), dan penangan cURL mengirimkannya sebagai Proxy-Authorization: Basic. Ada dua aturan praktis yang dapat disimpulkan dari hal tersebut.
Pertama, lakukan pengkodean persentase (percent-encode) pada bagian apa pun dalam kata sandi yang tidak aman untuk URL. Adanya @, :, atau / dalam kata sandi mentah akan mengganggu proses parsing URL dan menghasilkan kode status 407 yang membingungkan atau koneksi ke host yang salah. Bangun URL menggunakan rawurlencode(), bukan dengan menggabungkan string.
function proxyUrl(string $user, string $pass, int $port = 9000): string
{
return sprintf(
'http://%s:%s@premium-residential.geonode.com:%d',
rawurlencode($user),
rawurlencode($pass),
$port
);
}
Kedua, jangan sertakan kredensial dalam kode. Baca kredensial tersebut dari variabel lingkungan atau pengelola rahasia, dan jangan pernah mencatat URL proxy secara lengkap; keluaran Guzzle berupa debug menyertakan header Proxy-Authorization, jadi hapus bagian tersebut sebelum mengirim log ke mana pun.
Target HTTPS melalui proxy HTTP CONNECT
Hal yang sering membingungkan: skema URL proxy menjelaskan cara Anda berkomunikasi dengan proxy, bukan apa yang digunakan oleh target. Untuk target https://, Guzzle (melalui cURL) membuka koneksi HTTP biasa ke proxy, mengirimkan CONNECT host:443, lalu melakukan proses handshake TLS dengan target di dalam terowongan tersebut. Jadi, baik kunci http maupun https dalam bentuk array biasanya mengarah ke URL proxy http://. Menulis https://premium-residential.geonode.com:9000 akan memerintahkan cURL untuk membungkus lompatan proxy itu sendiri dalam TLS, yang tidak diharapkan oleh titik akhir.
Karena TLS berjalan dari ujung ke ujung di dalam terowongan, proxy tidak pernah melihat atau mengubah isi respons, dan verifikasi sertifikat normal Anda berlaku untuk situs tujuan persis seperti halnya tanpa proxy.
verify dan masalah sertifikat
Opsi verify mengontrol pemeriksaan sertifikat untuk situs tujuan. Biarkan pada nilai default true, yang menggunakan bundel CA sistem. Jika PHP tidak dapat menemukan bundel (umum terjadi pada gambar Docker minimal), arahkan verify ke berkas PEM daripada menonaktifkannya.
$client = new GuzzleHttp\Client([
'proxy' => $proxyUrl,
'verify' => '/etc/ssl/certs/ca-certificates.crt', // or true for the system default
]);
Mengatur verify => false untuk menonaktifkan pesan kesalahan yang muncul hanya setelah menambahkan proxy adalah kesalahan; terowongan CONNECT yang dikonfigurasi dengan benar tidak menyebabkan kesalahan sertifikat, jadi masalah sebenarnya ada di tempat lain, biasanya pada kekacauan skema https:// di atas.
Batas Waktu: timeout dan connect_timeout
Nilai default timeout pada Guzzle adalah 0, yang berarti menunggu tanpa batas waktu. Melalui proxy perumahan, pengaturan default ini buruk, karena perangkat keluar yang lambat dapat membuat pekerja terhenti tanpa batas waktu. Tetapkan dua nilai: connect_timeout membatasi koneksi TCP ke proxy dan hanya didukung oleh penangan cURL; timeout membatasi seluruh transfer dalam hitungan detik.
$client = new GuzzleHttp\Client([
'proxy' => $proxyUrl,
'connect_timeout' => 10, // seconds to reach the proxy
'timeout' => 45, // total seconds for the request
]);
Perangkat keluar residensial lebih lambat daripada yang ada di pusat data, jadi berikan batas waktu total yang lebih longgar daripada yang Anda berikan untuk permintaan langsung. Batas waktu koneksi yang terus-menerus terlewati menandakan adanya masalah jalur jaringan, bukan pada targetnya.
Menangani pengecualian dari proxy Guzzle
Guzzle melemparkan pengecualian bertipe, yang semuanya merupakan turunan dari GuzzleHttp\Exception\RequestException, dan dengan mengetahui jenis pengecualian yang Anda terima, Anda dapat mengetahui di mana kegagalan tersebut terjadi.
| Pengecualian | Kapan dilemparkan | Penyebab umum pada proxy |
|---|
ConnectException | Tidak ada respons HTTP sama sekali | Host proxy tidak dapat dijangkau, port salah, batas waktu koneksi habis, kegagalan DNS |
ClientException | Respons 4xx | Kredensial salah (407); 403/429 dari target |
ServerException | Respons 5xx | Kesalahan target, atau proxy tidak dapat menjangkau target |
RequestException | Seluruh kasus lainnya | Waktu tunggu habis setelah header, kegagalan TLS |
Kode 407 perlu diisolasi. Ini berarti proxy, bukan target, yang menolak permintaan, sehingga mencoba ulang tidak akan membantu sampai kredensial diubah.
<?php
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\RequestException;
try {
$response = $client->get('https://example.com/');
} catch (ConnectException $e) {
// Could not reach the proxy or the connect_timeout expired.
error_log('connect: ' . $e->getMessage());
} catch (ClientException $e) {
$status = $e->getResponse()->getStatusCode();
if ($status === 407) {
// Proxy rejected credentials: check username grammar and password.
throw new RuntimeException('proxy auth failed', 407, $e);
}
// 403, 404, 429 etc. come from the target site.
} catch (ServerException $e) {
// 5xx from the target (or the proxy failing to reach it).
} catch (RequestException $e) {
// Timeouts after connect, TLS errors and other transfer failures.
}
Menonaktifkan pengecualian dengan http_errors
Jika Anda lebih memilih untuk melakukan percabangan berdasarkan kode status daripada menangani pengecualian, atur http_errors => false. Guzzle kemudian akan mengembalikan respons untuk kode status apa pun, termasuk 407, dan Anda dapat memeriksa getStatusCode() sendiri. ConnectException tetap dilemparkan karena tidak ada respons yang dapat dikembalikan.
$response = $client->get('https://example.com/', ['http_errors' => false]);
if ($response->getStatusCode() === 407) {
// handle auth failure without an exception
}
Diagnostik: on_stats dan debug
Ketika permintaan proxy Guzzle berjalan lambat atau gagal secara sporadis, ada dua opsi yang dapat memberi tahu Anda apa yang terjadi pada saluran data. on_stats menerima objek GuzzleHttp\TransferStats setelah setiap transfer, yang berisi waktu total serta statistik penangan cURL seperti waktu pencarian nama, koneksi, dan TLS.
use GuzzleHttp\TransferStats;
$client->get('https://example.com/', [
'on_stats' => function (TransferStats $stats) {
printf("%s took %.3fs\n", $stats->getEffectiveUri(), $stats->getTransferTime());
if (!$stats->hasResponse()) {
var_dump($stats->getHandlerErrorData());
}
},
]);
debug => true mengaktifkan CURLOPT_VERBOSE dan menampilkan pertukaran CONNECT, termasuk baris respons proxy. Gunakan sekali untuk memastikan terowongan telah terbentuk, lalu hapus: ini akan menulis kredensial ke STDOUT. Menggunakan aliran fopen() alih-alih true akan mengarahkan keluaran ke berkas.
Sesi tetap di port 10000
Port 9000 mengganti alamat IP keluar pada setiap permintaan. Untuk alur multi-langkah (masuk, lalu memuat halaman, lalu memuat halaman berikutnya), Anda memerlukan alamat IP yang sama, dan itulah yang disediakan oleh port 10000. Sesi dipilih berdasarkan nama pengguna, bukan header: tambahkan -session-XXXXXXXX dengan ID alfanumerik 8 karakter, dan secara opsional -lifetime-SECONDS dari 180 hingga 86400. Sufiks penargetan seperti -country-us dimasukkan ke dalam string yang sama.
<?php
function stickyProxyUrl(string $baseUser, string $pass, string $country = 'us', int $lifetime = 600): string
{
$session = substr(bin2hex(random_bytes(4)), 0, 8); // 8 alphanumeric chars
$user = sprintf('%s-country-%s-session-%s-lifetime-%d', $baseUser, $country, $session, $lifetime);
return sprintf(
'http://%s:%s@premium-residential.geonode.com:10000',
rawurlencode($user),
rawurlencode($pass)
);
}
$client = new GuzzleHttp\Client([
'proxy' => stickyProxyUrl(getenv('GEONODE_USER'), getenv('GEONODE_PASS')),
'timeout' => 45,
]);
Setiap permintaan dari $client kini berbagi satu alamat IP keluar selama sepuluh menit. Buat klien baru (atau berikan opsi proxy baru) jika Anda ingin alamat IP yang berbeda.
Memverifikasi alamat IP keluar
Sebelum mempercayai suatu sesi, pastikan apa yang akan dilihat oleh target. Akses layanan echo IP melalui klien yang sama dua kali: dengan sesi yang melekat, kedua panggilan akan mengembalikan alamat yang sama; dengan port 9000, keduanya akan berbeda.
$first = json_decode((string) $client->get('https://api.ipify.org?format=json')->getBody(), true);
$second = json_decode((string) $client->get('https://api.ipify.org?format=json')->getBody(), true);
printf("exit IP: %s (stable: %s)\n", $first['ip'], $first['ip'] === $second['ip'] ? 'yes' : 'no');
Pembantu percobaan ulang sederhana yang mengganti ID sesi
Alamat IP pengguna dapat hilang di tengah sesi, dan situs tujuan terkadang memblokir alamat IP tertentu. Pendekatan praktisnya adalah mencoba kembali permintaan yang gagal dengan ID sesi baru, yang tidak hanya mengubah alamat IP exit tetapi juga menghindari penggunaan ulang koneksi yang sudah dalam kondisi buruk. Alat bantu di bawah ini membangun ulang klien pada setiap upaya dan hanya melakukan percobaan ulang saat terjadi kegagalan koneksi, timeout, dan kode status yang menandakan pemblokiran.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\ResponseInterface;
function fetchWithNewSession(string $url, int $attempts = 3): ResponseInterface
{
$lastError = null;
for ($i = 1; $i <= $attempts; $i++) {
$client = new Client([
'proxy' => stickyProxyUrl(getenv('GEONODE_USER'), getenv('GEONODE_PASS')),
'connect_timeout' => 10,
'timeout' => 45,
'http_errors' => false,
]);
try {
$response = $client->get($url);
$status = $response->getStatusCode();
if ($status === 407) {
throw new RuntimeException('proxy credentials rejected'); // do not retry
}
if (!in_array($status, [403, 429, 502, 503, 504], true)) {
return $response;
}
$lastError = new RuntimeException("status $status");
} catch (ConnectException | RequestException $e) {
$lastError = $e;
}
usleep(500_000 * $i); // simple linear backoff
}
throw new RuntimeException("gave up after $attempts attempts", 0, $lastError);
}
Kode yang sama juga berfungsi dengan port 9000 jika Anda menghapus sufiks sesi, karena setiap upaya tetap akan mendapatkan alamat IP baru. Jaga agar jumlah upaya tetap rendah: target yang merespons dengan kode 403 tiga kali berturut-turut memberi tahu Anda sesuatu tentang sidik jari permintaan, bukan tentang proxy.
Kesalahan umum
Menggunakan skema https:// dalam URL proxy
Lompatan proxy menggunakan HTTP biasa; HTTPS tujuan berjalan di dalam terowongan CONNECT. Kedua kunci array tersebut harus berisi URL proxy http://. Gejala yang umum terjadi adalah munculnya ConnectException yang menyebutkan proses handshake SSL dengan host proxy.
Melupakan daftar no setelah menetapkan proxy
Setelah Anda melewati proxy, NO_PROXY akan diabaikan. Pemeriksaan kesehatan internal, titik akhir metadata, dan basis data yang diakses melalui HTTP akan melewati proxy residensial dan gagal, atau lebih buruk lagi, bocor. Selalu lewati no secara eksplisit.
Membiarkan timeout bernilai nol
Secara default, Guzzle akan menunggu selamanya. Satu koneksi residen yang macet saja dapat menahan pekerja antrian hingga supervisor menghentikannya. Atur baik connect_timeout maupun timeout pada setiap klien yang diproksi.
Mencoba Kembali Saat Mendapat Kode 407
Kode 407 berarti proxy menolak kredensial. Mencoba kembali dengan nama pengguna dan kata sandi yang sama hanya akan membuang-buang waktu. Segera hentikan upaya, catat nama pengguna (jangan pernah mencatat kata sandi), dan periksa sufiks tata bahasa.
Langkah selanjutnya
Konfigurasi di atas dapat digunakan tanpa perubahan dengan proxy residensial Geonode: 2,5 juta alamat IP online di lebih dari 195 negara, HTTP, HTTPS, dan SOCKS5 pada host yang sama, serta sesi tetap hingga 24 jam yang dipilih sepenuhnya melalui nama pengguna. Penargetan berdasarkan negara, provinsi, kota, dan ASN menggunakan sintaks yang sama seperti yang ditunjukkan di sini, sehingga tidak diperlukan perubahan kode saat Anda mempersempit lokasi keluar. Paket yang tersedia adalah pay-as-you-go tanpa kontrak, mulai dari 10 GB seharga $7,92 per bulan, dan uji coba selama 3 hari seharga $5. Lihat proksi perumahan untuk detail lengkapnya, atau bandingkan tingkatan layanan di halaman harga. Untuk pola pengikisan data di luar pengaturan proxy, kasus penggunaan pengikisan web mencakup sisa alur kerja.
Pertanyaan yang Sering Diajukan
Bagaimana cara mengatur proxy Guzzle untuk setiap permintaan dari satu klien?
Tentukan opsi proxy di konstruktor klien, bukan per permintaan. new Client(['proxy' => 'http://user:pass@host:port']) menerapkan proxy ke setiap permintaan yang dikirim klien, dan opsi proxy tingkat permintaan apa pun akan menggantikannya. Ini adalah pola standar ketika semua lalu lintas dari seorang pekerja harus melewati penyedia yang sama.
Mengapa proxy Guzzle saya berfungsi untuk URL http:// tetapi gagal untuk target https://?
Biasanya kunci https dalam bentuk array tidak ada atau mengarah ke skema https://. Guzzle meneruskan target HTTPS melalui proxy HTTP dengan metode CONNECT, sehingga URL proxy untuk kunci https harus tetap dimulai dengan http://. Periksa bentuk array, dan pastikan port proxy mendukung metode CONNECT, yang didukung oleh port-port Geonode.
Apa arti 407 Proxy Authentication Required di Guzzle?
Proksi menolak kredensial Anda sebelum meneruskan apa pun. Guzzle menampilkannya sebagai ClientException dengan status 407 karena ini merupakan respons 4xx. Periksa format nama pengguna, kata sandi, dan pastikan karakter khusus dalam kata sandi telah dienkode dengan tanda persen (percent-encoded) dalam URL. Dengan http_errors disetel ke false, Anda akan mendapatkan objek respons 407 alih-alih pengecualian.
Apakah Guzzle menghormati variabel lingkungan HTTP_PROXY dan NO_PROXY?
Ya, jika Anda tidak menetapkan opsi proxy, Guzzle akan membaca nilai HTTP_PROXY, HTTPS_PROXY, dan NO_PROXY dari variabel lingkungan. Begitu Anda menetapkan opsi proxy sendiri, penanganan otomatis NO_PROXY akan berhenti, sehingga Anda perlu menetapkan kunci no secara eksplisit, biasanya explode(',', getenv('NO_PROXY')).
Bisakah saya mempertahankan alamat IP keluar yang sama di beberapa permintaan Guzzle?
Ya, gunakan sesi yang melekat (sticky session). Arahkan proxy Guzzle ke port 10000 dan tambahkan -session-XXXXXXXX (8 karakter alfanumerik) ditambah sufiks -lifetime-SECONDS opsional ke nama pengguna. Setiap permintaan yang menggunakan kembali nama pengguna tersebut akan berbagi satu alamat IP keluar selama masa berlaku yang Anda tentukan, antara 180 detik hingga 24 jam.
Apakah ada batasan yang perlu saya pertimbangkan saat melakukan scraping melalui proxy Guzzle?
Bandwidth perumahan dihitung per gigabyte, sehingga halaman HTML dan gambar berukuran besar akan menambah penggunaan. Sesi yang melekat (sticky sessions) berlangsung paling lama 24 jam dan dapat berakhir lebih awal jika perangkat hulu terputus, sehingga kode harus dapat menyesuaikan dengan perubahan alamat IP keluar. Patuhi juga ketentuan situs target, aturan robot, dan batasan laju; proxy tidak mengubah kewajiban hukum Anda.