PHP curl 发起 GET 请求最简版本是什么样?

PHP curl 发起 GET 请求最简版本只需要三步:curl_init 初始化、curl_setopt 设置参数、curl_exec 执行请求,加上必要的错误检查和资源释放。

<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com/api/data');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);

$response = curl_exec($ch);

if (curl_errno($ch)) {
    echo 'Curl error: ' . curl_error($ch);
} else {
    echo $response;
}

curl_close($ch);

这段代码可以跑起来,但只适合调试和调用完全开放的公共 API。真正的数据采集场景,这段代码在生产环境活不过一天——原因不在 curl 本身,而在缺失的三样东西:合理的 headers、代理 IP、错误处理与重试。

生产环境最小可用版本至少要加上 headers 设置、代理配置、超时控制、SSL 校验开关、跟随重定向选项。下文分五个模块拆解。

GET 请求必须带的 headers 有哪些?

GET 请求必须带的 headers 至少包括:User-Agent、Accept、Accept-Language、Accept-Encoding,更贴近浏览器的还需要 Referer、Connection、Sec-Fetch 系列。

对采集来说,headers 组合的一致性比单个字段的具体值更重要。一个"看起来像浏览器"的请求头组合,比只设了一个 UA 但其他字段全空的组合成功率高得多。

<?php
$headers = [
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
        . 'AppleWebKit/537.36 (KHTML, like Gecko) '
        . 'Chrome/120.0.0.0 Safari/537.36',
    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8',
    'Accept-Encoding: gzip, deflate, br',
    'Connection: keep-alive',
    'Upgrade-Insecure-Requests: 1',
    'Sec-Fetch-Site: same-origin',
    'Sec-Fetch-Mode: navigate',
    'Sec-Fetch-User: ?1',
    'Sec-Fetch-Dest: document',
    'Referer: https://example.com/',
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com/api/data');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_ENCODING, ''); // 自动解压
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);

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

CURLOPT_ENCODING 传空字符串是关键——它会让 curl 主动接受 gzip、deflate、br 压缩,并自动解压;不设这一项,拿到的响应可能是一堆乱码。

curl 如何接入代理 IP?

curl 接入代理只需两行:CURLOPT_PROXY 设代理地址、CURLOPT_PROXYUSERPWD 设账密。类型上支持 HTTP、HTTPS、SOCKS4、SOCKS5,通过 CURLOPT_PROXYTYPE 指定。

HTTP 代理接入

<?php
curl_setopt($ch, CURLOPT_PROXY, 'proxy.example.com:8080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'your_user:your_pass');

SOCKS5 代理接入

<?php
curl_setopt($ch, CURLOPT_PROXY, 'proxy.example.com:1080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'your_user:your_pass');

从 API 拉取代理 IP 并轮换

<?php
function fetchProxyList($apiUrl) {
    $raw = file_get_contents($apiUrl);
    $lines = array_filter(array_map('trim', explode("\n", $raw)));
    return array_values($lines);
}

function pickProxy(array $pool) {
    return $pool[array_rand($pool)];
}

$pool = fetchProxyList('http://proxy-api.example.com/fetch?num=20');
$proxy = pickProxy($pool);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://target.example.com/api/list');
curl_setopt($ch, CURLOPT_PROXY, $proxy);
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);

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

生产环境不建议每次请求都调 API 拉 IP——API 有调用频次限制,并且每次拉取有网络延迟。建议启动时拉一批缓存到本地(内存或 Redis),按失效反馈动态补充。

代理类型适用场景注意点
HTTP 代理大部分 Web 采集HTTPS 目标同样通过 HTTP 代理转发
HTTPS 代理需要代理与客户端加密通信较少用,配置需匹配代理端证书
SOCKS5 代理非 HTTP 协议采集、部分特殊网络支持 UDP 转发,通用性强

curl 超时、重试、错误处理怎么做?

超时、重试、错误处理是决定采集稳定性的三个基础模块,缺一个就等于把稳定性交给运气。

双超时策略

<?php
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);  // 连接建立超时
curl_setopt($ch, CURLOPT_TIMEOUT, 15);         // 整个请求超时

连接超时短(3-5 秒),整体超时稍长(10-30 秒)。连接超时短是为了快速淘汰不可用的代理 IP,整体超时长是为了容忍目标站点的慢响应。

带重试的完整封装

