如何使用正則做文本數據的清洗(附免費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技術視頻免費領取》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM