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

2次阅读

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

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

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

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

或 Open Graph 标签(如 <meta property="og:title">)。这种方式对静态网站(如早期 MyAnimeList)效果良好,但面对现代<a href="https://seo.sqjnqi.com/tag/%e5%89%8d%e7%ab%af%e6%a1%86%e6%9e%b6/"><b>前端框架</b></a>构建的单页应用(SPA),例如 <a href="https://www.php.cn/link/84a98e9bea194d59e442e2be756a2e08" rel="nofollow" target="_blank">AniList</a>,它往往失效——你得到的不是动画实际名称(如 “Pokémon”),而是站点默认标题 “AniList”。</p> <p>根本原因在于:<strong>AniList 使用 Vue.<a href="https://seo.sqjnqi.com/tag/js/"><b>js</b></a> 在客户端动态注入内容并修改 <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