用python3教你任意Html主內(nèi)容提取功能
本文將和大家分享一些從互聯(lián)網(wǎng)上爬取語料的經(jīng)驗(yàn)。
0x1 工具準(zhǔn)備
工欲善其事必先利其器,爬取語料的根基便是基于python。
我們基于python3進(jìn)行開發(fā),主要使用以下幾個(gè)模塊:requests、lxml、json。
簡單介紹一個(gè)各模塊的功能
01|requests
requests是一個(gè)Python第三方庫,處理URL資源特別方便。它的官方文檔上寫著大大口號(hào):HTTP for Humans(為人類使用HTTP而生)。相比python自帶的urllib使用體驗(yàn),筆者認(rèn)為requests的使用體驗(yàn)比urllib高了一個(gè)數(shù)量級(jí)。
我們簡單的比較一下:
urllib:
import urllib2
import urllib
URL_GET = "https://api.douban.com/v2/event/list"
#構(gòu)建請求參數(shù)
params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'})
#發(fā)送請求
response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params)
#Response Headers
print(response.info())
#Response Code
print(response.getcode())
#Response Body
print(response.read())
requests:
import requests
URL_GET = "https://api.douban.com/v2/event/list"
#構(gòu)建請求參數(shù)
params = {'loc':'108288','day_type':'weekend','type':'exhibition'}
#發(fā)送請求
response = requests.get(URL_GET,params=params)
#Response Headers
print(response.headers)
#Response Code
print(response.status_code)
#Response Body
print(response.text)
我們可以發(fā)現(xiàn),這兩種庫還是有一些區(qū)別的:
1. 參數(shù)的構(gòu)建:urllib需要對參數(shù)進(jìn)行urlencode編碼處理,比較麻煩;requests無需額外編碼處理,十分簡潔。
2. 請求發(fā)送:urllib需要額外對url參數(shù)進(jìn)行構(gòu)造,變?yōu)榉弦蟮男问?;requests則簡明很多,直接get對應(yīng)鏈接與參數(shù)。
3. 連接方式:看一下返回?cái)?shù)據(jù)的頭信息的“connection”,使用urllib庫時(shí),"connection":"close",說明每次請求結(jié)束關(guān)掉socket通道,而使用requests庫使用了urllib3,多次請求重復(fù)使用一個(gè)socket,"connection":"keep-alive",說明多次請求使用一個(gè)連接,消耗更少的資源
4. 編碼方式:requests庫的編碼方式Accept-Encoding更全,在此不做舉例
綜上所訴,使用requests更為簡明、易懂,極大的方便我們開發(fā)。
02|lxml
BeautifulSoup是一個(gè)庫,而XPath是一種技術(shù),python中最常用的XPath庫是lxml。
當(dāng)我們拿到requests返回的頁面后,我們怎么拿到想要的數(shù)據(jù)呢?這個(gè)時(shí)候祭出lxml這強(qiáng)大的HTML/XML解析工具。python從不缺解析庫,那么我們?yōu)槭裁匆诒姸鄮炖镞x擇lxml呢?我們選擇另一款出名的HTML解析庫BeautifulSoup來進(jìn)行對比。
我們簡單的比較一下:
BeautifulSoup:
from bs4 import BeautifulSoup #導(dǎo)入庫
# 假設(shè)html是需要被解析的html
#將html傳入BeautifulSoup 的構(gòu)造方法,得到一個(gè)文檔的對象
soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8')
#查找所有的h4標(biāo)簽
links = soup.find_all("h4")
lxml:
from lxml import etree
# 假設(shè)html是需要被解析的html
#將html傳入etree 的構(gòu)造方法,得到一個(gè)文檔的對象
root = etree.HTML(html)
#查找所有的h4標(biāo)簽
links = root.xpath("http://h4")
我們可以發(fā)現(xiàn),這兩種庫還是有一些區(qū)別的:
1. 解析html: BeautifulSoup的解析方式和JQ的寫法類似,API非常人性化,支持css選擇器;lxml的語法有一定的學(xué)習(xí)成本
2. 性能:BeautifulSoup是基于DOM的,會(huì)載入整個(gè)文檔,解析整個(gè)DOM樹,因此時(shí)間和內(nèi)存開銷都會(huì)大很多;而lxml只會(huì)局部遍歷,另外lxml是用c寫的,而BeautifulSoup是用python寫的,明顯的性能上lxml>>BeautifulSoup。
綜上所訴,使用BeautifulSoup更為簡明、易用,lxml雖然有一定學(xué)習(xí)成本,但總體也很簡明易懂,最重要的是它基于C編寫,速度快很多,對于筆者這種強(qiáng)迫癥,自然而然就選lxml啦。
03|json
python自帶json庫,對于基礎(chǔ)的json的處理,自帶庫完全足夠。但是如果你想更偷懶,可以使用第三方j(luò)son庫,常見的有demjson、simplejson。
這兩種庫,無論是import模塊速度,還是編碼、解碼速度,都是simplejson更勝一籌,再加上兼容性 simplejson 更好。所以大家如果想使用方庫,可以使用simplejson。
0x2 確定語料源
將武器準(zhǔn)備好之后,接下來就需要確定爬取方向。
以電競類語料為例,現(xiàn)在我們要爬電競類相關(guān)語料。大家熟悉的電競平臺(tái)有企鵝電競、企鵝電競和企鵝電競(斜眼),所以我們以企鵝電競上直播的游戲作為數(shù)據(jù)源進(jìn)行爬取。
我們登陸企鵝電競官網(wǎng),進(jìn)入游戲列表頁,可以發(fā)現(xiàn)頁面上有很多游戲,通過人工去寫這些游戲名收益明顯不高,于是我們就開始我們爬蟲的第一步:游戲列表爬取。

