標籤:

如何從任意HTML頁面里提取正文?(Python3)

記得知乎上有人把這個當做練習題發出來過,正好自己也進行過這方面的嘗試,在這裡把自己的思路寫下來,拋磚引玉。希望大家一起討論。

提取正文這件事可以很簡單,也可以很複雜,跟你對它的要求直接有關,要不要提取其中的圖片?要不要保留格式?這個程序是只針對一個網站還是要針對大部分乃至所有你想提取正文的網站?如果你只想開發針對一個網站的程序,那其實不管你對正文內容的要求有多高相對也是比較容易的,BeautifulSoup、css選擇器、xpath……只要分析一下你的目標網站,按照它的格式規律把各個部分提取出來即可,我們接下來用BeautifulSoup舉例,雖然BeautifulSoup的速度很慢,但就我目前的經驗來看,它也目前我接觸到的解析器里相對性能最強的。

比如你想提取新浪的正文,首先當然我們要提取整個頁面,比如我們要提取這個頁面:

簡氏稱中國軍艦繼續下餃子:3天內兩艘下水1艘服役

import requestsnfrom bs4 import BeautifulSoupnurl = http://mil.news.sina.com.cn/china/2017-04-05/doc-ifycwymx3854291.shtmlnpage = requests.get(url)npage.encoding = utf-8n#編碼……恐怕是Python里最讓人頭疼的問題了nsoup = BeautifulSoup(str(page.text), html.parser)n

我們用Chrome自帶的開發者工具(網頁上右鍵,檢查)分析一下這個網頁,輕而易舉的發現,這些內容都在一個class是content,id是artibody的元素內,接下來就很簡單了,我們通過選擇id也好,選擇class也好,不過一句話的事。n

article = soup.select(.content)[0].textn#或者narticle = soup.select(#artibody)[0].textn#Select返回一個list,[0]選中其中第一個元素(其實在這個網頁里,這個list只會有一個元素)n

就可以得到正文,就是這麼簡單。

我們來數一下代碼,1,2,3……一共就用了5行,我要是再喪心病狂點,還可以更少。

但是這種方法可以說,不出意外的話,基本只能適用於新浪這一個網站,誰知道你正文用的是content還是article呢,我們看看網易。

果然,class name變成了post_text,id則是endText,當然,其實內容值得看的網站也就那麼幾個,就算你針對它們一個個的去分別設立不同的規則也不虧啊,畢竟就算設10個網站,也才5x10 = 50行而已。噗,其實也沒有那麼簡單拉,但是總的來說,這種方法是可以考慮的。

但是當我在寫這個程序的時候,我也不知道就哪來一股軸勁,一定想寫出來一種能在所有有文章的頁面通用的正文提取規則,還要把圖片也都按順序弄下來。事實上我當時自己用WordPress在自己的台式機做的伺服器上搭了一個網站,我的目標就是從網站上把需要的文章弄下來,然後貼到自己的網站上(當然不是為了盈利,也在文章開頭著名了原文鏈接)。

於是我就開始想辦法了,就像我之前寫的那樣,我最開始考慮的也是提取正文文字的規則,因為正文那一塊看起來最好找嘛。

雖然沒法從元素的類別或名字等信息來識別出哪個元素里是文章的正文了,但是看看下面這段正文附近的源代碼,我們還是不難發現一個很顯著的特徵:在正文部分,html代碼的密度瞬間低到了極點,形成這樣現象的原因有多個,但其實這樣的排版方式並不是必須的,理論上說,只要一個網站想,它完全可以讓正文部分的html代碼的密度幾乎不低於其他部分,而在前台的顯示效果並不會受到影響。但很幸運的是,我至今還沒有碰到過刻意這樣做的網站,或許也是因為這樣做的成本太過高於所得的收益了吧。

總之,我們發現,我們可以通過判斷元素內容內中文文字的佔比來判斷這個元素內是不是正文。思路有了,我們擼起袖子就開干。

首先,我們知道,html使用div來將網頁區分成不同的區域。所以,除非網站非常非常的特立獨行,用了一種無比詭異的實現方法實現了網頁的顯示(估計也是成本遠大於收益的事,所以我目前還沒見過有人去做。),一般來說文章肯定是放在某個div里,對於一個程序來說,剛拿到一個網頁,不可能馬上就知道哪個是含有正文的那個div,所以我們選中所有div元素。

part = soup.select(div)n

