一篇文章看懂如何對爬取的數據進行處理(正則,json,lxml,Beautiful Soup) = ̄ω ̄= Python爬蟲

數據處理

爬蟲爬取的數據我們可以大致分爲非結構化語言HTML與結構化語言json與XML。

Python中的正則表達式

正則表達式(regular expression): 一種廣泛用於匹配字符串的工具。它用一個“字符串”來描述一個特徵,然後去驗證另一個“字符串”是否符合這個特徵。

元字符

元字符 含義
. 在默認模式,匹配除了換行的任意字符
| 邏輯或操作符
[] 匹配字符集中的一個字符
[^] 匹配字符集的取反中的一個字符
[A-B] 匹配從A到B的字符
\ 對緊跟其後的字符轉義
() 對錶達式進行分組,將圓括號內的內容當做一個整體,並獲得匹配的值

重複匹配

重複匹配 含義
{n} 表達式重複n次
{m,n} 表達式至少重複m次,最多重複n次
{m,} 表達式至少重複m次
* 對它前面的正則式匹配0到任意次重複。相當於{0,}
+ 對它前面的正則式匹配1到任意次重複{1,}
? 對它前面的正則式匹配0到1次重複 {0,1}

注:重複匹配時正則會默認使用貪婪模式,即儘量多的匹配,如果需要非貪婪匹配可以在重複匹配後加?解決

data = '11A23456a232e'
貪婪模式 = re.search(r'2.*2', data)
非貪婪模式 = re.search(r'2.*?2', data)
print('貪婪模式:', 貪婪模式.group(), '非貪婪模式:', 非貪婪模式.group())
# 貪婪模式: 23456a232 非貪婪模式: 23456a2

位置匹配

位置匹配 含義
^ 匹配字符串的開頭
$ 匹配字符串尾或者換行符的前一個字符

預定意義字符

預定意義字符 意義
\d 匹配任何Unicode十進制數
\D 匹配任何非十進制數字的字符
\w 包含了可以構成詞語的絕大部分字符,也包括數字和下劃線
\W 匹配任何不是單詞字符的字符。
\s 匹配任何Unicode空白字符(包括 [ \t\n\r\f\v] ,還有很多其他字符,比如不同語言排版規則約定的不換行空格)
\b 匹配空字符串,但只在單詞開始或結尾的位置。(比如r’\bx\b’可以匹配’x’, ‘x.’, ‘a x b’, (x); 但如果是’axb’或’x1’就無法匹配)
\B 與\b取反
\A 只匹配字符串開始。相當於^
\Z 只匹配字符串尾。相當於$

預定字符在不同模式下會有不同的表現方式,具體可以看官網文檔。以上所有意義都是在Unicode模式下的情況。(如果使用預定意義字符,其中\會被認爲爲轉義,所有加入我們需要使用\d時候,需要輸入\d或者可以使用r’‘來取消轉義,比如r’\d’)

常用正則表達式

