如何正确抓取动态渲染网页的标题(如AniList)

12次阅读

如何正确抓取动态渲染网页的标题(如 AniList)

本文详解为何传统 php dom 解析无法获取 javascript 动态更新的页面标题,并提供基于 api 调用与无头浏览器的两种可靠解决方案,附完整代码示例与实践建议。

本文详解为何传统 php dom 解析无法获取 javascript 动态更新的页面标题,并提供基于 api 调用与无头浏览器的两种可靠解决方案,附完整代码示例与实践建议。

在开发网页信息提取功能时,许多开发者会使用 file_get_contents() + DOMDocument 的方式解析 HTML 并提取

或 Open Graph 标签(如 <meta property="og:title">)。这种方式对静态网站(如早期 MyAnimeList)效果良好,但面对现代前端框架构建的单页应用(SPA),例如 <a href="https://www.php.cn/link/84a98e9bea194d59e442e2be756a2e08" rel="nofollow" target="_blank">AniList</a>,它往往失效——你得到的不是动画实际名称(如 “Pokémon”),而是站点默认标题 “AniList”。</p> <p> 根本原因在于:<strong>AniList 使用 Vue.js 在客户端动态注入内容并修改 <title> 和 og:title 标签 。当你用 PHP 的 file_get_contents_curl() 获取原始 HTML 时,拿到的是未执行 JS 的“骨架页面”,此时 尚未被 Vue 渲染引擎填充(或仍为占位值),而 data-vue-meta=”true” 正是 Vue Meta 插件的典型标记,印证了该行为。

✅ 正确方案一:优先调用官方 API(推荐)

AniList 提供稳定、结构化且无需渲染的 GraphQL API,可精准获取动画元数据:

function getAniListTitleById($animeId) {$query = 'query ($id: Int!) {Media(id: $id, type: ANIME) {title { romaji english native} } }';     $variables = ['id' => (int)$animeId];      $payload = json_encode(['query' => $query, 'variables' => $variables]);     $ch = curl_init('https://graphql.anilist.co');     curl_setopt_array($ch, [         CURLOPT_RETURNTRANSFER => true,         CURLOPT_POST => true,         CURLOPT_POSTFIELDS => $payload,         CURLOPT_HTTPHEADER => ['Content-Type: application/json'],     ]);      $response = curl_exec($ch);     curl_close($ch);      $data = json_decode($response, true);     if (isset($data['data']['Media']['title']['romaji'])) {return $data['data']['Media']['title']['romaji']; // e.g. "Pocket Monsters"     }     return $data['data']['Media']['title']['english'] ?? 'Unknown Title'; }  // 示例:https://www.php.cn/link/84a98e9bea194d59e442e2be756a2e08/anime/527/Pocket-Monsters/ echo getAniListTitleById(527); // 输出:Pocket Monsters

✅ 优势:响应快、稳定性高、免反爬、支持批量查询、字段语义清晰(含多语言标题)。
⚠️ 注意:需解析 URL 中的 ID(如 /anime/527/ → 527),可借助正则 #/anime/(d+)/# 提取。

✅ 正确方案二:服务端渲染(SSR)/ 无头浏览器

若必须从任意 URL(非 AniList)提取最终渲染后标题,需模拟真实浏览器环境。推荐使用轻量级无头方案,如 Puppeteer(Node.js)或其 PHP 封装(如 spatie/browsershot):

composer require spatie/browsershot
use SpatieBrowsershotBrowsershot;  function getTitleFromRenderedPage($url) {try {         // 截图非必需,此处仅等待 JS 执行完成并提取 document.title         $title = Browsershot::url($url)             ->setOption('waitUntil', 'networkidle0')             ->evaluate("document.title");         return trim($title);     } catch (Exception $e) {return 'Failed to render page:' . $e->getMessage();     } }  echo getTitleFromRenderedPage('https://www.php.cn/link/84a98e9bea194d59e442e2be756a2e08/anime/527/Pocket-Monsters/'); // 输出:Pokémon —— 真实浏览器最终呈现的标题 

⚠️ 注意事项:

  • 需部署 Chrome/Chromium 环境,增加服务器资源开销;
  • 响应延迟显著高于 API 方案(通常 1–3 秒);
  • 频繁请求可能触发风控,建议添加合理限速与 User-Agent。

❌ 为什么原代码失效?关键总结

环节 原代码行为 实际问题
数据获取 file_get_contents_curl() 仅拉取初始 HTML 未执行 JS,og:title 为空或为默认值
DOM 解析 DOMDocument::loadHTML() 解析静态结构 无法感知运行时 DOM 变更
判断逻辑 依赖 property=”og:title” 属性存在即取值 属性虽存在,但 content 值尚未被 JS 填充

? 核心结论 :对于任何依赖客户端 JavaScript 渲染关键元信息的网站(如 AniList、React/Vue/Angular 应用),纯服务端 HTML 解析注定失败。务必转向 API 优先策略;若无 API,则必须引入浏览器环境。

选择方案时,请始终遵循: 有 API → 用 API;无 API → 用无头浏览器;绝不依赖静态 HTML 抓取动态标题。

text=ZqhQzanResources