如何“扫描”一个网站(或页面)以获取信息,并将其导入到我的程序中?

57

我正在努力找出如何从网页中提取信息并将其带入我的Java程序。例如,如果我知道我想要的确切页面,为了简单起见,是一个Best Buy商品页面,那么我如何从该页面获取所需的适当信息?比如标题、价格和描述等?这个过程应该被称为什么?我甚至不知道从哪里开始研究。

编辑: 好的,我正在测试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


1
你的LinkedList出现了问题,因为LinkedList.peekFirst是在Java 1.6中出现的,而你似乎使用的是早期版本。 - zamza
2
这个过程通常称为“屏幕抓取”,当API(如SOAP)不可用但有一个Web GUI时使用。它涉及让您的应用程序模拟一个Web浏览器并手动解析HTML页面。我建议您考虑以下列出的API之一,可以自动化大部分解析工作。 - Chris Nava
10个回答

104
请使用像Jsoup这样的HTML解析器。相对于Java中可用的其他HTML解析器,我更喜欢它,因为它支持类似于jQueryCSS选择器。此外,它表示节点列表的类Elements实现了Iterable,因此您可以在增强for循环中遍历它(因此不需要在平均Java DOM解析器中烦恼使用冗长的NodeNodeList等类)。
这是一个基本的启动示例(只需将最新的Jsoup JAR文件放入类路径即可):
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://dev59.com/0HE85IYBdhLWcg3wUByz";
        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());
        }
    }

}

正如你可能已经猜到的那样,这将打印出你自己的问题和所有回答者的姓名。


2
哇,这很不错!不过我有一个问题,我只是复制并粘贴了这个来进行测试,但我一直收到这个错误(请看编辑后的原帖)。 - James
2
@James:这需要至少Java 1.6(已经超过3年了)。所提到的LinkedList#peekFirst()方法是在Java 1.6中引入的。升级您的JVM(JDK)或将您的IDE(Eclipse?)配置为Java 6兼容模式。 - BalusC
10
如果有任何 .NET 程序员感兴趣的话,我已经将 jsoup 移植到了 .NET 平台上:http://nsoup.codeplex.com/。希望这对大家有所帮助。 - GeReV
1
@BalusC,你提供的例子真是让我受益匪浅!我之前并不知道这个神奇的库存在!我曾经因为URL FETCH而疯狂了...原来我一直寻找的是Jsoup... 非常感谢! - Daniel
1
@CardinalSystem:是的,那正是OP所问的。不过,Jsoup也支持将HTML代码作为String变量输入,例如Document document = Jsoup.parse(html);。请参阅其文档。 - BalusC
显示剩余3条评论

10

这被称为屏幕抓取,维基百科有一篇关于更具体的网页抓取文章。这可能是一个很大的挑战,因为某些丑陋、混乱、除非有浏览器聪明才能修复的HTML存在,所以祝你好运。


5
我会使用JTidy - 它类似于JSoup,但我不太了解JSoup。 JTidy处理破损的HTML并返回w3c文档,因此您可以将其用作源来进行XSLT从而提取您真正感兴趣的内容。如果您不了解XSLT,则最好选择JSoup,因为文档模型比w3c更易于使用。

编辑:快速查看JSoup网站显示JSoup可能确实是更好的选择。它似乎支持CSS选择器,以从文档中提取内容。这可能比涉及XSLT要容易得多。


4
您可以使用html解析器(这里有许多有用的链接:java html parser)。
这个过程被称为“抓取网站内容”。搜索“grab website content java”进行进一步的调查。

3

2

虽然我回答这个问题的时间有点晚(已经过了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"));

}

这里可以找到另一个简短的例子:点击这里

希望这对某些人有所帮助!


2
你也可以尝试使用jARVEST

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);
  }

2

您可能需要查看HTML,以查找与您的文本唯一且接近的字符串,然后您可以使用行/字符偏移量来获取数据。

如果在Java中没有类似于C#中System.XML.Linq找到的XML类,则可能会很棘手。


1

JSoup的解决方案非常好,但如果您只需要提取一些非常简单的内容,使用正则表达式或String.indexOf可能更容易。

正如其他人已经提到的那样,这个过程被称为抓取。


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

-1
看看cURL库。我从未在Java中使用过它,但我相信一定有绑定。基本上,你要做的就是向任何想要“抓取”的页面发送一个cURL请求。该请求将返回一个包含页面源代码的字符串。从那里,您将使用正则表达式从源代码中解析出所需的任何数据。这通常是您要做的事情。

4
不要使用正则表达式解析HTML。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接