當完成了網頁html的download以後,下一步固然是從網頁中解析咱們想要的數據了。那如何解析這些網頁呢?Python中有許多種操做簡單且高效的工具能夠協助咱們來解析html或者xml,學會這些工具抓取數據是很容易了。
html
說到爬蟲的html/xml解析(如今網頁大部分都是html),可以使用的方法實在有不少種,如:正則表達式
正則表達式express
BeautifulSoup編程
Lxml併發
PyQueryapp
CSSselectoride
其實也不止這幾種,還有不少,那麼到底哪種最好呢?這個很難說,蘿蔔白菜各有所愛,這些方法各有特點,只能說選擇一款你用着順手的。博主將會陸續給你們介紹這些好用的解析器,可是本篇從正則表達式開始。函數
那是否是隻要掌握一種就能夠了?用不着會那麼多吧。確實,熟練掌握一種也能夠完成數據的抓取,但隨着你解析網頁的數量增多,你會發現有時候使用多種方法配合解析網頁會更簡單,高效,由於這些方法各有特點,不一樣環境下發揮的做用不同。所以,建議你們熟練掌握至少兩種爲佳,這樣當你面對複雜結構網頁的時候,解析方法會更靈活。工具
好了,開始咱們的解析之旅吧!性能
--------------------------
正則表達式
--------------------------
正則表達式(regular expression)簡稱(regex), 是一種處理字符串的強大工具。它做爲一種字符串的匹配模式,用於查看指定字符串是否存在於被查找字符串中,替換指定字符串,或是經過匹配模式查找指定字符串。正則表達式在不一樣的語言裏面,語法也基本是相同的,也就是說學會了一種語言的正則,再學習其它的就很快了。
其主要的匹配過程是:
先用正則語法定義一個規則(pattern)
而後用這個規則與你download的網頁字符串進行對比,根據pattern提取你想要的數據。
好了,讓咱們看看Python正則表達式的語法:
好亂,看不懂!!!
彆着急,開始都是這樣的(固然會的小夥伴能夠直接跳過)。下面看幾個例子,你立刻就學會了。
--------------------------
你的第一個正則表達式
--------------------------
咱們舉一個常遇到的一個例子。好比,一我的的郵箱是這樣的lixiaomei@qq.com,那麼咱們如何從一大堆的字符串把它提取出來呢?
根據正則語法,咱們能夠這樣來定義一個pattern:\w+@\w+\.com
爲何這麼定義呢?讓咱們來看看。
"\w" 的意思是單詞字符[A-Za-z0-9_]。注意是 "單字符串",能夠是A-Z或者a-z或者0-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核心編程》一書,關注公衆號併發送 「學習資料」 即可輕鬆拿到。
--------------------------
re模塊核心函數
--------------------------
上面簡單的介紹了正則表達式的pattern是如何設置的,那麼下一步咱們就能夠開始咱們的提取工做了。在Python的re模塊中有幾個核心的函數專門用來進行匹配和查找。
compile()函數
函數定義: compile(pattern, flag=0)
函數描述:編譯正則表達式pattern,而後返回一個正則表達式對象。
爲何要對pattern進行編譯呢?《Python核心編程 》裏面是這樣解釋的:
使用預編譯的代碼對象比直接使用字符串要快,由於解釋器在執行字符串形式的代碼前都必須把字符串編譯成代碼對象。
一樣的概念也適用於正則表達式。在模式匹配發生以前,正則表達式模式必須編譯成正則表達式對象。因爲正則表達式在執行過程當中將進行屢次比較操做,所以強烈建議使用預編譯。並且,既然正則表達式的編譯是必需的,那麼使用預編譯來提高執行性能無疑是明智之舉。re.compile()可以提供此功能。
原來是這樣,因爲compile的使用很簡單,因此將在如下幾個匹配查找的函數使用方法中體現。
match()函數
函數定義: match(pattern, string, flag=0)
函數描述:只從字符串的最開始與pattern進行匹配,匹配成功返回匹配對象(只有一個結果),不然返回None。
re s1 s2 pattern result1 re.matchpattern, s1result2 re.matchpattern, s2result1result2
注意:
match函數是從最開始匹配的,意思是若是第一個字符就不匹配,那就直接玩完,返回None。
Python中pattern字符串前面的 " r " 表明了原生字符串的意思。
問題來了,爲何result1結果有這麼多的東西啊?貌似最後一個纔是要匹配的對象。這個要怎麼提取出來呀?彆着急,咱們如今獲得的是匹配對象,須要用必定的方法提取,咱們後面會在《匹配對象的方法》章節來解決這個問題,繼續往下看。
search()函數
函數定義: search(pattern, string, flag=0)
函數描述:與match()工做的方式同樣,可是search()不是從最開始匹配的,而是從任意位置查找第一次匹配的內容。若是全部的字串都沒有匹配成功,返回None,不然返回匹配對象。
re s1 s2 pattern pattern_compile re.compilepatternresult1 re.searchpattern_compile, s1result2 re.searchpattern_compile, s2result1result2
能夠看到不管字符串最開始是否匹配pattern,只要在字符串中找到匹配的部分就會做爲結果返回(注意是第一次匹配的對象)。
findall()函數
函數定義: findall(pattern, string [,flags])
函數描述:查找字符串中全部(非重複)出現的正則表達式模式,並返回一個匹配列表
re s1 s2 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s2result2 re.searchpattern_compile, s1result1result2result3
上面同時列出了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方法
方法定義:group(num=0)
方法描述:返回整個的匹配對象,或者特殊編號的字組
re s1 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s1.groupresult2 re.matchpattern_compile, s1.startresult3 re.matchpattern_compile, s1.endresult4 re.matchpattern_compile, s1.spanresult1result2result3result4
這樣匹配字符串就提取出來了。
re s1 pattern pattern_compile re.compilepatternresult1 re.matchpattern_compile, s1.groupresult2 re.matchpattern_compile, s1.groupresult3 re.matchpattern_compile, s1.groupresult1result2result3
這裏就須要用到咱們以前提到的分組概念。
分組的意義在於:咱們不只僅想獲得匹配的整個字符串,咱們還想獲得整個字符串裏面的特定子字符串。
如上例中,整個字符串是「我12345+abcde」,可是想獲得 「abcde」,咱們就能夠用括號括起來。所以,你能夠對pattern進行任何的分組,提取你想獲得的內容。
另外,若是匹配對象時None,那麼繼續使用匹配對象方法會報錯AttributeError,所以也建議使用except異常來處理。
groups方法
方法定義:groups(default =None)
方法描述:返回一個含有全部匹配子組的元組,匹配失敗則返回空元組
re s1 pattern pattern_compile re.compilepatternresult1 re.searchpattern_compile, s1.groupsresult1
--------------------------
re模塊的屬性
--------------------------
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爲例:
re s1 pattern pattern_compile re.compilepattern, re.IGNORECASEresult1 re.findallpattern, s1result1
re s1 pattern result1 re.findallpattern, s1, re.IGNORECASEresult1
這裏注意:
若是咱們定義了compile編譯,須要將flag填到compile函數中,不然填到匹配函數中會報錯
若是沒有定義compile,則能夠直接在匹配函數findall中填寫flag