import requests
from lxml import etree
# 更新游戲列表
def _updateGameList():
# 發(fā)送HTTP請求時(shí)的HEAD信息,用于偽裝為瀏覽器
heads = {
'Connection': 'Keep-Alive',
'Accept': 'text/html, application/xhtml+xml, */*',
'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
'Accept-Encoding': 'gzip, deflate',
'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko'
}
# 需要爬取的游戲列表頁
url = 'https://egame.qq.com/gamelist'
# 不壓縮html,最大鏈接時(shí)間為10妙
res = requests.get(url, headers=heads, verify=False, timeout=10)
# 為防止出錯(cuò),編碼utf-8
res.encoding = 'utf-8'
# 將html構(gòu)建為Xpath模式
root = etree.HTML(res.content)
# 使用Xpath語法,獲取游戲名
gameList = root.xpath("http://ul[@class='livelist-mod']//li//p//text()")
# 輸出爬到的游戲名
print(gameList)
當(dāng)我們拿到這幾十個(gè)游戲名后,下一步就是對這幾十款游戲進(jìn)行語料爬取,這時(shí)候問題就來了,我們要從哪個(gè)網(wǎng)站來爬這幾十個(gè)游戲的攻略呢,taptap?多玩?17173?在對這幾個(gè)網(wǎng)站進(jìn)行分析后,發(fā)現(xiàn)這些網(wǎng)站僅有一些熱門游戲的文章語料,一些冷門或者低熱度的游戲,例如“靈魂籌碼”、“奇跡:覺醒”、“死神來了”等,很難在這些網(wǎng)站上找到大量文章語料,如圖所示:

