做者:xiaoyu
微信公衆號:Python數據科學
知乎:Python數據分析師html
當完成了網頁html的download以後,下一步固然是從網頁中解析咱們想要的數據了。那如何解析這些網頁呢?Python中有許多種操做簡單且高效的工具能夠協助咱們來解析html或者xml,學會這些工具抓取數據是很容易了。python
說到爬蟲的html/xml解析(如今網頁大部分都是html),可以使用的方法實在有不少種,如:正則表達式
其實也不止這幾種,還有不少,那麼到底哪種最好呢?這個很難說,蘿蔔白菜各有所愛,這些方法各有特點,只能說選擇一款你用着順手的。博主將會陸續給你們介紹這些好用的解析器,可是本篇從正則表達式
開始。express
那是否是隻要掌握一種就能夠了?用不着會那麼多吧。確實,熟練掌握一種也能夠完成數據的抓取,但隨着你解析網頁的數量增多,你會發現有時候使用多種方法配合解析網頁會更簡單,高效,由於這些方法各有特點,不一樣環境下發揮的做用不同。所以,建議你們熟練掌握至少兩種爲佳,這樣當你面對複雜結構網頁的時候,解析方法會更靈活。編程
好了,開始咱們的解析之旅吧!微信
正則表達式
(regular expression)簡稱(regex), 是一種處理字符串的強大工具。它做爲一種字符串的匹配模式,用於查看指定字符串是否存在於被查找字符串中,替換指定字符串,或是經過匹配模式查找指定字符串。正則表達式在不一樣的語言裏面,語法也基本是相同的,也就是說學會了一種語言的正則,再學習其它的就很快了。網絡
其主要的匹配過程
是:併發
好了,讓咱們看看Python正則表達式的語法
:
函數
彆着急,開始都是這樣的(固然會的小夥伴能夠直接跳過)。下面看幾個例子,你立刻就學會了。工具
咱們舉一個常遇到的一個例子。好比,一我的的郵箱是這樣的lixiaomei@qq.com
,那麼咱們如何從一大堆的字符串把它提取出來呢?
根據正則語法,咱們能夠這樣來定義一個pattern:\w+@\w+\.com
爲何這麼定義呢?讓咱們來看看。
"\w"
的意思是單詞字符[A-Za-z0-9_]
。注意是"+"
匹配前一個字符1次或無限次。那麼 "\w+"
[A-Za-z0-9_]
組合的字符串。"@"
是郵箱的特定字符,因此固定不變。"\w+"
與前一個是一個道理,匹配一次或無限次的[A-Za-z0-9_]
組合的字符串。" \. "
的含義是將" . "
轉義,由於 " . "
自己也是正則語法中的其中一種,爲了真的獲得 ".com"
" . "
, 因此在前面加上 "\"
轉義字符。因此,不管是例子中的 lixiaomei@qq.com
,仍是其它如xiaoxiao@126.com
之類的郵箱,只要符合規則全均可以匹配,怎麼樣,簡單吧!
問題來了,有的郵箱格式但是xiaoxiao@xxx.xxx.com
這樣的!這樣的話上面的規則就不能用了。沒錯,上面的規則比較特殊,只能匹配單一格式的郵箱名。那麼怎樣設計一個知足以上兩種格式的pattern呢?看看這個:\w+@(\w+\.)?\w+\.com
這個又是什麼意思?
\w+@
與以前同樣(\w+\.)?
中的「 ? 」
是匹配0次或1次括號分組內的匹配內容,"()"
則表示被括內容是一個分組,分組序號從pattern字符串起始日後依次排列。分組的概念很是重要,在後面 「匹配對象方法」 章節會着重介紹其如何使用。 \w+\.com
與以前同樣由於是匹配0次或1次,那麼就意味着括號內的部分是無關緊要的,因此這個pattern就可能匹配兩種郵箱格式。
「?」
是0次或1次,那麼 \w+@(\w+\.)*\w+\.com
模式就更厲害了," * "
能夠匹配0次或無限次。
明白了這個以後,相信你應該對正則表達式有一個概念了,但還有不少種語法以及組合方法須要在實踐中反覆練習。這裏只介紹Python中正則表達式比較常見的匹配模式,更多內容可參考《Python核心編程》
一書,關注微信公衆號Python網絡爬蟲
併發送 「學習資料」 即可輕鬆獲得。
上面簡單的介紹了正則表達式的pattern
是如何設置的,那麼下一步咱們就能夠開始咱們的提取工做了。在Python的re模塊
中有幾個核心的函數
專門用來進行匹配和查找。
compile(pattern, flag=0)
爲何要對pattern進行編譯呢?《Python核心編程》
裏面是這樣解釋的:
使用預編譯的代碼對象比直接使用字符串要快,由於解釋器在執行字符串形式的代碼前都必須把字符串編譯成代碼對象。一樣的概念也適用於正則表達式。在模式匹配發生以前,正則表達式模式必須編譯成正則表達式對象。因爲正則表達式在執行過程當中將進行屢次比較操做,所以強烈建議使用預編譯。並且,既然正則表達式的編譯是必需的,那麼使用預編譯來提高執行性能無疑是明智之舉。re.compile()可以提供此功能。
原來是這樣啊,因爲compile
的使用很簡單,因此將在如下幾個匹配查找的函數使用方法中體現。
match(pattern, string, flag=0)
最開始
與pattern進行匹配,匹配成功返回匹配對象(只有一個結果),不然返回None。import re s1 = '我12345abcde' s2 = '.12345abcde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'\w.+' # 編譯pattern pattern_compile = re.compile(pattern) # 對s1和s2分別匹配 result1 = re.match(pattern, s1) result2 = re.match(pattern, s2) print(result1) print(result2) >>> <_sre.SRE_Match object; span=(0, 11), match='我12345abcde'> >>> None
注意:
最開始
匹配的,意思是若是第一個字符就不匹配,那就直接玩完,返回None
。" r "
表明了原生字符串
的意思。
問題來了,爲何result1結果有這麼多的東西啊?貌似最後一個纔是要匹配的對象。這個要怎麼提取出來呀?
彆着急,咱們如今獲得的是匹配對象
,須要用必定的方法提取,咱們後面會在《匹配對象的方法》
章節來解決這個問題,繼續往下看。
search(pattern, string, flag=0
)match()
工做的方式同樣,可是search()不是從最開始匹配的
,而是從任意位置查找第一次匹配的內容。
若是全部的字串都沒有匹配成功,返回None,不然返回匹配對象。import re s1 = '我12345abcde' s2 = '+?!@12345abcde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'\w.+' pattern_compile = re.compile(pattern) result1 = re.search(pattern_compile, s1) result2 = re.search(pattern_compile, s2) print(result1) print(result2) >>> <_sre.SRE_Match object; span=(0, 11), match='我12345abcde'> >>> <_sre.SRE_Match object; span=(4, 14), match='12345abcde'>
能夠看到不管字符串最開始是否匹配pattern,只要在字符串中找到匹配的部分就會做爲結果返回(注意是第一次匹配的對象
)。
findall(pattern, string [,flags])
非重複
)出現的正則表達式模式,並返回一個匹配列表
import re s1 = '我12345abcde' s2 = '+?!@12345abcde@786ty' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'\d+' pattern_compile = re.compile(pattern) result1 = re.match(pattern_compile, s2) result2 = re.search(pattern_compile, s1) result3 = re.findall(pattern_compile, s2) print(result1) print(result2) print(result3) >>> None >>> <_sre.SRE_Match object; span=(1, 6), match='12345'> >>> ['12345', '786']
上面同時列出了match、search、findall
三個函數用法。findall與match和search不一樣的地方是它會返回一個全部無重複匹配的列表。若是沒找到匹配部分,就返回一個空列表。
以上re模塊函數的返回內容能夠分爲兩種:
<_sre.SRE_Match object; span=(0, 5), match='12345'>
這樣的對象,可返回匹配對象的函數有match、search、finditer
。findall
。所以匹配對象的方法只適用match、search、finditer
,而不適用與findall
。
經常使用的匹配對象方法有這兩個:group、groups
、還有幾個關於位置的如 start、end、span
就在代碼裏描述了。
group(num=0)
import re s1 = '我12345+abcde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'\w+' pattern_compile = re.compile(pattern) # 返回匹配的字符串 result1 = re.match(pattern_compile, s1).group() # 返回匹配開始的位置 result2 = re.match(pattern_compile, s1).start() # 返回匹配結束的位置 result3 = re.match(pattern_compile, s1).end() # 返回一個元組包含匹配 (開始,結束) 的位置 result4 = re.match(pattern_compile, s1).span() print(result1) print(result2) print(result3) print(result4) >>> 我12345 >>> 0 >>> 6 >>> (0, 6)
這樣匹配字符串就提取出來了,再來看看下面這種狀況。
import re s1 = '我12345+abcde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern) # 返回匹配的整個字符串 result1 = re.match(pattern_compile, s1).group() # 返回匹配的第一個子組字符串 result2 = re.match(pattern_compile, s1).group(1) # 返回匹配的第二個子組字符串 result3 = re.match(pattern_compile, s1).group(2) print(result1) print(result2) print(result3) >>> 我12345+abcde >>> 我12345 >>> abcde
這裏就須要用到咱們以前提到的分組
概念。
分組的意義在於:咱們不只僅想獲得匹配的整個字符串,咱們還想獲得整個字符串裏面的特定子字符串。
如上例中,整個字符串是「我12345+abcde」
,可是想獲得 「abcde」
,咱們就能夠用括號()
括起來。所以,你能夠對pattern
進行任何的分組,提取你想獲得的內容。
另外,若是匹配對象時None
,那麼繼續使用匹配對象方法會報錯AttributeErro
r,所以也建議使用except
異常來處理。
groups(default =None)
import re s1 = '我12345+abcde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern) # 返回含有全部子組的元組 result1 = re.search(pattern_compile, s1).groups() print(result1) >>> ('我12345', 'abcde')
re模塊的經常使用屬性有如下幾個:
re.I 或者 re.IGNORECASE
匹配不分大小寫;re.L 或者 re.LOCALE
根據使用的本地語言環境經過\w, \W, \b, \B, \s, \S
實現匹配;re.M 或者 re.MULTILINE
^和$分別匹配目標字符串中行的起始和結尾,而不是嚴格匹配整個字符串自己的起始和結尾;re.S 或者 rer.DOTALL
「.」(點號)一般匹配除了n(換行符)以外的全部單個字符;該標記表示「.」(點號)可以匹配所有字符;re.X 或者re.VERBOSE
經過反斜線轉義,不然全部空格加上#(以及在該行中全部後續文字)都被忽略,除非在一個字符類中或者容許註釋而且提升可讀性;其實re模塊的屬性就是函數中的flag
參數,以第一個大小寫flag爲例:
import re s1 = '我12345+aBCde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'(\w+)\+(\w+)' pattern_compile = re.compile(pattern, re.IGNORECASE) # 返回一個匹配的列表 result1 = re.findall(pattern, s1) print(result1) >>> [('我12345', 'abcde')] import re s1 = '我12345+aBCde' # pattern字符串前加 「 r 」 表示原生字符串 pattern = r'(\w+)\+(\w+)' # 返回一個匹配的列表 result1 = re.findall(pattern, s1, re.IGNORECASE) print(result1) >>> [('我12345', 'abcde')]
這裏注意:
compile編譯
,須要先將flag
填到compile
函數中,不然填到匹配函數中會報錯compile
,則能夠直接在匹配函數findall中填寫flag
本篇介紹正則表達式的快速入門方法,關於更多正則表達式的內容能夠參考以下連接:
https://docs.python.org/2/library/re.html
學習資料
」關注微信公衆號Python數據科學,獲取 120G
人工智能 學習資料。