页面内容是用 JavaScript 加载的,而 Jsoup 看不到它

Posted

技术标签:

【中文标题】页面内容是用 JavaScript 加载的,而 Jsoup 看不到它【英文标题】:Page content is loaded with JavaScript and Jsoup doesn't see it 【发布时间】:2011-11-21 06:40:55 【问题描述】:

页面上的一个块由 javascript 填充内容,在使用 Jsoup 加载页面后,没有任何信息。有没有办法在使用Jsoup 解析页面时也获取 JavaScript 生成的内容?

无法在此处粘贴页面代码,因为它太长了:http://pastebin.com/qw4Rfqgw

这是我需要的内容的元素:<div id='tags_list'></div>

我需要用 Java 获取这些信息。最好使用 Jsoup。元素是借助 JavaScript 的字段:

<div id="tags_list">
    <a href="/tagsc0t20099.html" style="font-size:14;">разведчик</a>
    <a href="/tagsc0t1879.html" style="font-size:14;">Sr</a>
    <a href="/tagsc0t3140.html" style="font-size:14;">стратегический</a>
</div>

Java 代码:

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;

public class Test

    public static void main( String[] args )
    
        try
        
            Document Doc = Jsoup.connect( "http://www.bestreferat.ru/referat-32558.html" ).get();
            Elements Tags = Doc.select( "#tags_list a" );

            for ( Element Tag : Tags )
            
                System.out.println( Tag.text() );
            
        
        catch ( IOException e )
        
            e.printStackTrace();
        
    

【问题讨论】:

【参考方案1】:

JSoup 是一个 HTML 解析器,而不是某种嵌入式浏览器引擎。这意味着它完全不知道在初始页面加载后由 Javascript 添加到 DOM 的任何内容。

要访问这种类型的内容,您需要一个嵌入式浏览器组件,关于这种组件的 SO 有很多讨论,例如 Is there a way to embed a browser in Java?

【讨论】:

任何其他libs 可用于android 以获取页面内容已加载javascript【参考方案2】:

在使用 Jsoup 解析页面时,有没有办法获取 javascript 生成的内容?

我猜不,想想这将是多么困难,而不用 Java 构建一个完整的 javascript 解释器。

【讨论】:

(顺便说一句,在写这个答案的时候就已经存在了,而且在技术上也是几年前。问题不是 Java 中的 JavaScript,而是 Java 中的可嵌入浏览器——JS 只是拼图的一部分。) @DaveNewton JSoup 已经包含了拼图的其他部分(DOM 实现、请求机制)。当然,放弃 JSoup 并使用可嵌入浏览器的组合功能(DOM、请求、javascript 解释器)会容易得多。我已将这篇文章回答的问题加粗。【参考方案3】:

我其实有一个“方法”!也许它比“方式”更像是“一种解决方法”......下面的代码检查元属性“REFRESH”和javascript重定向......如果它们中的任何一个存在RedirectedUrl变量被设置。所以你知道你的目标。 ..然后您可以检索目标页面并继续...

    String RedirectedUrl=null;
    Elements meta = page.select("html head meta");
    if (meta.attr("http-equiv").contains("REFRESH")) 
        RedirectedUrl = meta.attr("content").split("=")[1];
     else 
        if (page.toString().contains("window.location.href")) 
            meta = page.select("script");
            for (Element script:meta) 
                String s = script.data();
                if (!s.isEmpty() && s.startsWith("window.location.href")) 
                    int start = s.indexOf("=");
                    int end = s.indexOf(";");
                    if (start>0 && end >start) 
                        s = s.substring(start+1,end);
                        s =s.replace("'", "").replace("\"", "");        
                        RedirectedUrl = s.trim();
                        break;
                    
                
            
        
    

... now retrieve the redirected page again...

【讨论】:

【参考方案4】:

您需要了解正在发生的事情:

当您从网站查询页面时,无论是使用 Jsoup 还是您的浏览器,返回给您的是一些 HTML。 Jsoup 能够解析它。 但是,大多数网站在该 HTML 中包含 Javascript,或从该 HTML 链接,这将在页面中填充内容。您的浏览器能够执行 Javascript,从而填充页面。 Jsoup 不是。

理解这一点的方法如下:解析 HTML 代码很容易。执行 Javascript 代码并更新相应的 HTML 代码要复杂得多,并且是浏览器的工作。

以下是针对此类问题的一些解决方案:

    如果您可以找到 Javascript 代码正在执行的 Ajax 调用(即加载内容),您也许可以将这些调用的 URL 与 Jsoup 一起使用。为此,请使用浏览器中的开发人员工具。但这不能保证有效:

    可能是 url 是动态的,并且取决于当时页面上的内容 如果内容不公开,会涉及cookies,单纯查询资源URL是不够的

    在这些情况下,您需要“模拟”浏览器的工作。幸运的是,存在这样的工具。我知道并推荐的是PhantomJS。它适用于 Javascript,您需要通过启动一个新进程从 Java 启动它。如果您想坚持使用 Java,this post 列出了一些 Java 替代方案。

【讨论】:

【参考方案5】:

在我的情况下使用 com.codeborne.phantomjsdriver 解决了 注意:这是 groovy 代码。

