1. 程式人生 > >如何使用正則做文字資料的清洗(附免費AI視訊福利)

如何使用正則做文字資料的清洗(附免費AI視訊福利)

手工打造文字資料清洗工具

作者 白寧超

2019年4月30日09:43:59

前言:資料清理指刪除、更正錯誤、不完整、格式有誤或多餘的資料。資料清理不僅僅更正錯誤,同樣加強來自各個單獨資訊系統不同資料間的一致性。本章首先介紹了新聞語料的基本情況及語料構建的相關原則;然後,回顧對比遞迴遍歷與生成器遍歷,打造一款高效的檔案讀取工具;最後,結合正則資料清洗方法完成新聞語料的批量處理。(本文原創,轉載標明出處。限時福利:《福利:33套AI技術視訊免費領取》)

1 新聞語料的準備

語料可以理解為語言材料,包括口語材料和書面材料。語料的定義較為廣泛,其來源可能是教材、報紙、綜合刊物、新聞材料、圖書等,語料所涉及的學科門類也較為複雜。 本章所介紹的新聞語料,狹義上來講,就是為實驗或工程應用所準備的相對規範的資料集。其目的是通過有監督學習方法,訓練演算法模型以達到工程應用。本書新聞語料來源於復旦大學新聞語料摘選,原始語料達到近千萬條,為了更加適應教學,作者選擇平衡語料30餘萬條,具體語料資訊如下:

為什麼我們不是自己構建語料,而採用開源資料集?前面我們介紹的資料採集和爬蟲技術,可以完成對某特定領域資料的爬取和整理。然後結合第四章資料預處理技術,最終整理成相對規範的文字資訊,理論上講完全是可行的。但是,實際情況而言,語料庫構建需要遵循以下幾個原則:

  • 代表性:在應用領域中,不是根據量而劃分是否是語料庫,而是在一定的抽樣框架範圍內採集而來的,並且在特定的抽樣框架內做到代表性和普遍性。
  • 結構性:有目的的收集語料的集合,必須以電子形式存在,計算機可讀的語料集合結構性體現在語料庫中語料記錄的程式碼,元資料項、資料型別、資料寬度、取值範圍、完整性約束。
  • 平衡性:主要體現在平緩因子:學科、年代、文體、地域、登載語料的媒體、使用者的年齡、性別、文化背景、閱歷、預料用途(私信/廣告等),根據實際情況選擇其中一個或者幾個重要的指標作為平衡因子,最常見的平衡因子有學科、年代、文體、地域等。
  • 規模性:大規模的語料對語言研究特別是對自然語言研究處理很有用的,但是隨著語料庫的增大,垃圾語料越來越多,語料達到一定規模以後,語料庫功能不能隨之增長,語料庫規模應根據實際情況而定。
  • 元資料:元資料對於研究語料庫有著重要的意義,我們可以通過元資料瞭解語料的時間、地域、作者、文字資訊等;還可以構建不同的子語料庫;除此外,還可以對不同的子語料對比;另外還可以記錄語料知識版權、加工資訊、管理資訊等。

2 高效讀取檔案

2.1 遞迴遍歷讀取新聞

遞迴在電腦科學中是指一種通過重複將問題分解為同類的子問題而解決問題的方法。遞迴式方法可以被用於解決很多的電腦科學問題,因此它是電腦科學中十分重要的一個概念。絕大多數程式語言支援函式的自呼叫,在這些語言中函式可以通過呼叫自身來進行遞迴。計算理論可以證明遞迴的作用可以完全取代迴圈,因此有很多在函式程式語言中用遞迴來取代迴圈的例子。電腦科學家尼克勞斯·維爾特如此描述遞迴:遞迴的強大之處在於它允許使用者用有限的語句描述無限的物件。因此,在電腦科學中,遞迴可以被用來描述無限步的運算,儘管描述運算的程式是有限的。

事實上,遞迴演算法核心思想就是分而治之。在一些適用情形下可以讓人驚訝不已,但也不是任何場合都能發揮的作用的,諸如遍歷讀取大量檔案的時候。往往科學的實驗揭露事實的真相,我們開啟這樣一個實驗即遞迴演算法遍歷讀取CSCMNews資料夾下的30餘萬新聞語料,每讀取5000條資訊再螢幕上列印一條讀取完成的資訊。程式碼實現如下:

# 遍歷CSCMNews目錄檔案
def TraversalDir(rootDir):
    # 返回指定目錄包含的檔案或資料夾的名字的列表
    for i,lists in enumerate(os.listdir(rootDir)):
        # 待處理資料夾名字集合
        path = os.path.join(rootDir, lists)
        # 核心演算法,對檔案具體操作
        if os.path.isfile(path):
            if i%5000 == 0:
                print('{t} *** {i} \t docs has been read'.format(i=i, t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())))
        # 遞迴遍歷檔案目錄
        if os.path.isdir(path):
            TraversalDir(path)

