半步多 玄玉的博客

Jsoup抓取天涯论坛的帖子

2017-01-07
玄玉

简单介绍一下,Jsoup 是一个 Java HTML Parser,它可以直接解析某个 URL 地址或者 HTML 文本

它的官网为:https://jsoup.org/,Github地址:https://github.com/jhy/jsoup

其强大在于:提供了一套非常省力的API,允许我们通过 DOM、CSS 以及类似于 jQuery 的操作来解析HTML内容

说人话就是:看上去虽然写的是 Java 代码,实际使用的(包括思维)全部是 DOM 编程或 jQuery 的函数

下面是一个实际而又实用的例子:抓取天涯论坛的帖子内容,然后保存到桌面的 txt 文件中

package com.jadyer.engine.common.util;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * Jsoup帮助类
 * Created by 玄玉<https://jadyer.cn/> on 2017/1/7 14:20.
 */
public final class JsoupHelper {
    private JsoupHelper(){}

    /**
     * 抓取天涯论坛帖子内容
     */
    public static void getTianyaBBSTxt(String bbsURL){
        try {
            getTianyaBBSTxt(bbsURL, 999999);
        } catch (IOException e) {
            System.out.println("抓取失败,堆栈轨迹如下:");
            e.printStackTrace();
        }
    }

    /**
     * 抓取天涯论坛帖子内容
     * 目前只抓取楼主发言部分,且内容会存储到用户桌面的文章URL同名txt文件中
     * @param bbsURL      帖子地址(支持传入首页地址或本帖其它任意页面的地址)
     * @param finalPageNo 帖子的最大的页码(如传入页码超出实际最大页码,这里在抓取完最大页码内容后,会自动停止作业)
     */
    private static void getTianyaBBSTxt(String bbsURL, int finalPageNo) throws IOException {
        String txt;
        String author;
        String publishTime;
        Element atlInfo;
        Elements elements;
        Document document;
        //去掉URL中的参数
        bbsURL = bbsURL.endsWith("shtml") ? bbsURL : bbsURL.substring(0, bbsURL.indexOf(".shtml")+6);
        //计算待写入的txt文件,并预先清空里面的内容(如果已存在)
        String filePath = FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath();
        String fileName = bbsURL.substring(bbsURL.indexOf("post")).replace(".shtml", ".txt");
        File bbsFile = new File(filePath, fileName);
        FileUtils.writeStringToFile(bbsFile, "", StandardCharsets.UTF_8);
        //获取帖子的起始页码
        int pageNo = Integer.parseInt(bbsURL.substring(bbsURL.lastIndexOf("-")+1, bbsURL.lastIndexOf(".")));
        //开始处理所有页面的所有楼层
        for(int i=pageNo; i<finalPageNo; i++){
            if(i == 1){
                /*
                 * 单独处理首层楼(首层楼只存在于首页)
                 */
                document = Jsoup.connect(bbsURL).get();
                //读取作者和发布时间
                atlInfo = document.getElementById("post_head").select("div.atl-info").first();
                author = atlInfo.select("span").eq(0).select("a").first().text();
                publishTime = atlInfo.select("span").eq(1).text();
                //获取楼层内容:每一个<div class="atl-item"></div>都代表一个楼层,首层也不例外
                elements = document.getElementsByClass("atl-item");
                //楼层具体内容都是在<div class="bbs-content"></div>里面包着的
                txt = elements.first().select("div.bbs-content").html().replaceAll("<br>", "");
                //写入txt
                FileUtils.writeStringToFile(bbsFile, "楼主:"+author+","+publishTime+"\r\n", StandardCharsets.UTF_8, true);
                FileUtils.writeStringToFile(bbsFile, txt+"\r\n\r\n", StandardCharsets.UTF_8, true);
                //需要移除已处理过的首层楼
                elements.remove(0);
            }else{
                /*
                 * 对于非首页的帖子,每次都需重新计算URL,并重新抓取内容
                 */
                bbsURL = bbsURL.replace("-"+(i-1)+".shtml", "-"+i+".shtml");
                document = Jsoup.connect(bbsURL).get();
                //超出帖子最终页码的访问,会被天涯重定向到最终页码页
                if(!bbsURL.equals(document.location())){
                    System.out.println("帖子抓取完毕");
                    return;
                }
                //得到本页需要抓取的elements
                elements = document.getElementsByClass("atl-item");
            }
            /*
             * 上面两种条件,最终都是计算好本页需要迭代处理的elements
             */
            for(Element obj: elements){
                atlInfo = obj.select("div.atl-info").first();
                String authorType = atlInfo.select("strong.host").text();
                //作者类型为空就说明,该楼层非楼主发言,暂时不写入txt
                if(StringUtils.isNotBlank(authorType)){
                    txt = obj.select("div.bbs-content").html().replaceAll("<br>", "");
                    author = atlInfo.select("span").eq(0).select("a").first().text();
                    publishTime = atlInfo.select("span").eq(1).text();
                    FileUtils.writeStringToFile(bbsFile, authorType+":"+author+","+publishTime+"\r\n", StandardCharsets.UTF_8, true);
                    FileUtils.writeStringToFile(bbsFile, txt+"\r\n\r\n", StandardCharsets.UTF_8, true);
                }
            }
        }
    }
}

小测试一下

@Test
public void jsoupHelperTest(){
    JsoupHelper.getTianyaBBSTxt("http://bbs.tianya.cn/post-no05-284609-1.shtml?aa=bb");
}

这是用到的依赖包

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.5</version>
</dependency>
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.2</version>
</dependency>

Content