Мне нужно получить HTML с текущими стилями (возможно, вложенными) на странице, которая закончила выполнение рендеринга и закончила выполнение сценариев, используя приложение на стороне сервера, которому будет предоставлен только URL-адрес (никакой дополнительной информации, такой как файлы cookie, POST, никаких препятствующих форм , и т.д.).
Мост / прокси-сервер для временно запущенного браузера или автономной утилиты с использованием библиотеки браузера является общепринятым решением (однако выбранный браузер или библиотека браузера должны быть доступны на всех основных платформах и должны иметь возможность запускаться без использования графического интерфейса ОС настоящее или установленное).
Необязательное требование состоит в том, чтобы удалить все сценарии впоследствии (для этого уже существуют самостоятельные решения, добавив их здесь, потому что, возможно, данный ответ сможет удалить скрипты при рендеринге или что-то в этом роде).
Как получить моментальный снимок в HTML + CSS в одном .html-файле текущего документа HTML с текущими стилями (возможно, встроенными) и текущими изображениями (с использованием URI данных )?
Если это можно сделать с использованием чистого PHP, это будет плюс (хотя я сомневаюсь, я не нашел ничего интересного).
Изменить: я знаю, как загрузить ресурсы HTTP и получить HTML-код для URL-адреса, это не то, что я ищу;)
Редактировать 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://img.ruphp.com/php/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
, как URL-адрес изображения был преобразован в URI данных .
Например, некоторые из этих преобразований (встроенный CSS и <title>
) можно наблюдать при проверке документа с помощью инспектора Google Chrome.
Редактирование 3 : Замена внешних ресурсов на страницах (стилей и изображений) и удаление javascript – легкая часть. Жесткая часть – это вычисление стиля CSS после запуска javascript.
Редактировать 4 Может быть, это может быть сделано с помощью javascript (по-прежнему нужен браузер)?
PhantomJS является безглавым (GUI-less) WebKit с API JavaScript. Он работает на всех основных платформах, как я просил в моем вопросе.
Он может запускать скрипты Javascript для управления веб-браузером без использования графического интерфейса. У этого есть мощный 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
, которое считывается с использованием getComputedStyle () для всех узлов DOM.
теги <script>
и атрибуты обработчика событий удаляются.
Теги <link>
с rel="alternative"
также удаляются (возможно, их не должно быть, и вместо этого они фиксируются в абсолютном URL-адресе, если они относительны).
<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); } }); }