分享一種精確從文本中提取URL的思路及實現

     在今年三四月份,我接受了一個需求:從文本中提取URL。這樣的需求,可能算是非常小衆的需求了。大概只有QQ、飛信、阿里旺旺等之類的即時通訊軟件存在這樣的需求。在研究這個之前,我測試了這些軟件這塊功能,發現它們這塊的功能還是非常弱的。這類軟件往往也是惡意URL傳播的媒介,如果不能準確識別出URL,相應的URL安全檢測也無從談起。而且網上也有很多使用正則表達式的方法,可是我看了下,方法簡單但是不夠精確,對於要求不高的情況可以勝任,但是如果“壞人”想繞過這種提取也是很方便的。

    

URL介紹

全稱:Uniform Resource Locators。

  • 最常見“最”標準的URL
例子:
http://www.g.cn/
衍生出瀏覽器可以接受的URL(在地址欄輸入的URL首先會被瀏覽器截獲,瀏覽器可更具其對URL的理解進行相關容錯)
協議後對斜槓無要求
http:www.g.cn
http:\www.g.cn
http:\\/\www.g.cn
……
目前主流IM對最常見“最”標準的URL的識別沒有問題,但是對衍生出來的URL都是無法正確識別的。
  • 比較常見但是“不標準”(無協議頭)URL