我們可以發(fā)現(xiàn),“ 奇跡:覺醒”、“靈魂籌碼”的文章語料特別少,數(shù)量上不符合我們的要求。 那么有沒有一個(gè)比較通用的資源站,它擁有著無比豐富的文章語料,可以滿足我們的需求。
其實(shí)靜下心來想想,這個(gè)資源站我們天天都有用到,那就是百度。我們在百度新聞搜索相關(guān)游戲,拿到搜索結(jié)果列表,這些列表的鏈接的網(wǎng)頁內(nèi)容幾乎都與搜索結(jié)果強(qiáng)相關(guān),這樣我們數(shù)據(jù)源不夠豐富的問題便輕松解決了。但是此時(shí)出現(xiàn)了一個(gè)新的問題,并且是一個(gè)比較難解決的問題——如何抓取到任意網(wǎng)頁的文章內(nèi)容?
因?yàn)椴煌木W(wǎng)站都有不同的頁面結(jié)構(gòu),我們無法與預(yù)知將會(huì)爬到哪個(gè)網(wǎng)站的數(shù)據(jù),并且我們也不可能針對每一個(gè)網(wǎng)站都去寫一套爬蟲,那樣的工作量簡直難以想象!但是我們也不能簡單粗暴的將頁面中的所有文字都爬下來,用那樣的語料來進(jìn)行訓(xùn)練無疑是噩夢!
經(jīng)過與各個(gè)網(wǎng)站斗智斗勇、查詢資料與思索之后,終于找到一條比較通用的方案,下面為大家講一講筆者的思路。
0x3 任意網(wǎng)站的文章語料爬取
01|提取方法
1)基于Dom樹正文提取
2)基于網(wǎng)頁分割找正文塊
3)基于標(biāo)記窗的正文提取
4)基于數(shù)據(jù)挖掘或機(jī)器學(xué)習(xí)
5)基于行塊分布函數(shù)正文提取
02|提取原理
大家看到這幾種是不是都有點(diǎn)疑惑了,它們到底是怎么提取的呢?讓筆者慢慢道來。
1)基于Dom樹的正文提取:
這一種方法主要是通過比較規(guī)范的HTML建立Dom樹,然后地柜遍歷Dom,比較并識(shí)別各種非正文信息,包括廣告、鏈接和非重要節(jié)點(diǎn)信息,將非正文信息抽離之后,余下來的自然就是正文信息。
但是這種方法有兩個(gè)問題
① 特別依賴于HTML的良好結(jié)構(gòu),如果我們爬取到一個(gè)不按W3c規(guī)范的編寫的網(wǎng)頁時(shí),這種方法便不是很適用。
② 樹的建立和遍歷時(shí)間復(fù)雜度、空間復(fù)雜度都較高,樹的遍歷方法也因HTML標(biāo)簽會(huì)有不同的差異。
2) 基于網(wǎng)頁分割找正文塊 :
這一種方法是利用HTML標(biāo)簽中的分割線以及一些視覺信息(如文字顏色、字體大小、文字信息等)。
這種方法存在一個(gè)問題:
① 不同的網(wǎng)站HTML風(fēng)格迥異,分割沒有辦法統(tǒng)一,無法保證通用性。
3) 基于標(biāo)記窗的正文提取:
先科普一個(gè)概念——標(biāo)記窗,我們將兩個(gè)標(biāo)簽以及其內(nèi)部包含的文本合在一起成為一個(gè)標(biāo)記窗(比如 <h1>我是h1</h1> 中的“我是h1”就是標(biāo)記窗內(nèi)容),取出標(biāo)記窗的文字。
這種方法先取文章標(biāo)題、HTML中所有的標(biāo)記窗,在對其進(jìn)行分詞。然后計(jì)算標(biāo)題的序列與標(biāo)記窗文本序列的詞語距離L,如果L小于一個(gè)閾值,則認(rèn)為此標(biāo)記窗內(nèi)的文本是正文。
這種方法雖然看上去挺好,但其實(shí)也是存在問題的:
① 需要對頁面中的所有文本進(jìn)行分詞,效率不高。
② 詞語距離的閾值難以確定,不同的文章?lián)碛胁煌拈撝怠?/p>
4)基于數(shù)據(jù)挖掘或機(jī)器學(xué)習(xí)
使用大數(shù)據(jù)進(jìn)行訓(xùn)練,讓機(jī)器提取主文本。
這種方法肯定是極好的,但是它需要先有html與正文數(shù)據(jù),然后進(jìn)行訓(xùn)練。我們在此不進(jìn)行探討。
5)基于行塊分布函數(shù)正文提取
對于任意一個(gè)網(wǎng)頁,它的正文和標(biāo)簽總是雜糅在一起。此方法的核心有亮點(diǎn):① 正文區(qū)的密度;② 行塊的長度;一個(gè)網(wǎng)頁的正文區(qū)域肯定是文字信息分布最密集的區(qū)域之一,這個(gè)區(qū)域可能最大(評(píng)論信息長、正文較短),所以同時(shí)引進(jìn)行塊長度進(jìn)行判斷。
實(shí)現(xiàn)思路:
① 我們先將HTML去標(biāo)簽,只留所有正文,同時(shí)留下標(biāo)簽取出后的所有空白位置信息,我們稱其為Ctext;
② 對每一個(gè)Ctext取周圍k行(k<5),合起來稱為Cblock;
③ 對Cblock去掉所有空白符,其文字總長度稱為Clen;
④ 以Ctext為橫坐標(biāo)軸,以各行的Clen為縱軸,建立坐標(biāo)系。
以這個(gè)網(wǎng)頁為例: http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm 該網(wǎng)頁的正文區(qū)域?yàn)?45行至182行。

