Jsoup 属性选择器返回空
Jsoup attribute selector returning empty
我正在尝试从 google
获取图像
String url = "https://www.google.com/search?site=imghp&tbm=isch&source=hp&q=audi&gws_rd=cr";
org.jsoup.nodes.Document doc = Jsoup.connect(url).get();
Elements elements = doc.select("div.isv-r.PNCib.MSM1fd.BUooTd");
ImageData 以 base64 编码,因此为了获得实际图像 url 我首先获取设置为属性的数据 ID,这有效
for (Element element : elements) {
String id = element.attr("data-id")).get();
我需要与 url+"#imgrc="+id
建立新连接,
org.jsoup.nodes.Document imgdoc = Jsoup.connect(url+"#"+id).get();
现在在浏览器中检查 <div jsname="CGzTgf">
中存在我需要的数据,所以我也在 Jsoup
中做同样的事情
Elements images = imgdoc.select("div[jsname='CGzTgf']");
//futher steps
但图像总是 return 空,我找不到错误,我在 android 的新线程中这样做,任何帮助将不胜感激
事实证明,您这样做的方式完全看错了地方。 url 包含在响应中包含的某些 javascript <script>
标记中。
我已经提取并过滤了相关的 <script>
标签(一个包含属性 nonce
.
然后我过滤那些包含使用的特定函数名称和我期望找到的通用搜索字符串的标签(不会出现在其他 <script>
标签中的内容)。
接下来需要对获取到的值进行剥离,得到包含大约十万个数组的JSON对象。然后我(手动)导航了它,以提取包含相关 URL 节点的节点子集。然后我再次过滤它以获得 List<String>
以获得完整的 URLs.
最后,我在这里重新使用了早期解决方案中的一些代码: 与下载图片类似。
然后您还会得到一些控制台输出,详细说明哪个 URL 最终出现在哪个文件 ID 中。无论实际格式如何,文件都被标记为 image_[x].jpg
(因此您可能需要对其进行一些修改 - 提示:如果提供,请使用 url 的文件扩展名)。
import com.jayway.jsonpath.JsonPath;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
public class GoogleImageDownloader {
private static int TIMEOUT = 30000;
private static final int BUFFER_SIZE = 4096;
public static final String RELEVANT_JSON_START = "AF_initDataCallback(";
public static final String PARTIAL_GENERIC_SEARCH_QUERY = "/search?q";
public static void main(String[] args) throws IOException {
String url = "https://www.google.com/search?site=imghp&tbm=isch&source=hp&q=audi&gws_rd=cr";
Document doc = Jsoup.connect(url).get();
// Response with relevant data is in a <script> tag
Elements elements = doc.select("script[nonce]");
String jsonDataElement = getRelevantScriptTagContainingUrlDataAsJson(elements);
String jsonData = getJsonData(jsonDataElement);
List<String> imageUrls = getImageUrls(jsonData);
int fileId = 1;
for (String urlEntry : imageUrls) {
try {
writeToFile(fileId, makeImageRequest(urlEntry));
System.out.println(urlEntry + " : " + fileId);
fileId++;
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String getRelevantScriptTagContainingUrlDataAsJson(Elements elements) {
String jsonDataElement = "";
int count = 0;
for (Element element : elements) {
String jsonData = element.data();
if (jsonData.startsWith(RELEVANT_JSON_START) && jsonData.contains(PARTIAL_GENERIC_SEARCH_QUERY)) {
jsonDataElement = jsonData;
// IF there are two items in the list, take the 2nd, rather than the first.
if (count == 1) {
break;
}
count++;
}
}
return jsonDataElement;
}
private static String getJsonData(String jsonDataElement) {
String jsonData = jsonDataElement.substring(RELEVANT_JSON_START.length(), jsonDataElement.length() - 2);
return jsonData;
}
private static List<String> getImageUrls(String jsonData) {
// Reason for doing this in two steps is debugging is much faster on the smaller subset of json data
String urlArraysList = JsonPath.read(jsonData, "$.data[31][*][12][2][*]").toString();
List<String> imageUrls = JsonPath.read(urlArraysList, "$.[*][*][3][0]");
return imageUrls;
};
private static void writeToFile(int i, HttpURLConnection response) throws IOException {
// opens input stream from the HTTP connection
InputStream inputStream = response.getInputStream();
// opens an output stream to save into file
FileOutputStream outputStream = new FileOutputStream("image_" + i + ".jpg");
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
System.out.println("File downloaded");
}
// Could use JSoup here but I'm re-using this from an earlier answer
private static HttpURLConnection makeImageRequest(String imageUrlString) throws IOException {
URL imageUrl = new URL(imageUrlString);
HttpURLConnection response = (HttpURLConnection) imageUrl.openConnection();
response.setRequestMethod("GET");
response.setConnectTimeout(TIMEOUT);
response.setReadTimeout(TIMEOUT);
response.connect();
return response;
}
}
我测试的部分结果:
我使用 JsonPath 过滤相关节点,这在您只关心 JSON 的一小部分并且不想反序列化整个对象时非常有用。它遵循与 DOM/XPath/jQuery 导航类似的导航样式。
除了这一个库和Jsoup,用的库都很标准。
祝你好运!
我正在尝试从 google
获取图像String url = "https://www.google.com/search?site=imghp&tbm=isch&source=hp&q=audi&gws_rd=cr";
org.jsoup.nodes.Document doc = Jsoup.connect(url).get();
Elements elements = doc.select("div.isv-r.PNCib.MSM1fd.BUooTd");
ImageData 以 base64 编码,因此为了获得实际图像 url 我首先获取设置为属性的数据 ID,这有效
for (Element element : elements) {
String id = element.attr("data-id")).get();
我需要与 url+"#imgrc="+id
建立新连接,
org.jsoup.nodes.Document imgdoc = Jsoup.connect(url+"#"+id).get();
现在在浏览器中检查 <div jsname="CGzTgf">
中存在我需要的数据,所以我也在 Jsoup
Elements images = imgdoc.select("div[jsname='CGzTgf']");
//futher steps
但图像总是 return 空,我找不到错误,我在 android 的新线程中这样做,任何帮助将不胜感激
事实证明,您这样做的方式完全看错了地方。 url 包含在响应中包含的某些 javascript <script>
标记中。
我已经提取并过滤了相关的 <script>
标签(一个包含属性 nonce
.
然后我过滤那些包含使用的特定函数名称和我期望找到的通用搜索字符串的标签(不会出现在其他 <script>
标签中的内容)。
接下来需要对获取到的值进行剥离,得到包含大约十万个数组的JSON对象。然后我(手动)导航了它,以提取包含相关 URL 节点的节点子集。然后我再次过滤它以获得 List<String>
以获得完整的 URLs.
最后,我在这里重新使用了早期解决方案中的一些代码: 与下载图片类似。
然后您还会得到一些控制台输出,详细说明哪个 URL 最终出现在哪个文件 ID 中。无论实际格式如何,文件都被标记为 image_[x].jpg
(因此您可能需要对其进行一些修改 - 提示:如果提供,请使用 url 的文件扩展名)。
import com.jayway.jsonpath.JsonPath;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
public class GoogleImageDownloader {
private static int TIMEOUT = 30000;
private static final int BUFFER_SIZE = 4096;
public static final String RELEVANT_JSON_START = "AF_initDataCallback(";
public static final String PARTIAL_GENERIC_SEARCH_QUERY = "/search?q";
public static void main(String[] args) throws IOException {
String url = "https://www.google.com/search?site=imghp&tbm=isch&source=hp&q=audi&gws_rd=cr";
Document doc = Jsoup.connect(url).get();
// Response with relevant data is in a <script> tag
Elements elements = doc.select("script[nonce]");
String jsonDataElement = getRelevantScriptTagContainingUrlDataAsJson(elements);
String jsonData = getJsonData(jsonDataElement);
List<String> imageUrls = getImageUrls(jsonData);
int fileId = 1;
for (String urlEntry : imageUrls) {
try {
writeToFile(fileId, makeImageRequest(urlEntry));
System.out.println(urlEntry + " : " + fileId);
fileId++;
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static String getRelevantScriptTagContainingUrlDataAsJson(Elements elements) {
String jsonDataElement = "";
int count = 0;
for (Element element : elements) {
String jsonData = element.data();
if (jsonData.startsWith(RELEVANT_JSON_START) && jsonData.contains(PARTIAL_GENERIC_SEARCH_QUERY)) {
jsonDataElement = jsonData;
// IF there are two items in the list, take the 2nd, rather than the first.
if (count == 1) {
break;
}
count++;
}
}
return jsonDataElement;
}
private static String getJsonData(String jsonDataElement) {
String jsonData = jsonDataElement.substring(RELEVANT_JSON_START.length(), jsonDataElement.length() - 2);
return jsonData;
}
private static List<String> getImageUrls(String jsonData) {
// Reason for doing this in two steps is debugging is much faster on the smaller subset of json data
String urlArraysList = JsonPath.read(jsonData, "$.data[31][*][12][2][*]").toString();
List<String> imageUrls = JsonPath.read(urlArraysList, "$.[*][*][3][0]");
return imageUrls;
};
private static void writeToFile(int i, HttpURLConnection response) throws IOException {
// opens input stream from the HTTP connection
InputStream inputStream = response.getInputStream();
// opens an output stream to save into file
FileOutputStream outputStream = new FileOutputStream("image_" + i + ".jpg");
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.close();
inputStream.close();
System.out.println("File downloaded");
}
// Could use JSoup here but I'm re-using this from an earlier answer
private static HttpURLConnection makeImageRequest(String imageUrlString) throws IOException {
URL imageUrl = new URL(imageUrlString);
HttpURLConnection response = (HttpURLConnection) imageUrl.openConnection();
response.setRequestMethod("GET");
response.setConnectTimeout(TIMEOUT);
response.setReadTimeout(TIMEOUT);
response.connect();
return response;
}
}
我测试的部分结果:
我使用 JsonPath 过滤相关节点,这在您只关心 JSON 的一小部分并且不想反序列化整个对象时非常有用。它遵循与 DOM/XPath/jQuery 导航类似的导航样式。
除了这一个库和Jsoup,用的库都很标准。
祝你好运!