沒人否認(rèn),維基百科是現(xiàn)代最令人驚嘆的人類發(fā)明之一。
幾年前誰能想到,匿名貢獻(xiàn)者們的義務(wù)工作竟創(chuàng)造出前所未有的巨大在線知識(shí)庫?維基百科不僅是你寫大學(xué)論文時(shí)最好的信息渠道,也是一個(gè)極其豐富的數(shù)據(jù)源。
從自然語言處理到監(jiān)督式機(jī)器學(xué)習(xí),維基百科助力了無數(shù)的數(shù)據(jù)科學(xué)項(xiàng)目。
維基百科的規(guī)模之大,可稱為世上最大的百科全書,但也因此稍讓數(shù)據(jù)工程師們感到頭疼。當(dāng)然,有合適的工具的話,數(shù)據(jù)量的規(guī)模就不是那么大的問題了。
在介紹過程中,我們也會(huì)提及以下幾個(gè)數(shù)據(jù)科學(xué)中重要的問題:
1、從網(wǎng)絡(luò)中搜索和編程下載數(shù)據(jù)
2、運(yùn)用Python庫解析網(wǎng)絡(luò)數(shù)據(jù)(HTML, XML, MediaWiki格式)
3、多進(jìn)程處理、并行化處理
這個(gè)項(xiàng)目最初是想要收集維基百科上所有的書籍信息,但我之后發(fā)現(xiàn)項(xiàng)目中使用的解決方法可以有更廣泛的應(yīng)用。這里提到的,以及在Jupyter Notebook里展示的技術(shù),能夠高效處理維基百科上的所有文章,同時(shí)還能擴(kuò)展到其它的網(wǎng)絡(luò)數(shù)據(jù)源中。
本文中運(yùn)用的Python代碼的筆記放在GitHub,靈感來源于Douwe Osinga超棒的《深度學(xué)習(xí)手冊(cè)》。前面提到的Jupyter Notebooks也可以免費(fèi)獲取。
GitHub鏈接:
https://github.com/WillKoehrsen/wikipedia-data-science/blob/master/notebooks/Downloading%20and%20Parsing%20Wikipedia%20Articles.ipynb
免費(fèi)獲取地址:
https://github.com/DOsinga/deep_learning_cookbook
編程搜索和下載數(shù)據(jù)
任何一個(gè)數(shù)據(jù)科學(xué)項(xiàng)目第一步都是獲取數(shù)據(jù)。我們當(dāng)然可以一個(gè)個(gè)進(jìn)入維基百科頁面打包下載搜索結(jié)果,但很快就會(huì)下載受限,而且還會(huì)給維基百科的服務(wù)器造成壓力。還有一種辦法,我們通過dumps.wikimedia.org這個(gè)網(wǎng)站獲取維基百科所有數(shù)據(jù)的定期快照結(jié)果,又稱dump。
用下面這段代碼,我們可以看到數(shù)據(jù)庫的可用版本:
import requests# Library for parsing HTMLfrom bs4 import BeautifulSoupbase_url = 'https://dumps.wikimedia.org/enwiki/'index = requests.get(base_url).textsoup_index = BeautifulSoup(index, 'html.parser')# Find the links on the pagedumps = [a['href'] for a in soup_index.find_all('a') if a.has_attr('href')]dumps['../', '20180620/', '20180701/', '20180720/', '20180801/', '20180820/', '20180901/', '20180920/', 'latest/']
這段代碼使用了BeautifulSoup庫來解析HTML。由于HTML是網(wǎng)頁的標(biāo)準(zhǔn)標(biāo)識(shí)語言,因此就處理網(wǎng)絡(luò)數(shù)據(jù)來說,這個(gè)庫簡直是無價(jià)瑰寶。
本項(xiàng)目使用的是2018年9月1日的dump(有些dump數(shù)據(jù)不全,請(qǐng)確保選擇一個(gè)你所需的數(shù)據(jù))。我們使用下列代碼來找到dump里所有的文件。
dump_url = base_url + '20180901/'# Retrieve the htmldump_html = requests.get(dump_url).text# Convert to a soupsoup_dump = BeautifulSoup(dump_html, 'html.parser')# Find list elements with the class filesoup_dump.find_all('li', {'class': 'file'})[:3][
我們?cè)僖淮问褂肂eautifulSoup來解析網(wǎng)絡(luò)找尋文件。我們可以在https://dumps.wikimedia.org/enwiki/20180901/頁面里手工下載文件,但這就不夠效率了。網(wǎng)絡(luò)數(shù)據(jù)如此龐雜,懂得如何解析HTML和在程序中與網(wǎng)頁交互是非常有用的——學(xué)點(diǎn)網(wǎng)站檢索知識(shí),龐大的新數(shù)據(jù)源便觸手可及。
考慮好下載什么
上述代碼把dump里的所有文件都找出來了,你也就有了一些下載的選擇:文章當(dāng)前版本,文章頁以及當(dāng)前討論列表,或者是文章所有歷史修改版本和討論列表。如果你選擇最后一個(gè),那就是萬億字節(jié)的數(shù)據(jù)量了!本項(xiàng)目只選用文章最新版本。
所有文章的當(dāng)前版本能以單個(gè)文檔的形式獲得,但如果我們下載解析這個(gè)文檔,就得非常費(fèi)勁地一篇篇文章翻看,非常低效。更好的辦法是,下載多個(gè)分區(qū)文檔,每個(gè)文檔內(nèi)容是文章的一個(gè)章節(jié)。之后,我們可以通過并行化一次解析多個(gè)文檔,顯著提高效率。
“當(dāng)我處理文檔時(shí),我更喜歡多個(gè)小文檔而非一個(gè)大文檔,這樣我就可以并行化運(yùn)行多個(gè)文檔了?!?/p>
分區(qū)文檔格式為bz2壓縮的XML(可擴(kuò)展標(biāo)識(shí)語言),每個(gè)分區(qū)大小300~400MB,全部的壓縮包大小15.4GB。無需解壓,但如果你想解壓,大小約58GB。這個(gè)大小對(duì)于人類的全部知識(shí)來說似乎并不太大。
維基百科壓縮文件大小
下載文件
Keras 中的get_file語句在實(shí)際下載文件中非常好用。下面的代碼可通過鏈接下載文件并保存到磁盤中:
from keras.utils import get_filesaved_file_path = get_file(file, url)
下載的文件保存在~/.keras/datasets/,也是Keras默認(rèn)保存設(shè)置。一次性下載全部文件需2個(gè)多小時(shí)(你可以試試并行下載,但我試圖同時(shí)進(jìn)行多個(gè)下載任務(wù)時(shí)被限速了)
解析數(shù)據(jù)
我們首先得解壓文件。但實(shí)際我們發(fā)現(xiàn),想獲取全部文章數(shù)據(jù)根本不需要這樣。我們可以通過一次解壓運(yùn)行一行內(nèi)容來迭代文檔。當(dāng)內(nèi)存不夠運(yùn)行大容量數(shù)據(jù)時(shí),在文件間迭代通常是唯一選擇。我們可以使用bz2庫對(duì)bz2壓縮的文件迭代。
不過在測(cè)試過程中,我發(fā)現(xiàn)了一個(gè)更快捷(雙倍快捷)的方法,用的是system utility bzcat以及Python模塊的subprocess。以上揭示了一個(gè)重要的觀點(diǎn):解決問題往往有很多種辦法,而找到最有效辦法的唯一方式就是對(duì)我們的方案進(jìn)行基準(zhǔn)測(cè)試。這可以很簡單地通過%%timeit Jupyter cell magic來對(duì)方案計(jì)時(shí)評(píng)價(jià)。
迭代解壓文件的基本格式為:
data_path = '~/.keras/datasets/enwiki-20180901-pages-articles15.xml-p7744803p9244803.bz2# Iterate through compressed file one line at a timefor line in subprocess.Popen(['bzcat'], stdin = open(data_path), stdout = subprocess.PIPE).stdout: # process line
如果簡單地讀取XML數(shù)據(jù),并附為一個(gè)列表,我們得到看起來像這樣的東西:
維基百科文章的源XML
上面展示了一篇維基百科文章的XML文件。每個(gè)文件里面有成千上萬篇文章,因此我們下載的文件里包含百萬行這樣的語句。如果我們真想把事情弄復(fù)雜,我們可以用正則表達(dá)式和字符串匹配跑一遍文檔來找到每篇文章。這就極其低效了,我們可以采取一個(gè)更好的辦法:使用解析XML和維基百科式文章的定制化工具。
解析方法
我們需要在兩個(gè)層面上來解析文檔:
1、從XML中提取文章標(biāo)題和內(nèi)容
2、從文章內(nèi)容中提取相關(guān)信息
好在,Python對(duì)這兩個(gè)都有不錯(cuò)的應(yīng)對(duì)方法。
解析XML
解決第一個(gè)問題——定位文章,我們使用SAX(Simple API for XML) 語法解析器。BeautifulSoup語句也可以用來解析XML,但需要內(nèi)存載入整個(gè)文檔并且建立一個(gè)文檔對(duì)象模型(DOM)。而SAX一次只運(yùn)行XML里的一行字,完美符合我們的應(yīng)用場(chǎng)景。
基本思路就是我們對(duì)XML文檔進(jìn)行搜索,在特定標(biāo)簽間提取相關(guān)信息。例如,給出下面這段XML語句:
我們想篩出在
如果你不嘗試做一下,可能理解起來有點(diǎn)難度,但是Content handler的思想是尋找開始標(biāo)簽和結(jié)束標(biāo)簽之間的內(nèi)容,將找到的字符添加到緩存中。然后將緩存的內(nèi)容保存到字典中,其中相應(yīng)的標(biāo)簽作為對(duì)應(yīng)的鍵。最后我們得到一個(gè)鍵是標(biāo)簽,值是標(biāo)簽中的內(nèi)容的字典。下一步,我們會(huì)將這個(gè)字典傳遞給另一個(gè)函數(shù),它將解析字典中的內(nèi)容。
我們唯一需要編寫的SAX的部分是Content Handler。全文如下:
在這段代碼中,我們尋找標(biāo)簽為title和text的標(biāo)簽。每次解析器遇到其中一個(gè)時(shí),它會(huì)將字符保存到緩存中,直到遇到對(duì)應(yīng)的結(jié)束標(biāo)簽()。然后它會(huì)保存緩存內(nèi)容到字典中-- self._values。文章由
下面的代碼顯示了如何通過XML文件查找文章?,F(xiàn)在,我們只是將它們保存到handler._pages中,稍后我們將把文章發(fā)送到另一個(gè)函數(shù)中進(jìn)行解析。
# Object for handling xmlhandler = WikiXmlHandler()# Parsing objectparser = xml.sax.make_parser()parser.setContentHandler(handler)# Iteratively process filefor line in subprocess.Popen(['bzcat'], stdin = open(data_path), stdout = subprocess.PIPE).stdout: parser.feed(line) # Stop when 3 articles have been found if len(handler._pages) > 2: break
如果我們觀察handler._pages,我們將看到一個(gè)列表,其中每個(gè)元素都是一個(gè)包含一篇文章的標(biāo)題和內(nèi)容的元組:
handler._pages[0][('Carroll Knicely', "'''Carroll F. Knicely''' (born c. 1929 in [[Staunton, Virginia]] - died November 2, 2006 in [[Glasgow, Kentucky]]) was [[Editing|editor]] and [[Publishing|publisher]] ...)]
此時(shí),我們已經(jīng)編寫的代碼可以成功地識(shí)別XML中的文章?,F(xiàn)在我們完成了解析文件一半的任務(wù),下一步是處理文章以查找特定頁面和信息。再次,我們使用專為這項(xiàng)工作而創(chuàng)建的一個(gè)工具。
解析維基百科文章
維基百科運(yùn)行在一個(gè)叫做MediaWiki的軟件上,該軟件用來構(gòu)建wiki。這使文章遵循一種標(biāo)準(zhǔn)格式,這種格式可以輕易地用編程方式訪問其中的信息。雖然一篇文章的文本看起來可能只是一個(gè)字符串,但由于格式的原因,它實(shí)際上編碼了更多的信息。為了有效地獲取這些信息,我們引進(jìn)了強(qiáng)大的 mwparserfromhell, 一個(gè)為處理MediaWiki內(nèi)容而構(gòu)建的庫。
如果我們將維基百科文章的文本傳遞給mwparserfromhell,我們會(huì)得到一個(gè)Wikicode對(duì)象,它含有許多對(duì)數(shù)據(jù)進(jìn)行排序的方法。例如,以下代碼從文章創(chuàng)建了一個(gè)wikicode對(duì)象,并檢索文章中的wikilinks()。這些鏈接指向維基百科的其他文章:
import mwparserfromhell# Create the wiki articlewiki = mwparserfromhell.parse(handler._pages[6][1])# Find the wikilinkswikilinks = [x.title for x in wiki.filter_wikilinks()]wikilinks[:5]['Provo, Utah', 'Wasatch Front', 'Megahertz', 'Contemporary hit radio', 'watt']
有許多有用的方法可以應(yīng)用于wikicode,例如查找注釋或搜索特定的關(guān)鍵字。如果您想獲得文章文本的最終修訂版本,可以調(diào)用:
wiki.strip_code().strip()'KENZ (94.9 FM, " Power 94.9 " ) is a top 40/CHR radio station broadcasting to Salt Lake City, Utah '
因?yàn)槲业淖罱K目標(biāo)是找到所有關(guān)于書籍的文章,那么是否有一種方法可以使用解析器來識(shí)別某個(gè)類別中的文章呢?幸運(yùn)的是,答案是肯定的——使用MediaWiki templates。
文章模板
模板(templates)是記錄信息的標(biāo)準(zhǔn)方法。維基百科上有無數(shù)的模板,但與我們的目的最相關(guān)的是信息框(Infoboxes)。有些模板編碼文章的摘要信息。例如,戰(zhàn)爭與和平的信息框是:
維基百科上的每一類文章,如電影、書籍或廣播電臺(tái),都有自己的信息框。在書籍的例子中,信息框模板被命名為Infobox book。同樣,wiki對(duì)象有一個(gè)名為filter_templates()的方法,它允許我們從一篇文章中提取特定的模板。因此,如果我們想知道一篇文章是否是關(guān)于一本書的,我們可以通過book信息框去過濾。展示如下:
# Filter article for book templatewiki.filter_templates('Infobox book')
如果匹配成功,那我們就找到一本書了!要查找你感興趣的文章類別的信息框模板,請(qǐng)參閱信息框列表。
如何將用于解析文章的mwparserfromhell與我們編寫的SAX解析器結(jié)合起來?我們修改了Content Handler中的endElement方法,將包含文章標(biāo)題和文本的值的字典,發(fā)送到通過指定模板搜索文章文本的函數(shù)中。如果函數(shù)找到了我們想要的文章,它會(huì)從文章中提取信息,然后返回給handler。首先,我將展示更新后的endElement 。
def endElement(self, name): """Closing tag of element""" if name == self._current_tag: self._values[name] = ' '.join(self._buffer) if name == 'page': self._article_count += 1 # Send the page to the process article function book = process_article(**self._values, template = 'Infobox book') # If article is a book append to the list of books if book: self._books.append(book)
一旦解析器到達(dá)文章的末尾,我們將文章傳遞到函數(shù)process_article,如下所示:
def process_article(title, text, timestamp, template = 'Infobox book'): """Process a wikipedia article looking for template""" # Create a parsing object wikicode = mwparserfromhell.parse(text) # Search through templates for the template matches = wikicode.filter_templates(matches = template) if len(matches) >= 1: # Extract information from infobox properties = {param.name.strip_code().strip(): param.value.strip_code().strip() for param in matches[0].params if param.value.strip_code().strip()} # Extract internal wikilinks
雖然我正在尋找有關(guān)書籍的文章,但是這個(gè)函數(shù)可以用來搜索維基百科上任何類別的文章。只需將模板替換為指定類別的模板(例如Infobox language是用來尋找語言的),它只會(huì)返回符合條件的文章信息。
我們可以在一個(gè)文件上測(cè)試這個(gè)函數(shù)和新的ContentHandler。
Searched through 427481 articles.Found 1426 books in 1055 seconds.
讓我們看一下查找一本書的結(jié)果:
books[10]['War and Peace', {'name': 'War and Peace', 'author': 'Leo Tolstoy', 'language': 'Russian, with some French', 'country': 'Russia', 'genre': 'Novel (Historical novel)', 'publisher': 'The Russian Messenger (serial)', 'title_orig': 'Война и миръ', 'orig_lang_code': 'ru', 'translator': 'The first translation of War and Peace into English was by American Nathan Haskell Dole, in 1899', 'image': 'Tolstoy - War and Peace - first edition, 1869.jpg', 'caption': 'Front page of War and Peace, first edition, 1869 (Russian)', 'release_date': 'Serialised 1865–1867; book 1869', 'media_type': 'Print', 'pages': '1,225 (first published edition)'}, ['Leo Tolstoy', 'Novel', 'Historical novel', 'The Russian Messenger', 'Serial (publishing)', 'Category:1869 Russian novels', 'Category:Epic novels', 'Category:Novels set in 19th-century Russia', 'Category:Russian novels adapted into films', 'Category:Russian philosophical novels'], ['https://books.google.com/?id=c4HEAN-ti1MC', 'https://www.britannica.com/art/English-literature', 'https://books.google.com/books?id=xf7umXHGDPcC', 'https://books.google.com/?id=E5fotqsglPEC', 'https://books.google.com/?id=9sHebfZIXFAC'], '2018-08-29T02:37:35Z']
對(duì)于維基百科上的每一本書,我們把信息框中的信息整理為字典、書籍在維基百科中的wikilinks信息、書籍的外部鏈接和最新編輯的時(shí)間戳。(我把精力集中在這些信息上,為我的下一個(gè)項(xiàng)目建立一個(gè)圖書推薦系統(tǒng))。你可以修改process_article函數(shù)和WikiXmlHandler類,以查找任何你需要的信息和文章!
如果你看一下只處理一個(gè)文件的時(shí)間,1055秒,然后乘以55,你會(huì)發(fā)現(xiàn)處理所有文件的時(shí)間超過了15個(gè)小時(shí)!當(dāng)然,我們可以在一夜之間運(yùn)行,但如果可以的話,我不想浪費(fèi)額外的時(shí)間。這就引出了我們將在本項(xiàng)目中介紹的最后一種技術(shù):使用多處理和多線程進(jìn)行并行化。
并行操作
與其一次一個(gè)解析文件,不如同時(shí)處理其中的幾個(gè)(這就是我們下載分區(qū)的原因)。我們可以使用并行化,通過多線程或多處理來實(shí)現(xiàn)。
多線程與多處理
多線程和多處理是同時(shí)在計(jì)算機(jī)或多臺(tái)計(jì)算機(jī)上執(zhí)行許多任務(wù)的方法。我們磁盤上有許多文件,每個(gè)文件都需要以相同的方式進(jìn)行解析。一個(gè)簡單的方法是一次解析一個(gè)文件,但這并沒有充分利用我們的資源。因此,我們可以使用多線程或多處理同時(shí)解析多個(gè)文件,這將大大加快整個(gè)過程。
通常,多線程對(duì)于輸入/輸出綁定任務(wù)(例如讀取文件或發(fā)出請(qǐng)求)更好(更快)。多處理對(duì)于cpu密集型任務(wù)更好(更快)。對(duì)于解析文章的過程,我不確定哪種方法是最優(yōu)的,因此我再次用不同的參數(shù)對(duì)這兩種方法進(jìn)行了基準(zhǔn)測(cè)試。
學(xué)習(xí)如何進(jìn)行測(cè)試和尋找不同的方法來解決一個(gè)問題,你將會(huì)在數(shù)據(jù)科學(xué)或任何技術(shù)的職業(yè)生涯中走得更遠(yuǎn)。
相關(guān)報(bào)道:
https://towardsdatascience.com/wikipedia-data-science-working-with-the-worlds-largest-encyclopedia-c08efbac5f5c
【今日機(jī)器學(xué)習(xí)概念】
Have a Great Definition
-
編程
+關(guān)注
關(guān)注
88文章
3565瀏覽量
93536 -
python
+關(guān)注
關(guān)注
55文章
4767瀏覽量
84375
原文標(biāo)題:維基百科中的數(shù)據(jù)科學(xué):手把手教你用Python讀懂全球最大百科全書
文章出處:【微信號(hào):CAAI-1981,微信公眾號(hào):中國人工智能學(xué)會(huì)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論