由上圖可知,正確的文本區(qū)域全都是分布函數(shù)圖上含有最值且連續(xù)的一個(gè)區(qū)域,這個(gè)區(qū)域往往含有一個(gè)驟升點(diǎn)和一個(gè)驟降點(diǎn)。因此,網(wǎng)頁正文抽取問題轉(zhuǎn)化為了求行塊分布函數(shù)上的驟升點(diǎn)和驟降點(diǎn)兩個(gè)邊界點(diǎn),這兩個(gè)邊界點(diǎn)所含的區(qū)域包含了當(dāng)前網(wǎng)頁的行塊長度最大值并且是連續(xù)的。
經(jīng)過大量實(shí)驗(yàn),證明此方法對于中文網(wǎng)頁的正文提取有較高的準(zhǔn)確度,此算法的優(yōu)點(diǎn)在于,行塊函數(shù)不依賴與HTML代碼,與HTML標(biāo)簽無關(guān),實(shí)現(xiàn)簡單,準(zhǔn)確率較高。
主要邏輯代碼如下:
# 假設(shè)content為已經(jīng)拿到的html
# Ctext取周圍k行(k<5),定為3
blocksWidth = 3
# 每一個(gè)Cblock的長度
Ctext_len = []
# Ctext
lines = content.split('n')
# 去空格
for i in range(len(lines)):
if lines[i] == ' ' or lines[i] == 'n':
lines[i] = ''
# 計(jì)算縱坐標(biāo),每一個(gè)Ctext的長度
for i in range(0, len(lines) - blocksWidth):
wordsNum = 0
for j in range(i, i + blocksWidth):
lines[j] = lines[j].replace("\s", "")
wordsNum += len(lines[j])
Ctext_len.append(wordsNum)
# 開始標(biāo)識(shí)
start = -1
# 結(jié)束標(biāo)識(shí)
end = -1
# 是否開始標(biāo)識(shí)
boolstart = False
# 是否結(jié)束標(biāo)識(shí)
boolend = False
# 行塊的長度閾值
max_text_len = 88
# 文章主內(nèi)容
main_text = []
# 沒有分割出Ctext
if len(Ctext_len) < 3:
return '沒有正文'
for i in range(len(Ctext_len) - 3):
# 如果高于這個(gè)閾值
if(Ctext_len[i] > max_text_len and (not boolstart)):
# Cblock下面3個(gè)都不為0,認(rèn)為是正文
if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
boolstart = True
start = i
continue
if (boolstart):
# Cblock下面3個(gè)中有0,則結(jié)束
if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
end = i
boolend = True
tmp = []
# 判斷下面還有沒有正文
if(boolend):
for ii in range(start, end + 1):
if(len(lines[ii]) < 5):
continue
tmp.append(lines[ii] + "n")
str = "".join(list(tmp))
# 去掉版權(quán)信息
if ("Copyright" in str or "版權(quán)所有" in str):
continue
main_text.append(str)
boolstart = boolend = False
# 返回主內(nèi)容
result = "".join(list(main_text))
0x4 結(jié)語
至此我們就可以獲取任意內(nèi)容的文章語料了,但這僅僅是開始,獲取到了這些語料后我們還需要在一次進(jìn)行清洗、分詞、詞性標(biāo)注等,才能獲得真正可以使用的語料。
總結(jié)
以上所述是小編給大家介紹的用python3教你任意Html主內(nèi)容提取功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
Python圖像濾波處理操作示例【基于ImageFilter類】
這篇文章主要介紹了Python圖像濾波處理操作,結(jié)合實(shí)例形式分析了Python基于ImageFilter類實(shí)現(xiàn)的濾波處理相關(guān)操作技巧,需要的朋友可以參考下2019-01-01
python3?http.client?網(wǎng)絡(luò)請求方式
這篇文章主要介紹了python3?http.client?網(wǎng)絡(luò)請求方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Pandas 合并多個(gè)Dataframe(merge,concat)的方法
今天小編就為大家分享一篇Pandas 合并多個(gè)Dataframe(merge,concat)的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-06-06
python 函數(shù)進(jìn)階之閉包函數(shù)
這篇文章主要介紹了python 函數(shù)進(jìn)階之閉包函數(shù),內(nèi)函數(shù)使用了外函數(shù)的局部變量,并且外函數(shù)把內(nèi)函數(shù)返回出來的過程叫做閉包,里面的內(nèi)函數(shù)是閉包函數(shù),下文相關(guān)介紹需要的小伙伴可以參考一下2022-04-04
Python趣味編程實(shí)現(xiàn)手繪風(fēng)視頻示例
本文與計(jì)算機(jī)視覺相關(guān),使用Python將圖片由自然風(fēng)轉(zhuǎn)化為手繪風(fēng),期間未對圖片進(jìn)行任何預(yù)處理、后處理;代碼中只借助了兩個(gè)常見庫,核心計(jì)算由Numpy負(fù)責(zé) ,Pillow 負(fù)責(zé)圖片讀寫2021-10-10
實(shí)踐Vim配置python開發(fā)環(huán)境
這篇文章給大家分享了Vim配置python開發(fā)環(huán)境的實(shí)踐心得,大家可以跟著嘗試操作下。2018-07-07
詳解在python中如何使用zlib模塊進(jìn)行數(shù)據(jù)壓縮和解壓縮
Python有一些內(nèi)置庫用于處理數(shù)據(jù)壓縮和解壓縮,其中一個(gè)就是zlib模塊,這個(gè)模塊為DEFLATE壓縮算法和相關(guān)的gzip(文件格式)提供了支持,在這篇文章中,我們將深入探討如何使用zlib模塊進(jìn)行數(shù)據(jù)壓縮和解壓縮2023-06-06

