美文网首页
[译] 如何从网页中获取主要文本信息

[译] 如何从网页中获取主要文本信息

作者: 墨同学_imink | 来源:发表于2017-03-01 12:09 被阅读0次

原文 Extracting meaningful content from raw HTML

解析HTML页面不算难事, 开源库Beautiful Soup就给你提供了紧凑直观的接口来处理网页, 让你使用喜欢的语言去实现. 但这也仅仅是第一步. 更有趣的问题是: 怎么才能把有用的信息(主题内容)从网页中抓取出来?

我在过去的几天里一直在尝试回答这个问题, 以下是我找到的.

Arc90 Readability

我最喜欢的方法是叫做 Arc90 Readability 的算法. 由 Arc90 Labs 开发, 目的是用来使网页阅读更佳舒适(例如在移动端上). 你可以找到它的chrome plugin. 整个项目放在Google Code上, 但更有趣的是它的算法, Nirmal Patel用python实现了这个算法, 源码在这里.

整个算法基于HTML-ID-names和HTML-CLASS-names生成了2个列表. 一个是包含了褒义IDs和CLASSes, 一个列表包含了贬义IDs和CLASSes. 如果一个tag有褒义的ID和CLASS, 那么它会得到额外的分数. 反之,如果它包含了贬义的ID和CLASS则丢分. 当我们算完了所有的tag的分数之后, 我们只需要渲染出那些得分较高的tags, 就得到了我们想要的内容.例子如下:

<div id="post"><h1>My post</h1><p>...</p></div>
<div class="footer"><a...>Contact</a></div>

