用于显示网页并返回 HTML 的 Java GUI

Posted

技术标签:

【中文标题】用于显示网页并返回 HTML 的 Java GUI【英文标题】:Java GUI to display webpages and return HTML 【发布时间】:2013-12-01 19:49:44 【问题描述】:

我需要如下的工作流程:

// 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();

我很惊讶地发现,JavaFX (http://lexandera.com/2009/01/extracting-html-from-a-webview/) 和 Swing 等 Java GUI 很难做到这一点。

有没有一些简单的方法可以在 Java 中获得这个功能?

【问题讨论】:

您看过嵌入在 JavaFX 运行时中的 WebKit 吗? 是的,很难从 JavaFX (lexandera.com/2009/01/extracting-html-from-a-webview) 中获取 HTML。 @moeb 您提供的链接适用于 android WebView,而不是 zenbeni 建议的 JavaFX。 我不知道这是否有用,但您可以查看此链接:***.com/questions/14273450/… 【参考方案1】:

这是一个使用 JavaFX 将 html 内容打印到 System.out 的人为示例 - 适应创建 getHtml() 方法应该不会太复杂。 (我已经用 JavaFX 8 对其进行了测试,但它也应该适用于 JavaFX 2)。

每次加载新页面时,代码都会打印 HTML 内容。

注意:我从this answer借用了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<Worker.State>() 
                @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/xsltindent-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 线程上执行的。但是请注意,load 不加载页面,它只要求 WebEngine 安排页面加载任务 - WebEngine 然后使用后台线程实际加载页面以避免阻塞 UI。加载完成后,WebEngine 将调用 JavaFX 线程上的 changed 方法。有关线程模型的更多详细信息,请参阅javadoc。 谢谢。我想要加载和打印之间的顺序。类似于以下内容 - load a page, wait till print is complete, load another page, wait till print is complete, &lt;repeat&gt;。我该怎么做? @Moeb 你可以在changed 方法中加载下一页:printDocument(...); webView.getEngine().load(getNextPageUrl()); - 而getNextPageUrl 是一个简单的方法,它返回数组的项目并在每次索引时递增索引例如称为。类似:private String[] pages = ...; private index i; private String getNextPage() return pages[++i]; 。现在没有时间写一个完整的例子,抱歉。 您好,感谢您的解决方案。我试过这个。这显示了我加载的相同 html。如何获得执行所有脚本后呈现的最终 html?【参考方案2】:

您将在下面找到一个SimpleBrowser 组件,它是一个包含WebViewPane

Source code at 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://***.com", new Runnable() 
    @Override
    public void run() 
        System.out.println(browser.getHTML());
    
);

browser.getHTML() 放在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<Worker.State>() 
            @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://***.com");

                Button go = new Button("Go");

                go.setOnAction(new EventHandler<ActionEvent>() 
                    @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://***.com");
             catch (Exception e) 
                e.printStackTrace();
            
        

        public static void main(String[] args) 
            launch(args);
        
    

【讨论】:

【参考方案3】:

没有简单的解决方案。事实上,除了构建自己的浏览器之外,甚至可能根本没有解决方案。

关键问题是交互。如果您只想显示内容,那么JEditorPane 和许多第三方库使这个目标更容易实现。如果您确实需要用户与网页交互,那么:

让用户使用普通浏览器进行交互 构建一个调用 Web 服务/url 以进行交互的 GUI,但显示由您决定。

在返回 HTML 方面,听起来您正在尝试捕获历史记录或刷新页面。无论哪种情况,听起来您都使用了错误的技术。要么修改原始站点,要么在浏览器中使用 Greasemonkey 或类似的东西添加一些 java 脚本。

【讨论】:

当然可行,Selenium 做到了。不仅如此,Selenium 还可以在您(或它)与页面交互时捕获渲染页面的屏幕截图。【参考方案4】:

您可能想查看djproject。但您可能会发现 JavaFX 的使用更容易。

【讨论】:

【参考方案5】:

根据我对您的项目的了解,这不是天才就是愚蠢,但您可以改用真正的浏览器并使用Selenium Webdriver 对其进行检测。仅建议这一点,因为从其他答案看来,您正走在一条艰难的道路上。

还有一个关于使用 webdriver here 提取 html 的问题。这是关于使用python,但webdriver也有一个java api。

【讨论】:

【参考方案6】:

我能够得到执行的 html。在 javascript 中加载 html 后,我保留了警报语句。我使用 webEngine.setOnAlert 方法检查警报是否已执行,然后打印 html。我得到了正确的回应。下面是代码

HTML

alert("ready");

JavaFx 应用程序

webEngine.setOnAlert(new EventHandler<WebEvent<String>>()

                        @Override
                        public void handle(WebEvent<String> event) 
                            //labelWebTitle.setText(webEngine.getTitle());
                             if("ready".equals(event.getData()))
                                 //TODO: initialize
                                 System.out.println("HTML Ready");
                                 WebEngine engine = (WebEngine)event.getSource();
                                 String html = (String) engine.executeScript("document.documentElement.outerHTML");
                                 org.jsoup.nodes.Document doc = Jsoup.parse(html);
                                 Element image = doc.getElementById("canvasImage");
                                 System.out.println(image.attr("src"));
                            
                        

                    );

【讨论】:

以上是关于用于显示网页并返回 HTML 的 Java GUI的主要内容,如果未能解决你的问题,请参考以下文章

如何用html编写的网页中显示java返回值

用于在 html 网页的警报弹出框中显示整个数据库的 Java 代码或 php 代码

让java gui在网络浏览器中打开网页

网页请求到页面显示的过程描述

用于在 iPhone 设备上显示持久存储内容的 GUI

将图片转成数据:image/png;base64用于网页显示