作者:李曉飛
資料來源:python技術(shù)
爬蟲程序大家應(yīng)該都很熟悉。隨便寫什么都可以獲得網(wǎng)頁上的信息,甚至可以通過請求自動生成python腳本[1]。
最近我遇到一個爬蟲項目,需要爬取網(wǎng)上的文章。感覺沒有什么特別的,但問題是沒有限定爬取范圍,意味著沒有明確的頁面的結(jié)構(gòu)。
對于一個頁面來說,除了核心文章內(nèi)容外,還有頭部,尾部,左右列表欄等等。有的頁面框架用 div 布局,有的用 table,即使都用 div,不太的網(wǎng)站風(fēng)格和布局也不同。
但問題必須解決,我想,既然搜索引擎抓取到各種網(wǎng)頁的核心內(nèi)容,我們也應(yīng)該可以搞定,拎起 Python, 說干就干!
各種嘗試
如何解決呢?
生成PDF
開始想了一個取巧的方法,就是利用工具(wkhtmltopdf[2])將目標(biāo)網(wǎng)頁生成 PDF 文件。
好處是不必關(guān)心頁面的具體形式,就像給頁面拍了一張照片,文章結(jié)構(gòu)是完整的。
雖然 PDF 是可以源碼級檢索,但是,生成 PDF 有諸多缺點:
耗費(fèi)計算資源多、效率低、出錯率高,體積太大。
幾萬條數(shù)據(jù)已經(jīng)兩百多G,如果數(shù)據(jù)量上來光存儲就是很大的問題。
提取文章內(nèi)容
不生成PDF,有簡單辦法就是通過 xpath[3] 提取頁面上的所有文字。
但是內(nèi)容將失去結(jié)構(gòu),可讀性差。更要命的是,網(wǎng)頁上有很多無關(guān)內(nèi)容,比如側(cè)邊欄,廣告,相關(guān)鏈接等,也會被提取下來,影響內(nèi)容的精確性。
為了保證有一定的結(jié)構(gòu),還要識別到核心內(nèi)容,就只能識別并提取文章部分的結(jié)構(gòu)了。像搜索引擎學(xué)習(xí),就是想辦法識別頁面的核心內(nèi)容。
我們知道,通常情況下,頁面上的核心內(nèi)容(如文章部分)文字比較集中,可以從這個地方著手分析。
于是編寫了一段代碼,我是用 Scrapy[4] 作為爬蟲框架的,這里只截取了其中提取文章部分的代碼 :
divs = re("body//div")
sel = None
maxvalue = 0
for d in divs:
ds = len(".//div"))
ps = len(".//p"))
value = ps - ds
if value > maxvalue:
sel = {
"node": d,
"value": value
}
maxvalue = value
print("".join(sel['node'].getall()))
- response 是頁面的一個響應(yīng),其中包含了頁面的所有內(nèi)容,可以通過 xpath 提取想要的部分
- "body//div" 的意思是提取所以 body 標(biāo)簽下的 div 子標(biāo)簽,注意:// 操作是遞歸的
- 遍歷所有提取到的標(biāo)簽,計算其中包含的 div 數(shù)量,和 p 數(shù)量
- p 數(shù)量 和 div 數(shù)量的差值作為這個元素的權(quán)值,意思是如果這個元素里包含了大量的 p 時,就認(rèn)為這里是文章主體
- 通過比較權(quán)值,選擇出權(quán)值最大的元素,這便是文章主體
- 得到文章主體之后,提取這個元素的內(nèi)容,相當(dāng)于 jQuery[5] 的 outerHtml
簡單明了,測試了幾個頁面確實挺好。
不過大量提取時發(fā)現(xiàn),很多頁面提取不到數(shù)據(jù)。仔細(xì)查看發(fā)現(xiàn),有兩種情況。
- 有的文章內(nèi)容被放在了 <article> 標(biāo)簽里了,所以沒有獲取到
- 有的文章每個 <p> 外面都包裹了一個 <div>,所以 p 的數(shù)量 和 div 的抵消了
再調(diào)整了一下策略,不再區(qū)分 div,查看所有的元素。
另外優(yōu)先選擇更多的 p,在其基礎(chǔ)上再看更少的 div。調(diào)整后的代碼如下:
divs = re("body//*")
sels = []
maxvalue = 0
for d in divs:
ds = len(".//div"))
ps = len(".//p"))
if ps >= maxvalue:
sel = {
"node": d,
"ps": ps,
"ds": ds
}
maxvalue = ps
(sel)
(lambda x: x.ds)
sel = sels[0]
print("".join(sel['node'].getall()))
- 方法主體里,先挑選出 p 數(shù)量比較大的節(jié)點,注意 if 判斷條件中 換成了 >= 號,作用時篩選出同樣具有 p 數(shù)量的結(jié)點
- 經(jīng)過篩選之后,按照 div 數(shù)量排序,然后選取 div 數(shù)量最少的
經(jīng)過這樣修改之后,確實在一定程度上彌補(bǔ)了前面的問題,但是引入了一個更麻煩的問題。
就是找到的文章主體不穩(wěn)定,特別容易受到其他部分有些 p 的影響。
選擇最優(yōu)
既然直接計算不太合適,需要重新設(shè)計一個算法。
我發(fā)現(xiàn),文字集中的地方是往往是文章主體,而前面的方法中,沒有考慮到這一點,只是機(jī)械地找出了最大的 p。
還有一點,網(wǎng)頁結(jié)構(gòu)是個顆 DOM 樹[6]
那么越靠近 p 標(biāo)簽的地方應(yīng)該越可能是文章主體,也就是說,計算是越靠近 p 的節(jié)點權(quán)值應(yīng)該越大,而遠(yuǎn)離 p 的結(jié)點及時擁有很多 p 但是權(quán)值也應(yīng)該小一點。
經(jīng)過試錯,最終代碼如下:
def find(node, sel):
value = 0
for n in node.xpath("*"):
if n.xpath("local-name()").get() == "p":
t = "".join([s.strip() for s in ('text()').getall() + n.xpath("*/text()").getall())])
value += len(t)
else:
value += find(n, a)*0.5
if value > sel["value"]:
sel["node"] = node
sel["value"] = value
return value
sel = {
'value': 0,
'node': None
}
find(re("body"), sel)
- 定義了一個 find 函數(shù),這是為了方便做遞歸,第一次調(diào)用的參數(shù)是 body 標(biāo)簽,和前面一樣
- 進(jìn)入方法里,只找出該節(jié)點的直接孩子們,然后遍歷這些孩子
- 判斷如果孩子是 p 節(jié)點,提取出其中的所有文字,包括子節(jié)點的,然后將文字的長度作為權(quán)值
- 提取文字的地方比較繞,先取出直接的文本,和間接文本,合成 list,對每部分文本做了去除前后空字符,最后合并為一個字符串,得到了所包含的文本
- 如果孩子節(jié)點不是 p,就遞歸調(diào)用 find 方法,而 find 方法返回的是 指定節(jié)點所包含的文本長度
- 在獲取子節(jié)點的長度時,做了縮減處理,用以體現(xiàn)距離越遠(yuǎn),權(quán)值越低的規(guī)則
- 最終通過 引用傳遞的 sel 參數(shù),記錄權(quán)值最高的節(jié)點
通過這樣改造之后,效果特別好。
為什么呢?其實利用了密度原理,就是說越靠近中心的地方,密度越高,遠(yuǎn)離中心的地方密度成倍的降低,這樣就能篩選出密度中心了。
50% 的坡度比率是如何得到的呢?
其實是通過實驗確定的,剛開始時我設(shè)置為 90%,但結(jié)果時 body 節(jié)點總是最優(yōu)的,因為 body 里包含了所有的文字內(nèi)容。
反復(fù)實驗后,確定 50% 是比較好的值,如果在你的應(yīng)用中不合適,可以做調(diào)整。
總結(jié)
描述了我如何選取文章主體的方法后,后沒有發(fā)現(xiàn)其實很是很簡單的方法。而這次解決問題的經(jīng)歷,讓我感受到了數(shù)學(xué)的魅力。
一直以來我認(rèn)為只要了解常規(guī)處理問題的方式就足以應(yīng)對日常編程了,可以當(dāng)遇到不確定性問題,沒有辦法抽取出簡單模型的問題時,常規(guī)思維顯然不行。
所以平時我們應(yīng)該多看一些數(shù)學(xué)性強(qiáng)的,解決不確定性問題的方法,以便提高我們的編程適應(yīng)能力,擴(kuò)展我們的技能范圍。
1.《關(guān)于網(wǎng)頁下載怎么提取鏈接,你需要知道這些提取任意網(wǎng)頁核心內(nèi)容——像搜索引擎一樣精準(zhǔn)》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識,僅代表作者本人觀點,與本網(wǎng)站無關(guān),侵刪請聯(lián)系頁腳下方聯(lián)系方式。
2.《關(guān)于網(wǎng)頁下載怎么提取鏈接,你需要知道這些提取任意網(wǎng)頁核心內(nèi)容——像搜索引擎一樣精準(zhǔn)》僅供讀者參考,本網(wǎng)站未對該內(nèi)容進(jìn)行證實,對其原創(chuàng)性、真實性、完整性、及時性不作任何保證。
3.文章轉(zhuǎn)載時請保留本站內(nèi)容來源地址,http://f99ss.com/gl/3053006.html