活動(dòng)模式小例(四)

Applied Active Pattern in F# (4)

原創(chuàng):顧遠(yuǎn)山
著作權(quán)歸作者所有逸爵,轉(zhuǎn)載請(qǐng)標(biāo)明出處。

前文提要

活動(dòng)模式允許你通過(guò)定義命名分區(qū)對(duì)輸入數(shù)據(jù)進(jìn)行分割 士嚎,并在模式匹配表達(dá)式中如可區(qū)分聯(lián)合一般使用呜魄。在實(shí)際項(xiàng)目中,活動(dòng)模式的應(yīng)用場(chǎng)景很多莱衩,比如各類(lèi)解析器爵嗅,這些工具能助力后續(xù)的數(shù)據(jù)分析任務(wù)”恳希活動(dòng)模式可用于前端輸入和后端邏輯的解耦睹晒。前端輸入通過(guò)活動(dòng)模式解析后可以利用模式匹配連接后端邏輯趟庄。利用F#的語(yǔ)言特性,解耦的代碼可以接近業(yè)務(wù)的領(lǐng)域?qū)S谜Z(yǔ)言册招。

在這之前岔激,這個(gè)系列已經(jīng)有三篇文章,小例(一)介紹了F#中的活動(dòng)模式是掰,小例(二)把活動(dòng)模式應(yīng)用于日期解析器虑鼎,小例(三)通過(guò)活動(dòng)模式演示了如何把前端輸入和后端邏輯解耦。本文是該系列的最后一篇键痛,筆者將用F#實(shí)現(xiàn)一只迷你網(wǎng)絡(luò)爬蟲(chóng)作為例子炫彩,對(duì)特定網(wǎng)頁(yè)進(jìn)行爬取,并借力活動(dòng)模式從原始文本中抽取字段數(shù)據(jù)絮短。

問(wèn)題描述

現(xiàn)有格式相同的公共網(wǎng)頁(yè)若干江兢,樣例如下,網(wǎng)頁(yè)內(nèi)的表格有兩列丁频,左邊是字段名稱(chēng)杉允,右邊是字段對(duì)應(yīng)的值,我們只需要把右邊那列的值抽取出來(lái)即可。比如對(duì)應(yīng)課程名稱(chēng)這個(gè)字段,我們期待獲取的值是World Bachelor in Business口柳。

網(wǎng)頁(yè)樣例

高階設(shè)計(jì)

表格看上去很規(guī)整捻勉,人肉復(fù)制粘貼到Excel再行列轉(zhuǎn)置一下就能達(dá)到目的店诗,但隨著網(wǎng)頁(yè)數(shù)量增多,操作者難免身心俱疲。所以爬蟲(chóng)是更為現(xiàn)實(shí)的解決方案。值得留意的是秕狰,這個(gè)表面上看著格式非常統(tǒng)一的表格,其實(shí)背后各字段值所在的HTML標(biāo)記卻不盡相同躁染,截取其中一小段以說(shuō)明鸣哀,如下:

字段值分布于形式各異的HTML標(biāo)記內(nèi)

同樣都是數(shù)據(jù)值對(duì)應(yīng)的行列<tr>...<td>...</td>...</tr>里,以上四個(gè)字段就出現(xiàn)了四種不同的情況:

  • 黃色目標(biāo)值工商管理出現(xiàn)在唯一的<span>...</span>標(biāo)記里
  • 綠色目標(biāo)值48(以月計(jì))出現(xiàn)在若干個(gè)(此處為兩個(gè))<span>...</span>標(biāo)記里
  • 藍(lán)色目標(biāo)值面授作為若干個(gè)<li>...</li>列表項(xiàng)目(此次為一個(gè))出現(xiàn)在列表<ol>...</ol>標(biāo)記里
  • 洋紅目標(biāo)值1其他2語(yǔ)文能力則被嵌套在一個(gè)若干行兩行<tr>...</tr>(此處為兩行)若干列<td>...</td>(此處為兩列)的表格<table>...</table>標(biāo)記里

所以不同的字段需要在不同的HTML標(biāo)記里尋值吞彤。