第一个div tag含有一个褒义的ID (“id"=“post”), 所以很有可能这个tag包含了真正的内容(post). 然而, 第二行的tag footer是一个贬义的tag, 我们可以认为这个tag所包含的东西不是我们想要的真正内容. 基于以上, 我们可以得到如下方法:

  1. 在HTML源码里找到所有的p tag(即paragraph)
  2. 对于每一个paragraph段落:
  3. 把该段落的父级标签加入到列表中
  4. 并且把该父级标签初始化为0分
  5. 如果父级标签包含有褒义的属性, 加分
  6. 如果父级标签包含有贬义的属性, 减分
  7. 可选: 加入一些额外的标准, 比如限定tag的最短长度
  8. 找到得分最多的父级tag
  9. 渲染得分最多的父级tag

这里我参考了Nirmal Patel的代码, 写了一个简单的实现. 主要的区别是: 我在算法之前, 写了一个用来清除杂项的代码. 这样最终会得到一个没有脚本, 图片的文本内容, 就是我们想要的网页内容.

import re
from bs4 import BeautifulSoup
from bs4 import Comment
from bs4 import Tag
 
NEGATIVE = re.compile(".*comment.*|.*meta.*|.*footer.*|.*foot.*|.*cloud.*|.*head.*")
POSITIVE = re.compile(".*post.*|.*hentry.*|.*entry.*|.*content.*|.*text.*|.*body.*")
BR = re.compile("<br */? *>[ rn]*<br */? *>")
 
def extract_content_with_Arc90(html):
 
    soup = BeautifulSoup( re.sub(BR, "</p><p>", html) )
    soup = simplify_html_before(soup)
 
    topParent = None
    parents = []
    for paragraph in soup.findAll("p"):
        
        parent = paragraph.parent
        
        if (parent not in parents):
            parents.append(parent)
            parent.score = 0
 
            if (parent.has_key("class")):
                if (NEGATIVE.match(str(parent["class"]))):
                    parent.score -= 50
                elif (POSITIVE.match(str(parent["class"]))):
                    parent.score += 25
 
            if (parent.has_key("id")):
                if (NEGATIVE.match(str(parent["id"]))):
                    parent.score -= 50
                elif (POSITIVE.match(str(parent["id"]))):
                    parent.score += 25
 
        if (len( paragraph.renderContents() ) > 10):
            parent.score += 1
 
        # you can add more rules here!
 
    topParent = max(parents, key=lambda x: x.score)
    simplify_html_after(topParent)
    return topParent.text
 
def simplify_html_after(soup):
 
    for element in soup.findAll(True):
        element.attrs = {}    
        if( len( element.renderContents().strip() ) == 0 ):
            element.extract()
    return soup
 
def simplify_html_before(soup):
 
    comments = soup.findAll(text=lambda text:isinstance(text, Comment))
    [comment.extract() for comment in comments]
 
    # you can add more rules here!
 
    map(lambda x: x.replaceWith(x.text.strip()), soup.findAll("li"))    # tag to text
    map(lambda x: x.replaceWith(x.text.strip()), soup.findAll("em"))    # tag to text
    map(lambda x: x.replaceWith(x.text.strip()), soup.findAll("tt"))    # tag to text
    map(lambda x: x.replaceWith(x.text.strip()), soup.findAll("b"))     # tag to text
    
    replace_by_paragraph(soup, 'blockquote')
    replace_by_paragraph(soup, 'quote')
 
    map(lambda x: x.extract(), soup.findAll("code"))      # delete all
    map(lambda x: x.extract(), soup.findAll("style"))     # delete all
    map(lambda x: x.extract(), soup.findAll("script"))    # delete all
    map(lambda x: x.extract(), soup.findAll("link"))      # delete all
    
    delete_if_no_text(soup, "td")
    delete_if_no_text(soup, "tr")
    delete_if_no_text(soup, "div")
 
    delete_by_min_size(soup, "td", 10, 2)
    delete_by_min_size(soup, "tr", 10, 2)
    delete_by_min_size(soup, "div", 10, 2)
    delete_by_min_size(soup, "table", 10, 2)
    delete_by_min_size(soup, "p", 50, 2)
 
    return soup
 
def delete_if_no_text(soup, tag):
    
    for p in soup.findAll(tag):
        if(len(p.renderContents().strip()) == 0):
            p.extract()
 
def delete_by_min_size(soup, tag, length, children):
    
    for p in soup.findAll(tag):
        if(len(p.text) < length and len(p) <= children):
            p.extract()
 
def replace_by_paragraph(soup, tag):
    
    for t in soup.findAll(tag):
        t.name = “p"
        t.attrs = {}  

空格符渲染

这个方法是主要思路很简单: 把HTML源码里面的所有tag (所有在<和>之间的代码) 用空格符代替. 当你再次渲染网页的时候, 所有的文本块(text block)依然是”块”状, 但是其他部分变成了包含很多空格符的分散的语句. 你剩下唯一要做的就是把文本快给找出来, 并且移除掉所有其他的内容.
我写了一个简单的实现.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import requests
import re
 
from bs4 import BeautifulSoup
from bs4 import Comment
 
if __name__ == "__main__":
    
    html_string = requests.get('http://www.zdnet.com/windows-8-microsofts-new-coke-moment-7000014779/').text
    
    soup = BeautifulSoup(str( html_string ))
    
    map(lambda x: x.extract(), soup.findAll("code"))
    map(lambda x: x.extract(), soup.findAll("script"))
    map(lambda x: x.extract(), soup.findAll("pre"))
    map(lambda x: x.extract(), soup.findAll("style"))
    map(lambda x: x.extract(), soup.findAll("embed"))
    
    comments = soup.findAll(text=lambda text:isinstance(text, Comment))
        
    [comment.extract() for comment in comments]
    
    white_string = ""
    isIn = False;
    
    for character in soup.prettify():
 
        if character == "<":
            isIn = True;
        
        if isIn:
            white_string += " "
        else:
            white_string += character
            
        if character == ">":
            isIn = False;
            
    for string in white_string.split("           "):    # tune here!
        
        p = string.strip()
        p = re.sub(' +',' ', p)
        p = re.sub('n+',' ', p)
        
        if( len( p.strip() ) > 50):
            print p.strip()

这里有个问题是, 这个方法不是很通用. 你需要进行参数调优来找到最合适空格符长度来分割得到的字符串. 那些带有大量markup标记的网站通常要比普通网站有更多的空格符. 除此之外, 这个还是一个相当简单有效的方法.

开源社区的库
有句话说: 不要重新造轮子. 事实上有很多的开源库可以解决网页内容抓取的问题. 我最喜欢的是Boilerpipe. 你可以在这里找到它的web服务(demo)http://boilerpipe-web.appspot.com/, 以及它的JAVA实现https://code.google.com/p/boilerpipe/. 相比较上面2个简单的算法, 它真的很有用, 代码也相对更复杂. 但是如果你只把它当做黑盒子使用, 确实是一个非常好的解决方法.

Best regards,
Thomas Uhrig

相关文章

  • [译] 如何从网页中获取主要文本信息

    原文 Extracting meaningful content from raw HTML 解析HTML页面不算...

  • Python 爬虫第一篇(urllib+regex)

    爬虫的主要用途即从网站上获取网页,并将网页中的有用信息解析出来。从网站上获取网页内容可以通过 python 内置的...

  • 2018-09-13爬虫——数据大盗

    Urllib库 什么是Urllib? 它是python自带的标准库,主要用它来获取网页信息。 怎么获取网页信息??...

  • 爬取NCBI中GEO中的数据

    获取GEO中GSE网页的信息 获取GEO中GSM的信息 汇总写成函数 获取附加材料文件

  • 阅读能力五维度

    阅读能力的五个维度是: 一、提取信息,从文本中获取多元信息的能力。 二、整体感知,形成对文本内容整体感知...

  • 阅读能力五维度

    阅读能力的五个维度是: 一、提取信息,从文本中获取多元信息的能力。 二、整体感知,形成对文本内容整体感知...

  • 浅析微信支付:微信公众号网页授权

    本文是【浅析微信支付】系列文章的第四篇,主要讲解微信支付前如何获取获取网页授权及用户信息获取。 浅析微信支付系列已...

  • pydoc用法简介

    python中pydoc模块可以从python代码中获取docstring,然后生成帮助信息。 纯文本帮助 win...

  • 内存优化

    如何获取Android系统中申请对象的信息 [译]Android内存泄漏的八种可能 Android中图片的三级缓存

  • 一文看懂什么是文本挖掘

    一、什么是文本挖掘 文本挖掘指的是从文本数据中获取有价值的信息和知识,它是数据挖掘中的一种方法。文本挖掘中最重要最...

网友评论

      本文标题:[译] 如何从网页中获取主要文本信息

      本文链接:https://www.haomeiwen.com/subject/tlmqgttx.html