关于新闻博客类页面正文抽取

关于新闻博客类页面正文抽取

对于爬虫来说,结构化页面或许是一件工作量很大的活。也许很多时候我们需要不停的去写页面的结构规则。但是如果能有一个方法,让我们更容易的得到部分页面规则,那么是不是会减少我们很多工作量呢?

如果只是为了使用的朋友可以直接使用文章最下方提供的jar包

下面让我为大家介绍一下我们正文抽取的设计思路。

  1. 页面中很多标签和正文没有关系,哪怕全部去掉也不会影响正文格式。
  2. 正文中,很多标签项会影响正文的文本密度,这些标签并不表示实际内容,只是控制页面格式。
  3. 如果可以有多个页面进行对比,可以将所有页面中,重复的内容去除。对于页面来说,有用的数据一般是可变的。

这里我粘贴一些我页面预处理的代码:

public String htmlStrInto(String html) {
    html = html.replaceAll("(?i)<a.*?>(?s).*?</a>", "");
    html = html.replaceAll("(?i)<script.*?>.*?</script>", "");
    html = html.replaceAll("(?i)<.?span.*?>", "");
    html = html.replaceAll("(?i)<.?em.*?>", "");
    html = html.replaceAll("(?i)<.?strong.*?>", "");
    html = html.replaceAll("(?i)<!--(?s).*?-->", "");
    html = html.replaceAll("(?i)<.?blockquote.*?>", "");
    html = html.replaceAll("(?i)&nbsp;", " ");
    html = html.replaceAll("(?i)<.?pre.*?>", "");
    html = html.replaceAll("(?i)<.?code.*?>", "");
    html = html.replaceAll("(?i)<.?table.*?>", "");
    html = html.replaceAll("(?i)<.?tbody.*?>", "");
    html = html.replaceAll("(?i)<.?thead.*?>", "");
    html = html.replaceAll("(?i)<.?tr.*?>", "");
    html = html.replaceAll("(?i)<.?td.*?>", "");
    html = html.replaceAll("(?i)<.?th.*?>", "");
    return html;
}

接下来就是要将整个HTML页面构造成一个DOM树型结构,以方便我们判断,这里我使用了jsoup.jar作为HTML解析工具

先写一个小工具类,用来获取某个标签的ID以及Class

public String cssAndIdStr(Node node) {
    String classs = node.attr("class").trim();
    String id = node.attr("id");
    classs = classs.matches(".*\\d+.*") ? "" : classs;
    id = id.matches(".*\\d+.*") ? "" : id;
    String clazz = classs.equals("") ? "" : "[class*=" + classs + "]";
    String idzz = id.equals("") ? "" : "[id=" + id + "]";
    return node.nodeName() + idzz + clazz;
}

在这里,我将ID和Class中所有的数字全部去掉了,应为很多ID和Class是自动生成的,可能换个页面就无法使用了。
接下来就是获取结构树的过程了
我使用递归的方式来遍历以及生成整个domTree
这里我将整个DOM树存储在一个Map结构中,其中Eqkey存储了每个树枝的节点信息,这个类我粘贴在文档最后。

Map<String, EqKey> map = new HashMap<>();
public Map<String, EqKey> getHtmlTree(Node node) {
    if (node instanceof Document) {
        node = ((Document) node).select("body").first();
        getDomTree(node, "body");
    } else {
        getDomTree(node, "");
    }
    return map;
}
private void getDomTree(Node node, String str) {
    List<Node> childNodes = node.childNodes();
    if (childNodes.size() == 0) {
        String nodeName = node.nodeName();
        String text = node.outerHtml().trim();
        if (nodeName.equals("#text") && text.length() > 10) {
            if (map.get(str) == null) {
                map.put(str, new EqKey(str, text));
            } else {
                EqKey eqKey = map.get(str);
                eqKey.setValue(eqKey.getValue() + text);
                eqKey.setNumber(eqKey.getNumber() + 1);
                map.put(str, eqKey);
            }
        }
    }
    for (Node child : childNodes) {
        String id = child.attr("id");
        if (id == null || id.trim().length() == 0)
            getDomTree(child, str + ">" + cssAndIdStr(child));
        else {
            String cssAndIdStr = cssAndIdStr(child);
            if (!cssAndIdStr.matches(".*?[id=.*?//d+?.*?].*?"))
                getDomTree(child, cssAndIdStr(child));
            else
                getDomTree(child, str + ">" + cssAndIdStr(child));
        }
    }
}

然后,我们将所有树形结构的节点去进行打分。
通过文字数和节点数进行判定。

public List<EqKey> getText() {
    if (htmlStr == null) {
        //自定义异常
        throw new NotHtmlException("Not have HTML");
    }
    List<EqKey> list = new ArrayList<>();
    String html;
    //HTML预处理
    html = htmlStrInto(htmlStr);
    Document doc = Jsoup.parse(html);
    //获取文章文字长度
    float allLength = doc.text().trim().length();
    //获取页面p标签长度
    int tagNum = doc.select("p").size();
    float score;
    //得到结构树
    Map<String, EqKey> htmlTree = getHtmlTree(doc);
    //遍历树结构,在Map中键为页面树形结构
    for (String key : htmlTree.keySet()) {
        score = 0;
        //获取节点内文字长度
        score = htmlTree.get(key).getValue().length() / allLength * 100;
        //获取同结构中节点个数
        score += htmlTree.get(key).getNumber();
        //封装信息
        EqKey eqKey = new EqKey(key, htmlTree.get(key).getValue());
        //通过页面个数和页面文字数,得到一个分值
        eqKey.setScore(score);
        list.add(eqKey);
    }
    return list;
}

