美文网首页我爱编程
40 行代码搞定网页文本抽取

40 行代码搞定网页文本抽取

作者: Pope怯懦懦地 | 来源:发表于2018-06-21 20:33 被阅读166次

    这篇《我为开源做贡献,网页正文提取——Html2Article》给了我很大启发。以下就是对基于文本密度提取算法的改进。

    这个算法源于我们的两个观察:

    1. 正文所在的区域往往字符比较密集,排除掉 html 标签后。
    2. 正文文本往往连成一片。

    所以,我们设想:在我们清洗掉 html 标签后,会不会得到这样一种画面:

    横轴是「行号」,纵轴是「该行的字符数」

    如果真是这样,我们只要找出这个「窗口」的范围,就可以大致提取出文本正文了。然后再上一些小措施,提高下识别率,想想也是~~

    require "open-uri"
    require "nokogiri"
    require "json"
    require 'pp'
    
    url = "http://news.sohu.com/20131229/n392604462.shtml"
    html = open(url).read.force_encoding('gbk').encode('utf-8')
    
    strip_html = html.gsub(/<script.+?<\/script>/im, '')
                     .gsub(/<\/?.+?>/m, '')
                     .gsub(/^[\t\s]+/, '')
                     .gsub(/[\t\s]+$/, '')
                     .gsub(/(&nbsp;)+/, '')
                     .gsub(/(&#160;)+/, '')
                     .gsub(/https?:\/\/[\w\/\.]+/, '')
                     .gsub(/\r\n/, "\n")
    
    line_sizes = []
    strip_html.each_line do |line|
        puts "#{line.size}:\t>>#{line}"
    end
    
    左边是「该行的字符数」,「>>」之后是清洗过的原文。正文是 27~81 行。

    好吧,还是画张图吧。

    MB,骗子🤥!!!说好的驼峰呢

    别急别急,我们先做个平滑处理,看能不能把曲线弄得好看(特征明显)一点:

    平滑处理 逐渐放大平滑窗口的效果

    好吧,好吧,单独把「平滑窗口 = 7」的曲线拎出来。

    k = 7

    现在可以清晰地看到,曲线在 21 附近陡然爬升,又在 81 附近骤然下降,而这段区间正好对应正文的位置( 27 ~ 81 行)。

    下面怎么把这两个边界提取出来呢?很自然地想到了看看「斜率」或是「曲率」:

    呃~~,谁说的「求导试试」的?!你出来,我保证不打死你!

    但仔细一看,也不是全无用处嘛。毕竟在 21 和 81 附近挣扎得也挺卖命的嘛。有没有什么办法,即可以展示出坡度的变化,又能展示出值在高位徘徊?要不加个当前曲线值试试?

    取边界

    如果 x_i-1 和 x_i 很接近,边界指示值就接近 0 ;而如果两者相差很大(不管谁大),其值都不会小。好像很有道理的样子🤔

    呃~~,谁说「绝对好使,信我」的?!你出来,我保证不打死你!

    等等,等等,好像 21 和 81 附近有两处小起伏,要不乘个「放大系数」再试试:

    我擦!好像成嘞!!!其实上不上「放大系数」,只对人眼有意义,对机器毫无意义。 凑巧把原文全覆盖了

    完整代码:

    require "open-uri"
    require "nokogiri"
    require "json"
    
    url = "http://news.sohu.com/20131229/n392604462.shtml"
    html = open(url).read.force_encoding('gbk').encode('utf-8')
    
    strip_html = html.gsub(/<script.+?<\/script>/im, '')
                     .gsub(/<\/?.+?>/m, '')
                     .gsub(/^[\t\s]+/, '')
                     .gsub(/[\t\s]+$/, '')
                     .gsub(/(&nbsp;)+/, '')
                     .gsub(/(&#160;)+/, '')
                     .gsub(/https?:\/\/[\w\/\.]+/, '')
                     .gsub(/\r\n/, "\n")
    
    line_sizes = []
    strip_html.each_line { |line| line_sizes << line.size }
    
    def smooth(vs, k)
        w = []
        vs[0..-k].each_with_index do |l, i|
            w << (vs[i..(i + k - 1)].sum / k.to_f)
        end
        w
    end
    
    alpha = 100
    xs = smooth(line_sizes, 7)
    ys = []
    ys[0] = 0
    (1...xs.size).each do |i|
        ys[i] = alpha * (1 - xs[i - 1] / xs[i].to_f)
    end
    
    y_b = ys.index(ys.max)
    y_e = ys.index(ys.min)
    
    puts strip_html.split(/\n/)[y_b..y_e].join("\n")
    

    其实还可以更简单

    其实在想到这个算法之前,我就找到了这个:URL2io ,一个专做网页文本提取的服务。上去注册个账号,然后调接口就行了。

    require "json"
    require "faraday"
    
    url = "http://news.sohu.com/20131229/n392604462.shtml"
    con = Faraday.new 
    res = con.get do |req| 
        req.url 'http://api.url2io.com/article'
        req.params['token'] = 'xxxxxxxxx'
        req.params['url'] = url
    end
    
    pp JSON.parse(res.body)
    

    你知道,要在知道有现成服务的情况下,还去写完同样功能的算法,需要多大的毅力吗?!🤦‍♂️

    改进

    试了几个网页,效果还不错。但也有严重跑偏的,比如这个网页

    分析其原因,没有考虑「文本密度的绝对大小」。尝试修改下公式:

    def edge(xs, k = 9)
        alpha = 2
        epsilon = 0.00001
        ys = Array.new(k, 0)
        (k..(xs.size - k)).each do |i|
            if xs[i - 1] == 0 and xs[i] == 0
                ys[i] = 0
            else
                if xs[i - 1] < xs[i]
                    beta_i = xs[i..(i + k - 1)].sum / k.to_f
                else
                    beta_i = xs[(i - k + 1)..i].sum / k.to_f
                end
                ys[i] = alpha * beta_i * (1 - xs[i - 1] / (xs[i] + epsilon))
            end
        end
        ys
    end
    
    ys = edge(smooth(line_sizes, 7))
    y_b = ys.index(ys.max)
    y_e = ys.index(ys.min)
    

    嗯,效果好多了。

    相关文章

      网友评论

        本文标题:40 行代码搞定网页文本抽取

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