【问题标题】:How to "scan" a website (or page) for info, and bring it into my program?如何“扫描”网站(或页面)以获取信息,并将其带入我的程序?
【发布时间】:2011-02-19 14:24:43
【问题描述】:

嗯,我非常想弄清楚如何从网页中提取信息,并将其带入我的程序(用 Java 编写)。

例如,如果我知道我想从中获取信息的确切页面,为简单起见,百思买商品页面,我如何从该页面获取我需要的适当信息?喜欢标题,价格,描述?

这个过程甚至会被称为什么?我什至不知道要开始研究这个。

编辑: 好的,我正在对 JSoup(BalusC 发布的那个)进行测试,但我不断收到此错误:

Exception in thread "main" java.lang.NoSuchMethodError: java.util.LinkedList.peekFirst()Ljava/lang/Object;
at org.jsoup.parser.TokenQueue.consumeWord(TokenQueue.java:209)
at org.jsoup.parser.Parser.parseStartTag(Parser.java:117)
at org.jsoup.parser.Parser.parse(Parser.java:76)
at org.jsoup.parser.Parser.parse(Parser.java:51)
at org.jsoup.Jsoup.parse(Jsoup.java:28)
at org.jsoup.Jsoup.parse(Jsoup.java:56)
at test.main(test.java:12)

我确实有 Apache Commons

【问题讨论】:

  • 你的LinkedList有问题,因为LinkedList.peekFirst出现在java 1.6,你好像用的是更早的版本
  • 此过程通常称为“屏幕抓取”,当 API(如 SOAP)不可用但 Web GUI 可用时使用。它涉及让您的应用程序伪装成 Web 浏览器并手动解析 HTML 页面(或多或少)。我建议您考虑下面列出的 API 之一,它可以自动完成大部分解析。

标签: java html web-scraping jsoup


【解决方案1】:

您可以使用 html 解析器(这里有很多有用的链接:java html parser)。

该过程称为“抓取网站内容”。搜索“grab website content java”以进行进一步调查。

