python文本分析的开源工具_重磅开源:TN文本分析语言
tn是desert(沙漠之鷹)和tan共同開發(fā)的一種用于匹配,轉(zhuǎn)寫和抽取文本的語言(DSL)。并為其開發(fā)和優(yōu)化了專用的編譯器。基于遞歸下降方法和正則表達(dá)式,能解析自然文本并轉(zhuǎn)換為樹和字典,識別時間,地址,數(shù)量等復(fù)雜序列模式。
github地址:https://github.com/ferventdesert/tnpy
0.設(shè)計理由
字符串分析和處理幾乎是每個員程序必備的工作,簡單到分割類似"1,2,3,4"這樣的字符串,稍微復(fù)雜一些如字符串匹配,再復(fù)雜如編譯和分析SQL語法。字符串幾乎具有無窮的表達(dá)能力,解決字符串問題,就解決了計算機90%的問題。
雖然字符串處理如此深入人心,但當(dāng)分割字符時,本來都是按照逗號分割的,突然出現(xiàn)分號,程序就可能出錯。再如日期處理,每個程序員肯定都對各種奇怪詭異的時間表達(dá)方式感到頭疼,處理起來非常費時。這些功能,幾乎只能以硬編碼實現(xiàn)。它們是與外界交互的最底層模塊,然而卻如此脆弱。
如何將”一百二十三“轉(zhuǎn)換為數(shù)字?
如何將”2013年12月14日“識別為時間并轉(zhuǎn)換為時間類型?
如何分析一個XML或JSON文件?
正則表達(dá)式雖提供了強大的匹配功能,成為必備的工具,但它有不少局限,我們擴展了正則表達(dá)式引擎,使之能力大大增強。
在線演示:http://www.desertlambda.com:81/extracttext.html
1. 如何學(xué)習(xí)?
基本上程序員都讀過“30分鐘學(xué)會正則表達(dá)式”這篇文章吧?最后沒幾個人能在30分鐘內(nèi)就讀完它。不過相信我,TN引擎只需要15分鐘就可以學(xué)會。
詳細(xì)的語法說明在這里:
[tn基本語法][1]
[使用tn構(gòu)造自然語言計算器][2]
[tn實現(xiàn)的xml解析器][3]
TN可以實現(xiàn)文本的匹配,轉(zhuǎn)寫和信息抽取,可以理解為模板引擎的逆運算。簡單的操作用正則表達(dá)式更方便,但不少問題是正則無法解決的。這時就需要使用TN了。
TN的解釋器有Python,C#和C三種版本。C#版本已經(jīng)不再維護(hù)。使用C#或Java等語言的,建議使用IronPython或Jython進(jìn)行跨語言編譯。
tnpy是tn的Python解釋器,Python良好的可讀性讓代碼寫起來非常方便,代碼不超過1000行,單文件,無第三方庫依賴。推薦使用Python3。
tn是解釋型語言,需要編寫規(guī)則文件,并使用tnpy加載,再對文本進(jìn)行處理。
1. 基礎(chǔ)的匹配和替換:
首先我們先編寫一個最簡單的規(guī)則文件learn,內(nèi)容如下:
#%Order% 1
hello= ("你好");
接著,執(zhí)行下面的python代碼:
from src.tnpy import RegexCore
core = RegexCore('../rules/learn')
matchs=core.Match('領(lǐng)導(dǎo)你好!老婆你好');
for m in matchs:
print('match',m.mstr, 'pos:',m.pos)
引入tnpy命名空間,之后從learn規(guī)則文件初始化引擎,匹配該文本:
success load tn rules:../rules/learn
match 你好 pos: 2
match 你好 pos: 7
上面輸出了文本的匹配結(jié)果和位置。當(dāng)然這一點正則也能做到。
如果我們匹配的是領(lǐng)導(dǎo)你好,老婆您好,并想把所有的你好和您好,都轉(zhuǎn)寫為hello。
為此我們添加hello2和hello3兩個子規(guī)則:
hello2= $(hello)| ("您好");
#%Order% 1
hello3= $(hello2) : (//:/hello/);
hello2引用了剛才的hello規(guī)則,同時添加了“您好”。
hello3是主規(guī)則,負(fù)責(zé)將將hello2匹配的內(nèi)容都轉(zhuǎn)寫為hello
($代表引用一條規(guī)則,|表示將幾個規(guī)則并列排列,匹配最長的那個規(guī)則,:代表轉(zhuǎn)寫。)
執(zhí)行下面的代碼:`
print(core.Rewrite('領(lǐng)導(dǎo)你好!老婆您好'));
結(jié)果為:
領(lǐng)導(dǎo)hello!老婆hello
如果我們想替換順序,把“你好”放在前面呢?可以這樣寫:
people= ("老婆") | ("領(lǐng)導(dǎo)");
#%Order% 1
reorder= $(people) $(hello3) : $2 $1;
先用people定義如何描述老婆,領(lǐng)導(dǎo),然后用reorder來修改順序, 注意reorder是個順序結(jié)構(gòu),people匹配老婆和領(lǐng)導(dǎo),hello3匹配您好/你好,并將其轉(zhuǎn)換為hello。 $2和$1修改了轉(zhuǎn)寫順序,執(zhí)行Rewrite后輸出:
hello領(lǐng)導(dǎo)!hello老婆
我們把類似$(name1) $(name2)的結(jié)構(gòu),稱為順序表達(dá)式,把$(name1) | $(name2)稱為或表達(dá)式。
如果將剛才所有的規(guī)則繪制成圖,則是下面的樣子:
![foo.png-34.5kB][4]
2. 正則表達(dá)式
僅僅使用文本,表現(xiàn)力太差了。我們引入正則表達(dá)式來完成,正則表達(dá)式需要放在(//)中,注意和文本("")的區(qū)別。
如果要進(jìn)行轉(zhuǎn)寫,則標(biāo)注為(/match/:/rewrite/);
下面的表達(dá)式將所有的長空白符轉(zhuǎn)換為一個空白符:
byte_det_space = (/ */://);
下面將所有字母轉(zhuǎn)換為空白:
low_letter_to_null = (/[a-z]/ ://);
#或者下面:
low_letter= (/[a-z]/);
translate= $(low_letter) : ("");
覺得沒有挑戰(zhàn)?我們接著看下面的。
3. 復(fù)雜組合:中文數(shù)字轉(zhuǎn)阿拉伯?dāng)?shù)字
二十三如何轉(zhuǎn)換為23?這種用普通的編程會比較困難。我們嘗試用TN解決,會發(fā)現(xiàn)一點都不難。
先定義漢字的一二三到九轉(zhuǎn)換為1-9,你肯定會寫出這樣的規(guī)則:
#定義0-9
int_1 = ("一" : "1");
int_0 =("零" : "0");
int_2 = ("二" : "2") | ("兩" : "2");
int_3_9 = ("三" : "3") | ("四" : "4") | ("五" : "5") | ("六" : "6") | ("七" : "7") | ("八" : "8") | ("九" : "9");
int_1_9 = $(int_1) | $(int_2) | $(int_3_9) | (/\d/);
int_0_9 = $(int_0) | $(int_1_9);
int_del_0 = (/零/ : /0/) | (// : /0/);
int_0_9_null = $(int_del_0) | $(int_0_9);
之所以要把0,1,2分開寫,是因為這些數(shù)有特殊情況,如兩和二都代表2,需要在后面特殊處理。
上面的int_0_9_null規(guī)則,就可以把五七零二轉(zhuǎn)寫為5702。但沒法處理二十三這樣的情況。
再定義下面的規(guī)則,這樣一十三可以轉(zhuǎn)寫為13
int_del_0 = (/零/ : /0/) | (// : /0/);
int_0_9_null = $(int_del_0) | $(int_0_9);
#定義10,十
int_1_decades = (/十/ : /1/) | (/一十/ : /1/);
再加上下面的規(guī)則,int_1_9_decades定義了十位數(shù)如何轉(zhuǎn)寫,而int_10_99定義了從十到九十九的轉(zhuǎn)寫規(guī)則。
int_10_99 = $(int_1_9_decades) $(int_0_9_null) | (/[1-9][0-9]/) ;
int_1_99 = $(int_1_9) | $(int_10_99) ;
int_01_99 = $(int_1_9) | $(int_10_99) | (/\d{1,2}/);
#%Order% 3
int_0_99 = $(int_0) | $(int_1_9) | $(int_10_99);
看看下面的例子:
print({r:core.Rewrite(r) for r in ['十','三十七','一十三','68']});
運行結(jié)果:
{'一十三': '13', '68': '68', '十': '10', '三十七': '37'}
是不是感到很神奇?三十七是如何被轉(zhuǎn)寫為37的?
仔細(xì)看規(guī)則,規(guī)則自底向上構(gòu)造成了一棵規(guī)則樹,in_0_99是整棵樹的根節(jié)點。結(jié)構(gòu)如下圖:
![foo.png-132.1kB][5]
下面的log文件給出了匹配過程:
int_0_99,Table,Raw =三十七
int_0,String,Raw =三十七
int_0,String,NG
int_1_9,Table,Raw =三十七
int_1,String,Raw =三十七
int_1,String,NG
int_2,Table,Raw =三十七
int_2_merge,Regex,Raw =三十七
int_2_merge,Regex,NG
int_2,Table,NG
int_3_9,Table,Raw =三十七
int_3_9_merge,Regex,Raw =三十七
int_3_9_merge,Regex,Match=三
int_3_9,Table,Match=三
int_1_9_3,Regex,Raw =三十七
int_1_9_3,Regex,NG
int_1_9,Table,Match=三
int_10_99,Table,Raw =三十七
int_10_99_0,Sequence,Raw =三十七
int_1_9_decades,Table,Raw =三十七
int_1_decades,Table,Raw =三十七
int_1_decades_0,Regex,Raw =三十七
int_1_decades_0,Regex,Match=十
int_1_decades_1,Regex,Raw =三十七
int_1_decades_1,Regex,NG
int_1_decades,Table,Match=十
int_1_9_decades_1,Sequence,Raw =三十七
int_1_9,Table,Raw =三十七
int_1_9,Table,Buff =三
unknown,Regex,Raw =十七
unknown,Regex,Match=十
int_1_9_decades_1,Sequence,Match=三十
int_1_9_decades,Table,Match=三十
int_0_9_null,Table,Raw =七
int_del_0,Table,Raw =七
int_del_0_0,Regex,Raw =七
int_del_0_0,Regex,NG
int_del_0_1,Regex,Raw =七
int_del_0_1,Regex,Match=
int_del_0,Table,Match=
int_0_9,Table,Raw =七
int_0,String,Raw =七
int_0,String,NG
int_1_9,Table,Raw =七
int_1,String,Raw =七
int_1,String,NG
int_2,Table,Raw =七
int_2_merge,Regex,Raw =七
int_2_merge,Regex,NG
int_2,Table,NG
int_3_9,Table,Raw =七
int_3_9_merge,Regex,Raw =七
int_3_9_merge,Regex,Match=七
int_3_9,Table,Match=七
int_1_9_3,Regex,Raw =七
int_1_9_3,Regex,NG
int_1_9,Table,Match=七
int_0_9,Table,Match=七
int_0_9_null,Table,Match=七
int_10_99_0,Sequence,Match=三十七
int_10_99_1,Regex,Raw =三十七
int_10_99_1,Regex,NG
int_10_99,Table,Match=三十七
int_0_99,Table,Match=三十七
引擎從文本的左向右,沿著規(guī)則樹尋找最長的文本,如果在一個順序表達(dá)式上的任何一步失敗,那么整個順序表達(dá)式被拋棄。或表達(dá)式會遍歷每個子表達(dá)式,直到發(fā)現(xiàn)最長的那個,返回結(jié)果。具體的匹配原理,以及優(yōu)化,會在專門的文章中介紹。
4. 由規(guī)則構(gòu)造更復(fù)雜的規(guī)則
自然而然的,知道怎么定義三十七,就可以定義五百三十七,那不過是int_1_9_hundreds+int_0_99(這個已經(jīng)定義過了)。
int_1_9_hundreds = $(int_1_9) ("百" : "");
int_100_999 = $(int_1_9_hundreds) ("" : "00") | $(int_1_9_hundreds) $(int_10_99);
int_1_999 = $(int_1_99) | $(int_100_999);
int_1_999可以處理類似五百三十七這樣的問題!
進(jìn)而,我們可以處理幾千,幾萬,這個延伸到萬以后,就可以自然而然地衍生出億,萬億的表達(dá)。
如何處理負(fù)數(shù)?這還不簡單!
signed_symbol0 = ("正" : "") | ("負(fù)" : "-") | ("正負(fù)" : "±") | ("\+" : "+") | ("\-" : "-") | ("±" : "±") ;
signed_symbol = $(signed_symbol0) | $(null_2_null);
接下來,我們默認(rèn)正整數(shù)為integer_int,那么,整數(shù)(包含正負(fù))就是:
integer_signed = $(signed_symbol) $(integer_int)
5. 屬性提取
沿著剛才的路,我們自然而然地能定義分?jǐn)?shù),但僅僅是轉(zhuǎn)寫還不夠,遇到三分之一,我們不僅要將其處理為1/3,還要計算出它的值,這就涉及到屬性抽取。也就是把信息從文本中提取為字典。
分?jǐn)?shù),不過是整數(shù)+分之+整數(shù),可以定義成下面的形式:
fraction_cnv_slash = ("分之" : "/");
fraction2 = ("/" : "/");
percent_transform= ("%" : "100") | ("‰" : "1000");
#%Type% DOUBLE
#%Property% Denominator,,Numerator| Numerator ,, Denominator | Denominator ,, Numerator
#%Order% 101
fraction = $(integer_int_extend) $(fraction_cnv_slash) $(integer_int) : $3 $2 $1
| $(integer_int) $(fraction2) $(integer_int)
| $(pure_decimal) ("" : "/") $(percent_transform);
這個有點復(fù)雜,但容我慢慢講解。分?jǐn)?shù)有三種情況,如剛才的三分之一,或是1/3,或是30%。分別對應(yīng)上面fraction規(guī)則的三個子規(guī)則。仔細(xì)地看上面的規(guī)則,不難理解。
值得注意的是Property這個標(biāo)簽,該標(biāo)簽定義了如何抽取信息。也是用豎線分隔,每個名稱對應(yīng)下面的一個子規(guī)則,為空的直接跳過。那么”十三分之二十四“中,“十三”就對應(yīng)Numerator, 而“二十四”對應(yīng)Denominator。來測試一下:
print(core.Extract('十三分之二十四',entities=[core.Entities['fraction']]))
我們用Extract函數(shù)來抽取文本,返回的是一個字典,entites是可選參數(shù),我們限制只用fraction規(guī)則來匹配,獲得輸出:
'#match': '十三分之二十四', 'Denominator': '13', '#pos': 3}]```
是不是很贊?
###6.嵌入Python腳本
有一種需求還沒談到,將所有的大寫字母轉(zhuǎn)換為小寫字母,你可能會想定義26個字符串規(guī)則,并用或表達(dá)式來拼接起來吧?這樣太費事了。我們可以直接這樣:
`low_to_up_letter = (/[A-Z]/) : "str.lower(mt)";`
`[A-Z]`匹配了所有的大寫字母,將匹配結(jié)果送到后半段的轉(zhuǎn)寫,內(nèi)置的解釋器會執(zhí)行那段python代碼,將其轉(zhuǎn)換為小寫,mt代表前面表達(dá)式的匹配串,rt代表轉(zhuǎn)寫串。好在`[A-Z]`不執(zhí)行轉(zhuǎn)寫,可以認(rèn)為`mt==rt`.
這是在轉(zhuǎn)寫過程中嵌入python的例子,還能在匹配時嵌入轉(zhuǎn)寫:
`foo = "findsecret" : "print(mt)"`;
前面的findsecret函數(shù)負(fù)責(zé)在字符串中找到“神秘文本”,后面的轉(zhuǎn)寫代碼打印出來,并將原始的字符返回…
##6. 你在15分鐘內(nèi)讀完了么?
我相信你沒有,因為讀懂那個匹配規(guī)則的日志文件,就需要最少五分鐘,但如果你有編譯原理和正則基礎(chǔ)的話,還是能很快理解的。而從零開發(fā)這個引擎,到反復(fù)優(yōu)化和完善,花了一年之久。
定義了各種數(shù)字之后,我們就能很快地定義時間,日期,電話號碼,地址…而你看到的只是TN語言的冰山一角。
- 它能夠分析文本的模式,解析諸如ABCABC這樣的序列,從而發(fā)現(xiàn)這是一個重復(fù)模式。
- 不僅能夠順序匹配,還能逆向,甚至亂序匹配,這就能夠抽取類似“學(xué)校的校訓(xùn)”這樣的問題。
- 規(guī)則可以調(diào)用自身,配合腳本,因此能夠?qū)崿F(xiàn)遞歸下降解析。例如30行代碼實現(xiàn)xml解析,或20行規(guī)則實現(xiàn)自然語言計算器。
- 規(guī)則可以嵌入腳本,甚至動態(tài)生成代碼,因此,甚至在理論上,TN能夠自己編譯自己。
- TN還能做一個簡單的SQL解釋器,或是中文英文的簡單互相翻譯的工具。
是不是已經(jīng)激動地顫抖了?唯一限制你能力的就是你的想象力。本博客將會進(jìn)一步發(fā)布一系列有關(guān)tn的內(nèi)容,包括高級語法,tn優(yōu)化等。
感興趣的可以聯(lián)系作者:buptzym@qq.com,或在本文下面留言。
[1]: http://www.cnblogs.com/buptzym/p/5355827.html
[2]: http://www.cnblogs.com/buptzym/p/5361121.html
[3]: http://www.cnblogs.com/buptzym/p/5355920.html
[4]: http://static.zybuluo.com/buptzym/ksl5ggrfcn1psmdf2f81i8wg/foo.png
[5]: http://static.zybuluo.com/buptzym/itwhlmz8ua2h3jgbqdq5z48g/foo.png
總結(jié)
以上是生活随笔為你收集整理的python文本分析的开源工具_重磅开源:TN文本分析语言的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pb公共变量怎么找_阿迪达斯的4D怎么就
- 下一篇: pb string最大长度_跑马备赛训练