我們執行main主函式:

if __name__ == '__main__':
    t1=time.time()
    # 根目錄檔案路徑
    rootDir = r"../Corpus/CSCMNews"
    TraversalDir(rootDir)
​
    t2=time.time()
    print('totally cost %.2f' % (t2-t1)+' s')

經過實驗證明,完成約30萬新聞文字讀取花費65.28秒(這裡還沒有對檔案執行任何操作),隨著語料數量增加,執行速度也將會越來越慢。遞迴遍歷讀取新聞語料結果如圖所示:

2.2 高效遍歷讀取新聞

前面介紹的yield生成器大大提升了執行效率,對於讀取檔案操作,我們只需要構建一個類檔案就可以,其中loadFiles類負責載入目錄檔案,而loadFolders類負責載入資料夾下的子檔案,實現如下:

 

# 載入目錄檔案
class loadFiles(object):
    def __init__(self, par_path):
        self.par_path = par_path
    def __iter__(self):
        folders = loadFolders(self.par_path)
        # level directory
        for folder in folders:              
            catg = folder.split(os.sep)[-1]
            #secondary directory
            for file in os.listdir(folder):     
                yield catg, file
​
# 載入目錄下的子檔案
class loadFolders(object):   # 迭代器
    def __init__(self, par_path):
        self.par_path = par_path
    def __iter__(self):
        for file in os.listdir(self.par_path):
            file_abspath = os.path.join(self.par_path, file)
            # if file is a folder
            if os.path.isdir(file_abspath): 
                yield file_abspath

上述程式碼最大的變化就是return關鍵字改為yield,如此便成了生成器函數了,我們通過yield生成器函式遍歷30餘萬新聞檔案,每完成5000個檔案讀取變列印一條資訊,呼叫main函式如下:

if __name__=='__main__':
    start = time.time()
    filepath = os.path.abspath(r'../Corpus/CSCMNews')
    files = loadFiles(filepath)
    for i, msg in enumerate(files):
        if i%5000 == 0:
            print('{t} *** {i} \t docs has been Read'.format(i=i,t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())))
    end = time.time()
    print('total spent times:%.2f' % (end-start)+ ' s')

執行以上函式,執行結果如下圖所示:

遞迴遍歷讀取30萬新聞資料耗時65.28秒,而yield生成器僅僅耗時0.71秒。前者是後者的87倍多,隨著對檔案操作和資料量的增加,這種區別可以達到指數級。本節封裝的yield生成器類檔案,讀者可以保留下來,複用到其他檔案操作之中。

3 正則表示式提取文字資訊

正則表示式(程式碼中常簡寫regex、regexp或RE),是電腦科學的一個概念。正則表示式使用單個字串來描述、匹配一系列匹配某個句法規則的字串。在很多文字編輯器裡,正則表示式通常被用來檢索、替換那些匹配某個模式的文字。許多程式設計語言都支援利用正則表示式進行字串操作。正則表示式是一種用來匹配字串的強有力的武器。它的設計思想是用一種描述性的語言來給字串定義一個規則,凡是符合規則的字串,我們就認為它“匹配”了,否則,該字串就是不合法的。regular.py包含以下正則案例實現。

 

  • 提取0結束的字串
line = 'this is a dome about  this  scrapy2.0'
regex_str='^t.*0$'
match_obj = re.match(regex_str,line)
if match_obj:
    print(match_obj.group(1))
  • 提取指定字元t與t之間的子串
line = 'this is a dome about  this  scrapy2.0'
regex_str=".*?(t.*?t).*" # 提取tt之間的子串
match_obj = re.match(regex_str,line)
if match_obj:
    print(match_obj.group(0))
  • 提取課程前面內容
line = '這是Scrapy學習課程,這次課程很好'
regex_str=".*?([\u4E00-\u9FA5]+課程)"
match_obj = re.match(regex_str,line)
if match_obj:
    print(match_obj.group(0))
  • 提取日期內容
line = 'xxx出生於1989年'
regex_str=".*?(\d+)年"
match_obj = re.match(regex_str,line)
if match_obj:
    print(match_obj.group(0))
  • 提取不同格式的出生日期
line = '張三出生於1990年10月1日'
line = '李四出生於1990-10-1'
line = '王五出生於1990-10-01'
line = '孫六出生於1990/10/1'
line = '張七出生於1990-10'
​
regex_str='.*出生於(\d{4}[年/-]\d{1,2}([月/-]\d{1,2}|[月/-]$|$))'
match_obj = re.match(regex_str,line)
if match_obj:
    print(match_obj.group(1))

