From 4d6735f7c7d0e9ed674403f311721fdcfa3b0bb5 Mon Sep 17 00:00:00 2001 From: Leslie Bellony Date: Tue, 9 Dec 2025 17:33:18 +0100 Subject: [PATCH] Add ExtractorJson from BL (#698) --- modules/pom.xml | 6 ++ .../modules/extractor/ExtractorJson.java | 76 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 modules/src/main/java/org/archive/modules/extractor/ExtractorJson.java diff --git a/modules/pom.xml b/modules/pom.xml index 69ef4e84..62f0fa72 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -111,6 +111,12 @@ junit-jupiter true + + + com.fasterxml.jackson.core + jackson-databind + 2.20.0 + diff --git a/modules/src/main/java/org/archive/modules/extractor/ExtractorJson.java b/modules/src/main/java/org/archive/modules/extractor/ExtractorJson.java new file mode 100644 index 00000000..e40a4737 --- /dev/null +++ b/modules/src/main/java/org/archive/modules/extractor/ExtractorJson.java @@ -0,0 +1,76 @@ +package org.archive.modules.extractor; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.archive.modules.CrawlURI; +import org.archive.util.UriUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Extracts URIs from JSON resources. + *

+ * n.b. chokes on JSONP, e.g. + *

+ * breakingNews({"pollPeriod":30000,"isError":false,"html":""}) + * + * @author rcoram + */ +public class ExtractorJson extends ContentExtractor { + public final static String JSON_URI = "^https?://[^/]+/.+\\.json\\b.*$"; + private static final Logger LOGGER = Logger.getLogger(ExtractorJson.class.getName()); + private final JsonFactory factory = new JsonFactory(); + private final ObjectMapper mapper = new ObjectMapper(factory); + + @Override + protected boolean innerExtract(CrawlURI curi) { + try { + List links = new ArrayList<>(); + JsonNode rootNode = mapper.readTree(curi.getRecorder().getContentReplayInputStream()); + parse(rootNode, links); + for (String link : links) { + try { + int max = getExtractorParameters().getMaxOutlinks(); + addRelativeToBase(curi, max, link, LinkContext.INFERRED_MISC, Hop.INFERRED); + numberOfLinksExtracted.incrementAndGet(); + } catch (org.archive.url.URIException e) { + logUriError(e, curi.getUURI(), link); + } + } + } catch (Exception e) { + // Only record this as INFO, as malformed JSON is fairly common. + LOGGER.log(Level.INFO, curi.getURI() + " : " + e.getMessage()); + } + return false; + } + + @Override + protected boolean shouldExtract(CrawlURI curi) { + String contentType = curi.getContentType(); + if (contentType != null && contentType.contains("json")) { + return true; + } + return curi.isSuccess() && curi.toString().matches(JSON_URI); + } + + protected List parse(JsonNode rootNode, List links) { + for (Map.Entry field : rootNode.properties()) { + if (field.getValue().textValue() != null + && UriUtils.isVeryLikelyUri(field.getValue().textValue())) { + links.add(field.getValue().textValue()); + } else if (field.getValue().isObject()) { + parse(field.getValue(), links); + } else if (field.getValue().isArray()) { + field.getValue() + .propertyStream() + .forEach(fieldValue -> parse(fieldValue.getValue(), links)); + } + } + return links; + } +} \ No newline at end of file