寫爬蟲,不會正則怎麼行?

寫爬蟲,不會正則怎麼行?
寫爬蟲,不會正則怎麼行?

1、正則基礎1.1、基礎語法1.2、修飾符1.3、貪婪與懶惰2、正則進階2.1、捕獲分組2.2、零寬斷言2.3、條件匹配2.4、findall結語

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

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

1、正則基礎
1.1、基礎語法
(1)常用元字符

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

語法 描述

  • 重複零次或更多次
  • 重複一次或更多次
    ? 重複零次或一次

{n} 重複 n 次
{n,} 重複 n 次或更多次
{n,m} 重複 n 到 m 次
(3)常用反義詞

語法 描述
B 匹配非單詞的開始或結束
D 匹配非數字
S 匹配任意可見字符, 1
W 匹配任意非 Unicode 字符集
2 除 a、b、c 以外的任意字符
(4)字符族

語法 描述
[abc] a、b 或 c
2 除 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&&3] a 到 z,除了 b 和 c:[ad-z](減去)
[a-z&&4] 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 = 'aaarnbbbrnccc'

re.findall('^[sw]*?$', s)
re.findall('^[sw]*?$', s, re.M)
結果:

['aaarnbbbrnccc']        # 單行模式
['aaar', 'bbbr', 'ccc']    # 多行模式
(3)re.S
這個簡單,直接看個例子。

s = 'aaarnbbbrnccc'

re.findall('^.*', s)
re.findall('^.*', s, re.S)
結果:

['aaar']
['aaarnbbbrnccc']
(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 的組匹配的文本
注意:在其他語言或者網上的一些正則工具中,分組命名的語法是 (?exp) 或 (?'name'exp),但在 Python 裏,這樣寫會報錯:This named group syntax is not supported in this regex dialect。Python 中正確的寫法是:(?Pexp)

示例一:

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

s = '姓名:張三;性別:男;電話:138123456789'
m = re.search('姓名::.*?電話::', s)
if m:
    name = m.group(1)
    phone = m.group(2)
    print(f'name:{name}, phone:{phone}')
結果:

name:張三, phone:13812345678
示例二:

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

s = '''
張三
30
138123456789
'''

pattern = r'<(?P.?)>(.?)(?P=name)>'
It = re.findall(pattern, s)
結果:

[('name', '張三'), ('age', '30'), ('phone', '138123456789')]
2.2、零寬斷言
語法 描述
(?=exp) 匹配exp前面的位置
(?<=exp) 匹配exp後面的位置
(?!exp) 匹配後面跟的不是exp的位置
(?注意:正則中常用的前項界定 (?<=exp) 和前項否定界定 (?

(?<=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# 做比較,在沒看註釋之前我想當然的掉坑裏去了)。我們看這個方法的官方註釋:

Return a list of all non-overlapping matches in the string.

If one or more capturing groups are present in the pattern, return 
a list of groups; this will be a list of tuples if the pattern 
has more than one group.

Empty matches are included in the result.
簡單來說,就是

如果沒有分組,則返回整條正則匹配結果的列表;
如果有 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 有分組時只取分組結果的特性,就可以模擬出非定長前項界定的效果了。

結語
其實正則就像是一個數學公式,會背公式不一定會做題。但其實這公式一點也不難,至少比學校裏學的數學簡單多了,多練習幾次也就會了。
原文地址https://www.cnblogs.com/gl1573/p/11363079.html


  1. fnrtv
  2. abc
  3. bc
  4. m-p
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章