然后就是提取标题

    public static String getTitle(String html) {
        Document doc = Jsoup.parse(html);
        String title = doc.select("html>head>title").text().toString().trim();
        if (title != null && title.length() != 0) {

        } else if (title == null || title.length() == 0) {
            try {
                title = doc.select("title").get(0).text().trim();
            } catch (IndexOutOfBoundsException e) {
                // System.out.println(doc);
            }
        }
        if (title == null || title.length() == 0) {
            try {
                title = doc.select("h1").get(0).text().trim();
            } catch (IndexOutOfBoundsException e) {
                // System.out.println(doc);
            }
        }
        return title;
    }

之后,我们按照之间的打分,取出分值最高的一项
我们这里,通过回溯几个路径,来判断是否更加符合我们判断正文的条件,
我们返回一个List集合,集合中就是我们所有按照文本密度产生的结果。
下面我把暴露在外,供使用者使用。

public class HtmlPageData{
    private String html;
    //构造函数:提供一个HTML页面字符串形式
    //也可以是一个连接如http://blog.csdn.net/lowprofile_coding/article/details/78004224
    public HtmlPageData(String html){
        if(html.matches("[a-zA-z]+://[^\\s]*")){
            Map<String, String> htmlString = new Reptile().getHTMLString(html);         this.html=htmlString.get(HTMLConsts.HTML_CONTEXT);
            System.out.println(this.html);
        }else{
            System.out.println(2);
            this.html=html;
        }

    }
    //构造函数:传递一个URL对象
    public HtmlPageData(URL url){
        Map<String, String> htmlString = new Reptile().getHTMLString(html);
        this.html=htmlString.get(HTMLConsts.HTML_CONTEXT);
    }
    //获得正文内容:这部分我逻辑较为混乱。
    //如果有更好的建议,请发我邮箱whb3299065@126.com
    public Map<String,String> getText(boolean b){
        String context="";
        Document doc=Jsoup.parse(html);
        Document doc2=Jsoup.parse(html);
        if(html==null || "".equals(html)){
            throw new AutoPageUtil.NotHtmlException("html file don't have");
        }
        AutoPageUtil page= new AutoPageUtil(html);
        //获取标题
        String title=page.getTitle();
        List<EqKey> textList = page.getText();
        EqKey max = Collections.max(textList,EqKey.getScoreCompareTo());
        String key=max.getKey();
        //目录到p标签之前
        key=key.replaceAll(">p(>|\\[).*?#text$", "");
        //替换完成后,如果发现文本节点还在,就去掉文本节点
        if(key.endsWith(">#text")){
            key=key.replaceAll(">#text", "");
        }

        context=b?doc.select(key).toString():doc.select(key).text();
        try{
        //循环判断去掉一些标签后是否为正文信息
        for(int i=0;i<2;i++){
            String str1=doc2.select(key).text();
            String keyK=key.substring(0, key.lastIndexOf(">"));
            String str2=doc2.select(keyK).text();
            if(str2.length()-str1.length()>600){
                if(doc.select(keyK).select("a").size()-doc.select(key).select("a").size()>10);
                else{
                    key=keyK;
                    context=b?doc.select(key).toString():doc.select(key).text();
                }
            }else if(str2.length()-str1.length()<5){
                key=keyK;
                context=b?doc.select(key).toString():doc.select(key).text();
            }
        }
        }catch (StringIndexOutOfBoundsException e) {
        }
        Map<String,String>map=new HashMap<String, String>();
        map.put("title", title);
        map.put("text", context);
        map.put("rule", key);
        return map;
    }
    @Test
    public void test(){
    }
}

Eqkey:

public class EqKey {
    private int id;
    private String key;
    private String value;
    private Integer number=1;
    private float score;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
    public String getValue() {
        return value;
    }
    public void setValue(String value) {
        this.value = value;
    }
    public EqKey(String key, String value) {
        super();
        this.key = key;
        this.value = value;
    }
    public EqKey() {
        super();
    }
    public Integer getNumber() {
        return number;
    }
    public void setNumber(Integer number) {
        this.number = number;
    }
    public float getScore() {
        return score;
    }
    public void setScore(float score) {
        this.score = score;
    }
    public static  Comparator<EqKey> getScoreCompareTo(){
        Comparator<EqKey> c=new Comparator<EqKey>() {
            @Override
            public int compare(EqKey o1, EqKey o2) {
                return o1.score-o2.score>0?1:-1;
            }

        };
        return c;
    }
    public static Comparator<EqKey> getNumCompareTo(){
        Comparator<EqKey> c=new Comparator<EqKey>() {

            @Override
            public int compare(EqKey o1, EqKey o2) {
                return o1.number-o2.number;
            }

        };
        return c;
    }
    @Override
    public String toString() {
        return "EqKey [score=" + score + ", key=" + key + ", id=" + id + ", value=" + value + ", number=" + number
                + "]";
    }

}

原谅我收取两个积分,如果非常想得到,且没有积分的话,可以联系我whb3299065@126.com
工具类下载地址:
http://download.csdn.net/download/whb3299065/9984700

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值