1. 程式人生 > 其它 >寫爬蟲,怎麼可以不會正則呢?

寫爬蟲,怎麼可以不會正則呢?

技術標籤:百度正則表示式bmpregex人工智慧

導讀:正則表示式在各語言中的使用是有差異的,本文以 Python 3 為基礎。本文主要講述的是正則的語法,對於 re 模組不做過多描述,只會對一些特殊地方做提示。

很多人覺得正則很難,在我看來,這些人一定是沒有用心。其實正則很簡單,根據二八原則,我們只需要懂 20% 的內容就可以解決 80% 的問題了。我曾經有幾年幾乎每天都跟正則打交道,剛接手專案的時候我對正則也是一無所知,花半小時百度了一下,然後寫了幾個 demo,就開始正式接手了。三年多時間,我用到的正則鮮有超出我最初半小時百度到的知識的。

1、正則基礎

1.1、基礎語法

(1)常用元字元

語法描述
\b匹配單詞的開始或結束
\d匹配數字
\s匹配任意不可見字元(空格、換行符、製表符等),等價於[ \f\n\r\t\v]。
\w匹配任意 Unicode 字符集,包括字母、數字、下劃線、漢字等
.匹配除換行符(\n)以外的任意字元
^ 或 \A匹配字串或行的起始位置
$ 或 \Z匹配字串或行的結束位置

(2)限定詞(又叫量詞)

語法描述
*重複零次或更多次
+重複一次或更多次
?重複零次或一次
{n}重複 n 次
{n,}重複 n 次或更多次
{n,m}重複 n 到 m 次

(3)常用反義詞

語法描述
\B匹配非單詞的開始或結束
\D匹配非數字
\S匹配任意可見字元, [^ \f\n\r\t\v]
\W匹配任意非 Unicode 字符集
[^abc]除 a、b、c 以外的任意字元

(4)字元族

語法描述
[abc]a、b 或 c
[^abc]除 a、b、c 以外的任意字元
[a-zA-Z]a 到 z 或 A 到 Z
[a-d[m-p]]a 到 d 或 m 到 p,即 [a-dm-p](並集)
[a-z&&[def]]d、e 或 f(交集)
[a-z&&[^bc]]a 到 z,除了 b 和 c:[ad-z](減去)
[a-z&&[^m-p]]a 到 z,減去 m 到 p:[a-lq-z](減去)

以上便是正則的基礎內容,下面來寫兩個例子看下:

s='123abc你好'
re.search('\d+',s).group()
re.search('\w+',s).group()

結果:

123
123abc你好

是不是很簡單?

1.2、修飾符

修飾符在各語言中也是有差異的。

Python 中的修飾符:

修飾符描述
re.A匹配 ASCII字元類,影響 \w, \W, \b, \B, \d, \D
re.I忽略大小寫
re.L做本地化識別匹配(這個極少極少使用)
re.M多行匹配,影響 ^和 $
re.S使 . 匹配包括換行符(\n)在內的所有字元
re.U匹配 Unicode 字符集。與 re.A 相對,這是預設設定
re.X忽略空格和 # 後面的註釋以獲得看起來更易懂的正則。

(1)re.A

修飾符 A 使 \w 只匹配 ASCII 字元,\W 匹配非 ASCII 字元。

s='123abc你好'
re.search('\w+',s,re.A).group()
re.search('\W+',s,re.A).group()

結果:

123abc
你好

但是描述中還有 \d\D,數字不都是 ASCII 字元嗎?這是什麼意思?別忘了,還有 全形和半形

s='0123456789'#全形數字
re.search('\d+',s,re.U).group()

結果:

0123456789

(2)re.M
多行匹配的模式其實也不常用,很少有一行行規整的資料。

s='aaa\r\nbbb\r\nccc'

re.findall('^[\s\w]*?$',s)
re.findall('^[\s\w]*?$',s,re.M)

結果:

['aaa\r\nbbb\r\nccc']#單行模式
['aaa\r','bbb\r','ccc']#多行模式

(3)re.S
這個簡單,直接看個例子。

s='aaa\r\nbbb\r\nccc'

re.findall('^.*',s)
re.findall('^.*',s,re.S)

結果:

['aaa\r']
['aaa\r\nbbb\r\nccc']

(4)re.X
用法如下:

rc=re.compile(r"""
\d+#匹配數字
#和字母
[a-zA-Z]+
""",re.X)
rc.search('123abc').group()

結果:

123abc

注意,用了 X 修飾符後,正則中的所有空格會被忽略,包括正則裡面的原本有用的空格。如果正則中有需要使用空格,只能用 \s 代替。

(5)(?aiLmsux)
修飾符不僅可以程式碼中指定,也可以在正則中指定。(?aiLmsux) 表示了以上所有的修飾符,具體用的時候需要哪個就在 ? 後面加上對應的字母,示例如下,(?a)re.A 效果是一樣的:

s='123abc你好'
re.search('(?a)\w+',s).group()
re.search('\w+',s,re.A).group()

結果是一樣的:

123abc
123abc

1.3、貪婪與懶惰

當正則表示式中包含能接受重複的限定符時,通常的行為是(在使整個表示式能得到匹配的前提下)匹配儘可能多的字元。

s='aabab'
re.search('a.*b',s).group()#這就是貪婪
re.search('a.*?b',s).group()#這就是懶惰

