关于新闻博客类页面正文抽取
对于爬虫来说,结构化页面或许是一件工作量很大的活。也许很多时候我们需要不停的去写页面的结构规则。但是如果能有一个方法,让我们更容易的得到部分页面规则,那么是不是会减少我们很多工作量呢?
如果只是为了使用的朋友可以直接使用文章最下方提供的jar包
下面让我为大家介绍一下我们正文抽取的设计思路。
- 页面中很多标签和正文没有关系,哪怕全部去掉也不会影响正文格式。
- 正文中,很多标签项会影响正文的文本密度,这些标签并不表示实际内容,只是控制页面格式。
- 如果可以有多个页面进行对比,可以将所有页面中,重复的内容去除。对于页面来说,有用的数据一般是可变的。
这里我粘贴一些我页面预处理的代码:
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) ", " ");
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