功能 表達式
Email地址 ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
域名 [a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
手機號碼 ^(13[0-9]
身份證號 ^\d{15}
日期格式 ^\d{4}-\d{1,2}-\d{1,2}
空白行的正則表達式 \n\s*\r (可以用來刪除空白行)
IP地址提取 \d+.\d+.\d+.\d+ (提取IP地址時有用)
騰訊QQ號 [1-9][0-9]{4,} (騰訊QQ號從10000開始)

re庫

re庫是Python完整支持正則表達式的內置庫。

re庫三大搜索方法

re庫方法 作用
.match(正則表達式,要匹配的字符串,[匹配模式]) 嘗試從字符串的起始位置匹配一個模式(單值匹配,找到一處即返回)
.search(正則表達式,要匹配的字符串,[flags=匹配模式]) 從文本中查找,(單值匹配,找到一處即返回)
.findall(正則表達式,要匹配的字符串,[flags=匹配模式]) 全文匹配,找到字符串中所以匹配的對象並且已列表形式返回
.compile(正則表達式,[flags=匹配模式]) 使用此方法創建的正則表達式時候時查找效率更高
.split(正則表達式,要匹配的字符串,maxsplit=最大拆分次數,[flags=匹配模式]) 和Python內置的.split方法類似,按照指定的正則將字符串拆封
.sub(正則表達式,取代對象,要匹配的字符串,maxsplit=最大取代次數,[flags=匹配模式]) 在指定的字符串中根據正則匹配的內容進行取代替換

flag匹配模式

flag匹配模式 作用
re.A ASCII字符模式
re.I 使匹配對大小寫不敏感,也就是不區分大小寫的模式
re.L 做本地化識別(locale-aware)匹配
re.M 多行匹配,影響 ^ 和 $
re.S 使 . 這個通配符能夠匹配包括換行在內的所有字符,針對多行匹配
re.U 根據Unicode字符集解析字符。這個標誌影響 \w, \W, \b, \B
re.X 該標誌通過給予你更靈活的格式以便你將正則表達式寫得更易於理解(支持將正則表達式用三引號寫成多行模式)
data = 'week哈_end,endfor11A23456a23,end'
# 使用compile方法將正則表達式和匹配模式寫入
regular = re.compile(r'.a23', re.I)
# match,search,findall(正則表達式三大搜索函數)
regular1 = regular.match(data)
regular2 = regular.search(data)
regular3 = regular.findall(data)
print(regular1, regular2, regular3)
# 使用split分組
split = regular.split(data)
# 使用sub替換
sub = regular.sub('替換物', data)
print(split, sub)

分組

使用小括號()分組後的正則表達式可以使用分組

分組使用的方法 作用
() 對錶達式進行分組,將圓括號內的內容當做一個整體,並獲得匹配的值
.group(組數) 取出搜索內容並進行匹配
.groups() 將所以分組以列表形式取出
.groupdict() 需要和(?P…)配合使用
data = '<html>我是一個標籤</html>'
# \1表示第一組中的內容(?P<name>\w*),第一組獲得的值是html,也就是說\1=html
regular = re.compile(r'<(\w*)>(?P<data>.*)<(/\1)>')
regular1 = regular.match(data)
regular2 = regular.search(data)
regular3 = regular.findall(data)
# group默認取出的爲所以組,跟上數字後即可指定取出的組
print(regular1.group(2), regular2.group(2))
# 分組後findall不支持group方法,但其值和groups相似,都會取出所有組,groupdict方法必須在有(?P<命名>正則表達式)的時候才能生效
print(regular3, regular2.groups(), regular2.groupdict())

json

json是一種非常常見的用於數據存儲和傳輸的數據交換格式,當前大多數網站都會用到json進行數據交換,並且json可以轉換爲Python內建數據類型,便於數據的處理。

Python的json庫

Python提供的json庫可以方便快捷的讓我們對json數據類型與Python內建數據類型快速互相轉換。下列代碼中,我們先拿到一個返回json數據類型的免費的api接口作爲測試.使用.loads()方法將json數據類型對象轉換成爲Python數據類型對象,使用.dumps()將Python數據類型對象轉換成爲json數據類型。(.load().dump()方法可以將json文件和Python文件中的數據類型相互轉換,使用頻率較少,如有需要自行測試)

import requests
import json


class DataProcessing:
    def __init__(self):
        # 使用彩雲天氣測試免費用的api接口讀取一個json數據
        self.api_url_json = 'https://api.caiyunapp.com/v2/TAkhjf8d1nlSlspN/121.6544,25.1552/realtime.json'
        self.header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4029.0 Safari/537.36'}

    def get_json(self):
        """獲取json對象的網頁"""
        json_data = requests.get(self.api_url_json, self.header)
        print('\n我是從網站中抓取出來的數據:', json_data.text, '我當前的數據類型是:', type(json_data.text))
        return json_data

    def json_change_dict(self, j):
        json_data = j
        dict_data = json.loads(json_data.text)
        print('我是經過.loads轉換後的數據類型(將json數據類型對象轉換爲Python數據類型對象):', dict_data, '我當前的數據類型是:', type(dict_data))
        return dict_data

    def dict_change_json(self, dic):
        dict_data = dic
        json_data = json.dumps(dict_data)
        print('我是經過.dumps轉換後的數據類型(將Python數據類型對象轉換爲json數據類型對象):', json_data, '我當前的數據類型是:', type(json_data))
        return json_data

    def main(self):
        """主函數"""
        # 網頁中的數據
        web_data = self.get_json()
        # 將網頁中的json數據通過.loads()方法轉換爲Python數據類型
        dict_data = self.json_change_dict(web_data)
        # 將代碼中的Python數據類型轉換爲json數據類型.dumps()
        json_data = self.dict_change_json(dict_data)


if __name__ == '__main__':
    item = DataProcessing()
    item.main()

XML

對於大多數人程序員來說或多或少都接觸過HTML,當我們初看XML時會覺得他很像HTML,都是用一組組標籤構成的,不過相對於HTML,XML有着更加嚴格的語法。而且HTML和XML應用方向也完全不同。

XML 被設計用來傳輸和存儲數據。
HTML 被設計用來顯示數據。

當前包括在我常用的博客(csdn在內)用XML傳輸數據的地方非常普遍

<root>
<p lang="zh"></p>
<p>有沒有</p>
<p>很像</p>
<p>HTML</p>
</root>

xpath

xpath是專門用於快速定位特定HTML和XML元素以及獲取節點信息的一種工具。在XML或者HTML中每一對標籤都是一個結點,其中常用的有根結點、文檔結點與屬性結點
上述xml中<root>根結點,<p>我</p>元素結點,文本結點,lang="zh"屬性結點
xpath大多數就是根據上述結點定位信息。

xpath下載

雖然大多數瀏覽器的控制檯中都支持複製xpath,但流程較爲繁瑣,我在這安利兩個火狐中的xpath,並且之後很多很多測試我也會基於這兩個工具進行。
Try XPath - 可以根據指定的xpath在瀏覽器中定位元素的工具
在這裏插入圖片描述
xPath Finder - 快速找到xpath的工具(如果不嫌麻煩可以使用瀏覽器自己生成的xpath)
在這裏插入圖片描述

xpath的常用語法

結點選擇 作用
/ 從根節點選取
// 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置
. 選取當前節點
選取當前節點的父節點
@ 選取
* 匹配任何元素結點。
@* 匹配任何屬性結點。
node() 匹配任何類型的節點。
/root/element[1] 選取屬於 root子元素的第一個 element元素。
/root/element[last()] 選取屬於 root子元素的最後一個 element元素。
/root/element[last()-1] 選取屬於 root子元素的倒數第二個 element元素。
/root/element[position()<3] 選取最前面的兩個屬於 root元素的子元素的 element元素。
//element[@name] 選取所有擁有名爲 name的屬性的 element元素。
//element[@name='xunmi'] 選取所有 element元素,且這些元素擁有值爲 xunmi的 name屬性。
/root/element[num>3] 選取 root元素的所有 element元素,且其中的 num元素的值須大於 3。
text() 獲取文本結點的內容

微軟官網有更多更詳細的xpath用法
首先是常規使用,我們從根目錄下尋找/html/body/header/div使用
在這裏插入圖片描述
如果當前頁面的存在獨一無二的屬性我們可以直接使用該屬性來快速定位指定結點
//*[@id="toolber-keyword"]
在這裏插入圖片描述
同一個地方可以有多種不同的xpath表達方式,就比如下圖中可以用多種xpath表達
//div[@class="article-list"]/div[1]
/html/body/div[6]/main/div[2]/div[1]
//div[6]/main/div[2]/div[1]
在這裏插入圖片描述

lxml庫

lxml 是 一個HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 數據。
利用etree.HTML,將字符串轉化爲Element對象(轉換時,當HTML標籤缺少時,會自動修復)
etree.tostring(etree.HTML(html代碼))使用以上方法即可修復標籤缺少的HTML。
先將頁面使用etree.HTML(html頁面)方法轉換後,即可使用.xpath()方法,如果需要讀取文字,這需要使用text()來獲取文本結點的內容

from lxml import etree
import requests

class xpath:
    def __init__(self):
        self.url = 'https://www.qq.com/'
        self.header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0'}

    def get_Blog(self):
        html = requests.get(self.url, self.header)
        return html.text

    def data_list(self, html_str):
        html = etree.HTML(html_str)
        items = html.xpath('//div[@class="nav-mod cf"]/ul/li/a/text()')
        return items

    def main(self):
        html = self.get_Blog()
        print(self.data_list(html))


if __name__ == '__main__':
    start = xpath().main()

Beautiful Soup

Beautiful Soup和lxml比較類似,但使用起來更加簡單,如果你之前對前端有所瞭解特別是對css比較熟悉,那麼它對於你來說應該會非常容易上手使用。但Beautiful Soup相較於正則和lxml在效率上略低一些。
Beautiful Soup的官方文檔非常詳細,這裏就簡單介紹一下它的幾個常用的方法。
首先我們需要使用.BeautifulSoup()方法將html轉化爲bs4.BeautifulSoup對象,
官方推薦使用lxmlBeautifulSoup(html, "lxml")因爲lxml更加高效,速度更快,但如果使用lxml需要下載lxml庫,可以使用html.parser,這是一個Python的內置庫,所以無需下載即可使用BeautifulSoup(markup, "html.parser")
轉化後就可以使用find()find_all()定位元素,方便快捷
使用.string可以獲取單個標籤中的值(如果是 find_all()取出的多個元素,使用會報錯,可以遍歷後在使用)如果tag中包含多個字符串,可以使用 .strings 獲取多行值,如果需要去除多行中的空格與換行可以使用.stripped_strings
.select()也是常用的方法之一,他可以通過css樣式來更準確的定位元素

class BS4:
    def __init__(self):
        self.html = """
        <html>
        <head>
        <title>我是標籤</title>
        </head>
        
        <body>
            <div class="idiv">
                <p>'我是p標籤' 
                '我有一個換行符'</p>
                <p>我是p標籤</p>
            </div>
            <div class="i">
                <ul>
                    <li class="text1 text2">class測試1</li>
                    <li class="text2">class測試2</li>
                    <li class="text3">class測試3</li>
                    <li id="text">測試</li>
                </ul>
            </div>
        </body>
        </html>
        """

    def data_processing(self, html):
        data = BeautifulSoup(html, 'lxml')
        # 使用html標籤快速查找
        item1 = data.title
        item2 = data.find('p')
        items = data.find_all("li")
        # .string可以獲取標籤中的值
        print(item1, '\n', item2.string, '\n', items)
		# 使用select方法可以更加精準的定位,items1與items2在這裏定位到的對象相同
        items1 = data.select('div[class="idiv"], div[class="i"]')
        items2 = data.select('.idiv, .i')
        for i in items1:
            # 多行輸出
            print(list(i.strings))
        for i in items2:
            # 去除空格和換行符的多行輸出
            print(list(i.stripped_strings))

    def run(self):
        """運行"""
        self.data_processing(self.html)


if __name__ == '__main__':
    BS4().run()
Beautiful Soup常用方法小結 作用
.BeautifulSoup() 轉化html對象,官方推薦lxml,還可以使用html.parserxmlhtml5lib
find() 尋找元素,優先找最先出現的第一個
find_all() 尋找所以元素,將元素已返回值形式返回
.select() 可以通過class等方式更加準確的定位元素
.string 獲取標籤中的值
.strings 獲取多行標籤中的值
.stripped_strings 獲取多行標籤中的值並且去除空格
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章