【问题标题】:Android parse nested tables with JsoupAndroid 使用 Jsoup 解析嵌套表
【发布时间】:2020-10-19 07:50:21
【问题描述】:

我正在尝试在线解析 HTML 页面以使用 Jsoup 从表中检索数据。我要解析的页面包含多个表格。

我该怎么做?

这是我要解析的示例页面:

https://www.cpu-world.com/info/AMD/AMD_A4-Series.html

我要提取的数据是Model Name和详情页的URL。

编辑:

这是我用来从详细信息页面提取数据的一些代码。

            try {
                /**
                 * Works to iterate through the items at the following website
                 * https://www.cpu-world.com/CPUs/K10/AMD-A4-Series%20A4-3300.html
                 */
                URL url = new URL("https://www.cpu-world.com/CPUs/K10/AMD-A4-Series%20A4-3300.html");
                
                Document doc = Jsoup.parse(url, 3000);
                
                // spec_table is the name of the class associated with the table
                Elements table = doc.select("table.spec_table");
                Elements rows = table.select("tr");
                
                Iterator<Element> rowIterator = rows.iterator();
                rowIterator.next();
                boolean wasMatch = false;
                
                // Loop through all items in list
                while (rowIterator.hasNext()) {
                    Element row = rowIterator.next();
                    Elements cols = row.select("td");
                    String rowName = cols.get(0).text();
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

我一直在阅读一些教程和文档,但我似乎无法弄清楚如何浏览网页以提取我正在寻找的数据。我了解 HTML 和 CSS,但我只是在学习 Jsoup。

(我将其标记为 Android,因为那是我使用 Java 代码的地方。我想没必要那么具体。)

【问题讨论】:

  • 是的,这是可能的。向我们展示您到目前为止所尝试的内容,添加一些代码。网上有很多教程。顺便说一句,你为什么用这个问题标记 android?
  • 您是否只是单击了 Chrome 或 Edge 浏览器中的 View Source 按钮?此页面是一个 AJAX 页面,特别是,该表是由 JavaScript 加载的,因此 JSoup 不会获得您请求的数据。您想要表格中每个产品的URL 和型号吗?你的问题是这样问的吗?我可以研究解决方案,但它不会使用 JSoup 库。
  • 我在 Chrome 中按 F12 来查看那里的代码。我从来没有想过它是这样的(AJAX),只是我可以毫无问题地从许多其他站点获取数据。你怎么知道它是 AJAX,所以我知道下次要找什么?是的,网址和型号正是我要找的。​​span>
  • 嗯... 问题 1 你怎么知道是 Java-ScriptAJAX 还是 Angular JS、Type Script、React JS) - 打印出在您轮询 Web 服务器时下载的 HTML...View Source 按钮有时会有所帮助,但有时会让您感到困惑,因为 它向您显示的 HTML 并不总是与服务器的第一次轮询所下载的完全一致。 问题 2 有没有办法从script loaded 页面获取信息?是的,有点...有脚本执行包,但我刚生病,我需要躺下,所以我不能写它... :)
  • 对不起“免费 cmets”(没有答案)......我需要先睡觉。

标签: java web-scraping jsoup


【解决方案1】:

这看起来像你所追求的:

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

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

public class CpuWorld {

    public static void main(String[] args) throws IOException {
        URL url = null;
        try {
            /**
             * Works to iterate through the items at the following website
             * https://www.cpu-world.com/CPUs/K10/AMD-A4-Series%20A4-3300.html
             */
             url = new URL("https://www.cpu-world.com/CPUs/K10/AMD-A4-Series%20A4-3300.html");
        } catch (IOException e) {
            e.printStackTrace();
        }

        Document doc = Jsoup.parse(url, 3000);
        // spec_table is the name of the class associated with the table
        String modelNumber = doc.select("table tr:has(td:contains(Model number)) td b a").text();
        String modelUrl = doc.select("table tr:has(td:contains(Model number)) td b a").attr("href");

        System.out.println(modelNumber + " : " + modelUrl);
    }
}

如果这不是你想要的,请告诉我

编辑:结果:

A4-3300 : https://www.cpu-world.com/CPUs/K10/AMD-A4-Series%20A4-3300.html

Process finished with exit code 0

编辑:

这比一盒青蛙还要疯狂,但我们开始吧...我会让您将 2 和 2 放在一起,以遍历 URL 以获得您所追求的各个细节:

import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public class CpuWorld {

    public static final String CPU_WORLD_COM_URL = "https://www.cpu-world.com/info/AMD/AMD_A4-Series.html";

    public static final String SCRAMBLED_DATA_HEADER = "<!--\r\nfunction JSC_Process () {var bk,xg,qh,k,aj,y,e,cq,u,a,ei;\r\na=\"";
    public static final String SCRAMBLED_DATA_FOOTER = "//- qh=[\"\"];k=[2];cq=[7600];if (CW_AB){if\t((AB_v!='0')&&(AB_v!='X')&&(AB_Gl((AB_v=='')?99:3)==3)){y=1;AB_cb=function(){JSC_Process();};}else{y=2;}}for(aj=e=0;aj<k.length;aj++){ei=cq[aj];bk=qh[aj];if (!bk) bk=\"JSc_\"+aj;u=CW_E(bk);if (u){bk=\" jsc_a\";if (y>=k[aj]){xg=a.substr(e,ei);xg=xg.replace(/(.)(.)/g,\"$2$1\");u.innerHTML=xg.replace(/\\\\n/g,\"\\n\");bk='';}u.className=u.className.replace(/(^| )jsc_\\w+$/,bk);}e+=ei;}}JSC_Process();";

    private static RestTemplate restTemplate = new RestTemplate();

    public static void main(String[] args) throws IOException {
        Document tableData = getTableData(CPU_WORLD_COM_URL);

        List<String> fullUrls = tableData.select("table tr td:contains(a) a").stream()
                .map(e -> "https://www.cpu-world.com/" + e.attr("href"))
                .collect(Collectors.toList());

        List<String> fullModels = tableData.select("table tr td:contains(a) a").stream()
                .map(e -> e.text())
                .collect(Collectors.toList());

        for (int i=0; i< fullUrls.size(); i++) {
            System.out.println(fullModels.get(i) + " : " + fullUrls.get(i));
        }
    }

    private static Document getTableData(String url) {
        Connection.Response response = null;
        try {
            response = Jsoup
                    .connect(url)
                    .headers(getHeaders())
                    .method(Connection.Method.GET)
                    .data()
                    .execute();

        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        Elements script = Jsoup.parse(response.body()).select("script");

        // take substring of the child node from after the header and before the footer (- 6 more chars which seem dynamic)
        // The script tag containing JSC_Process is the one with the data in (but all mangled).
        Optional<String> scrambledData = script.stream()
                .filter(element -> element.data().contains("JSC_Process"))
                .map(node -> node.data().substring(SCRAMBLED_DATA_HEADER.length(), (node.data().length() - SCRAMBLED_DATA_FOOTER.length()-6)))
                .findFirst();

        String tableData = Unscrambler.unscramble(scrambledData.orElseThrow(() -> new RuntimeException("scrambled data not found in relevant script tag")));

        Document doc = Jsoup.parse(tableData);
        return doc;
    }

    private static boolean isNotEmptyString(Element node) {
        return node.data() != null && !node.data().equals("");
    }

    /**
    * trick server into thinking we're not a bot
    * by telling the server we were referred by the server itself
    * and give tell it we're using a Mozilla/Safari browser
    **/
    private static Map<String, String> getHeaders() {
        Map<String, String> headersMap = new HashMap<>();
        headersMap.put("User-Agent", "Mozilla/5.0 Safari/537.36");
        headersMap.put("Referer", CPU_WORLD_COM_URL);
        return headersMap;
    }
}

class Unscrambler {

    public static final String SCRAMBLED_DATA_HEADER = "<!--\r\nfunction JSC_Process () {var bk,xg,qh,k,aj,y,e,cq,u,a,ei;\r\na=\"";
    public static final String SCRAMBLED_DATA_FOOTER = "qh=[\"\"];k=[2];cq=[7600];if (CW_AB){if\t((AB_v!='0')&&(AB_v!='X')&&(AB_Gl((AB_v=='')?99:3)==3)){y=1;AB_cb=function(){JSC_Process();};}else{y=2;}}for(aj=e=0;aj<k.length;aj++){ei=cq[aj];bk=qh[aj];if (!bk) bk=\"JSc_\"+aj;u=CW_E(bk);if (u){bk=\" jsc_a\";if (y>=k[aj]){xg=a.substr(e,ei);xg=xg.replace(/(.)(.)/g,\"$2$1\");u.innerHTML=xg.replace(/\\\\n/g,\"\\n\");bk='';}u.className=u.className.replace(/(^| )jsc_\\w+$/,bk);}e+=ei;}}JSC_Process();";

    public static String unscramble(String data) {
        String a=data.replace("\\\"","'")
                .replace("\\\\", "\\")
                .replace("\\r", "")
                .replace("\\n", "")
                .replace("\"+\r\n\"", ""); // remove gunk that mucks up processing in java
        StringBuffer buffer = new StringBuffer();
        int e = 0;
        int ei = 2;

        // This is effectively what the code in the footer is doing. Heavily un-obfuscated below.
        // swap two chars around - through
        for (int aj=0; aj < a.length()-2; aj+=2) {
            String xg = a.substring(e, ei);
            buffer.append(xg.substring(1,2) + xg.substring(0,1));
            e+=2;
            ei+=2;
        }
        return buffer.toString().replace("\n","");
    }
}

【讨论】:

  • 我复制了你直接粘贴的代码...我得到了:HTTP-403: [Forbidden], Category: Client errors
  • 我使用 Google Cloud Platform 来运行 Java - 所以除非 CPU World 有禁止 GCP,我还是不明白...(我不使用 JSoup,但我有 JAR 并运行了这个class,并收到了HTTP 403)
  • 很有趣,但您为什么要在 GCP 中运行?我怀疑可能有一个 IP 范围过滤器,所以代理 url 可能会起作用?首先使用 -vv 选项通过 curl 调试 URL 是否值得?
  • 嗯...(这是我自己的启蒙,我不是OP)...所以..你说你编译了那个代码 在上面的答案中发布,并检索到modelNumbermodelURL 就好了吗?我的意思是,当我的东西坏了时,我想找出原因... YES 我使用 GCP 进行所有开发,我是在这一点上刚刚习惯了他们的 IDE - 他们的服务器拥有超快的处理器(比我的桌面更快)...我编译了你的课程和 CPU World Server给我发了一个403 Forbidden...我会努力思考...谢谢!
  • 完美运行......我已经更新了上面的结果以显示输出
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多