【讨论】:

    【解决方案2】:

    查看 cURL 库。我从未在 Java 中使用过它,但我确信它必须有绑定。基本上,你要做的就是向你想要“抓取”的任何页面发送一个 cURL 请求。该请求将带有源代码的字符串返回到页面。从那里,您将使用正则表达式从源代码中解析您想要的任何数据。这就是您通常要这样做的方式。

    【解决方案3】:

    这被称为屏幕抓取,维基百科有这篇文章更具体的web scraping。这可能是一个重大挑战,因为那里有一些丑陋的、混乱的、如果不是浏览器聪明的 HTML 的话,那么祝你好运。

    【讨论】:

      【解决方案4】:

      您可能希望查看 HTML 以查看是否可以找到唯一且靠近您的文本的字符串,然后您可以使用 line/char-offsets 来获取数据。

      如果没有任何类似于 C# 中 System.XML.Linq 中的 XML 类,在 Java 中可能会很尴尬。

      【讨论】:

        【解决方案5】:

        使用像 Jsoup 这样的 HTML 解析器。我的偏好高于other HTML parsers available in Java,因为它supportsjQuery 就像CSS selectors。此外,它的代表节点列表的类Elements 实现了Iterable,因此您可以在enhanced for loop 中对其进行迭代(因此无需为冗长的NodeNodeList 之类的类而烦恼)平均 Java DOM 解析器)。

        这是一个基本的启动示例(只需将 latest Jsoup JAR file 放在类路径中):

        package com.stackoverflow.q2835505;
        
        import org.jsoup.Jsoup;
        import org.jsoup.nodes.Document;
        import org.jsoup.nodes.Element;
        import org.jsoup.select.Elements;
        
        public class Test {
        
            public static void main(String[] args) throws Exception {
                String url = "https://stackoverflow.com/questions/2835505";
                Document document = Jsoup.connect(url).get();
        
                String question = document.select("#question .post-text").text();
                System.out.println("Question: " + question);
        
                Elements answerers = document.select("#answers .user-details a");
                for (Element answerer : answerers) {
                    System.out.println("Answerer: " + answerer.text());
                }
            }
        
        }
        

        您可能已经猜到了,这会打印您自己的问题和所有回答者的姓名。

        【讨论】:

        • 哇,这太棒了!不过我有一个问题,我只是复制并粘贴它只是为了进行测试运行,但我不断收到此错误(查看已编辑的 OP)
        • @James:这至少需要 Java 1.6(已经超过 3 年了)。提到的LinkedList#peekFirst() 方法是在Java 1.6 中引入的。升级您的 JVM (JDK) 或将您的 IDE (Eclipse?) 配置为 Java 6 合规模式。
        • 如果有任何 .NET 程序员感兴趣,我已将 jsoup 移植到 .NET:nsoup.codeplex.com。希望这对任何人都有帮助。
        • @BalusC ,这是你给的例子:让我的一天!不知道这个神奇的图书馆!!! URL FETCH 我快疯了... Jsoup 是我应该一直在寻找的...非常感谢!
        • @CardinalSystem:是的,这正是 OP 所要求的。然而,Jsoup 也只支持在 String 变量中获取 HTML 代码,就像在 Document document = Jsoup.parse(html); 中一样。另请参阅其文档。
        【解决方案6】:

        我会使用JTidy - 它与 JSoup 类似,但我不太了解 JSoup。 JTidy 处理损坏的 HTML 并返回一个 w3c 文档,因此您可以使用它作为 XSLT 的源来提取您真正感兴趣的内容。如果您不了解 XSLT,那么您不妨使用 JSoup,作为文档模型比 w3c 更好用。

        编辑:快速浏览 JSoup 网站表明 JSoup 确实可能是更好的选择。它似乎支持从文档中提取内容的 CSS 选择器。这可能比进入 XSLT 更容易使用。

        【讨论】:

          【解决方案7】:

          JSoup 解决方案很棒,但如果您只需要提取一些非常简单的东西,使用 regex 或 String.indexOf 可能会更容易

          正如其他人已经提到的那样,该过程称为抓取

          【讨论】:

          • 为什么使用正则表达式会更容易?我已经尝试过正则表达式,它确实无法处理现实生活中的 html,并且使用解析 html 可能很危险。 Jsoup 是开箱即用的解决方案,只需几行代码,你就可以用你的 html 做任何你需要做的事情。
          • 过于简单的示例 - 想象一下,您想要的只是提取页面的生成日期。因此,您检查 html 并看到类似 <span id='date'>07/07/07</span> 的内容。那么,我会使用 String.indexOf 或我自己的一些实用程序,例如 textBetween("", "")。另一个好处是您不必解析整个 html。我已经成功地使用本土 StringScanner 类从 html 中提取数据,该类具有 moveBefore(String what)、moveAfter(String what)、getTextUpTo(String what) 等方法……这完全取决于您的问题有多复杂。
          【解决方案8】:

          jsoup 支持 java 1.5

          https://github.com/tburch/jsoup/commit/d8ea84f46e009a7f144ee414a9fa73ea187019a3

          看起来那个堆栈是一个错误,并且已经修复

          【讨论】:

            【解决方案9】:

            你也可以试试jARVEST

            它基于 JRuby DSL,通过纯 Java 引擎来蜘蛛抓取转换网站。

            示例

            查找网页内的所有链接(wgetxpath 是 jARVEST 语言的结构):

            wget | xpath('//a/@href')
            

            在 Java 程序中:

            Jarvest jarvest = new Jarvest();
              String[] results = jarvest.exec(
                "wget | xpath('//a/@href')", //robot! 
                "http://www.google.com" //inputs
              );
              for (String s : results){
                System.out.println(s);
              }
            

            【讨论】:

              【解决方案10】:

              我的回答可能对这个问题的作者没有用(我迟到了 8 个月,所以我猜这不是正确的时机),但我认为它可能对可能遇到这个答案的许多其他开发人员有用。

              今天,我刚刚(以我公司的名义)发布了一个 HTML 到 POJO 的完整框架,您可以使用它来将 HTML 映射到任何 POJO 类,只需一些注释。该库本身非常方便,并且具有许多其他功能,同时非常易于插入。你可以在这里看看:https://github.com/whimtrip/jwht-htmltopojo

              如何使用:基础知识

              假设我们需要解析以下 html 页面:

              <html>
                  <head>
                      <title>A Simple HTML Document</title>
                  </head>
                  <body>
                      <div class="restaurant">
                          <h1>A la bonne Franquette</h1>
                          <p>French cuisine restaurant for gourmet of fellow french people</p>
                          <div class="location">
                              <p>in <span>London</span></p>
                          </div>
                          <p>Restaurant n*18,190. Ranked 113 out of 1,550 restaurants</p>  
                          <div class="meals">
                              <div class="meal">
                                  <p>Veal Cutlet</p>
                                  <p rating-color="green">4.5/5 stars</p>
                                  <p>Chef Mr. Frenchie</p>
                              </div>
              
                              <div class="meal">
                                  <p>Ratatouille</p>
                                  <p rating-color="orange">3.6/5 stars</p>
                                  <p>Chef Mr. Frenchie and Mme. French-Cuisine</p>
                              </div>
              
                          </div> 
                      </div>    
                  </body>
              </html>
              

              让我们创建我们想要映射到的 POJO:

              public class Restaurant {
              
                  @Selector( value = "div.restaurant > h1")
                  private String name;
              
                  @Selector( value = "div.restaurant > p:nth-child(2)")
                  private String description;
              
                  @Selector( value = "div.restaurant > div:nth-child(3) > p > span")    
                  private String location;    
              
                  @Selector( 
                      value = "div.restaurant > p:nth-child(4)"
                      format = "^Restaurant n\*([0-9,]+). Ranked ([0-9,]+) out of ([0-9,]+) restaurants$",
                      indexForRegexPattern = 1,
                      useDeserializer = true,
                      deserializer = ReplacerDeserializer.class,
                      preConvert = true,
                      postConvert = false
                  )
                  // so that the number becomes a valid number as they are shown in this format : 18,190
                  @ReplaceWith(value = ",", with = "")
                  private Long id;
              
                  @Selector( 
                      value = "div.restaurant > p:nth-child(4)"
                      format = "^Restaurant n\*([0-9,]+). Ranked ([0-9,]+) out of ([0-9,]+) restaurants$",
                      // This time, we want the second regex group and not the first one anymore
                      indexForRegexPattern = 2,
                      useDeserializer = true,
                      deserializer = ReplacerDeserializer.class,
                      preConvert = true,
                      postConvert = false
                  )
                  // so that the number becomes a valid number as they are shown in this format : 18,190
                  @ReplaceWith(value = ",", with = "")
                  private Integer rank;
              
                  @Selector(value = ".meal")    
                  private List<Meal> meals;
              
                  // getters and setters
              
              }
              

              现在还有Meal 类:

              public class Meal {
              
                  @Selector(value = "p:nth-child(1)")
                  private String name;
              
                  @Selector(
                      value = "p:nth-child(2)",
                      format = "^([0-9.]+)\/5 stars$",
                      indexForRegexPattern = 1
                  )
                  private Float stars;
              
                  @Selector(
                      value = "p:nth-child(2)",
                      // rating-color custom attribute can be used as well
                      attr = "rating-color"
                  )
                  private String ratingColor;
              
                  @Selector(
                      value = "p:nth-child(3)"
                  )
                  private String chefs;
              
                  // getters and setters.
              }
              

              我们在 github 页面上对上述代码提供了更多解释。

              目前,让我们看看如何废弃它。

              private static final String MY_HTML_FILE = "my-html-file.html";
              
              public static void main(String[] args) {
              
              
                  HtmlToPojoEngine htmlToPojoEngine = HtmlToPojoEngine.create();
              
                  HtmlAdapter<Restaurant> adapter = htmlToPojoEngine.adapter(Restaurant.class);
              
                  // If they were several restaurants in the same page, 
                  // you would need to create a parent POJO containing
                  // a list of Restaurants as shown with the meals here
                  Restaurant restaurant = adapter.fromHtml(getHtmlBody());
              
                  // That's it, do some magic now!
              
              }
              
              
              private static String getHtmlBody() throws IOException {
                  byte[] encoded = Files.readAllBytes(Paths.get(MY_HTML_FILE));
                  return new String(encoded, Charset.forName("UTF-8"));
              
              }
              

              另一个简短的例子可以找到here

              希望这会对那里的人有所帮助!

              【讨论】:

                猜你喜欢
                • 2016-09-23
                • 2010-09-21
                • 2023-03-17
                • 2013-06-19
                • 1970-01-01
                • 2013-04-09
                • 1970-01-01
                • 2014-05-04
                • 1970-01-01
                相关资源
                最近更新 更多