PHP 抓取动态页面

wen PHP项目 4

本文目录导读:

PHP 抓取动态页面

  1. 方案一:使用无头浏览器(推荐)
  2. 方案二:分析 XHR/API 接口(最佳实践)
  3. 方案三:寻找伪静态或服务器端渲染(SSR)后备方案
  4. 总结与建议

在 PHP 中抓取动态页面(即页面内容由 JavaScript 渲染生成)时,传统的 file_get_contents()curl 只能获取到未执行 JavaScript 的原始 HTML 源码,无法获取到渲染后的数据。

以下是几种常见的解决方案,从简单到复杂:

使用无头浏览器(推荐)

这是目前最主流的做法,通过 Headless Chrome(无头浏览器)来执行页面 JS,然后返回渲染后的 DOM 内容。

使用 Puppeteer + Node.js(跨语言调用)

虽然这是 Node.js 工具,但 PHP 可以通过 exec()shell_exec() 调用它,这是最稳定的方案。

  • 步骤 1:安装 Node.js 环境,并安装 Puppeteer(内含 Chromium 浏览器)。

  • 步骤 2:编写一个 Node.js 抓取脚本 scrape.js

    // scrape.js
    const puppeteer = require('puppeteer');
    async function scrape(url) {
        const browser = await puppeteer.launch({ 
            headless: 'new', 
            args: ['--no-sandbox', '--disable-setuid-sandbox'] 
        });
        const page = await browser.newPage();
        await page.goto(url, { waitUntil: 'networkidle0', timeout: 60000 });
        // 获取渲染后的 HTML 内容
        const content = await page.content(); 
        await browser.close();
        return content;
    }
    // 接收命令行参数
    const url = process.argv[2];
    scrape(url).then(html => {
        console.log(html);
    }).catch(err => {
        console.error('Error:', err.message);
        process.exit(1);
    });
  • 步骤 3:在 PHP 中调用该脚本:

    <?php
    function fetchDynamicPage($url) {
        $scriptPath = '/path/to/scrape.js';
        // 注意使用 escapeshellarg 防止注入
        $cmd = 'node ' . $scriptPath . ' ' . escapeshellarg($url) . ' 2>&1';
        $output = shell_exec($cmd);
        return $output;
    }
    $html = fetchDynamicPage('https://example.com/dynamic-page');
    // 接下来可以用 DOMDocument 或正则解析 $html
    ?>

使用 PHP 库:facebook/webdriverphp-webdriver/php-webdriver

这需要一个独立的 Selenium Server 来驱动浏览器。

  • 步骤 1:下载 Selenium Server(Java 包)并启动。

  • 步骤 2:通过 Composer 安装库:composer require php-webdriver/webdriver

  • 步骤 3:PHP 代码示例:

    <?php
    require_once 'vendor/autoload.php';
    use Facebook\WebDriver\Remote\RemoteWebDriver;
    use Facebook\WebDriver\Remote\DesiredCapabilities;
    use Facebook\WebDriver\Chrome\ChromeOptions;
    function fetchWithSelenium($url) {
        // 启动 Chrome 选项,启用 headless 模式
        $options = new ChromeOptions();
        $options->addArguments(['--headless', '--disable-gpu', '--no-sandbox']);
        $caps = DesiredCapabilities::chrome();
        $caps->setCapability(ChromeOptions::CAPABILITY, $options);
        // 连接 Selenium 服务器
        $driver = RemoteWebDriver::create('http://localhost:9515', $caps);
        $driver->get($url);
        // 等待特定元素加载(可选,提高稳定性)
        // $driver->wait(10)->until(
        //     WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::cssSelector('selector'))
        // );
        // 获取页面源代码
        $html = $driver->getPageSource();
        $driver->quit();
        return $html;
    }
    $html = fetchWithSelenium('https://example.com');
    ?>

    需要注意的是,这种方式需要 Java 环境,配置较繁琐。


使用 symfony/panther(纯 PHP 方案,基于 ChromeDriver)

Panther 是 Symfony 的一个组件,它封装了 ChromeDriver 的交互,API 类似于 Facebook WebDriver,但不需要独立的 Selenium Server,直接驱动 ChromeDriver。

<?php
require_once 'vendor/autoload.php';
use Symfony\Component\Panther\Client;
$client = Client::createChromeClient(); // 自动下载 Chromedriver(如果必要)
// 或使用已有的 Chrome 安装:Client::createChromeClient(null, ['--headless' => true]);
$crawler = $client->request('GET', 'https://example.com');
// 等待异步请求完成(等待某个元素出现)
$client->waitFor('.dynamic-content'); 
// 获取完整 HTML
$html = $client->getCrawler()->html();
// 也可以使用 DOMXPath 提取数据
// $values = $crawler->filter('.item')->extract(['_text']);
?>

优点:配置相对简单,如果是抓取单页面内容,效率较高。


分析 XHR/API 接口(最佳实践)

很多“动态页面”实际上是通过 AJAX 请求后端 JSON 接口渲染的。优先尝试在浏览器 F12 -> Network 面板中查找 XHR 请求,如果能找到数据接口,直接模拟该接口请求即可,性能最优。

// 假设页面渲染数据来自 /api/data?page=1
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com/api/data?page=1');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'); // 伪装浏览器
// 处理 Cookie 或添加 Referer 等 Header
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-Requested-With: XMLHttpRequest',
    'Referer: https://example.com/page'
]);
$response = curl_exec($ch);
curl_close($ch);
if ($response !== false) {
    $data = json_decode($response, true); // 直接处理 JSON 数据
    print_r($data);
}

寻找伪静态或服务器端渲染(SSR)后备方案

如果页面是 Vue/React 同构渲染(SSR),服务器会直接返回渲染后的 HTML,curl 就能获取到内容,如果页面是 Vue 的 SPA(单页应用) 且历史模式为 history,可以通过添加特殊参数(如 )或查看是否有 ?_escaped_fragment_= 的 SEO 版本。


总结与建议

方案 技术难度 性能消耗 稳定性 适用场景
API 直连 极低 能找到数据接口时
Puppeteer ⭐⭐⭐ 高(需Node) 复杂渲染、需要执行大量 JS 或需要登录
Panther ⭐⭐⭐ 彻底使用 PHP 技术栈且需要模拟交互
Selenium ⭐⭐⭐⭐ 需要复杂的浏览器交互操作(点击、滑动)

推荐路线

  1. 先看请求:优先使用 curl 模拟 AJAX 接口。
  2. 再看页面:如果你的页面是纯 JS 渲染且没有可逆的 API,推荐使用 Puppeteer + Node.js 配合 PHP 调用,这是目前生态最好、抗检测能力最强的方案。
  3. 最后才是:如果强制要求纯 PHP,使用 symfony/panther(需要服务器安装 Chrome 和 ChromeDriver)。

抱歉,评论功能暂时关闭!