页面内容使用JavaScript加载,而Jsoup无法识别。

46
页面上的一个区块是由JavaScript填充的,但是使用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();
        }
    }
}
8个回答

31

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

如果要访问该类型的内容,则需要一个内嵌的浏览器组件,关于这种组件有许多关于此类组件的讨论,例如Is there a way to embed a browser in Java?


4
有没有其他用于Android的“libs”可获取已加载JavaScript的页面内容? - Nikunj Paradva

15

在我的情况下,使用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 实现的任何解决方案吗? - Nikunj Paradva
Android应用案例:我不确定它是否有帮助。而且我从未在Android视图中遇到过相同的行为。 - ilu
  1. Android Web案例:Phantomjs驱动程序的问题解决方案:您不需要运行UI驱动程序。实际上,Phantomjs驱动程序是“一个带有内置JavaScript API的无界面WebKit”。它是一个无UI驱动程序。通常我只用它来收集数据。您不能确定UI视图是否正确。
- ilu

12

你需要理解正在发生的事情:

  • 当你使用Jsoup或浏览器查询网站页面时,返回给你的是一些HTML代码,Jsoup可以解析它。
  • 然而,大多数网站都会在HTML中包含JavaScript代码,或链接到HTML中,用以加载内容。你的浏览器可以执行JavaScript代码,从而填充页面。但Jsoup无法执行。

要理解这个问题,可以这样想:解析HTML代码很容易,但执行JavaScript代码并更新相应的HTML代码则更加复杂,需要浏览器来完成。

以下是解决此类问题的方法:

  1. 如果你能找出JavaScript代码所调用的Ajax请求,即加载内容的请求,就可以尝试使用Jsoup使用这些请求的URL。为此,请使用浏览器的开发人员工具。但是这并不能保证能够成功:

    • 可能URL是动态的,并且取决于该页面上的内容。
    • 如果内容不是公共的,则涉及到Cookies,仅查询资源URL将不足够。
  2. 在这些情况下,您需要“模拟”浏览器的工作,幸运的是,这样的工具已经存在。我知道并推荐的是PhantomJS。它能够使用JavaScript并通过启动新进程从Java中运行。如果你想坚持使用Java,这篇文章列出了一些Java替代方案。


7
你可以结合 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();

一个复杂的例子:加载登录页,获取会话和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());

1
事实上,有一种“方法”!也许更像是一种“解决方法”,而不是“方法”。下面的代码检查meta属性“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...

1
可以通过将 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]");

0

-7

尝试:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接