我需要使用服务器端应用程序获取已完成渲染并完成运行脚本的页面的当前样式(可能是内联)的HTML(仅提供URL(没有cookie,没有POST等额外信息)没有阻碍形式等。)。
使用浏览器库的临时运行浏览器或独立实用程序的桥接/代理是一种可接受的解决方案(但是,所选的浏览器或浏览器库必须在所有主要平台上都可用,并且必须能够在没有OS GUI存在或安装)。
一个可选的要求是之后删除所有脚本(已经有了独立的解决方案,在这里添加它,因为可能给定的答案将能够在渲染时删除脚本或类似的东西)。
如何在当前样式(可能是内联的)和当前图像(使用data URI)的单个.html文件中获取HTML + CSS中的快照?
如果可以使用纯PHP来完成它将是一个加号(虽然我怀疑它,我没有发现任何有趣的东西)。
编辑:我知道如何加载HTTP资源并获取网址的HTML,这不是我想要的;)
编辑2 示例输入HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link rel="stylesheet" type="text/css" href="/css/example.css">
<script type="text/javascript" src="/javascript/example.js"></script>
<script type="text/javascript">
window.addEventListener("load",
function(event){
document.title="New title";
document.getElementById("pic_0").style.border="0px";
}
);
</script>
<style type="text/css">
p{
color: blue;
}
</style>
</head>
<body>
<p>Hello world!</p>
<p>
<img
alt=""
style="border: 1px"
id="pic_0"
src="http://linuxgazette.net/144/misc/john/helloworld.png"
>
</p>
</body>
</html>
示例输出:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>New title</title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<style type="text/css">
b{font-weight: bold}
</style>
<style type="text/css">
p{
color: blue;
}
</style>
</head>
<body>
<p>Hello world!</p>
<p>
<img
alt=""
style="border: 0px"
id="pic_0"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoBAMAAAB+0KVeAAAAK3RFWHRDcmVhdGlvbiBUaW1lAFYgMzEgYXVnLiAyMDEyIDE3OjU4OjU1ICswMjAwWMdbPwAAAAd0SU1FB9wIHw8ABeoUyU4AAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAAABlBMVEX///8AAABVwtN+AAAAXklEQVR42uWQUQ6AMAhD6Q3a+19WqsawwMf+NLEfy3iDlC7idTGQp/YglFAsUMqSwjlQOhN3mIMTHDq70SeEWBbt0EG8POWkDySvmCh/SssvNfwIfb+hFmgjFKPf6gDQBAQ368m09AAAAABJRU5ErkJggg=="
>
</p>
</body>
</html>
注意<title>
标记如何更改,border: 1px
变为border: 0px
的方式,图片网址如何转换为data URI。
例如,在使用Google Chrome检查器检查文档时,可以观察到其中一些转换(内联CSS和<title>
标记)。
编辑3 :用页面替换外部资源(样式和图片)并删除javascript是一件容易的事。困难的部分是在运行javascript后计算CSS样式。
编辑4 也许这可以使用注入的javascript完成(但仍然需要浏览器控制)?
答案 0 :(得分:6)
PhantomJS是一个带有JavaScript API的无头(无GUI)WebKit。 它在所有主要平台上运行,正如我在我的问题中所要求的那样。
它可以运行Javascript脚本来控制无GUI的Web浏览器。它有一个强大的API,还有很多很多例子。
在过去2-3天的业余时间里,我为我的问题编写了解决方案,它完美地涵盖了所有要求。我还没有找到一个不起作用的网页。
用法,命令行:
phantomjs save_as_html.js http://stackoverflow.com/q/12215844/584490 saved.html
Javascript在其他所有内容加载后都可以运行n
秒,它甚至可以用于完全由javascript生成的网页。
注意:
在可能的情况下,XHR加载资源优先于HTML5的画布渲染,因为文件大小减少,质量损失(重用原始文件比任何东西都好)。
<link>
和<img>
标记保留在原位,data:
URI分别在href和src属性中使用,而不是URL。 background-image
也是如此,它在所有DOM节点上使用getComputedStyle()读取。
<script>
标记和事件处理程序属性已删除。
<link>
的 rel="alternative"
标记也会被删除(也许它们不应该被删除,而是固定在绝对网址中,如果是相对的)。
<iframe>
,其src属性设置为about:blank
。
请注意解除所有跨站点脚本安全限制,以便可以加载所有资源。确保在使用Facebook帐户的某些秘密凭据时不要尝试保存恶意网页:)。
save_as_html.js
内容:
//http://stackoverflow.com/a/12256190/584490
var page = require('webpage').create();
page.onConsoleMessage = function (msg) { console.log(msg); };
var system = require('system');
var address, output, size;
if (system.args.length!=3)
{
console.log('Usage: save_as_html.js URL filename');
phantom.exit(1);
}
else
{
address = system.args[1];
output = system.args[2];
page.viewportSize = {
width: 1680,
height: 1050,
};
//SECURITY_ERR: DOM Exception 18: An attempt was made to break through the security policy of the user agent.
//Enable cross site scripting:
page.settings.XSSAuditingEnabled=false;
page.settings.localToRemoteUrlAccessEnabled=true;
page.settings.webSecurityEnabled=false;
page.settings.userAgent="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1";
page.settings.ignoreSslErrors=true;
page.open(address, function (status){
if (status!=='success')
{
console.log("Unable to load URL, returned status: "+status);
phantom.exit(1);
}
else
{
window.setTimeout(function (){
page.evaluate(function(){
var nodeList=document.getElementsByTagName("*");
var arrEventHandlerAttributes=[
"onblur", "onchange", "onclick", "ondblclick", "onfocus", "onkeydown", "onkeyup", "onkeypress", "onkeyup","onload",
"onmousedown", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onreset", "onselect", "onsubmit", "onunload"
];
//http://stackoverflow.com/a/7372816/584490
var base64Encode=function(str)
{
var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var out = "", i = 0, len = str.length, c1, c2, c3;
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += CHARS.charAt((c2 & 0xF) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
out += CHARS.charAt(c3 & 0x3F);
}
return out;
};
for(var n=nodeList.length-1; n>0; n--)
{
try
{
var el=nodeList[n];
if(el.nodeName=="IMG" && el.src.substr(0, 5)!="data:")
{
/*var canvas=document.createElement("canvas");
canvas.width=parseInt(el.width);
canvas.height=parseInt(el.height);
var ctx=canvas.getContext("2d");
ctx.drawImage(el, 0, 0);
el.src=canvas.toDataURL();*/
var xhr=new XMLHttpRequest();
xhr.open(
"get",
el.src,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
el.src="data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText);
}
else if(el.nodeName=="LINK")
{
if(el.rel=="alternate")
{
el.parentNode.removeChild(el);
}
else if(el.href.substr(0, 5)!="data:")
{
var xhr=new XMLHttpRequest();
xhr.open(
"get",
el.href,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
//var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
//el.href="data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText);
el.href="data:"+el.type+";base64,"+base64Encode(xhr.responseText);
}
continue;
}
else if(el.nodeName=="SCRIPT")
{
el.parentNode.removeChild(el);
continue;
}
else if(el.nodeName=="IFRAME")
{
el.src="about:blank";
continue;
}
for(var z=arrEventHandlerAttributes.length-1; z>=0; z--)
el.removeAttribute(arrEventHandlerAttributes[z]);
var strBackgroundImageURL=window.getComputedStyle(el).getPropertyValue("background-image").replace("/[\s]/g", "");
if(strBackgroundImageURL.substr(0, 4)=="url(" && strBackgroundImageURL.substr(4, 5)!="data:")
{
strBackgroundImageURL=strBackgroundImageURL.substr(4, strBackgroundImageURL.length-5);
/*var imageTemp=document.createElement("img");
imageTemp.src=strBackgroundImageURL;
imageTemp.onload=function(e){
var canvas=document.createElement("canvas");
canvas.width=parseInt(imageTemp.width);
canvas.height=parseInt(imageTemp.height);
var ctx=canvas.getContext("2d");
ctx.drawImage(imageTemp, 0, 0);
el.style.backgroundImage="url("+canvas.toDataURL()+")";
};
if (imageTemp.complete)
imageTemp.onload();
*/
var xhr=new XMLHttpRequest();
xhr.open(
"get",
strBackgroundImageURL,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
el.style.backgroundImage="url("+"data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText)+")";
}
if(el.nodeName=="A")
{
el.href="#";//TODO convert relative paths to absolute ones (keep URLs);
el.setAttribute("onclick", "return false;");//TODO: remove this when the above is fixed.
}
else if(el.nodeName=="FORM")
{
el.setAttribute("action", "");
el.setAttribute("onsubmit", "return false;");
}
}
catch(error)
{
//what can be done about it?
}
}
});
require("fs").write(output, page.content, "w");
phantom.exit();
}, 1000);
}
});
}
//http://stackoverflow.com/a/12256190/584490
var page = require('webpage').create();
page.onConsoleMessage = function (msg) { console.log(msg); };
var system = require('system');
var address, output, size;
if (system.args.length!=3)
{
console.log('Usage: save_as_html.js URL filename');
phantom.exit(1);
}
else
{
address = system.args[1];
output = system.args[2];
page.viewportSize = {
width: 1680,
height: 1050,
};
//SECURITY_ERR: DOM Exception 18: An attempt was made to break through the security policy of the user agent.
//Enable cross site scripting:
page.settings.XSSAuditingEnabled=false;
page.settings.localToRemoteUrlAccessEnabled=true;
page.settings.webSecurityEnabled=false;
page.settings.userAgent="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1";
page.settings.ignoreSslErrors=true;
page.open(address, function (status){
if (status!=='success')
{
console.log("Unable to load URL, returned status: "+status);
phantom.exit(1);
}
else
{
window.setTimeout(function (){
page.evaluate(function(){
var nodeList=document.getElementsByTagName("*");
var arrEventHandlerAttributes=[
"onblur", "onchange", "onclick", "ondblclick", "onfocus", "onkeydown", "onkeyup", "onkeypress", "onkeyup","onload",
"onmousedown", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onreset", "onselect", "onsubmit", "onunload"
];
//http://stackoverflow.com/a/7372816/584490
var base64Encode=function(str)
{
var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var out = "", i = 0, len = str.length, c1, c2, c3;
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += CHARS.charAt((c2 & 0xF) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += CHARS.charAt(c1 >> 2);
out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
out += CHARS.charAt(c3 & 0x3F);
}
return out;
};
for(var n=nodeList.length-1; n>0; n--)
{
try
{
var el=nodeList[n];
if(el.nodeName=="IMG" && el.src.substr(0, 5)!="data:")
{
/*var canvas=document.createElement("canvas");
canvas.width=parseInt(el.width);
canvas.height=parseInt(el.height);
var ctx=canvas.getContext("2d");
ctx.drawImage(el, 0, 0);
el.src=canvas.toDataURL();*/
var xhr=new XMLHttpRequest();
xhr.open(
"get",
el.src,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
el.src="data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText);
}
else if(el.nodeName=="LINK")
{
if(el.rel=="alternate")
{
el.parentNode.removeChild(el);
}
else if(el.href.substr(0, 5)!="data:")
{
var xhr=new XMLHttpRequest();
xhr.open(
"get",
el.href,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
//var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
//el.href="data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText);
el.href="data:"+el.type+";base64,"+base64Encode(xhr.responseText);
}
continue;
}
else if(el.nodeName=="SCRIPT")
{
el.parentNode.removeChild(el);
continue;
}
else if(el.nodeName=="IFRAME")
{
el.src="about:blank";
continue;
}
for(var z=arrEventHandlerAttributes.length-1; z>=0; z--)
el.removeAttribute(arrEventHandlerAttributes[z]);
var strBackgroundImageURL=window.getComputedStyle(el).getPropertyValue("background-image").replace("/[\s]/g", "");
if(strBackgroundImageURL.substr(0, 4)=="url(" && strBackgroundImageURL.substr(4, 5)!="data:")
{
strBackgroundImageURL=strBackgroundImageURL.substr(4, strBackgroundImageURL.length-5);
/*var imageTemp=document.createElement("img");
imageTemp.src=strBackgroundImageURL;
imageTemp.onload=function(e){
var canvas=document.createElement("canvas");
canvas.width=parseInt(imageTemp.width);
canvas.height=parseInt(imageTemp.height);
var ctx=canvas.getContext("2d");
ctx.drawImage(imageTemp, 0, 0);
el.style.backgroundImage="url("+canvas.toDataURL()+")";
};
if (imageTemp.complete)
imageTemp.onload();
*/
var xhr=new XMLHttpRequest();
xhr.open(
"get",
strBackgroundImageURL,
/*Asynchronous*/ false
);
xhr.overrideMimeType("text/plain; charset=x-user-defined");
xhr.send(null);
var strResponseContentType=xhr.getResponseHeader("Content-type").split(";")[0].replace(/[^a-z0-9\/-]/gi, "");
el.style.backgroundImage="url("+"data:"+strResponseContentType+";base64,"+base64Encode(xhr.responseText)+")";
}
if(el.nodeName=="A")
{
el.href="#";//TODO convert relative paths to absolute ones (keep URLs);
el.setAttribute("onclick", "return false;");//TODO: remove this when the above is fixed.
}
else if(el.nodeName=="FORM")
{
el.setAttribute("action", "");
el.setAttribute("onsubmit", "return false;");
}
}
catch(error)
{
//what can be done about it?
}
}
});
require("fs").write(output, page.content, "w");
phantom.exit();
}, 1000);
}
});
}