Вопрос-ответ

Page content is loaded with JavaScript and Jsoup doesn't see it

Содержимое страницы загружается с помощью JavaScript, а Jsoup его не видит

Один блок на странице заполнен содержимым с помощью JavaScript, и после загрузки страницы с помощью Jsoup никакой этой информации нет. Есть ли способ получить также содержимое, сгенерированное JavaScript, при синтаксическом анализе страницы с помощью Jsoup?

Не удается вставить сюда код страницы, поскольку он слишком длинный: 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 анализатор, а не какой-то встроенный браузерный движок. Это означает, что он совершенно не знает о каком-либо содержимом, которое добавляется в DOM с помощью Javascript после начальной загрузки страницы.

Чтобы получить доступ к этому типу содержимого, вам понадобится встроенный компонент браузера, на SO есть ряд обсуждений относительно такого компонента, например, Есть ли способ встроить браузер в Java?

Ответ 2

Решено в моем случае с помощью com.codeborne.phantomjsdriver ПРИМЕЧАНИЕ: это заводной код.

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))
Ответ 3

Вам нужно понять, что происходит :


  • Когда вы запрашиваете страницу с веб-сайта, независимо от того, используете ли вы Jsoup или свой браузер, вам возвращается некоторый HTML. Jsoup может его проанализировать.

  • Однако большинство веб-сайтов включают Javascript в этот HTML-код или ссылаются на этот HTML-код, который заполняет страницу контентом. Ваш браузер способен выполнять Javascript и, таким образом, заполнять страницу. Jsoup этого не видит.

Понять это можно следующим образом: синтаксический анализ HTML-кода прост. Выполнение кода Javascript и обновление соответствующего HTML-кода намного сложнее, и это работа браузера.

Вот несколько решений для такого рода проблем:


  1. Если вы сможете найти, какие вызовы Ajax выполняет код Javascript, то есть загружает содержимое, вы сможете использовать URL этих вызовов с помощью Jsoup. Для этого используйте инструменты разработчика в вашем браузере. Но это не гарантирует, что сработает:



    • возможно, URL-адрес является динамическим и зависит от того, что находится на странице в данный момент

    • если содержимое не является общедоступным, будут задействованы файлы cookie, и простого запроса URL ресурса будет недостаточно


  2. В этих случаях вам нужно будет "имитировать" работу браузера. К счастью, такие инструменты существуют. Тот, который я знаю и рекомендую, - это PhantomJS. Он работает с Javascript, и вам нужно будет запустить его с Java, запустив новый процесс. Если вы хотите придерживаться Java, в этом посте перечислены некоторые альтернативы Java.


Ответ 4

Вы можете использовать комбинацию 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();

Сложный пример: загружаем login, получаем 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());
java