我需要一个如下工作流程:
// load xyz.com in the browser window
// the browser is live, meaning users can interact with it
browser.load("http://www.google.com");
// return the HTML of the initially loaded page
String page = browser.getHTML();
// after some time
// user might have navigated to a new page, get HTML again
String newpage = browser.getHTML();
我很惊讶地看到Java GUI如JavaFX(http://lexandera.com/2009/01/extracting-html-from-a-webview/)和Swing有多难。
有一些简单的方法可以在Java中获得此功能吗?
您是否看过嵌入JavaFX运行时的WebKit?
是的,很难从JavaFX中获取HTML(lexandera.com/2009/01/extracting-html-from-a-webview)。
@moeb你提供的链接是针对Android WebView的,而不是像zenbeni建议的JavaFX。
我不知道这是否有用,但您可以查看此链接:stackoverflow.com/questions/14273450/
这是一个使用JavaFX的人为例子,它将html内容打印到System.out--它不应该太复杂,无法适应创建getHtml()方法。 (我已经使用JavaFX 8对其进行了测试,但它也适用于JavaFX 2)。
每次加载新页面时,代码都会打印HTML内容。
注意:我从这个答案中借用了printDocument代码。
public class TestFX extends Application {
@Override
public void start(Stage stage) throws Exception {
try {
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
Scene scene = new Scene(webView);
stage.setScene(scene);
stage.setWidth(1200);
stage.setHeight(600);
stage.show();
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener() {
@Override
public void changed(ObservableValue extends State> ov, State t, State t1) {
if (t1 == Worker.State.SUCCEEDED) {
try {
printDocument(webEngine.getDocument(), System.out);
} catch (Exception e) { e.printStackTrace(); }
}
}
});
webView.getEngine().load("http://www.google.com");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void printDocument(Document doc, OutputStream out) throws IOException, TransformerException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"no");
transformer.setOutputProperty(OutputKeys.METHOD,"xml");
transformer.setOutputProperty(OutputKeys.INDENT,"yes");
transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","4");
transformer.transform(new DOMSource(doc), new StreamResult(new OutputStreamWriter(out,"UTF-8")));
}
public static void main(String[] args) {
launch(args);
}
}
谢谢。一个问题 - changed()内部代码的执行模型究竟是什么?它是否在与调用load()的线程的单独线程中执行?
上面的代码中没有任何内容在JavaFX Thread上执行。但请注意,load不会加载页面,它只要求WebEngine安排页面加载任务 - 然后WebEngine使用后台线程实际加载页面以避免阻止UI。加载完成后,WebEngine将调用JavaFX Thread上的changed方法。有关线程模型的更多详细信息,请参阅javadoc。
谢谢。我想在加载和打印之间进行排序。像下面这样的东西 - load a page, wait till print is complete, load another page, wait till print is complete,。我怎样才能做到这一点?
@Moeb你可以在changed方法中加载下一页:printDocument(...); webView.getEngine().load(getNextPageUrl()); - getNextPageUrl是一个简单的方法,它返回一个数组的项目,并在每次调用时递增索引。像:private String[] pages = ...; private index i; private String getNextPage() { return pages[++i]; }。现在没有时间写一个完整的例子,抱歉。
这是一个非常好的提示,谢谢!
下面是一个SimpleBrowser组件,它是一个Pane,包含WebView。
gist的源代码。
样品用法:
SimpleBrowser browser = new SimpleBrowser()
.useFirebug(true);
// ^ useFirebug(true) option - will enable Firebug Lite which can be helpful for
// | debugging - i.e. to inspect a DOM tree or to view console messages
Scene scene = new Scene(browser);
browser.load("http://stackoverflow.com", new Runnable() {
@Override
public void run() {
System.out.println(browser.getHTML());
}
});
browser.getHTML()放在Runnable中,因为需要等待网页下载和渲染。尝试在页面加载之前调用此方法将返回一个空页面,因此将其包装到runnable中是一种简单的方法,我想出来等待页面加载。
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.scene.layout.*;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class SimpleBrowser extends Pane {
protected final WebView webView = new WebView();
protected final WebEngine webEngine = webView.getEngine();
protected boolean useFirebug;
public WebView getWebView() {
return webView;
}
public WebEngine getEngine() {
return webView.getEngine();
}
public SimpleBrowser load(String location) {
return load(location, null);
}
public SimpleBrowser load(String location, final Runnable onLoad) {
webEngine.load(location);
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener() {
@Override
public void changed(ObservableValue extends Worker.State> ov, Worker.State t, Worker.State t1) {
if (t1 == Worker.State.SUCCEEDED) {
if(useFirebug){
webEngine.executeScript("if (!document.getElementById('FirebugLite')){E = document['createElement' + 'NS'] && document.documentElement.namespaceURI;E = E ? document['createElement' + 'NS'](E, 'script') : document['createElement']('script');E['setAttribute']('id', 'FirebugLite');E['setAttribute']('src', 'https://getfirebug.com/' + 'firebug-lite.js' + '#startOpened');E['setAttribute']('FirebugLite', '4');(document['getElementsByTagName']('head')[0] || document['getElementsByTagName']('body')[0]).appendChild(E);E = new Image;E['setAttribute']('src', 'https://getfirebug.com/' + '#startOpened');}");
}
if(onLoad != null){
onLoad.run();
}
}
}
});
return this;
}
public String getHTML() {
return (String)webEngine.executeScript("document.getElementsByTagName('html')[0].innerHTML");
}
public SimpleBrowser useFirebug(boolean useFirebug) {
this.useFirebug = useFirebug;
return this;
}
public SimpleBrowser() {
this(false);
}
public SimpleBrowser(boolean useFirebug) {
this.useFirebug = useFirebug;
getChildren().add(webView);
webView.prefWidthProperty().bind(widthProperty());
webView.prefHeightProperty().bind(heightProperty());
}
}
演示浏览器:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.layout.VBoxBuilder;
import javafx.stage.Stage;
public class FXBrowser {
public static class TestOnClick extends Application {
@Override
public void start(Stage stage) throws Exception {
try {
SimpleBrowser browser = new SimpleBrowser()
.useFirebug(true);
final TextField location = new TextField("http://stackoverflow.com");
Button go = new Button("Go");
go.setOnAction(new EventHandler() {
@Override
public void handle(ActionEvent arg0) {
browser.load(location.getText(), new Runnable() {
@Override
public void run() {
System.out.println("---------------");
System.out.println(browser.getHTML());
}
});
}
});
HBox toolbar = new HBox();
toolbar.getChildren().addAll(location, go);
toolbar.setFillHeight(true);
VBox vBox = VBoxBuilder.create().children(toolbar, browser)
.fillWidth(true)
.build();
Scene scene = new Scene( vBox);
stage.setScene(scene);
stage.setWidth(1024);
stage.setHeight(768);
stage.show();
VBox.setVgrow(browser, Priority.ALWAYS);
browser.load("http://stackoverflow.com");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
}
根据我不了解你的项目的东西,无论是天才还是愚蠢,但你可以使用真正的浏览器并使用Selenium Webdriver进行测量。只是从另一个答案中可以看出这一点,你走的是一条艰难的道路。
还有一个关于用webdriver提取html的问题。这是关于使用python,但webdriver也有一个java api。
你可能想看到djproject。但可能你会发现JavaFX的使用更容易。
没有一个简单的解决方案。事实上,构建自己的浏览器可能根本就没有解决方案。
关键问题是互动。如果您只想显示内容,那么JEditorPane和许多第三方库使这个目标更容易实现。如果您确实需要用户与网页进行交互,则可以:
让用户使用普通浏览器进行交互
构建一个GUI,调用Web服务/ URL进行交互,但显示由您决定。
在返回HTML方面,听起来您正在尝试捕获历史记录或刷新页面。在任何一种情况下,听起来你都是错误的技术。修改原始站点,或在浏览器中使用Greasemonkey或类似的东西添加一些java脚本。
当然它是可行的,Selenium做到了。不仅如此,Selenium可以在您(或它)与页面交互时捕获呈现页面的屏幕截图。