活动模式小例(四)

作者: 顾远山 | 来源:发表于2021-01-14 12:59 被阅读0次

Applied Active Pattern in F# (4)

原创:顾远山
著作权归作者所有,转载请标明出处。

前文提要

活动模式允许你通过定义命名分区对输入数据进行分割 ,并在模式匹配表达式中如可区分联合一般使用。在实际项目中,活动模式的应用场景很多,比如各类解析器,这些工具能助力后续的数据分析任务。活动模式可用于前端输入和后端逻辑的解耦。前端输入通过活动模式解析后可以利用模式匹配连接后端逻辑。利用F#的语言特性,解耦的代码可以接近业务的领域专用语言。

在这之前,这个系列已经有三篇文章,小例(一)介绍了F#中的活动模式,小例(二)把活动模式应用于日期解析器,小例(三)通过活动模式演示了如何把前端输入和后端逻辑解耦。本文是该系列的最后一篇,笔者将用F#实现一只迷你网络爬虫作为例子,对特定网页进行爬取,并借力活动模式从原始文本中抽取字段数据。

问题描述

现有格式相同的公共网页若干,样例如下,网页内的表格有两列,左边是字段名称,右边是字段对应的值,我们只需要把右边那列的值抽取出来即可。比如对应课程名称这个字段,我们期待获取的值是World Bachelor in Business

网页样例

高阶设计

表格看上去很规整,人肉复制粘贴到Excel再行列转置一下就能达到目的,但随着网页数量增多,操作者难免身心俱疲。所以爬虫是更为现实的解决方案。值得留意的是,这个表面上看着格式非常统一的表格,其实背后各字段值所在的HTML标记却不尽相同,截取其中一小段以说明,如下:

字段值分布于形式各异的HTML标记内

同样都是数据值对应的行列<tr>...<td>...</td>...</tr>里,以上四个字段就出现了四种不同的情况:

  • 黄色目标值工商管理出现在唯一的<span>...</span>标记里
  • 绿色目标值48(以月计)出现在若干个(此处为两个)<span>...</span>标记里
  • 蓝色目标值面授作为若干个<li>...</li>列表项目(此次为一个)出现在列表<ol>...</ol>标记里
  • 洋红目标值1其他2語文能力则被嵌套在一个若干行两行<tr>...</tr>(此处为两行)若干列<td>...</td>(此处为两列)的表格<table>...</table>标记里

所以不同的字段需要在不同的HTML标记里寻值。

当下有些编程语言自带或社区有开源的HTML解析器,能直接把网页的原始文本转换为HTML DOM格式,继而通过操作树形表达结构中的标记抽取数据。F#也有,但本例不使用它现成的解析器,而是通过活动模式进行数据定义、数据分割、数据抽取,以凸显其在数据处理过程中的优势。

逻辑非常清晰,解决方案可以分为以下四个步骤:

  • 网页爬取
  • 字段抽取
  • 数据清洗
  • 数据塑形

其中关键的步骤是字段抽取,正是活动模式的强项,易得高阶设计如下:

高阶设计

鉴于输出的直观性,我们直接复用问题描述作为测试用例。

利用活动模式实现

首先,我们需要把网址对应的原始网页文本爬取下来,故有函数getPage如下:

let getPage url = Http.RequestString(url, responseEncodingOverride = "UTF-8")

其中url为网址,另外网页内容有繁体中文,应答编码需要重写成UTF-8

接下来是字段数据的抽取,在此我们复用小例(二)中实现的正则表达式匹配活动模式如下:

let (|RegexMatch|_|) pattern input = ... //详见《活动模式小例(二)》

易得函数extractColsBy如下:

let extractColsBy pattern page =         
    match pageText with
    | RegexMatch pattern (_::columns) -> columns
    | _ -> []

pattern为用于匹配数据的正则表达式字符串,page为原始网页文本。网页原始文本若能匹配正则表达式,则返回由各字段值顺序组成的字符串列表,若不能则返回空列表。

不得不说的是,使用正则表达式匹配活动模式搭配模式匹配对网页原始文本进行按字段分割的操作实在猛如虎,本质上就一行代码,看第一眼什么都没做,再看一眼发现都做完了。我们仔细看一下这行代码。它无非就是一个再普通不过的基于活动模式的模式匹配,其中输出是匹配成功的分组值列表。

