一种精确从文本中提取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
? ? 格式省略或者特殊的UR
- 頂級域名后包含“點”
- 例子:www.g.cn.(同www.g.cn)
? ? 部分省略
- 例子:www.g.cn.?wd=3(同www.g.cn./?wd=3、www.g.cn/?wd=3)
包含用戶名和密碼的URL
- 密碼不為空
- 例子:username:password@www.g.cn
- 密碼為空
- 例子: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>
? ? ? ? 例子:mailto:1@g.cn
一些其他特殊協議
| 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:password@g.cn)如果出現“:”、 “@”或“/”時要加密,這將幫助尋找到URL的起始位置(@user:pass@g.cn提取的URL是user:pass@g.cn)。
? ? ? ? 基于以上問題,可以有種折中方案:將URL范式和現在已知的toplabel結合,構成一個新的范式。以下是RFC文檔中BNF范式結合實際問題被修改成的正則表達式:
((((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等之前的那部分)上,所以優化比較困難。而且這個正則還沒考慮一些特殊問題,比如將“。”識別成"."。
域名
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形式從頭到尾檢測一次,然后綜合兩個結果的方法,這樣的設計會比我目前這樣的設計快一個數量級(已測)。
(轉載請指明出處)
總結
以上是生活随笔為你收集整理的一种精确从文本中提取URL的思路及实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 进程间通信:同步双工管道
- 下一篇: 使用VC实现一个“智能”自增减线程池