4 正則清洗文字資料

 正則處理文字資料,可以剔除髒資料和指定條件的資料篩選。我們這些選用體育新聞中的一篇文字資訊,讀取文字資訊如下:

# 讀取文字資訊
def readFile(path):
    str_doc = ""
    with open(path,'r',encoding='utf-8') as f:
        str_doc = f.read()
    return str_doc
​
# 1 讀取文字
path= r'../Corpus/CSCMNews/體育/0.txt'
str_doc = readFile(path)
print(str_doc

原始新聞文字節選如下:

馬曉旭意外受傷讓國奧警惕 無奈大雨格外青睞殷家軍 記者傅亞雨瀋陽報道 來到瀋陽,國奧隊依然沒有擺脫雨水的困擾。7月31日下午6點,國奧隊的日常訓練再度受到大雨的干擾,無奈之下隊員們只慢跑了25分鐘就草草收場。 31日上午10點,國奧隊在奧體中心外場訓練的時候,天就是陰沉沉的,氣象預報顯示當天下午瀋陽就有大雨,但幸好隊伍上午的訓練並沒有受到任何干擾。 下午6點,當球隊抵達訓練場時,大雨已經下了幾個小時,而且絲毫沒有停下來的意思。抱著試一試的態度,球隊開始了當天下午的例行訓練,25分鐘過去了,天氣沒有任何轉好的跡象,為了保護球員們,國奧隊決定中止當天的訓練,全隊立即返回酒店。

我們假設需要清除文字中的特殊符號、標點、英文、數字等,僅僅只保留漢字資訊,甚至於去除換行符,還有多個空格轉變成一個空格。當然,以上這幾點也是資料清洗中常見的情況,其程式碼實現如下:

def textParse(str_doc):
    # 正則過濾掉特殊符號、標點、英文、數字等。
    r1 = '[a-zA-Z0-9’!"#$%&\'()*+,-./::;;|<=>?@,—。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
    # 去除換行符
    str_doc=re.sub(r1, ' ', str_doc)
    # 多個空格成1個
    str_doc=re.sub(r2, ' ', str_doc)
    return str_doc
​
# 正則清洗字串
word_list=textParse(str_doc)
print(word_list)

執行上述程式碼,文字資訊清洗後結果如下:

以上實驗,只是簡單的使用正則方法處理文字資訊,具體正則使用情況視情形而定。有時候我們面對的不一定是純文字資訊,也有可能是網頁資料,或者是微博資料。我們如何去清洗這些半結構化資料呢?

5 正則HTML網頁資料

設想我們現在有這樣一個需求,任務是做資訊抽取,然後構建足球球員技能資料庫。你首先想到的是一些足球網站,然後編寫爬蟲程式碼去爬取足球相關的新聞,並對這些網頁資訊本地化儲存如下圖所示:

乍一看非常頭疼,我們如何抽取文字資訊?一篇篇手工處理顯然不現實,採用上面正則方法會出現各種形式干擾資料。這裡,我們介紹一種網頁資料通用的正則處理方法。實現程式碼如下:

 

# 清洗HTML標籤文字
# @param htmlstr HTML字串.
def filter_tags(htmlstr):
    # 過濾DOCTYPE
    htmlstr = ' '.join(htmlstr.split()) # 去掉多餘的空格
    re_doctype = re.compile(r'<!DOCTYPE .*?> ', re.S)
    s = re_doctype.sub('',htmlstr)
    # 過濾CDATA
    re_cdata = re.compile('//<!CDATA\[[ >]∗ //\] > ', re.I)
    s = re_cdata.sub('', s)
    # Script
    re_script = re.compile('<\s*script[^>]*>[^<]*<\s*/\s*script\s*>', re.I)
    s = re_script.sub('', s)  # 去掉SCRIPT
    # style
    re_style = re.compile('<\s*style[^>]*>[^<]*<\s*/\s*style\s*>', re.I)
    s = re_style.sub('', s)  # 去掉style
    # 處理換行
    re_br = re.compile('<br\s*?/?>')
    s = re_br.sub('', s)     # 將br轉換為換行
    # HTML標籤
    re_h = re.compile('</?\w+[^>]*>')
    s = re_h.sub('', s)  # 去掉HTML 標籤
    # HTML註釋
    re_comment = re.compile('<!--[^>]*-->')
    s = re_comment.sub('', s)
    # 多餘的空行
    blank_line = re.compile('\n+')
    s = blank_line.sub('', s)
    # 剔除超連結
    http_link = re.compile(r'(http://.+.html)')
    s = http_link.sub('', s)
    return s
​
# 正則處理html網頁資料
s=filter_tags(str_doc)
print(s)

執行上述程式碼,得到以下結果:

6 實戰案例:批量新聞文字資料清洗

我們詳細的介紹了迭代遍歷與yield生成器遍歷的兩個小實驗,通過實驗對比,高效檔案讀取方式效果顯著。上節我們只是讀取檔名,那麼對檔案內容如何修改?這是本節側重的知識點。其中loadFolders方法保持不變,主要對loadFiles方法進行修改。實現批量新聞文字資料清洗。

class loadFiles(object):
    def __init__(self, par_path):
        self.par_path = par_path
    def __iter__(self):
        folders = loadFolders(self.par_path)
        for folder in folders:              # level directory
            catg = folder.split(os.sep)[-1]
            for file in os.listdir(folder):     # secondary directory
                file_path = os.path.join(folder, file)
                if os.path.isfile(file_path):
                    this_file = open(file_path, 'rb') #rb讀取方式更快
                    content = this_file.read().decode('utf8')
                    yield catg, content
                    this_file.close()

在統計學中,抽樣是一種推論統計方法,它是指從目標總體中抽取一部分個體作為樣本,通過觀察樣本的某一或某些屬性,依據所獲得的資料對總體的數量特徵得出具有一定可靠性的估計判斷,從而達到對總體的認識。抽樣方法諸多,常見的包括以下幾個方法:

  • 簡單隨機抽樣,也叫純隨機抽樣。從總體N個單位中隨機地抽取n個單位作為樣本,使得每一個容量為樣本都有相同的概率被抽中。特點是:每個樣本單位被抽中的概率相等,樣本的每個單位完全獨立,彼此間無一定的關聯性和排斥性。簡單隨機抽樣是其它各種抽樣形式的基礎。通常只是在總體單位之間差異程度較小和數目較少時,才採用這種方法。
  • 系統抽樣,也稱等距抽樣。將總體中的所有單位按一定順序排列,在規定的範圍內隨機地抽取一個單位作為初始單位,然後按事先規定好的規則確定其他樣本單位。先從數字1到k之間隨機抽取一個數字r作為初始單位,以後依次取r+k、r+2k……等單位。這種方法操作簡便,可提高估計的精度。
  • 分層抽樣,將抽樣單位按某種特徵或某種規則劃分為不同的層,然後從不同的層中獨立、隨機地抽取樣本。從而保證樣本的結構與總體的結構比較相近,從而提高估計的精度。
  • 整群抽樣,將總體中若干個單位合併為組,抽樣時直接抽取群,然後對中選群中的所有單位全部實施調查。抽樣時只需群的抽樣框,可簡化工作量,缺點是估計的精度較差。

接下來,我們實現新聞文字的抽樣讀取,我們假設抽樣率為5即每隔5條資訊處理一篇文章,然後每處理5000篇文章在螢幕列印一條資訊,其執行程式碼如下:

if __name__=='__main__':
    start = time.time()
    filepath = os.path.abspath(r'../Corpus/CSCMNews')
    files = loadFiles(filepath)
    n = 5  # n 表示抽樣率
    for i, msg in enumerate(files):
        if i % n == 0:
            if int(i/n) % 1000 == 0:
                print('{t} *** {i} \t docs has been dealed'.format(i=i,             t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())))
    end = time.time()
    print('total spent times:%.2f' % (end-start)+ ' s')

我們使用簡單抽樣的方法,資訊處理結果如下圖所示,總耗時為1120.44秒。

 

我們最終目的是通過批量操作去清洗文字資訊,到目前為止,我們只是實現了檔案的遍歷和文字提取。並且我們知道如何使用簡單抽樣,距離最終目的只是一步之遙。這裡我們還有提前前文正則處理文字的textParse方法,本節直接匯入即可,接下來,我們就是提取文章類別、文章內容、正則清洗。其實現程式碼如下

if __name__=='__main__':
    start = time.time()
    filepath = os.path.abspath(r'../Corpus/CSCMNews')
    files = loadFiles(filepath)
    n = 5  # n 表示抽樣率, n抽1
    for i, msg in enumerate(files):
        if i % n == 0:
            catg = msg[0]    # 文章類別
            content = msg[1] # 文章內容
            content = textParse(content) # 正則清洗
            if int(i/n) % 1000 == 0:
                print('{t} *** {i} \t docs has been dealed'
                      .format(i=i, t=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())),'\n',catg,':\t',content[:20])
    end = time.time()
    print('total spent times:%.2f' % (end-start)+ ' s')

執行main函式實現最終結果:

 >> 限時福利:《福利:33套AI技術視訊免費領取