为了弥补正则表达式匹配的粗颗粒度,以及消除网页编码和自然语言的差异,我们继续清洗数据的步骤,遂得函数refine如下:

let refine = 
    let itemize str = Regex.Replace(str, "<li>","|")                           //列表转换
    let removeTags str = Regex.Replace(str,@"<[^!].+?>","")                    //移除标记
    let removeSpaces str = Regex.Replace(str,@"<\s{2,}","")                    //移除空格
    let removeComments str = Regex.Replace(str,@"<[^!].+?>","")                //移除注释
    let humanize (str:string) = str.Replace("&nbsp;"," ").Replace("&amp;","&") //符号转换
    itemize >> removeTags >> removeSpaces >> removeComments >> humanize        //逐步处理

得益于F#是函数式编程语言,我们很方便就能把几个操作字符串的子函数通过运算符>>组合起来,而refine本身也是个函数,类型为string -> string

得到清洗数据的refine函数后,我们可以把它作为入参传到高阶映射函数map继而应用于列表中每一个值,如下:

let map rawVals = List.map refine rawVals

从数据本身的角度,清洗动作已经完成。

为了方便后续数据利用,我们对数据进行塑形,不妨做个归约,如下:

let reduce refinedVals = List.reduce (fun a b -> a + "\r\n" + b) refinedVals  //按行列示

至此,高阶设计全部实现,通过如下代码即可获取结果:

let url = @"..."                             //略
let pattern = """..."""                      //详见附录
let result = url |> getPage                  //网页爬取(高阶设计步骤一)                   
                 |> extractColsBy pattern    //字段抽取(高阶设计步骤二)
                 |> map refine               //数据清洗(高阶设计步骤三)
                 |> reduce                   //数据塑性(高阶设计步骤四)

这段代码与高阶设计完全契合,且接近自然语言,易读性强,而结果与用例相符,测试通过。

测试结果

结语

F#的活动模式结合正则表达式能对文本进行高效匹配从而实现数据分割抽取。在活动模式的加持下,用F#可以随手以蝇量级代码按需实现自定义的网络爬虫,助力数据分析。

附录

严格意义上本例只能算伪爬虫,因为只有一行代码属于爬取过程(获取网页原始文本),其余代码都是数据处理。

从原始网页文本抽取数据的正则表达式测试参考如下,本例使用的是免费工具Expresso。

正则表达式测试

相关文章

  • 活动模式小例(四)

    Applied Active Pattern in F# (4) 原创:顾远山著作权归作者所有,转载请标明出处。 ...

  • 活动模式小例(三)

    Applied Active Pattern in F# (3) 原创:顾远山著作权归作者所有,转载请标明出处。 ...

  • 活动模式小例(二)

    Applied Active Pattern in F# (2) 原创:顾远山著作权归作者所有,转载请标明出处。 ...

  • 活动模式小例(五)

    Applied Active Pattern in F# (5) 原创:顾远山著作权归作者所有,转载请标明出处。 ...

  • 活动模式小例(一)

    Applied Active Pattern in F# (1) 原创:顾远山著作权归作者所有,转载请标明出处。 ...

  • Java四种单例设计模式

    Java中的四种单例模式 单例模式是最容易理解的设计模式之一,介绍Java中单例模式的四种写法。 1.基本单例模式...

  • 单例模式

    一、实现单例模式 或者 二、透明的单例模式 三、用代理实现单例模式 四、JavaScript中的单例模式 在Jav...

  • 单例模式只有饿汉式和懒汉式吗?这几种单例模式你见过吗

    设计模式之单例模式-单例模式的几种实现方式及小案例 本文出处:凯哥Java(wx:kaigejava) 单例模式有...

  • Python之单例模式总结

    一、单例模式 a、单例模式分为四种:文件,类,基于__new__方法实现单例模式,基于metaclass方式实...

  • 单例模式

    1.属性值可修改的单例模式 2.属性值不可修改的单例模式 优缺点: 优点: 在单例模式中,活动的单例只有一个实例,...

网友评论

    本文标题:活动模式小例(四)

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