<?php
function fetchWithRetry($url, array $pool, array $headers, $maxRetry = 3) {
    for ($i = 0; $i < $maxRetry; $i++) {
        $proxy = $pool[array_rand($pool)];
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_PROXY => $proxy,
            CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_CONNECTTIMEOUT => 5,
            CURLOPT_TIMEOUT => 15,
            CURLOPT_SSL_VERIFYPEER => false,
        ]);

        $response = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_error($ch);
        curl_close($ch);

        if ($response !== false && $code === 200 && strlen($response) > 500) {
            return ['ok' => true, 'body' => $response];
        }

        usleep(mt_rand(500000, 1500000)); // 0.5-1.5 秒随机延迟
    }
    return ['ok' => false, 'body' => null, 'last_error' => $err ?? null];
}

错误处理要区分四种情况:网络层错误(curl_error 非空)、HTTP 4xx、HTTP 5xx、内容异常(200 但内容为空或结构错误)。四种情况的重试策略不同——网络错误立即换 IP 重试,HTTP 4xx 中的 403、429 换 IP 并延长冷却,5xx 稍等再试,内容异常先记录再重采。

采集乱码怎么解决?

采集乱码 99% 的情况是编码不匹配,解决顺序是:先看响应头 Content-Type 的 charset 声明,再看 HTML 里的 meta charset,最后靠 mb_detect_encoding 兜底。

<?php
function normalizeEncoding($html) {
    // 优先从 Content-Type 或 meta 提取
    if (preg_match('/<meta[^>]+charset=["\']?([a-zA-Z0-9\-]+)/i', $html, $m)) {
        $srcCharset = strtoupper($m[1]);
    } else {
        $srcCharset = mb_detect_encoding($html, ['UTF-8', 'GBK', 'GB2312', 'BIG5'], true);
    }

    if ($srcCharset && $srcCharset !== 'UTF-8') {
        $html = mb_convert_encoding($html, 'UTF-8', $srcCharset);
    }
    return $html;
}

启用了 CURLOPT_ENCODING => '' 之后,gzip、deflate、br 压缩会自动解压,这一步无需手动处理。剩下要处理的是字符集(UTF-8、GBK 之间的转换)。

如果响应体本身声明的字符集与实际字节流不符(部分老站点有这种问题),mb_detect_encoding 可能判错。这时可以传入一个候选列表按顺序尝试,直到转换后无乱码为止。

UA、Referer 如何轮换?

UA 与 Referer 轮换的关键是"组合合理"——UA 与 Referer 要匹配,不能出现"移动端 UA + 桌面版 Referer"这种破绽。

UA 池分类管理

<?php
$uaPool = [
    'desktop_chrome' => [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    ],
    'desktop_firefox' => [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
    ],
    'mobile_safari' => [
        'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
    ],
];

function pickUa($category, array $uaPool) {
    return $uaPool[$category][array_rand($uaPool[$category])];
}

function pickReferer($category) {
    $map = [
        'desktop_chrome' => 'https://www.google.com/',
        'desktop_firefox' => 'https://www.baidu.com/',
        'mobile_safari' => 'https://m.google.com/',
    ];
    return $map[$category] ?? 'https://www.google.com/';
}

对同一目标站点,建议一个采集任务内固定使用一类 UA(比如都用 desktop_chrome),不要在 desktop 与 mobile 之间频繁横跳。频繁横跳会让站点侧看到"同一 IP 一会儿桌面一会儿手机"的可疑组合。

GET 返回被降级或被限制怎么判断?

GET 返回被降级或被限制的判断有四个信号:HTTP 状态码、响应体长度、关键字段完整性、页面标题内容。四个信号交叉印证,单看任何一个都可能误判。

判断清单:

  • HTTP 状态码:403、429、503 明确表示限制;301、302 跳转到验证页也是限制信号
  • 响应体长度:如果历史成功响应普遍 20KB 以上,突然只有 2KB,大概率是精简版或错误页
  • 关键字段:JSON 接口检查关键字段是否存在,HTML 检查页面结构标签是否完整
  • 页面标题:检查 <title> 是否包含"验证"、"访问受限"、"稍后再试"等提示
  • 响应时间:异常快(几十毫秒)或异常慢(超过 10 秒)的响应都值得警惕,常伴随简化返回

php

