Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
代碼如下:
#以下代碼是通過python的正則表達(dá)式re模塊匹配出html標(biāo)簽以及內(nèi)容 import re #必須導(dǎo)入內(nèi)置的正則表達(dá)式re模塊 info='<html><h2>zhangsan</h2></html>' #上面一行代碼定義info為html標(biāo)簽內(nèi)容,包含2個標(biāo)簽,第1個標(biāo)簽是html,第2個標(biāo)簽是h2,標(biāo)簽內(nèi)容是zhangsan pattern1=re.compile(r'<(\w+)><(\w+)>(.+)</\2></\1>$') #上面這行代碼的(\w+)代表至少1個數(shù)字字母下劃線,(.+)匹配除換行符 \n 之外的任何至少1個單字符,</\2>代表第2個標(biāo)簽的</h2>,</\1>代表第1個標(biāo)簽的</html> result1=re.match(pattern1,info) #上面這行代碼代表從頭開始匹配info里符合正則規(guī)則pattern1的內(nèi)容,并賦值給result1 print("result1:",result1) print("result1.group():",result1.group()) #上面這行代碼代表要打印匹配出來的所有結(jié)果,group()代表所有的 print("result1.group(1):",result1.group(1)) #上面這行代碼代表要打印匹配出來的第一個結(jié)果,group(1)代表第一個結(jié)果 print("result1.group(2):",result1.group(2)) #上面這行代碼代表要打印匹配出來的第二個結(jié)果,group(2)代表第二個結(jié)果 print("result1.group(3):",result1.group(3)) #上面這行代碼代表要打印匹配出來的第三個結(jié)果,group(3)代表第三個結(jié)果
代碼運(yùn)行結(jié)果如下:
result1: <re.Match object; span=(0, 30), match='<html><h2>zhangsan</h2></html>'>
result1.group(): <html><h2>zhangsan</h2></html>
result1.group(1): html
result1.group(2): h2
result1.group(3): zhangsan
圖片示例如下:
一節(jié)介紹了re模塊中常用函數(shù)的用法,我們可以通過使用re模塊和正則表達(dá)式對文本進(jìn)行解析并獲取值。當(dāng)并未真正獲取到search()、match()等函數(shù)匹配到的字符串。本節(jié)將會介紹如何獲取匹配到的字符串。
在模塊re中,查找與模式匹配的子串的函數(shù)都在找到時返回MatchObject對象。這種對象包含與模式匹配的子串的信息,還包含模式的哪部分與子串的哪部分匹配的信息。這些子串部分稱為編組(group)。
編組就是放在圓括號內(nèi)的子模式,它們是根據(jù)左邊的括號數(shù)編號的,其中編組0指的是整個模式。因此,在下面的模式中:
'There (was a (wee) (cooper)) who (lived in Fyfe)'
包含如下編組:
0 There was a wee cooper who lived in Fyfe
1 was a wee cooper
2 wee
3 cooper
4 lived in Fyfe
通常,編組包含諸如通配符和重復(fù)運(yùn)算符等特殊字符,因此你可能想知道與給定編組匹配的內(nèi)容。re模塊為我們提供了匹配對象的一些重要的方法,用于獲取編組對應(yīng)的值。具體如下表所示:
方法 | 描述 |
group([group1, ...]) | 獲取與給定子模式(編組)匹配的子串 |
start([group]) | 返回與給定編組匹配的子串的起始位置 |
end([group]) | 返回與給定編組匹配的子串的終止位置(與切片一樣,不包含終止位置) |
span([group]) | 返回與給定編組匹配的子串的起始和終止位置 |
group():返回與模式中給定編組匹配的子串。如果沒有指定編組號,則默認(rèn)為0。如果只指定了一個編組號(或使用默認(rèn)值0),將只返回一個字符串;否則返回一個元組,其中包含與給定編組匹配的子串。
start():返回與給定編組(默認(rèn)為0,即整個模式)匹配的子串的起始索引。
end():與start()類似,但返回終止索引加1。
span():返回一個元組,其中包含與給定編組(默認(rèn)為0,即整個模式)匹配的子串的起始索引和終止索引。
上面關(guān)于匹配對象方法的用法如下my_re_continue.py程序所示:
my_re_continue.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
string = 'www.guanhu.name'
# group()方法,獲取與給定子模式(編組)匹配的子串
m = re.match(r'www\.(.*)\..{4}', string)
print('m = {}'.format(m))
print(m.group(1))
# start()方法,返回與給定編組匹配的子串的起始索引
print(m.start(1))
# end()方法
print(m.end(1))
# span()方法
print(m.span(1))
程序執(zhí)行結(jié)果如下所示:
m = <re.Match object; span=(0, 15), match='www.guanhu.name'>
guanhu
4
10
(4, 10)
在上一節(jié)的示例程序中,我們使用re.sub()函數(shù),將一個字串替換為了另一個字串。當(dāng)然這也可以使用字符串中的replace()方法輕松完成。但是,正則表達(dá)式很有用,它能夠讓我們以更靈活的方式進(jìn)行檢索,還能夠執(zhí)行更復(fù)雜的替換。
為利用re.sub的強(qiáng)大功能,最簡單的方式是在替代字符串中使用組號。在替換字符串中,任何類似于'\n'的轉(zhuǎn)義序列都將被替換為與模式中編組n匹配的字符串。例如,假設(shè)要將'*something*'替換為'<em>something</em>',其中前者是在純文本文檔(如電子郵件)表示突出的普通方式,而后者是相應(yīng)的HTML代碼(用于網(wǎng)頁中)。下面先創(chuàng)建一個正則表達(dá)式,具體如下所示:
>>> pat = r'\*([^\*]+)\*'
創(chuàng)建模式之后,我們就可以使用re.sub來完成所需的替換了,具體如下所示:
>>> re.sub(pat, r'<em></em>', string)
'<em>something</em>'
簡短的兩行代碼,就將純文本轉(zhuǎn)換成了網(wǎng)頁所需的HTML代碼。通過使用函數(shù)來替換內(nèi)容,可執(zhí)行更復(fù)雜的替換。在這里,re.sub()這個函數(shù)將MatchObject作為唯一的參數(shù),它返回的字符串即為替換后的內(nèi)容。我們可以對匹配的字符串做任何處理,并通過細(xì)致的處理來生成替換內(nèi)容。
重復(fù)運(yùn)算默認(rèn)都是貪婪的,因此,它們將會匹配盡可能多的內(nèi)容。重寫前面的突出程序,在其中使用了如下模式:
>>> pat = r'\*(.+)\*'
這個模式與以星號打頭并以星號結(jié)尾的內(nèi)容匹配,這看起來毫無破綻,但是實(shí)際情況并非如此,如下所示:
>>> re.sub(pat, r'<em></em>', '*This* is *it*!')
'<em>This* is *it</em>!'
從上面的執(zhí)行結(jié)果,可以看出這個模式匹配了從第一個星號開始到最后一個星號之間的全部內(nèi)容,其中包括了另外兩個星號!這就是貪婪的意思:能匹配多少就匹配多少。
顯然,這并不是我們想要的結(jié)果,當(dāng)我們知道不應(yīng)該將某個特定的字符包含在內(nèi)時,使用本節(jié)前面的解決方案能夠很好的解決我們的問題。但是,如果我們要使用'**something**'來表示突出內(nèi)容呢?在這種情形下,在要強(qiáng)調(diào)的內(nèi)容中包含單個星號不是問題,但是如何避免過度貪婪呢?
這實(shí)際上很容易,只需使用重復(fù)運(yùn)算符的非貪婪版即可。對于所有的重復(fù)運(yùn)算符,都可在后面加上問號來將其指定為非貪婪的。
>>> re.sub(r'\*\*(.+?)\*\*', r'<em></em>', '**some** **thing**')
'<em>some</em> <em>thing</em>'
這里使用的是運(yùn)算符+?而不是+。這意味著與以前一樣,這個模式將匹配一個或多個通配符,但匹配盡可能少的內(nèi)容,因?yàn)樗欠秦澙返?。因此,這個模式只匹配到下一個'\*\*',即它末尾的內(nèi)容。
本節(jié)主要介紹了以下內(nèi)容:
下一節(jié)將會介紹一些實(shí)例來鞏固正則表達(dá)式以及re模塊中常用的方式和方法,敬請關(guān)注!
創(chuàng)作不容易,還請點(diǎn)個免費(fèi)贊!喜歡的小伙伴請點(diǎn)關(guān)注、收藏!歡迎大家轉(zhuǎn)發(fā)、評論!
#Python#
在《Python進(jìn)階記錄之urllib模塊》中,我們介紹了Python內(nèi)置的HTTP請求庫urllib模塊的基本用法,需要重點(diǎn)掌握使用urllib的request模塊進(jìn)行簡單的get、post請求。今天我們講一下Python內(nèi)置的HTML解析庫HTMLParser模塊,并結(jié)合之前的re模塊和urllib模塊實(shí)現(xiàn)爬取指定新聞頁提取新聞文本內(nèi)容的小需求。
我們使用urllib模塊進(jìn)行HTTP請求獲取到的是整個網(wǎng)頁的HTML,但是我們往往只需要其中一部分對我們有用的內(nèi)容。這時我們就可以使用HTMLParser模塊來幫助我們處理HTML。
HTMLParser是Python內(nèi)置的專門用來解析HTML的模塊。利用HTMLParser,我們可以分析出一段HTML里面的標(biāo)簽、數(shù)據(jù)等,是一種處理HTML的簡便途徑。我們先來看一個官方的例子。
HTMLParser模塊官方例子
從上述代碼中可以看出,HTMLParser模塊來自html.parser,導(dǎo)包時要格外注意。使用HTMLParser時,我們需要定義一個繼承自HTMLParser的子類,并根據(jù)需要重寫HTMLParser父類中的成員方法。例子中使用的各方法作用如下:
handle_starttag(tag, attrs):識別HTML的開始標(biāo)簽,例如<html>、<title>、<body>、<div>等。
handle_endtag(tag):識別HTML的結(jié)束標(biāo)簽,例如</html>、</body>、</div>、</p>等。
handle_data(data):識別HTML標(biāo)簽內(nèi)容,例如“<p>Test</p>”中的Test。
handle_startendtag(tag, attrs):識別沒有結(jié)束標(biāo)簽的HTML標(biāo)簽,例如<img />等。
handle_comment(data):識別HTML中的注釋內(nèi)容,一般是“<!-- 注釋 -->”中的注釋內(nèi)容。
HTMLParser采用的是一種事件驅(qū)動的模式,HTMLParser找到一個特定的標(biāo)記時,它會去調(diào)用一個用戶定義的函數(shù),以此來通知程序處理。
我們可以利用這些方法來實(shí)現(xiàn)HTML解析相關(guān)的功能。其中參數(shù)tag表示的是HTML標(biāo)簽,attrs是一個列表,列表元素為一個個“(屬性,值)”形式的元組。HTMLParser會自動將tag和attrs都轉(zhuǎn)為小寫,解析時調(diào)用feed( )方法,把待解析的HTML字符串傳入即可。
現(xiàn)在有以下網(wǎng)頁,我們需要獲取出里面的新聞文本內(nèi)容。
待請求網(wǎng)頁
首先就是獲取該網(wǎng)頁的HTML。經(jīng)過上節(jié)內(nèi)容的學(xué)習(xí),我們很容易想到利用urllib庫請求獲取這個網(wǎng)頁的HTML。
獲取網(wǎng)頁HTML
代碼很簡單,使用urlopen( )方法,傳入url即可。此時,我們已經(jīng)得到了整個網(wǎng)頁的HTML,但是我們要獲取的是新聞內(nèi)容,顯然此時的HTML中有太多我們不需要的東西。
觀察整個網(wǎng)頁HTML,我們發(fā)現(xiàn)新聞內(nèi)容是包含在一個div中的。
新聞內(nèi)容相關(guān)HTML
我們可以使用正則表達(dá)式re模塊將包含新聞內(nèi)容的這個div提取出來。
提取新聞內(nèi)容相關(guān)的HTML
新聞內(nèi)容的div格式主要是:<div class="article-content">...</div>。由于該div下嵌套了其他div,如果我們直接使用r'<div class=\"article-content\">(.*?)</div>'進(jìn)行正則提取,會發(fā)現(xiàn)在下一個</div>處就截斷了。為了正確提取所有新聞內(nèi)容相關(guān)的HTML,我們需要在</div>前加一個</p>,以保證是在新聞內(nèi)容結(jié)束的</div>處截斷。
此時,我們已經(jīng)得到了新聞內(nèi)容相關(guān)的HTML?,F(xiàn)在還剩最后一部,就是把HTML標(biāo)簽去掉,保留新聞文本內(nèi)容。這一步,我們就可以利用HTMLParser來實(shí)現(xiàn)了。
HTMLParser提取新聞內(nèi)容
我們定義一個繼承自HTMLParser的子類,然后重寫handle_data(data)方法獲取當(dāng)前HTML中的文本內(nèi)容即可。由于我們定義的私有變量__text是通過一個列表來逐條接收新聞內(nèi)容的,所以我們在類中定義一個獲取私有變量__text的方法,并將列表轉(zhuǎn)換成字符串。至此,我們已經(jīng)獲取到了新聞的文本內(nèi)容。
然而,當(dāng)前文本內(nèi)容一整段在一起,看起來并不美觀,與實(shí)際分段的新聞文本內(nèi)容也有差別。我們可以使用HTMLParser來美化新聞內(nèi)容。我們知道,網(wǎng)頁上的新聞內(nèi)容是通過“\n”、“\t”、“<br/>”等特殊符號或標(biāo)簽進(jìn)行間隔和分段的。因此,我們只需要在解析時替換掉這些特殊符號和標(biāo)簽即可。
替換特殊符號和標(biāo)簽
重寫handle_data(data)方法,識別文本的同時替換掉“\t”、“\r”、“\n”等特殊字符;重寫handle_starttag(tag, attrs)方法,識別出<p>、<br>進(jìn)行替換;重寫handle_startendtag(tag, attrs)方法,識別出<br/>進(jìn)行替換。再次運(yùn)行程序,可以看到,新聞內(nèi)容進(jìn)行了分段,看起來就美觀多了。
上述過程實(shí)現(xiàn)了一個非常簡單的爬蟲,爬取新聞網(wǎng)頁,提取新聞內(nèi)容。當(dāng)然,由于我們目前還沒介紹其他第三方庫,實(shí)現(xiàn)起來還不夠靈活,例如獲取新聞內(nèi)容的div我們現(xiàn)在只能通過正則表達(dá)式實(shí)現(xiàn),但實(shí)際上,如果使用lxml(etree、xpath)、BeautifulSoup等第三方庫會更簡單實(shí)用。
以上內(nèi)容介紹了Python內(nèi)置的HTML解析庫HTMLParser模塊,需要重點(diǎn)掌握HTMLParser類常用方法的作用,能夠重寫這些方法進(jìn)行自定義解析。感謝大家的支持與關(guān)注,歡迎一起學(xué)習(xí)交流~
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。