結果:

aabab
aab

簡單來說:

  • 所謂貪婪,就是儘可能 的匹配;

  • 所謂懶惰,就是儘可能 的匹配。

  • *+{n,} 這些表示式屬於貪婪;

  • *?+?{n,}? 這些表示式就是懶惰(在貪婪的基礎上加上 ?)。

2、正則進階

2.1、捕獲分組

語法描述
(exp)匹配exp,並捕獲文字到自動命名的組裡
(?Pexp)匹配exp,並捕獲文字到名稱為 name 的組裡
(?:exp)匹配exp,不捕獲匹配的文字,也不給此分組分配組號
(?P=name)匹配之前由名為 name 的組匹配的文字

注意:在其他語言或者網上的一些正則工具中,分組命名的語法是 (?<name>exp)(?'name'exp) ,但在 Python 裡,這樣寫會報錯:This named group syntax is not supported in this regex dialect。Python 中正確的寫法是:(?P<name>exp)

示例一:

分組可以讓我們用一條正則提取出多個資訊,例如:

s ='姓名:張三;性別:男;電話:138123456789'
m=re.search('姓名[::](\w+).*?電話[::](\d{11})',s)
ifm:
name=m.group(1)
phone=m.group(2)
print(f'name:{name},phone:{phone}')

結果:

name:張三,phone:13812345678

示例二:

(?P<name>exp) 有時還是會用到的, (?P=name) 則很少情況下會用到。我想了一個 (?P=name) 的使用示例,給大家看下效果:

s='''
<name>張三</name>
<age>30</age>
<phone>138123456789</phone>
'''

pattern=r'<(?P<name>.*?)>(.*?)</(?P=name)>'
It=re.findall(pattern,s)

結果:

[('name','張三'),('age','30'),('phone','138123456789')]

2.2、零寬斷言

語法描述
(?=exp)匹配exp前面的位置
(?<=exp)匹配exp後面的位置
(?!exp)匹配後面跟的不是exp的位置
(?<!exp)匹配前面不是exp的位置

注意:正則中常用的前項界定 (?<=exp) 和前項否定界定 (?<!exp) 在 Python 中可能會報錯:look-behind requires fixed-width pattern,原因是 python 中 前項界定的表示式必須是定長的,看如下示例:

(?<=aaa)#正確
(?<=aaa|bbb)#正確
(?<=aaa|bb)#錯誤
(?<=\d+)#錯誤
(?<=\d{3})#正確

2.3、條件匹配

這大概是最複雜的正則表示式了。語法如下:

語法描述
(?(id/name)yes|no)如果指定分組存在,則匹配 yes 模式,否則匹配 no 模式

此語法極少用到,印象中只用過一次。

以下示例的要求是:如果以 _ 開頭,則以字母結尾,否則以數字結尾。

s1='_abcd'
s2='abc1'

pattern='(_)?[a-zA-Z]+(?(1)[a-zA-Z]|\d)'

re.search(pattern,s1).group()
re.search(pattern,s2).group()

結果:

_abcd
abc1

2.4、findall

Python 中的 re.findall 是個比較特別的方法(之所以說它特別,是跟我常用的 C# 做比較,在沒看註釋之前我想當然的掉坑裡去了)。我們看這個方法的官方註釋:

Returnalistofallnon-overlappingmatchesinthestring.

Ifoneormorecapturinggroupsarepresentinthepattern,return
alistofgroups;thiswillbealistoftuplesifthepattern
hasmorethanonegroup.

Emptymatchesareincludedintheresult.

簡單來說,就是

  • 如果沒有分組,則返回整條正則匹配結果的列表;

  • 如果有 1 個分組,則返回分組匹配到的結果的列表;

  • 如果有多個分組,則返回分組匹配到的結果的元組的列表。

看下面的例子:

s='aaa123bbb456ccc'

re.findall('[a-z]+\d+',s)#不包含分組
re.findall('[a-z]+(\d+)',s)#包含一個分組
re.findall('([a-z]+(\d+))',s)#包含多個分組
re.findall('(?:[a-z]+(\d+))',s)#?:不捕獲分組匹配結果

結果:

['aaa123','bbb456']
['123','456']
[('aaa123','123'),('bbb456','456')]
['123','456']

零寬斷言中講到 Python 中前項界定必須是定長的,這很不方便,但是配合 findall 有分組時只取分組結果的特性,就可以模擬出非定長前項界定的效果了。

結語

其實正則就像是一個數學公式,會背公式不一定會做題。但其實這公式一點也不難,至少比學校裡學的數學簡單多了,多練習幾次也就會了。

END


往期精選

geopandas,python畫地圖這麼簡單!

最全資料科學小抄,趕緊收藏吧!

教你如何使用Python製作酷炫二維碼

numba,讓python飛起來!
100行python程式碼爬取豆瓣《哪吒》短評

教你如何使用Python下載全網視訊

小白如何入門Python爬蟲

那些不為人知的優秀視覺化庫

Python機器學習·微教程

小白入門Python資料科學全教程

什麼是matplotlib?

一文搞懂python匿名函式

一文讀懂Python的map、reduce函式

一文搞懂python迭代器和生成器

Python大資料分析

data creatvalue

長按二維碼關注