<?php
function isDegraded($response, $code, $expectedFields = []) {
    if ($code !== 200) return true;
    if (strlen($response) < 500) return true;
    if (preg_match('/(访问受限|请稍后|验证|blocked|access denied)/iu', $response)) return true;
    foreach ($expectedFields as $field) {
        if (strpos($response, $field) === false) return true;
    }
    return false;
}

被判定降级的响应不应直接进入主数据库,而是走一个"待复采队列",换 IP、换 UA、隔一段时间再试。三次以上都被降级的目标,升级到人工复核队列。

FAQ

Q:PHP curl 采集为什么经常出现 SSL 证书错误?

SSL 证书错误多数是本地 CA 证书不全或过期。快速方案是设置 CURLOPT_SSL_VERIFYPEER => false 跳过校验,但只适合内部采集环境;正规做法是从 curl 官网下载最新的 cacert.pem,通过 CURLOPT_CAINFO 指定路径,这样既保证安全又不会出错。

Q:curl 采集速度慢怎么优化?

优化点从上到下:一是用 curl_multi_* 系列并发发起多个请求;二是启用 keep-alive 减少连接建立开销;三是合理设置连接超时(3-5 秒)快速淘汰慢代理;四是把 DNS 解析结果缓存起来,避免每次请求都走 DNS。并发数不建议超过 50,再高会给本机 IO 和目标站点都造成压力。

Q:curl_multi 并发采集怎么写?

curl_multi_init 建一个多任务句柄,循环 curl_multi_add_handle 加入子句柄,然后 curl_multi_exec 执行,curl_multi_select 等待。完成后 curl_multi_getcontent 取结果、curl_multi_remove_handle 清理。并发数建议控制在 20-50 之间,过高会导致本机文件描述符耗尽。

Q:采集到的 JSON 解析失败是什么原因?

大多数情况是响应内容前后有多余字符——比如 BOM 头、HTML 错误页残留、或者站点用 JSONP 包了一层 callback。解决方式是先 trim 掉两端空白与 BOM,再检查是否被 callback 包裹,必要时正则提取花括号之间的内容再 json_decode

Q:PHP curl 支持 HTTP/2 吗?

支持,前提是编译时链接了 nghttp2 库。通过 CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0 指定。部分站点强制要求 HTTP/2 才返回完整数据,遇到"数据显著缺失"的情况可以试试切到 HTTP/2。

Q:代理 IP 生效但采集依然失败,可能是哪些原因?

按可能性从大到小:请求 headers 不完整或组合可疑、单 IP 请求间隔太短、同一 UA 反复使用、Cookie 没有 IP 级隔离、TLS 指纹被识别、目标接口需要额外的签名或时间戳参数。IP 只是入场券,行为特征才是留场券。

青果网络代理IP - CTA Banner
点赞(34)
数据采集实战复盘:企业级避坑清单与5类失败根因
数据采集 企业级代理 代理IP池 动态代理
2026-08-19

企业级数据采集的失败很少来自单一原因,多是网络层、协议层、风控层、数据层、合规层的问题叠加。本篇按5层给出可对照的避坑清单,并附3个脱敏案例说明"看似同一个问题、根因不同"的诊断路径。

2026HTTP代理服务商9家对比分析:全维度性能与多场景应用综合对比
HTTP代理 代理服务商 IP代理 IP池
2026-08-18

HTTP代理选型的分水岭不是"谁 IP 池大、谁可用率高",而是"产品类型、协议边界、计费方式和业务场景是否吻合"。青果网络以短效/隧道/独享/长效四类产品覆盖 APP 大数据分析、网站采集器、拓客数据三类主流场景;极安代理适合预算敏感的中小团队;被点名的另外 7 家 HTTP 代理各有差异化落点,其中协议边界差异会直接影响选型可行性。

503服务不可用怎么解决?服务器维护完整排查指南
IP代理 代理IP池 国内代理IP
2026-08-13

503 Service Unavailable表示服务器暂时无法处理请求,常见成因包括后端过载、反向代理超时、维护模式未关闭、资源耗尽、配置错误和依赖服务故障六类,按成因对症排查可将恢复时间从小时级缩短到分钟级。

爬虫采集如何降低请求失败率?先从IP策略开始
爬虫代理IP 数据采集 代理IP池 SOCKS5代理
2026-08-12

请求失败率降不下来,先别急着加重试和延时。IP策略是失败率的第一杠杆,池型选择、复用节奏、切换阈值三个维度调对,重试次数往往能少一半。本文按失败分层、池型组合、模式适配、非IP因素、排查清单五步走。

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部