這裡有一個很不幸的事情,一般來說只要IDE有限制控制台內顯示的代碼行數,你在執行這句指令之後得到的part的內容一定會超過這個行數,因為幾乎所有的網頁都會有div嵌套,在最終的網頁上看來,只有最大的那個div會佔用實實在在的空間,而裡面的div佔用的空間都是在其之內的,但這條指令會選中所有的div,也就是說,它不僅會選中包含了所有小div的最大div塊,還會將每個較小的div都分別選中,並把其中的內容也記錄下來。如果你把這些東西都寫入文件里仔細比較,你會發現part的大小經常一下就膨脹到了soup的兩到三倍。

不過對Python來說其實這都沒差啦,接下來我們只要寫一個循環,判斷裡面每個div的中文佔比,剔除掉那些中文文字數佔比很小的div就行啦(話說寫到這裡我才突然意識到我這個程序對英文網頁無效,改天再研究一下怎麼辦)

統計中文我們可以用正則表達式,通過統計其中與中文Unicode範圍相匹配的字元數,理論上匹配成功的次數就是中文的字數,實踐上雖然這兩個結果有一定差異,但是差異很小,基本不會影響判斷,因為正文部分與其他元素中文佔比的差距實在是太懸殊了,所以就算統計有一點誤差,我們也有很大的定標準的空間。給一個參考的統計中文字數,並計算其在整個div字元中佔比的函數:

def countchn(string):n pattern = re.compile(u[?-?h]+?)n result = pattern.findall(string)n chnnum = len(result) #list的長度即是中文的字數n possible = chnnum/len(str(string)) #possible = 中文字數/總字數n return (chnnum, possible)n

然後是遍歷,當可能性大於一定程度,比如0.7,我們就決定是它了:

for paragraph in part:n chnstatus = countchn(str(paragraph))n possible = chnstatus[1]n if possible > 0.7:n article = str(paragraph)n

但是!聰明的小朋友一定已經發現了,這段程序其實很脆弱。首先div既然是可以嵌套的,裝著文章的最小div外面很可能還嵌套著別的div,是有可能同時出現好幾個possible > 0.7的情況的,如果最後一個出現的不正好就是真正的正文div,最後解析出的結果就有可能帶有其他無關的內容。

那我們應該再提高一點這個准入的門檻嗎?似乎也不行,因為有些文章,尤其是圖多的文章,由於圖片的url會跟文章正文內容一起寫在正文div里,極大的降低了div內中文字元的佔比。要是possible的標準定的太高了,很多這類的文章會被誤判的。

所以綜合各種情況之後,我想了一種辦法,這不一定是最優解,但經過我爬取各大門戶網站頁面至今的結果,這個方法的效果至少還是比較靠譜的,我在這裡寫在下面,也歡迎各路大神來打我的臉。

思路就是把possible標準調低一點,然後建立一個list,把所有符合標準的div都裝進去。在裡面再比較一次,統計每個div的字數,通過設立一個最低的字數門檻,把由於標準降低可能意外篩選進來的一些元素(比如列表項很多很密集的div)一個一個從list里刪掉,一般來說,剩下的就是嵌套的幾個包含著正文的div了,這時只要選其中最短的一個就好了。或許從一開始就採用最低字數、概率、長短綜合考慮的比較方式會比較方便一點,但是我想我這種方法應該能快一些,畢竟並不總是需要比較三個參數。代碼如下:

def findtext(part): n length = 50000000n l = []n for paragraph in part:n chnstatus = countchn(str(paragraph))n possible = chnstatus[1]n if possible > 0.15: n l.append(paragraph)n l_t = l[:]n #這裡需要複製一下表,在新表中再次篩選,要不然會出問題,跟Python的內存機制有關n for elements in l_t:n chnstatus = countchn(str(elements))n chnnum2 = chnstatus[0]n if chnnum2 < 300: n #最終測試結果表明300字是一個比較靠譜的標準,低於300字的正文咱也不想要了對不n l.remove(elements)n elif len(str(elements))<length:n length = len(str(elements))n paragraph_f = elementsn

不出意外的話,最終得出的paragraph_f就會是我們想要的包含html代碼的正文div了,接下來只要像前面那樣,.text一下,就能完成正文的提取了~

我也不知道為什麼,在眾多語言中,只有Python讓我感覺看著很舒服,所以這也算是我第一個好好學習了的語言吧。改天有時間再寫寫我是怎麼從原文中提取圖片,並按原順序發到我的網站上的吧,今天就寫到這了。大家再見ヾ( ̄▽ ̄)/


推薦閱讀:

寫爬蟲很簡單但也很難(附某美女站爬蟲源碼)
從零開始寫Python爬蟲 --- 爬蟲應用:一號店 商品信息查詢程序
我這樣破解pexels獲取的高清原圖
電影《戰狼2》的可視化分析

TAG:Python | 爬虫 | HTML |