例子:
無協議頭,無二級域名
例子:g.cn
無協議頭,有二級域名www
例子:www.g.cn
無協議頭,有二級域名,但是不是www
例子:mp3.g.cn
目前國內主流IM對URL的判別上,在沒有協議頭(http://等)時,尋找有沒有“www.”,如果存在“www.”,則認爲其後是URL。
  • 比較少見的URL
例子:
格式省略或者特殊的URL
頂級域名後包含“點”
www.g.cn.(同www.g.cn)
部分省略
www.g.cn.?wd=3(同www.g.cn./?wd=3、www.g.cn/?wd=3)
包含用戶名和密碼的URL
密碼不爲空
密碼爲空
username:@www.g.cn
目前國內主流IM對這類URL判斷是不準確的,如上例只能識別爲www.g.cn

  • 比較特殊的URL
例子:
完全沒有分隔符的
g.cnclick this
可以識別爲g.cn,但是國內IM都不會去這麼識別
比較難以歸類的
mailto:@g.cn
以mailto協議標準,這個URL不符合RFC規定,因爲mailto:後面@之前應該有“用戶名”
以http或者ftp協議標準,這個URL是合法的,因爲這個URL中用戶名位mailto,密碼爲空。
囧啊!驚恐

看一下國內一些IM的表現:
  • URL標準定義
定義於RFC1738,詳細請見http://tools.ietf.org/html/rfc1738
具有相似的格式(ftp,http,https,wais,nntp……)
<scheme> ://<user>:<password>@<host>:<port>/<url-path>
“<user>:<password>@”, “:<password>”,“:<port>”,和“/<url-path>” 是可選的。
“<user>:<password>@”可以是“<user>@”(不需要密碼),也可以是“<user>:@”(密碼爲空)。
形式多樣的(mailto,news)
形式太多樣,定義寬鬆
一些其他特殊協議(afs……)
要麼不用了,要麼這份RFC沒給出定義,要麼很少用。

  • 格式相似的協議的URL Scheme的BNF範式
HTTP(用來指定互聯網資源)
http://<host>:<port>/<path>?<searchpart>
gopher (用來指定互聯網資源,已經很少用了)
gopher://<host>:<port>/<gopher-path>
nntp(網絡新聞傳輸協議)
nntp://<host>:<port>/<newsgroup-name>/<article-number>
telnet(Internet遠程登陸服務的標準協議和主要方式)
telnet://<user>:<password>@<host>:<port>/
wais(廣域信息查詢系統)
wais://<host>:<port>/<database>
wais://<host>:<port>/<database>?<search>
wais://<host>:<port>/<database>/<wtype>/<wpath>

  • 格式相似的協議的URL Scheme的BNF範式
file(描述文件資源)
file://<host>/<path>
prospero(Be used to designate resources that are accessed via the Prospero Directory Servic)
prospero://<host>:<port>/<hsoname>;<field>=<value>
  • 形式多樣的協議的URL Scheme的BNF範式
news
news:<newsgroup-name>
news:<message-id>
例子:
news:msnews.microsoft.com
mailto
mailto:<rfc822-addr-spec>
例子:

  • 一些其他特殊協議
afs Andrew File System global file names.
mid Message identifiers for electronic mail.
cid Content identifiers for MIME body parts.
nfs Network File System (NFS) file names.
tn3270 Interactive 3270 emulation sessions.
mailserver Access to data available from mail servers.
z39.50 Access to ANSI Z39.50 services.

  • URL的RFC文檔對提取URL的幫助
提供了所有的協議頭,幫助準確找到URL起始位置
提供了http、ftp等協議名
定義了各種URL的範式,爲準確得提取URL有很大的幫助
如ali-inc.com中的ali-inc部分要求“-”是可選的,且在存在“-”時,要求其左右存在數字或者字母。
如user name和password部分(username:[email protected])如果出現“:”、 “@”或“/”時要加密,這將幫助尋找到URL的起始位置(@user:[email protected]提取的URL是user:[email protected])。
  • 基於以上問題,可以有種折中方案:將URL範式和現在已知的toplabel結合,構成一個新的範式。以下是RFC文檔中BNF範式結合實際問題被修改成的正則表達式:
  1. ((((ftp:|https:|http:)([\Q/\\E])*)|())(((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+!*'(),;?&=\E]))+(:((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+!*'(),;?&=\E]))*)?@)?(((((([a-zA-Z0-9]){1}([a-zA-Z0-9\-])*([a-zA-Z0-9]{1}))|([a-zA-Z0-9]))\.)+(biz|com|edu|gov|info|int|mil|name|net|org|pro|aero|cat|coop|jobs|museum|travel|arpa|root|mobi|post|tel|asia|geo|kid|mail|sco|web|xxx|nato|example|invalid|test|bitnet|csnet|onion|uucp|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))|([0-9]{1,3}(\.[0-9]{1,3}){3}))(\:([0-9]+))?(([\Q/\\E])+((((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+\!*'(),;:@&=\E]))*)(([\Q/\\E])*((%[0-9a-fA-F]{2})|([a-zA-Z0-9])|([\Q$-_.+\!*'(),;:@&=\E]))*)*)(\?((%[0-9a-fA-F]{2})|([a-zA-Z0-9])|([\Q$-_.+!*'(),;:@&=<>#"{}[] ^`~|\/\E]))*)*)*)  
((((ftp:|https:|http:)([\Q/\\E])*)|())(((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+!*'(),;?&=\E]))+(:((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+!*'(),;?&=\E]))*)?@)?(((((([a-zA-Z0-9]){1}([a-zA-Z0-9\-])*([a-zA-Z0-9]{1}))|([a-zA-Z0-9]))\.)+(biz|com|edu|gov|info|int|mil|name|net|org|pro|aero|cat|coop|jobs|museum|travel|arpa|root|mobi|post|tel|asia|geo|kid|mail|sco|web|xxx|nato|example|invalid|test|bitnet|csnet|onion|uucp|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))|([0-9]{1,3}(\.[0-9]{1,3}){3}))(\:([0-9]+))?(([\Q/\\E])+((((%[0-9a-fA-F][0-9a-fA-F])|([a-zA-Z0-9])|([\Q$-_.+\!*'(),;:@&=\E]))*)(([\Q/\\E])*((%[0-9a-fA-F]{2})|([a-zA-Z0-9])|([\Q$-_.+\!*'(),;:@&=\E]))*)*)(\?((%[0-9a-fA-F]{2})|([a-zA-Z0-9])|([\Q$-_.+!*'(),;:@&=<>#"{}[] ^`~|\/\E]))*)*)*)
看着是不是特別複雜?是的!這將導致效率非常低下。(這是很久前一個做實驗的版本,不能保證其準確性)利用這個正則表達式中我們可以發現很多域名,這些域名都是我從某款安全輔助軟件的二進制文件中扒下來了大笑。可能有人會認爲這個正則效率的瓶頸在匹配這些域名上,其實不是,我做個實驗,主要的瓶頸在domainlabel(就是.com等之前的那部分)上,所以優化比較困難。而且這個正則還沒考慮一些特殊問題,比如將“。”識別成"."。
  • 域名
  1. biz|com|edu|gov|info|int|mil|name|net|org|pro|aero|cat|coop|jobs|museum|travel|arpa|root|mobi|post|tel|asia|geo|kid|mail|sco|web|xxx|nato|example|invalid|test|bitnet|csnet|onion|uucp|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw  
biz|com|edu|gov|info|int|mil|name|net|org|pro|aero|cat|coop|jobs|museum|travel|arpa|root|mobi|post|tel|asia|geo|kid|mail|sco|web|xxx|nato|example|invalid|test|bitnet|csnet|onion|uucp|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw
這個域名還是比較全的,我沒有全部測試,只是大致看了下,畢竟是別人總結的,不知道別人是否在裏面放了“標記”信息。我曾經擔心過xxx這個域名,還搜了下,發現很大大笑!。還有請仔細看,這些域名中沒有數字,這爲我之後的設計提出了一種思路。

  • 國內IM對URL提取的處理
  • 解讀:
    目前對URL的提取思路基本上是先考慮是否存在協議部分(http,ftp等),如果存在協議部分,則認爲此協議之後URL可以接受的部分都是URL。這種方式存在很大的缺陷,如http://1也會被識別爲一個URL。而http:g.cn則不會識別爲一個完整的URL。
    對於不存在協議部分的情況,尋找www.,如果存在www.則認爲此串爲URL,如:www.1就會被識別爲URL,而mp3.g.cn則不會識別爲URL。以mp3.g.cn和www.g.cn爲例,.cn爲頂級域名,g.cn爲一級域名,而mp3.g.cn和www.g.cn都是二級域名。由於一開始時,人們習慣將二級域名www.g.cn指向了一級域名g.cn,久而久之,人們就認爲www.開頭的URL爲一級域名。我想可能這個是造成目前這種判斷URL的邏輯的原因。

  • 上述方法的優缺點
優點:
邏輯簡單
效率高
缺點:
判斷不準確
產生以上優缺點的原因
只是尋找http,https,ftp,file,mailto,www這幾個關鍵詞。因爲關鍵詞少,所以邏輯簡單也高效。有利有弊,因爲關鍵詞少,也一定程度上影響了URL判斷的不準確性。
  • 再次對URL進行分析和思考
常見的URL分類:
IP形式: 192.168.1.1,10.20.11.1
Domain形式:g.cn、www.g.cn,mp3.g.cn
觀察可以見得:IP形式的URL結構最爲簡單:4個小於255的數字被.分割;domain形式比較複雜,但是它們有共性:都具有頂級域名.cn。

提取URL的大致思路
通過以上的規律,可以發現,使用頂級域名來識別URL比使用協議或者www二級域名的方式要準確,同時輔助以IP鑑別,以求達到最大覆蓋。

對前人做了總結和分析後,以下是我設計的提取邏輯
  • 提取URL的基本邏輯
  • 案例:
原始文字 提取結果
這個是g.cn g.cng.co
g.com/index.htm? g.com/index.htm?s=g.cn
s=g.cn1.2.3.456 1.2.3.45
g.cn和g.com g.cn
g.com
1.2.3.4.5 1.2.3.4
2.3.4.5




以上是設計的相關邏輯
  • 以下是我寫的一個demo的提取結果
  • 效率

最差URL形式

最優URL形式

URL形式

g.com.12.com.12.com.……

g.com/1111111111111……

遍歷次數

約 (n+1)*n/2(O(n^2))

約n(O(1))


URL長度

最差耗時(ms/10,000次查找)

最優耗時(ms/100,000次查找)

200

1529

400

410

3921

578

810

11703

953

1620

39463

1719

2450

82980

2453

3270

143151

3219

4300

266341

4141



  • 優化
目前的代碼還是存在很多可以優化的地方:
因爲我採用的遞歸調用,所以在最差情況下,執行效率大概是26ms一條,所以可以將遞歸改成循環來解決。
我使用的是C++類寫的,如果改成C並_fastcall調用約定也會快些。
目前這個邏輯大致思路是從頭到尾走一遍(不包括回溯),提取出以domain形式和IP形式的URL。在此之前,我設計成以domain形式從頭到尾檢測一次,和以IP形式從頭到尾檢測一次,然後綜合兩個結果的方法,這樣的設計會比我目前這樣的設計快一個數量級(已測)。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章