文章目錄
數據處理
爬蟲爬取的數據我們可以大致分為非結構化語言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.parser 、xml 、html5lib |
find() |
尋找元素,優先找最先出現的第一個 |
find_all() |
尋找所以元素,將元素已返回值形式返回 |
.select() |
可以通過class等方式更加准確的定位元素 |
.string |
獲取標簽中的值 |
.strings |
獲取多行標簽中的值 |
.stripped_strings |
獲取多行標簽中的值並且去除空格 |