pom.xml

        <dependency>
          <groupId>com.codeborne</groupId>
          <artifactId>phantomjsdriver</artifactId>
          <version> <here goes last version> </version>
        </dependency>

PhantomJsUtils.groovy

import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.openqa.selenium.WebDriver
import org.openqa.selenium.phantomjs.PhantomJSDriver

class PhantomJsUtils 
    private static String filePath = 'data/temp/';

    public static Document renderPage(String filePath) 
        System.setProperty("phantomjs.binary.path", 'libs/phantomjs') // path to bin file. NOTE: platform dependent
        WebDriver ghostDriver = new PhantomJSDriver();
        try 
            ghostDriver.get(filePath);
            return Jsoup.parse(ghostDriver.getPageSource());
         finally 
            ghostDriver.quit();
        
    

    public static Document renderPage(Document doc) 
        String tmpFileName = "$filePath$Calendar.getInstance().timeInMillis.html";
        FileUtils.writeToFile(tmpFileName, doc.toString());
        return renderPage(tmpFileName);
    

ClassInProject.groovy

Document doc = PhantomJsUtils.renderPage(Jsoup.parse(yourSource))

【讨论】:

Android 实施的任何解决方案? 1. Android 应用案例:我不确定它是否有帮助。此外,我从未在 android 视图中遇到过相同的行为。 2. Android网页案例:Phantomjs驱动解决的问题:你不运行UI驱动。事实上,幻像驱动程序是“一个使用内置 JavaScript API 无头运行的 webkit”。它是一个无 UI 驱动程序。通常我只用它来收集数据。您无法确定 UI 视图是否正确。【参考方案6】:

试试:

Document Doc = Jsoup.connect(url)
    .header("Accept-Encoding", "gzip, deflate")
    .userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0")
    .maxBodySize(0)
    .timeout(600000)
    .get();

【讨论】:

【参考方案7】:

指定用户代理后,我的问题就解决了。

https://github.com/jhy/jsoup/issues/287#issuecomment-12769155

【讨论】:

【参考方案8】:

可以通过将JSoup 与另一个框架组合来解释网页,在我的示例中,我使用的是HtmlUnit

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

...

WebClient webClient = new WebClient();
HtmlPage myPage = webClient.getPage(URL);

Document document = Jsoup.parse(myPage.asXml());
Elements otherLinks = document.select("a[href]");

【讨论】:

【参考方案9】:

您可以结合使用JSoup和HtmlUnit来获取JavaScript脚本加载完成后的页面内容。

pom.xml

<dependency>
    <groupId>net.sourceforge.htmlunit</groupId>
    <artifactId>htmlunit</artifactId>
    <version>3.35</version>
</dependency>

简单示例来自文件https://riptutorial.com/jsoup/example/16274/parsing-javascript-generated-page-with-jsoup-and-htmunit

// load page using HTML Unit and fire scripts
WebClient webClient2 = new WebClient();
HtmlPage myPage = webClient2.getPage(new File("page.html").toURI().toURL());

// convert page to generated HTML and convert to document
Document doc = Jsoup.parse(myPage.asXml());

// iterate row and col
for (Element row : doc.select("table#data > tbody > tr"))
    for (Element col : row.select("td"))
        // print results
        System.out.println(col.ownText());

// clean up resources        
webClient2.close();

一个复杂的例子:加载登录,获取Session和CSRF,然后发布并等待主页完成加载(15秒)

import java.io.IOException;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URL;

import org.jsoup.Connection;
import org.jsoup.Connection.Method;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;

import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

//JSoup load Login Page and get Session Details
Connection.Response res = Jsoup.connect("https://loginpage").method(Method.GET).execute();

String sessionId = res.cookie("findSESSION");
String csrf = res.cookie("findCSRF");

HttpCookie cookie = new HttpCookie("findCSRF", csrf);
cookie.setDomain("domain.url");
cookie.setPath("/path");

WebClient webClient = new WebClient();
webClient.addCookie(cookie.toString(),
            new URL("https://url"),
            "https://referrer");

// Add other cookies/ Session ...

webClient.getOptions().setJavaScriptEnabled(true);
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setUseInsecureSSL(true);
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
webClient.getCookieManager().setCookiesEnabled(true);
webClient.setAjaxController(new NicelyResynchronizingAjaxController());
// Wait time
webClient.waitForBackgroundJavaScript(15000);
webClient.getOptions().setThrowExceptionOnScriptError(false);

URL url = new URL("https://login.path");
WebRequest requestSettings = new WebRequest(url, HttpMethod.POST);

requestSettings.setRequestBody("user=234&pass=sdsdc&CSRFToken="+csrf);
HtmlPage page = webClient.getPage(requestSettings);

// Wait
synchronized (page) 
    try 
        page.wait(15000);
     catch (InterruptedException e) 
        e.printStackTrace();
    


// Parse logged in page as needed
Document doc = Jsoup.parse(page.asXml());

【讨论】:

以上是关于页面内容是用 JavaScript 加载的,而 Jsoup 看不到它的主要内容,如果未能解决你的问题,请参考以下文章

javascript基础01

JavaScript DOM 编程艺术之 Ajax

javascript基础01

javascript脚本异步加载的几种方式

使用jquery的load方法设计动态加载,并解决被加载页面JavaScript失效问题

iframe如何使用javascript替换内容?