當(dāng)下有些編程語(yǔ)言自帶或社區(qū)有開(kāi)源的HTML解析器我衬,能直接把網(wǎng)頁(yè)的原始文本轉(zhuǎn)換為HTML DOM格式,繼而通過(guò)操作樹(shù)形表達(dá)結(jié)構(gòu)中的標(biāo)記抽取數(shù)據(jù)备畦。F#也有低飒,但本例不使用它現(xiàn)成的解析器许昨,而是通過(guò)活動(dòng)模式進(jìn)行數(shù)據(jù)定義懂盐、數(shù)據(jù)分割、數(shù)據(jù)抽取糕档,以凸顯其在數(shù)據(jù)處理過(guò)程中的優(yōu)勢(shì)莉恼。

邏輯非常清晰拌喉,解決方案可以分為以下四個(gè)步驟:

  • 網(wǎng)頁(yè)爬取
  • 字段抽取
  • 數(shù)據(jù)清洗
  • 數(shù)據(jù)塑形

其中關(guān)鍵的步驟是字段抽取,正是活動(dòng)模式的強(qiáng)項(xiàng)俐银,易得高階設(shè)計(jì)如下:

高階設(shè)計(jì)

鑒于輸出的直觀性尿背,我們直接復(fù)用問(wèn)題描述作為測(cè)試用例。

利用活動(dòng)模式實(shí)現(xiàn)

首先捶惜,我們需要把網(wǎng)址對(duì)應(yīng)的原始網(wǎng)頁(yè)文本爬取下來(lái)田藐,故有函數(shù)getPage如下:

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

其中url為網(wǎng)址,另外網(wǎng)頁(yè)內(nèi)容有繁體中文吱七,應(yīng)答編碼需要重寫(xiě)成UTF-8汽久。

接下來(lái)是字段數(shù)據(jù)的抽取,在此我們復(fù)用小例(二)中實(shí)現(xiàn)的正則表達(dá)式匹配活動(dòng)模式如下:

let (|RegexMatch|_|) pattern input = ... //詳見(jiàn)《活動(dòng)模式小例(二)》

易得函數(shù)extractColsBy如下:

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

pattern為用于匹配數(shù)據(jù)的正則表達(dá)式字符串踊餐,page為原始網(wǎng)頁(yè)文本景醇。網(wǎng)頁(yè)原始文本若能匹配正則表達(dá)式,則返回由各字段值順序組成的字符串列表吝岭,若不能則返回空列表三痰。

不得不說(shuō)的是,使用正則表達(dá)式匹配活動(dòng)模式搭配模式匹配對(duì)網(wǎng)頁(yè)原始文本進(jìn)行按字段分割的操作實(shí)在猛如虎窜管,本質(zhì)上就一行代碼散劫,看第一眼什么都沒(méi)做,再看一眼發(fā)現(xiàn)都做完了微峰。我們仔細(xì)看一下這行代碼舷丹。它無(wú)非就是一個(gè)再普通不過(guò)的基于活動(dòng)模式的模式匹配,其中輸出是匹配成功的分組值列表蜓肆。

為了彌補(bǔ)正則表達(dá)式匹配的粗顆粒度颜凯,以及消除網(wǎng)頁(yè)編碼和自然語(yǔ)言的差異,我們繼續(xù)清洗數(shù)據(jù)的步驟仗扬,遂得函數(shù)refine如下:

let refine = 
    let itemize str = Regex.Replace(str, "<li>","|")                           //列表轉(zhuǎn)換
    let removeTags str = Regex.Replace(str,@"<[^!].+?>","")                    //移除標(biāo)記
    let removeSpaces str = Regex.Replace(str,@"<\s{2,}","")                    //移除空格
    let removeComments str = Regex.Replace(str,@"<[^!].+?>","")                //移除注釋
    let humanize (str:string) = str.Replace("&nbsp;"," ").Replace("&amp;","&") //符號(hào)轉(zhuǎn)換
    itemize >> removeTags >> removeSpaces >> removeComments >> humanize        //逐步處理

得益于F#是函數(shù)式編程語(yǔ)言症概,我們很方便就能把幾個(gè)操作字符串的子函數(shù)通過(guò)運(yùn)算符>>組合起來(lái),而refine本身也是個(gè)函數(shù)早芭,類(lèi)型為string -> string彼城。

得到清洗數(shù)據(jù)的refine函數(shù)后,我們可以把它作為入?yún)鞯礁唠A映射函數(shù)map繼而應(yīng)用于列表中每一個(gè)值退个,如下:

let map rawVals = List.map refine rawVals

從數(shù)據(jù)本身的角度募壕,清洗動(dòng)作已經(jīng)完成。

為了方便后續(xù)數(shù)據(jù)利用语盈,我們對(duì)數(shù)據(jù)進(jìn)行塑形舱馅,不妨做個(gè)歸約,如下:

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

至此刀荒,高階設(shè)計(jì)全部實(shí)現(xiàn)代嗤,通過(guò)如下代碼即可獲取結(jié)果:

let url = @"..."                             //略
let pattern = """..."""                      //詳見(jiàn)附錄
let result = url |> getPage                  //網(wǎng)頁(yè)爬燃(高階設(shè)計(jì)步驟一)                   
                 |> extractColsBy pattern    //字段抽取(高階設(shè)計(jì)步驟二)
                 |> map refine               //數(shù)據(jù)清洗(高階設(shè)計(jì)步驟三)
                 |> reduce                   //數(shù)據(jù)塑性(高階設(shè)計(jì)步驟四)

這段代碼與高階設(shè)計(jì)完全契合干毅,且接近自然語(yǔ)言宜猜,易讀性強(qiáng),而結(jié)果與用例相符硝逢,測(cè)試通過(guò)姨拥。

測(cè)試結(jié)果

結(jié)語(yǔ)

F#的活動(dòng)模式結(jié)合正則表達(dá)式能對(duì)文本進(jìn)行高效匹配從而實(shí)現(xiàn)數(shù)據(jù)分割抽取。在活動(dòng)模式的加持下渠鸽,用F#可以隨手以蠅量級(jí)代碼按需實(shí)現(xiàn)自定義的網(wǎng)絡(luò)爬蟲(chóng)垫毙,助力數(shù)據(jù)分析。

附錄

嚴(yán)格意義上本例只能算偽爬蟲(chóng)拱绑,因?yàn)橹挥幸恍写a屬于爬取過(guò)程(獲取網(wǎng)頁(yè)原始文本)综芥,其余代碼都是數(shù)據(jù)處理。

從原始網(wǎng)頁(yè)文本抽取數(shù)據(jù)的正則表達(dá)式測(cè)試參考如下猎拨,本例使用的是免費(fèi)工具Expresso膀藐。

正則表達(dá)式測(cè)試
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市红省,隨后出現(xiàn)的幾起案子额各,更是在濱河造成了極大的恐慌,老刑警劉巖吧恃,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虾啦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡痕寓,警方通過(guò)查閱死者的電腦和手機(jī)傲醉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)呻率,“玉大人硬毕,你說(shuō)我怎么就攤上這事±裾蹋” “怎么了吐咳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)元践。 經(jīng)常有香客問(wèn)我韭脊,道長(zhǎng),這世上最難降的妖魔是什么单旁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任沪羔,我火速辦了婚禮,結(jié)果婚禮上慎恒,老公的妹妹穿的比我還像新娘任内。我一直安慰自己,他們只是感情好融柬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布死嗦。 她就那樣靜靜地躺著,像睡著了一般粒氧。 火紅的嫁衣襯著肌膚如雪越除。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天外盯,我揣著相機(jī)與錄音摘盆,去河邊找鬼。 笑死饱苟,一個(gè)胖子當(dāng)著我的面吹牛孩擂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播箱熬,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼类垦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了城须?” 一聲冷哼從身側(cè)響起蚤认,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎糕伐,沒(méi)想到半個(gè)月后砰琢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡良瞧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年陪汽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褥蚯。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掩缓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遵岩,到底是詐尸還是另有隱情你辣,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布尘执,位于F島的核電站舍哄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏誊锭。R本人自食惡果不足惜表悬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丧靡。 院中可真熱鬧蟆沫,春花似錦籽暇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至舟山,卻和暖如春绸狐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背累盗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工寒矿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人若债。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓符相,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蠢琳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子主巍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容