爬虫基本库的使用之正则表达式
我們已經(jīng)知道在上一條requests庫來獲取網(wǎng)頁的源代碼,得到HTML代碼。但我們想要的數(shù)據(jù)是包含在HTML代碼之中的,那么要怎么才能從HTML代碼中獲取想要的信息呢?正則表達(dá)式就是其中一個(gè)有效的方法。
1.實(shí)例引入
??????下面用幾個(gè)實(shí)例先來看一下它的用法
打開開源中國(guó)提供的正則表達(dá)式測(cè)試工具:http://tool.oschina.net/regex/,輸入待匹配的文本,然后選擇常用的正則表達(dá)式,就可以得出相應(yīng)的匹配結(jié)果了。
例如,這里輸入如下待匹配文本:
Hello, my phone number is 010-86432100 and email is cqc@cuiqingcai.com,??? and my website is
https://cuiqingcai.com
這段字符串中包含一個(gè)電話號(hào)碼,一個(gè)E-mail地址和一個(gè)URL,接下來就嘗試用正則表達(dá)式將這些內(nèi)容提取出來
打開開源正則匹配網(wǎng)址,在網(wǎng)頁右側(cè)選擇“匹配Email地址”,就可以看到下方出現(xiàn)了文本中的E-mail,如下圖:
?如果選擇‘匹配網(wǎng)址URL’,可以看到下方出現(xiàn)了文本中的URL,如下圖:
?其實(shí),這里使用了正則表達(dá)式匹配,也就是用一定的規(guī)則將特定文本提取出來。
那么我將列出所有常見的匹配規(guī)則,如下圖:
?看完這表確實(shí)會(huì)感覺有點(diǎn)暈暈的,下面我會(huì)詳細(xì)的講解一些常見規(guī)則的用法。
2.匹配方法之match
????????首先第一個(gè)常用的匹配方法--match,向它傳入要匹配的字符串以及正則表達(dá)式,就可以檢測(cè)到這個(gè)正則表達(dá)式是否和字符串相匹配。
? ? ? ? match方法會(huì)嘗試從字符串的起始位置開始匹配正則表達(dá)式,如果匹配,就返回匹配成功的結(jié)果,如果不匹配,就返回None。實(shí)例如下:
import recontent = "Hello 123 4567 World_This is a Regex Demo" print(len(content)) result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}',content) print(result) print(result.group()) print(result.span())??輸出結(jié)果如下:
?這里寫了一個(gè)正則表達(dá)式:
^Hello\s\d\d\d\s\d{4}\s\w{10}開頭的^表示字符串的開頭,也就是以Hello開頭;然后\s表示匹配空白字符,用來匹配目標(biāo)字符串里Hello后面的空格;\d表示匹配數(shù)字,3個(gè)\d用來匹配123;緊接著的一個(gè)\s表示匹配空格;目標(biāo)字符串的后面還有4567,我們其實(shí)可以不用重復(fù)\d四遍,而是用\d后面跟{4}的形式代表匹配四個(gè)數(shù)字;后面又是一個(gè)空白字符,最后\w{10}則表示匹配十個(gè)字母及下劃線。我其實(shí)沒有把整個(gè)字符串匹配完全,不過這樣也是可以的,只是匹配結(jié)果短一點(diǎn)而已。
在match方法中,第一個(gè)參數(shù)是傳入了正則表達(dá)式,第二個(gè)參數(shù)是傳入了要匹配的字符串。
輸出結(jié)果中span方法是輸出匹配的范圍,group方法是放回匹配的內(nèi)容。
(1).匹配目標(biāo)
用match方法確實(shí)可以實(shí)現(xiàn)匹配,但是如果想要提取字符串中一部分內(nèi)容,該怎么辦呢?
這個(gè)時(shí)候我們可以使用()來將想要提取的子字符括起來,實(shí)例如下:
? import recontent = "Hello 1234567 World_This is a Regex Demo" result = re.match('^Hello\s(\d+)\sWorld_This',content) print(result) print(result.group()) print(result.group(1)) print(result.span())?輸出結(jié)果如下:
?可以看到,我們成功的得到了1234567。這里用的是group(1),它與group()有所不同,后者也會(huì)輸出完整的匹配結(jié)果,前者會(huì)輸出第一個(gè)被()包圍。假設(shè)正則表達(dá)式后面還有被()包圍的內(nèi)容,那么可以依次用group(2)、group(3)等獲取。
(2).通用匹配
上面寫的正則表達(dá)式其實(shí)比較復(fù)雜,只要出現(xiàn)空白字符就需要寫\s匹配,出現(xiàn)數(shù)字就需要寫\d匹配,這樣的工作量其實(shí)非常大。那么這里有一個(gè)通用匹配就是.*,其中可以匹配任意字符(除換行符),*代表匹配前面的字符無限次,所以它們組合在一起就可以匹配任意字符了。
接著上面的例子,我們利用.*改寫一下正則表達(dá)式:
import recontent = "Hello 1234567 World_This is a Regex Demo" result = re.match('^Hello.*Demo$',content) print(result) print(result.group()) print(result.span())這里我們使用.*來代替中間部分,并在最后一個(gè)結(jié)尾加一個(gè)結(jié)尾字符串。運(yùn)行結(jié)果如下:
?(3).貪婪與非貪婪
使用通用匹配.*匹配到的內(nèi)容有時(shí)候不是我們想要的內(nèi)容。看下面的例子:
import recontent = "Hello 1234567 World_This is a Regex Demo" result = re.match('^He.*(\d+).*Demo$',content) print(result) print(result.group(1))這里我們依然想獲取目標(biāo)字符串中間的數(shù)字,所以正則表達(dá)式中間寫的依然是(\d+)。而數(shù)字兩側(cè)由于內(nèi)容比較雜亂,所以想省略來寫,于是都寫成.*。
運(yùn)行結(jié)果如下:
?為什么這個(gè)只得到了7一個(gè)數(shù)字?
那么這里就得涉及到貪婪匹配與非貪婪匹配的問題。在貪婪匹配下,.*會(huì)匹配盡可能多的字符。正則表達(dá)式中.*后面是\d+,也就是至少一個(gè)數(shù)字,因?yàn)?*盡可能多的匹配,那么它把123456都匹配了,只給\d+留下一個(gè)可滿足條件的數(shù)字7,因此最后得到的內(nèi)容也就是7。
其實(shí)這里只要使用非貪婪匹配就好了,非貪婪的匹配的寫法是.*?,比通用匹配多了一個(gè)?,那么它可以起到什么樣的效果呢?我們?cè)儆脤?shí)例來看一下:
import recontent = "Hello 1234567 World_This is a Regex Demo" result = re.match('^He.*?(\d+).*Demo$',content) print(result) print(result.group(1))結(jié)果如下:
?此時(shí)變成功的獲取到了1234567了。原因可想而知,貪婪匹配是匹配盡可能多的字符,非貪婪匹配就是匹配盡可能少的字符。當(dāng).*?匹配到Hello后面的空白字符時(shí),再往后的字符就是數(shù)字了,而\d+恰好可以匹配,于是這里.*?就不在匹配了,而是交給\d+去匹配。
所以說,在做匹配的時(shí)候,字符串中間盡可能使用非貪婪匹配,也就是用.*?代替.*,以免出現(xiàn)匹配缺失的情況。
但是注意,如果匹配的結(jié)果是在字符串結(jié)尾,.*?有可能匹配不到任何結(jié)果了,因?yàn)樗潜M可能匹配少的字符。例如:
import re content = 'http://weibo.com/comment/kEraCN' result1 = re.match('http.*?comment/(.*?)',content) result2 = re.match('http.*?comment/(.*)',content) print('result1',result1.group(1)) print('result2',result2.group(1))結(jié)果如下:
?
可以觀察到,.*?沒有匹配任何結(jié)果,而.*則是盡量匹配多內(nèi)容,成功得到匹配結(jié)果。
(4).修飾符
在正則表達(dá)式中,可以用一些可選標(biāo)志修飾符來控制匹配的模式,修飾符被指定為一個(gè)可選的標(biāo)志,我們可以用實(shí)例來看一下:
import re content = '''Hello 1234567 World_This is a Regex Demo ''' result = re.match('^He.*?(\d+).*?Demo$',content) print(result.group(1))這樣運(yùn)行的話就會(huì)出現(xiàn)報(bào)錯(cuò),也就是說正則表達(dá)式?jīng)]有匹配到這個(gè)字符串,返回結(jié)果為None,而我們又調(diào)用了group方法,導(dǎo)致AttributeError。
那么,中間content是有一個(gè)換行符的,加了一個(gè)換行符就匹配不到了,因?yàn)檫@里匹配的內(nèi)容是除換行符之外的任意字符,當(dāng)遇到換行符的時(shí)候,.*?就不能匹配了,所以導(dǎo)致匹配失敗。這里只需要加一個(gè)修飾符re.S,即可修正這個(gè)錯(cuò)誤。
result = re.match('^He.*?(\d+).*?Demo$',content,re.S)這個(gè)修飾符的作用就是匹配包括換行符在內(nèi)的所有字符。此時(shí)的運(yùn)行結(jié)果如下:
1234567
這個(gè)re.S在網(wǎng)頁匹配中經(jīng)常用到。因?yàn)镠TML節(jié)點(diǎn)經(jīng)常會(huì)有換行,加上它,就可以匹配節(jié)點(diǎn)與節(jié)點(diǎn)之間的換行了。
(5).轉(zhuǎn)義匹配
我們知道正則表達(dá)式定義了許多匹配模式,如,用于匹配除換行符之外的任意字符。但如果目標(biāo)字符串里面就包含.這個(gè)字符,那就要用到轉(zhuǎn)義字符了。
例如:
import re content = '(百度)www.baidu.com' result = re.match('\(百度\)www\.baidu\.com',content) print(result)當(dāng)目標(biāo)字符串中遇到用作正則匹配模式的特殊字符時(shí),在此字符前面加反斜線\轉(zhuǎn)義一下即可。
3.匹配方法之search
前面講的是match方法,它是從字符串的開頭開始匹配的,意味著一旦開頭不匹配,整個(gè)匹配就失敗。
所以match方法使用時(shí)得考慮字符串開頭的內(nèi)容,因此在匹配的時(shí)候并不是很方便。
而search方法,它在匹配的時(shí)候會(huì)掃描整個(gè)字符串,然后會(huì)返回第一個(gè)匹配成功的結(jié)果。在匹配時(shí),search方法會(huì)依次以每個(gè)字符串作為開頭掃描字符串,直到找到第一個(gè)符合規(guī)則的字符串,然后返回匹配結(jié)果。掃描完沒有找到符合規(guī)則的字符串,就返回None。
看下面的例子:
import re content = 'Extra strings Hello 1234567 World_This is a Regex Demo Extra strings' result = re.search('Hello.*?(\d+).*?Demo',content) print(result) print(result.group(1))運(yùn)行結(jié)果如下:
這樣就可以得到匹配結(jié)果了?。
學(xué)習(xí)到了這種方法的話,咱們來看幾個(gè)實(shí)例。
首先,準(zhǔn)備一段待匹配的HTML文本,接下來寫幾個(gè)正則表達(dá)式實(shí)例實(shí)現(xiàn)相應(yīng)信息的提取。
html=''' <div id="basketball"><h2 class="title">籃球</h2><p class="instuction">籃球明星列表</p><ul><li class="active"><a href="1.mp3">詹姆斯</a></li><li data-view="13"><a href="2.mp3">哈登</a></li><li data-view="35">杜蘭特</li><li class="active">庫里</li></ul> </div> </body> </html> '''1.提取class為active的文本和里面a標(biāo)簽的href值
import re result = re.search('<li.*?active.*?href="(.*?)">(.*?)</a>',html,re.S) if result:print(result.group(1),result.group(2))運(yùn)行結(jié)果如下:
2.提取含有data-view的文本
import re result = re.search('<li.*?data-view="(.*?)">(.*?)</li>',html,re.S) if result:print(result.group(1),result.group(2))運(yùn)行結(jié)果如下:
?
這里使用search方法會(huì)返回第一個(gè)符合條件的匹配目標(biāo)
3.不使用re.S看看結(jié)果
?運(yùn)行結(jié)果如下:
?因?yàn)樯厦嬉粋€(gè)有data-view屬性里面包含換行符,所以用.*?匹配不到,只有加上re.S才能對(duì)換行符進(jìn)行匹配。
?4.匹配方法之findall
?介紹完了search的用法后,它可以返回與正則表達(dá)式相匹配的第一個(gè)字符串。如果想要獲取與正則表達(dá)式相匹配的所有字符串,該如何處理?這里就要借用findall。
還是用HTML文本,如果想要獲取里面所有的a節(jié)點(diǎn)的href值和文本值。可以將search方法換成findall方法。返回結(jié)果是列表類型,需要通過遍歷來依次獲取每組內(nèi)容。代碼如下:
html=''' <div id="basketball"><h2 class="title">籃球</h2><p class="instuction">籃球明星列表</p><ul><li class="active"><a href="1.mp3">詹姆斯</a></li><li data-view="13"><a href="2.mp3">哈登</a></li><li data-view="35">杜蘭特</li><li class="active">庫里</li></ul> </div> </body> </html> '''import re results = re.findall('<li.*?href="(.*?)">(.*?)</a>',html,re.S) print(results) print(type(results)) for result in results:print(result)print(result[0],result[1])運(yùn)行結(jié)果如下:
?可以看到在每個(gè)列表中的每個(gè)元素都是元組類型,所以我們可以用索引值來取個(gè)條目。
總結(jié)一下,如果只想匹配到第一個(gè)字符,可以用search方法;如果需要提取多個(gè)內(nèi)容,可以使用findall。
5.修改方法之sub
除了使用正則表達(dá)式來提取信息,有時(shí)候還需要借助它來修改文本。例如:
import re content = '54ak325dsada3da9' content = re.sub('\d+','',content) print(content)那么這里使用re方法來消除數(shù)字,這里往sub方法里面加入的第一個(gè)參數(shù)是匹配所有的數(shù)字,第二參數(shù)傳入的是把數(shù)字都替換成空的字符串,第三個(gè)參數(shù)就是原字符。
結(jié)果如下:
?6.編譯方法之compile
?前面所說的都是用來處理字符串的方法,最后再介紹一下compile方法,這個(gè)方法可以將正則字符串編譯成正則表達(dá)式對(duì)象,以便在后面的匹配中復(fù)用。實(shí)例代碼如下:
import recontent1 = '2022-09-15 23:00' content2 = '2022-09-12 12:00' pattern = re.compile('\d{2}:\d{2}') result1 = re.sub(pattern,'',content1) result2 = re.sub(pattern,'',content2) print(result1,result2)運(yùn)行結(jié)果如下:
?
那么使用compile就是處理一下對(duì)象,將匹配到的對(duì)象用pattern來接收,再使用sub來進(jìn)行修飾。
7.總結(jié)
到此為止,正則表達(dá)式的基本用法都介紹完了,希望大家能仔細(xì)看完并且能深刻掌握理解,細(xì)節(jié)的部分到后面我會(huì)通過具體的實(shí)例來鞏固這些方法。
總結(jié)
以上是生活随笔為你收集整理的爬虫基本库的使用之正则表达式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: css代码 文字超出点点点
- 下一篇: 计算机网络的组成与分类