1. 程式人生 > >Python中一些簡單的正則表示式(爬蟲所需(.*?))

Python中一些簡單的正則表示式(爬蟲所需(.*?))

這篇部落格旨在介紹使用爬蟲時一些常用的正則表示式。

在之前,我一直都是一個談正則表示式色變的人。因為正則表示式實在是太多太多,想要記得除非是經常用,否則也很難完全掌握其中所有的內容。所以這些東西都是現用現查,然後要一個一個的搜尋,將自己所需要的進行查詢。所以學習正則表示式都是根據實際需求來進行學習。

正好由於爬蟲的需要(因為爬蟲時HTML原始碼的結構非常規律,我們找到規律後根據規律來提取內容,就需要用到正則表示式),所以再次對爬蟲所需的一些正則表示式做了整理。(簡單超詳細的爬蟲教程傳送門

實際應用

在爬蟲中,我們通常是要根據我們目標欄位前後的對應關係,然後在整個HTML原始碼中找到與之相匹配位置進行抓取。在爬蟲中,通常文字的一部分長這樣(html的內容):

html = """
<div class="comment-item" data-cid="1340993183">
<div class="avatar">
<a href="https://www.douban.com/people/ydyie/" title="時間之葬">
<img class="" src="https://img1.doubanio.com/icon/u2126832-179.jpg"/>
</a>
</div>
<div class="comment">
<h3>
<span class="comment-vote">
<span class="votes">52</span>
<input type="hidden" value="1340993183"/>
<a class="j a_show_login" href="javascript:;" onclick="">有用</a>
</span>
<span class="comment-info">
<a class="" href="https://www.douban.com/people/ydyie/">時間之葬</a>
<span>看過</span>
<span class="allstar40 rating" title="推薦"></span>
<span class="comment-time " title="2018-03-14 10:59:11">
                    2018-03-14
                </span>
</span>
</h3>
<p class=""> 作為一個故事它邏輯漏洞太多,作為一個創世寓言它缺少一點完整的體系,作為一種視覺和裝置藝術它略顯單調,但好在它提供了一種重新看待人類與萬物之間關係的可能
        </p>
</div>
</div>
"""

我們如果提取它其中的短評,也就是下述內容:

作為一個故事它邏輯漏洞太多,作為一個創世寓言它缺少一點完整的體系,作為一種視覺和裝置藝術它略顯單調,但好在它提供了一種重新看待人類與萬物之間關係的可能

需要通過 <p class=""></p> 這兩個字串來進行查詢,中間夾著的部分就是我們所需要的。其Python實現程式碼如下:

import re
re.findall(r'<p class=""> (.*?)<', html, re.S) # 使用 r'' 表示原始字串不需要轉義

一步一步來解析,首先看我們需要的是兩個字串中間夾著的內容,這裡使用的是 '<p class=""> (.*?)<'

,所以關鍵是 (.*?) 這個語句,其實它就是指代符合前面提到兩個字元中中間夾著的內容。

那裡面的 ().*? ,分別代表什麼呢?下面我們一步一步來執行,來進行探索發現。首先規定兩個字串:

str1 = 'aababaaba'

然後我們一個一個來進行嘗試。

. 匹配除換行符以外的任意一個字元

輸入:

>>> re.findall(r'a.b', str1)

輸出:

['aab', 'aab']

單純的一個 . 就是匹配除 \n (換行符)以外的任意一個字元。 a.b 在原字串 'aababaaba' 中,從左到右掃,第一個字母是a,就找第三個是不是b,發現確實是的,就將aab提取出來。然後接著掃,第四個字母是a,但是第六個不是b,所以不做任何操作。直到第六個字母是a且第八個是b,就將它們提取出來,同樣也是aab。後面沒有滿足條件的了,所以最終結果就是['aab', 'aab']

* 前面那個字元,重複0次或更多次

輸入:

>>> re.findall(r'a*b', str1)

輸出:

['aab', 'ab', 'aab']

在例子中,* 前面跟著a,所以a重複幾次都會被提取出來。如果有aaaaaaaaaaab,也一樣會被提取出來。

.* 貪婪演算法,儘可能重複多次

輸入:

>>> re.findall(r'a.*b', str1)

輸出:

['aababaab']

.*組合在一塊兒,就變成了另一個意思。在例子中,就是從第一個字母開始掃,遇到a就開始擷取,然後找所有的b,最後找到以b結尾儘可能長的一段字串。

.*? 非貪婪演算法,遇到一次停一次

輸入:

>>> re.findall(r'a.*?b', str1)

輸出:

['aab', 'ab', 'aab']

只要是以a開始,以b結束,我們就進行擷取,從左至右掃描著擷取。相比前面的.,只是多了一個'ab',也就是ab之間不含任何元素,同樣會被提取出來。注意,這裡掃過的元素不會再被掃。

(.*?) 非貪婪演算法,遇到一次停一次,只保留括號中的內容

輸入:

>>> re.findall(r'a(.*?)b', str1)

輸出:

['a', '', 'a']

這個相對於上一步操作只多了一個括號。它的意思也非常容易理解,就是我們只保留括號中間的內容,兩邊的內容全部都不要,所以相對於上一個不帶括號的操作,就是將提取出來字串左邊的a與右邊的b全部都刪掉。

re.S

細心的童鞋發現了我們前面的程式碼中還用到了 re.S 引數,其表示什麼呢?

表示遇到 \n 也就是換行操作時,不對操作進行中斷。下面列出一個例子:

str2 = 'aababaaba \n ddsdab'

輸入:

>>> re.findall(r'a.*?b', str2, re.S)

輸出:

['aab', 'ab', 'aab', 'a \n ddsdab']

輸入:

>>> re.findall(r'a.*?b', str2)

輸出:

['aab', 'ab', 'aab', 'ab']

造成上面兩種情況差異的就是\n會不會影響前面先掃到的a。不加re.S相當於每次換行都要重新掃一遍。

所以大家應該知道為什麼爬取短評和評分需要使用這兩個正則表示式了嗎?

re.findall(r'<span class="comment-info">(.*?)<span class="comment-time "', html, re.S)
re.findall(r'<span class="allstar(.*?)0 rating"', item, re.S)

最後,正則表示式博大精深,這裡只是其冰山一角,以後還是要多多進行學習。