當前位置:
首頁 > 知識 > 嘀 正則表達式快速上手指南

嘀 正則表達式快速上手指南

本文為雷鋒字幕組編譯的技術博客,原標題Regular Expressions for Data Scientists,來源dataquest。

翻譯 | 汪其香 Noddleleslee 陳亞彬 趙朋飛 楊婉迪 校對 | 餘杭 整理 | 凡江

作為數據科學家,快速處理海量數據是他們的必備技能。有時候,這包括大量的文本語料庫。例如,假設要找出在 Panama Papers(https://en.wikipedia.org/wiki/Panama_Papers) 泄密事件中郵件的發送方和接收方,我們需要詳細篩查1150萬封文檔!我們可以手工完成上述任務,人工閱讀每一封郵件,讀取每一份最後發給我們的郵件,或者我們可以藉助Python的力量。畢竟,代碼存在的一個至關重要的理由就是自動處理任務。

儘管如此,從頭開始編寫腳本、寫腳本、抓取數據需要大量的時間和精力。這正是正則表達式的用武之地。RE,regex 和regular patterns 表達的意思皆是正則表達式,它形成一門簡潔的語言幫助我們快速地整理和分析文本。

正則出現在1956年,Stephen Cole Kleene 創建它用於描述人類神經系統的MP模型(McCulloch and Pitts model)(http://aishack.in/tutorials/artificial-neurons-mccullochpitts-model/)的概念。1960年代,Ken Thompson 將這個概念添加到類似Windows記事本的文本編輯器中,自此正則開始壯大。

正則一個關鍵特性是節省腳本。我們可以視其為代碼的捷徑。沒有它,我們不得不為同樣目的敲大量的垃圾代碼。

本教程需要Python基礎知識。如果你理解if-else 表達式,while 語句和for 循環,列表和字典,本教程的大部分都可以搞定啦。此外你需要代碼編輯器,如Visual Studio Code,PyCharm 或Atom都可以。這樣當我們遍歷每一行代碼時就不會茫然,此外基礎的pandas庫也是必要的。如果你需要複習,可以跳轉到pandas 的教程(https://www.dataquest.io/blog/pandas-python-tutorial/)。

學完本教程,你會對正則的使用熟悉很多,可以使用re模塊的基礎模式和函數完成字元串分析。我們也學會如何高效地使用正則和pandas庫化大量紊亂的數據集為有序。

現在,讓我們看看正則可以做些什麼。


數據集介紹

我們使用Kaggle的欺詐郵件文本語料庫。它包括1998到2007發出的上千封釣魚郵件。點擊此處(https://www.kaggle.com/rtatman/fraudulent-email-corpus)可以下載數據集。在對整個語料庫操作之前,讓我們先學習在一封郵件應用正則表達。


Python 正則表達式模塊的介紹

首先打開文本文件讀取數據,設置為只讀模式,並讀取數據集,最後將上述操作結果賦給變數 fh(「file handle」 即文件句柄)。

請注意我們在設置目錄路徑之前添加 r。它將轉換字元串為原始字元串,避免機器讀取字元時候引起衝突,例如 Windows 的目錄路徑中的反斜杠。

你也許注意到我們現在並沒有使用整個語料庫。相反地,我們先人工挑選語料庫的相對靠前的一些郵件作為測試文件。本教程不打算每次都展示上千行的結果,每次都列印其中的一部分作為測試。這可能會讓人感到惱怒。你可以使用整個語料庫,也可以使用我們的測試文件。無論哪種方式,都能很好得獲得學習經驗。

現在,假設我們現在想知道郵件的來源。我們可以在自己的Python嘗試如下代碼:

或者,我們可以使用正則表達式:

我們來遍歷這段代碼。首先導入 re 模塊。然後敲出圖示餘下代代碼。這個例子中,這比原來的Python 代碼僅少 1 行 。然而隨著腳本行數的快速增長,正則表達式可以節省腳本的代碼量。

re.findall() 以列表形式返回字元串中符合模式的所有實例。它是Python內置 re 模塊中最經常使用的函數。讓我們來剖析 re.findall。re.findall(pattern, string)接受兩個參數。pattern表示我們想要搜索的子字元串,string 表示我們想要搜索的主字元串。主字元串可以由多行組成。

.* 是字元串模式的簡寫。我們很快就會解釋它的細節。現在它們與From: 域中的名稱和電子郵件地址相匹配。

在讓我們更深一步探索之前,先瀏覽一下常用的正則表達式。


常用的正則表達式

我們之前用到的 re.findall() 包含"From:"的字元串。這個函數當我們明確知道搜索目標時候十分有用,甚至包括明確字母拼寫和是否大小寫。如果我們不明確知道搜索目標時,該函數就會失效。幸運的是正則表達有解決這個問題的基本模式。讓我們看一些這篇文章將用到的:

w 匹配字母數字字元,即a-z,A-Z,0-9。它也匹配下劃線和波折號。

d 即0-9。

s matches 匹配空白格,包括製表符、換行字元、回車符和空格字元。

S 匹配非空白格字元。

. 匹配除換行字元
外的任意字元串。

有這些正則表達式的說明在手,你就可以在我們解釋上述代碼時能夠快速地理解。

使用正則表達式

現在我們來解釋re.findall("From:.*", text) 中.* 的作用。首先看. :

From:後面添加. ,表示尋找它旁邊的字元,因為.查找
外的任何字元,它也會捕捉肉眼不可見的空格。我們可以添加更多的點來驗證。

看起來添加很多點可以獲得行中我們想要的剩餘部分。但這是冗餘的而且我們不知道要敲多少個點。這就是很有用的*的由來。

* 匹配其左側表達式的0個或多個模式的實例。這意味它尋找重複模式。當我們尋找重複模式時,稱為貪婪搜索。否則,我們稱之為非貪婪搜索或懶惰搜索。

讓我們用* 構建一個對 . 的貪婪搜索。

因為 * 匹配其左側 0 個或多個模式類的實例,而 . 在其左側,因此我們可以獲得From: 到行末的所有字元。這種漂亮高效的方式可以輸出完整的行。

我們甚至可以更進一步,只分離出名字:

我們使用re.findall() 返回包含"From:.*" 模式的列表,就像我們以前做的那樣。為了簡潔起見 我們給match 變數賦以上述操作的結果。接下來,我們迭代列表。每一次循環,我們都再次執行re.findall 。這一次,這個函數從第一個引號開始匹配。

請注意我們在第一個引號旁使用反斜杠。反斜杠是用於轉義其他特殊字元的特殊字元。例如,當我們想使用引號作為字元串而不是特殊字元時,我們用反斜杠來表示轉義:"。如果不使用反斜杠表示轉義,就是"".*"",Python解釋器視作兩個空字元串之間讀取一個句點和一個星號。這就會出現錯誤,腳本不能運行。因此,關鍵是使用反斜杠表示轉義。

在第一個引號匹配之後,.* 獲取行中直到下一個轉義的引號的所有字元。獲取引號內的名字。每個名字都在方括弧內列印出,因為re.findall 以列表形式返回匹配內容。如果我們需要獲取電子郵件地址呢?

看起來很簡單不是嘛?只是匹配模式有些許不同,讓我們逐一攻破。

以下是如何匹配電子郵件地址的前面部分:

電子郵件總是包含@符號,讓我們從它開始。電子郵件@符號之前的部分可能包含字母數字字元,w 就派上用場。然而,因為一些郵件包含句點或破折號,這是不夠的。我們用S 來查找非空白字元。但wS 僅僅找到兩個字元。添加 * 重複尋找過程。因此模式前半部分是:wS*@。

現在來看看@符號後半部分的模式:

域名通常包含字母數字字元、句點和破折號。這很簡單,一個 . 就能搞定。為了使用貪婪模式,我們用*來擴展搜索。這使我們可以匹配直到行結束的任何字元。

如果我們仔細觀察這行,我們會發現每個電子郵件都封裝在尖括弧內,。 我們的模式.*包括閉合的尖括弧。讓我們糾正一下:

電子郵件地址以字母數字字元結束,所以我們用w模式覆蓋。因此@ 符號後面是.*w,這意味著我們想要的模式是一組以字母數字字元結尾的字元。這不包括>。

完整電子郵件地址模式是:wS*@.*w。

這是相當多的工作。熟練使用正則表達式需要一段時間,但是一旦您掌握它的模式,您就能夠更快地為字元串分析編寫代碼。接下來,我們將運行一些re 模塊常見函數,當我們開始重新整理語料庫時它們將非常有用。


常見的正則表達式函數

re.findall() 無疑是有用的,re 模塊提供了更多同樣便捷的函數。

包括:

re.search()

re.split()

re.sub()

在使用它們把雜亂無序的語料庫變為有序之前,我們對它們逐一分析。

re.search()

re.findall() 以列表形式返回匹配字元串中滿足模式的所有實例,re.search() 匹配字元串中模式的第一個實例,並將其作為一個re 模塊的匹配對象。

和 re.findall() 類似, re.search() 也接受兩個參數。第一個參數是匹配的模式,第二個參數是要搜索的字元串範圍。這裡為了簡潔起見,我們已經將結果賦值給match 變數。

因為 re.search() 返回一個re 模塊的匹配對象,我們不能直接列印出對應的名字和電子郵件地址。 相反,我們必須先採用 group()這個函數. 我們已經在上面的代碼中列印了它們類型,可以看出group() 將匹配對象轉化成一個字元串。

我們也可以看到列印match 時顯示的是對應的屬性而不是字元串本身, 而列印 match.group() 只顯示字元串。

re.split()

假設我們需要一種快速的方法來獲取電子郵件地址的域名。我們可以用三次正則操作,像這樣:

第一行用法前面已經提到了。我們返回一個字元串列表,每個字元串包含From: 欄位的內容,並將其賦給變數。接下來的通過遍歷這個列表來查找郵件的地址。同時通過迭代電子郵件地址和使用 re 模塊的split() 函數來把每一個地址剪成兩半,用 @作為分隔符。最後再列印出來。

re.sub()

另一個方便的 re 函數是 re.sub()。正如函數名所示,它用來替換字元串的各個部分。舉個例子:

前兩行已經在前面出現過了。

在第三行我們將 address 作為 re.sub() 函數的第三個參數,即郵件標題中完整的From: 欄位。

re.sub() 需要三個參數。第一個是被代替的子字元串,第二是想要放在目標位置的字元串,而第三是主字元串。


pandas 中的正則表達式

現在我們有了正則表達式的一些基礎知識,我們可以嘗試一些更複雜的。然而,我們需要正則表達式跟pandas Python數據分析庫結合。Pandas 庫中有一個很有用的把數據組織成整齊表格的對象,即 DataFrame 對象,也可以從不同的角度理解它。結合正則表達式的代碼,它就像用一個特別鋒利的刀雕刻軟黃油。

不用擔心從來沒用過 Pandas。我們會通過代碼一步一步進行,這樣你就不會感到困惑。正如我們在引言中提到的,如果你想詳細學習,請訪問Pandas tutotial(https://www.dataquest.io/blog/pandas-python-tutorial/)。

我們可以通過 Anaconda(https://docs.continuum.io/anaconda/) 或者 pip 來下載 pandas 庫。 詳情請查看安裝指南(http://pandas.pydata.org/pandas-docs/stable/install.html)。


用正則表達式和Pandas分揀郵件

Corpus 是一個包含數千封電子郵件的文本文件。我們將使用正則表達式和Pandas 來將每封電子郵件適當分類 使Corpus 語料庫更便於閱讀和分析。我們會將每封郵件分為以下幾個類別之一:

sender_name

sender_address

recipient_address

recipient_name

date_sent

subject

email_body

每個類別將成為我們Pandas數據幀或表格中的一列。這非常有用,因為我們可以自行處理每一列。例如,我們可以直接編寫來找出電子郵件來自哪個域名,而不需要首先編碼來將電子郵件地址與其他部分隔離開來。基本上,對數據集先分類可以讓我們編寫更簡潔的代碼。反過來,簡潔的代碼減少了機器所需的操作數量,這加快了我們的處理速度,特別是在處理大量數據集時。


準備Script

我們從上面一個簡單的腳本開始。從頭開始以便弄清楚它們內部運行的原理。

在代碼的一開始首先導入 re 和pandas 模塊,我們導入的Python email 包對於郵件正文很重要,如果僅僅使用正則表達式來處理電子郵件的正文會相當複雜,可能需要足夠的清理不必要信息方面的工作才能保證它能正常運行。

email 包。然後我們創建一個空的列表emails 用來存放包含每個電子郵件詳細信息的字典。

我們經常將代碼的結果列印到屏幕上來判斷代碼是對還是錯。然而,由於數據集中有成千上萬的電子郵件,列印出上千行到屏幕上會佔據本教程頁面。我們當然不想讓你一遍又一遍地滾動成千上萬行的結果。因此,正如我們在本教程開始時所做的,我們打開並閱讀了Corpus的較短版本。為了本次教程我們手工編寫一點。你可以使用實際的數據集。

每次運行 print() 函數,你只需幾秒鐘就可以把幾千行列印到屏幕上。

現在我們開始使用正則化表達式。

我們用 re 模塊的 split 函數將 fh 中整個文本塊拆分為一個單獨的電子郵件列表,分配給 contents。這很重要,因為我們希望通過循環遍歷列表來一個個地處理電子郵件。但是我們怎麼知道用 "From r"來分割呢?我們之所以知道這一點,是因為在編寫腳本之前查看了文件。我們沒有必要仔細閱讀數千電子郵件。只需要通過前幾行來大致看看數據的結構是什麼樣子的。正因為如此,每個電子郵件前面都是字元串 "From r"。我們已經截圖了文本文件的樣子:

郵件用 「From r」開頭

綠色部分是第一個電子郵件。藍色部分是第二個電子郵件。我們可以看到,這兩個電子郵件都是以 "From r"開頭,用紅色的框來顯示。

我們在這個教程中之所以使用 Fraudulent Email Corpus是為了表明當數據是無序的和不熟悉的時候,我們不能只依靠代碼來處理,它需要一雙眼睛。就像剛剛展示的那樣,我們需要查看 Corpus 來研究它的結構。另外 這樣的數據可能還需要再處理 ,這個 Corpus 語料庫也是同理。舉個例子,即使我們用本教程的完整腳本算出本數據集包含3977 封郵件,實際上更多。有些郵件的開頭沒有 "From r"欄位所以沒有被拆分成單獨的郵件。但是我們保留了這個結果以免它無窮無盡。

注意我們也用了 contents.pop(0)去掉列表中的第一個元素。那是在第一封電子郵件的前面有"From r" 字元串。當這個欄位被分割的時候,在索引0的位置生成了一個空字元串。我們即將編寫的腳本是為電子郵件而設計的。如果出現空字元串它可能會報錯。去掉空字元串可以讓我們避免這些錯誤打斷腳本的運行。

閱讀剩餘內容,敬請期待周日的「嘀~正則表達式快速上手指南(下篇)」。

雷鋒字幕組正在招募中


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 AI研習社 的精彩文章:

想快速部署機器學習項目?來看看幾大主流機器學習服務平台對比吧
用於可視化人工神經網路的 Python庫——ANN Visualizer

TAG:AI研習社 |