通过 AJAX 加载 SPA 网页
Posted
技术标签:
【中文标题】通过 AJAX 加载 SPA 网页【英文标题】:Load a SPA webpage via AJAX 【发布时间】:2018-01-22 14:35:04 【问题描述】:我正在尝试通过插入 URL 来使用 javascript 获取整个网页。但是,该网站是作为单页应用程序 (SPA) 构建的,它使用 JavaScript / backbone.js 在呈现初始响应后动态加载其大部分内容。
例如,当我路由到以下地址时:
https://connect.garmin.com/modern/activity/1915361012
然后在控制台中输入这个(页面加载后):
var $page = $("html")
console.log("%c✔: ", "color:green;", $page.find(".inline-edit-target.page-title-overflow").text().trim());
console.log("%c✔: ", "color:green;", $page.find("footer .details").text().trim());
然后我会得到动态加载的活动标题以及静态加载的页脚:
但是,当我尝试使用 $.get()
或 .load()
通过 AJAX 调用加载网页时,我只会收到初始响应(与查看时的内容相同 -来源):
view-source:https://connect.garmin.com/modern/activity/1915361012
因此,如果我使用以下任一 AJAX 调用:
// jQuery.get()
var url = "https://connect.garmin.com/modern/activity/1915361012";
jQuery.get(url,function(data)
var $page = $("<div>").html(data)
console.log("%c✖: ", "color:red;", $page.find(".page-title").text().trim());
console.log("%c✔: ", "color:green;", $page.find("footer .details").text().trim());
);
// jQuery.load()
var url = "https://connect.garmin.com/modern/activity/1915361012";
var $page = $("<div>")
$page.load(url, function(data)
console.log("%c✖: ", "color:red;", $page.find(".page-title").text().trim() );
console.log("%c✔: ", "color:green;", $page.find("footer .details").text().trim());
);
我仍会获得初始页脚,但不会获得任何其他页面内容:
我已经尝试了 solution here 到 eval()
的每个 script
标记的内容,但这看起来不够健壮,无法实际加载页面:
jQuery.get(url,function(data)
var $page = $("<div>").html(data)
$page.find("script").each(function()
var scriptContent = $(this).html(); //Grab the content of this tag
eval(scriptContent); //Execute the content
);
console.log("%c✖: ", "color:red;", $page.find(".page-title").text().trim());
console.log("%c✔: ", "color:green;", $page.find("footer .details").text().trim());
);
问:是否有任何选项可以完全加载可通过 JavaScript 抓取的网页?
【问题讨论】:
最终目标是什么?如果您想获取数据,也许直接访问原始数据更容易(取决于您的来源,显然如果您知道您的 ID,您可以在没有 cookie 或任何东西的情况下获取数据like that)。如果你真的想加载整个页面然后从 DOM 中挖掘数据,唯一通用的解决方案是使用“可测量”的无头浏览器,例如 PhantomJS 或 Headless Chrome 【参考方案1】:您永远无法完全复制任意 (SPA) 页面的功能。
我看到的唯一方法是使用无头浏览器,例如 PhantomJS 或 Headless Chrome 或 Headless Firefox。
我想尝试 Headless Chrome,所以让我们看看它可以对您的页面做什么:
使用内部 REPL 快速检查
使用 Chrome Headless 加载该页面(您需要在 Mac/Linux 上使用 Chrome 59,在 Windows 上需要 Chrome 60),并使用来自 REPL 的 JavaScript 查找页面标题:
% chrome --headless --disable-gpu --repl https://connect.garmin.com/modern/activity/1915361012
[0830/171405.025582:INFO:headless_shell.cc(303)] Type a Javascript expression to evaluate or "quit" to exit.
>>> $('body').find('.page-title').text().trim()
"result":"type":"string","value":"Daily Mile - Round 2 - Day 27"
注意:要让chrome
命令行在 Mac 上运行,我事先已经这样做了:
alias chrome="'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'"
以编程方式使用 Node 和 Puppeteer
Puppeteer 是一个 Node 库(由 Google Chrome 开发人员提供),它提供了一个高级 API 来通过 DevTools 协议控制无头 Chrome。它还可以配置为使用完整(非无头)Chrome。
(第 0 步:安装 Node 和 Yarn 如果你没有它们)
在新目录中:
yarn init
yarn add puppeteer
用这个创建index.js
:
const puppeteer = require('puppeteer');
(async() =>
const url = 'https://connect.garmin.com/modern/activity/1915361012';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Go to URL and wait for page to load
await page.goto(url, waitUntil: 'networkidle');
// Wait for the results to show up
await page.waitForSelector('.page-title');
// Extract the results from the page
const text = await page.evaluate(() =>
const title = document.querySelector('.page-title');
return title.innerText.trim();
);
console.log(`Found: $text`);
browser.close();
)();
结果:
$ node index.js
Found: Daily Mile - Round 2 - Day 27
【讨论】:
【参考方案2】:首先:避免eval
- 你的内容安全策略应该阻止它,它会让你容易受到 XSS 攻击。抓取机器人肯定不会运行它。
您所描述的问题对于所有 SPA 来说都是常见的 - 当一个人访问时,他们会获得您的应用程序 shell 脚本,然后加载其余内容 - 一切都很好。当机器人访问时,它们会忽略脚本并返回空壳。
解决方案是服务器端渲染。一种方法是,如果您在服务器上使用 JS 渲染器(例如 React)和 Node.js,您可以相当轻松地构建 JS 并静态提供它。
但是,如果您不是,那么您需要在您的服务器上运行一个无头浏览器,该浏览器执行用户将执行的所有 JS,然后将结果提供给机器人。
幸运的是,其他人已经有done all the work here。他们在网上放了一个演示,你可以try out with your site:
【讨论】:
我同意避免 eval,但最终需要一种方法来触发加载页面内容的脚本。 Garmin.com 不是 我的 站点,因此我无法启用任何服务器端渲染解决方案。 Renderton 似乎并没有像简单地导航到页面那样实际加载后续内容,至少在尝试几次之后不会(即使您包含的屏幕截图也是空白的)。但是无头浏览器可能值得探索。 @KyleMit 你不需要eval
。代替 AJAX 将 <script>
标记添加到引用您要执行的脚本的页面。我不推荐它,但你可以使用$.getScript
in jQuery。
@KyleMit 您可以使用无头浏览器来呈现其他人的网站,但您可能需要额外的步骤。 Rendertron 可以处理 Shadow DOM,但 Garmin 站点也在相当大的 D3 和 Map API JS 库中延迟加载,因此您需要等待这些库完成,然后再创建静态副本(Rendertron 不会等待默认)【参考方案3】:
我想你应该知道SPA的概念,
SPA 是单页应用程序,它只是静态 html 文件。当路由发生变化时,页面会动态创建或修改DOM
节点,以达到使用Javascript切换页面的效果。
因此,如果你使用$.get()
,服务器会响应一个静态的html文件,该文件有一个稳定的页面,所以你不会加载你想要的。
如果你想使用$.get()
,它有两种方式,第一种是使用headless browser
,例如headless chrome
,phantomJS
等。它会帮助你加载页面,你可以得到@ 987654331@加载页面的节点。第二个是s-s-r
(Server Slide Render
),如果你使用s-s-r
,你会直接通过$.get
获取页面的HTML数据,因为服务器响应时响应页面的HTML数据请求不同的路线。
参考:
s-s-r
vue的SRR框架:Nuxt.js
PhantomJS
Node API of Headless Chrome
【讨论】:
以上是关于通过 AJAX 加载 SPA 网页的主要内容,如果未能解决你的问题,请参考以下文章
Ajax 使用ajax加上get和post方法,通过后台加载数据,并在网页上进行显示案例+解释