目錄
一、正則
1 正則基礎
元字符
基本使用
通配符: '.'
字符集: '[]'
重復
位置
管道符和括號
轉義符
轉義功能
轉義元字符
2 正則進階
元字符組合(常用)
模式修正符
?re模塊的方法
有名分組
compile編譯
二、bs4
1 四種對象
2 導航文檔樹
嵌套選擇
子節點、子孫節點
父節點、祖先節點
兄弟節點
3 搜索文檔樹
name參數
? 標簽名
? 正則表達式
? 列表
? 函數(精致過濾)
關鍵字參數
文本參數
4 CSS選擇器
三、xpath
1 路徑表達式
2 謂語
3 通配符
4 其他用法
5 文件操作
文件句柄
讀操作
寫操作?
覆蓋寫
追加寫
一、正則
是描述一段文本排列規則的表達式
正則表達式并不是python的一部分,而是一套獨立于編程語言,用于處理復雜文本信息的強大的高級文本操作工具。
python提供re模塊或regex模塊來調用正則處理引擎
正則對字符串的操作:分割、匹配、查找和替換
1 正則基礎
元字符
元字符是具有特殊含義的字符
元字符 | 描述 |
---|---|
[] | 匹配一個中括號中出現的任意一個原子 |
[^原子] | 匹配一個沒有在中括號中出現的任意原子 |
\ | 轉義字符,可以把原子轉換特殊元字符,也可以把特殊元字符轉換成原子 |
^ | 叫開始邊界符或開始錨點符,匹配一行的開頭位置 |
$ | 叫結束邊界符或開始錨點符,匹配一行的結束位置 |
. | 叫通配符、萬能通配符或通配元字符,匹配一個除了換行符\n以外任何原子 |
* | 叫星號貪婪符,指定左邊原子出現0次或多次 |
? | 叫非貪婪符,指定左邊原子出現0次或1次 |
+ | 叫加號貪婪符,指定左邊原子出現1次或多次 |
{n,m} | 叫數量范圍貪婪符,指定左邊原子的數量范圍,有{n},{n,},{,m},{n,m}四種寫法, 其中n與m必須是非負整數 |
| | 指定原子或正則模式進行二選一或多選一 |
() | 對原子或正則模式進行捕獲提取和分組劃分整體操作 |
舉例和一些特殊組合如下所示
基本使用
import reret1 = re.findall("a", "a,b,c,d,e") # ['a']
通配符: '.'
ret2 = re.findall(".", "a,b,c,d,e") # ['a', ',', 'b', ',', 'c', ',', 'd', ',', 'e']
ret2 = re.findall("a.b", "a,b,c,d,e,acb,abb,a\nb,a\tb") # ['a,b', 'acb', 'abb', 'a\tb']
字符集: '[]'
ret3 = re.findall("[ace]", "a,b,c,d,e") # ['a', 'c', 'e']
ret3 = re.findall("a[bce]f", "af,abf,abbf,acef,aef") # ['abf', 'aef']
ret3 = re.findall("[a-zA-Z]", "a,b,c,d,e,A,V") # ['a', 'b', 'c', 'd', 'e', 'A', 'V']
ret3 = re.findall("[a-zA-Z0-9]", "a,b,c,d,1,e,A,V") # ['a', 'b', 'c', 'd', '1', 'e', 'A', 'V']
ret3 = re.findall("[^0-9]", "a,2,b,c,d,1,e,A,V") # ['a', ',', ',', 'b', ',', 'c', ',', 'd', ',', ',', 'e', ',', 'A', ',', 'V']
# [0-9] == \d [a-zA-Z0-9] == \w
重復
'*' :1-多次
'+' :0-多次
'?' :0/1次, 也可取消貪婪匹配
'{m,n}'?:m-n次
貪婪匹配,每次為最多次匹配
?取消貪婪匹配
ret4 = re.findall("\d+", "a,b,234,d,6,888") # ['234', '6', '888']
# ?取消貪婪匹配
ret4 = re.findall("\d+?", "a,b,234,d,6,888") # ['2', '3', '4', '6', '8', '8', '8']
+
ret4 = re.findall("\w", "apple,banana,orange,melon") # ['a', 'p', 'p', 'l', 'e', 'b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e', 'm', 'e', 'l', 'o', 'n']
ret4 = re.findall("\w+", "apple,banana,orange,melon") # ['apple', 'banana', 'orange', 'melon']
ret4 = re.findall("\w+?", "apple,banana,orange,melon") # ['a', 'p', 'p', 'l', 'e', 'b', 'a', 'n', 'a', 'n', 'a', 'o', 'r', 'a', 'n', 'g', 'e', 'm', 'e', 'l', 'o', 'n']
?注:"\w*" == ""和"\w"
*
ret4 = re.findall("\w*", "apple,banana,orange,melon") # ['apple', '', 'banana', '', 'orange', '', 'melon', '']
ret4 = re.findall("abc*", "abc,abcc,abe,ab") # ['abc', 'abcc', 'ab', 'ab']
?
ret4 = re.findall("\w{6}", "apple,banana,orange,melon") # ['banana', 'orange']
{m,n}
ret4 = re.findall("abc?", "abc,abcc,abe,ab") # ['abc', 'abc', 'ab', 'ab']
ret4 = re.findall("abc??", "abc,abcc,abe,ab") # ['ab', 'ab', 'ab', 'ab']
位置
'^' :匹配開頭符合條件的字符
'$' :匹配結尾符合條件的字符
^
ret5 = re.findall("^\d+", "34,banana,255,orange,5434") # ['34']
ret5 = re.findall("^\d+", "peath,34,banana,255,orange,5434") # []
$
ret5 = re.findall("\d+$", "34,banana,255,orange,5434") # ['5434']
ret5 = re.findall("\d+$", "peath,34,banana,255,orange") # []
管道符和括號
|:或
():優先提取/括號
(?:) 取消模式捕獲
ret6 = re.findall(",(\w{5}),", ",apple,banana,peach,orange,melon,") # ['apple', 'peach', 'melon']
ret6 = re.findall("\w+@(163|qq)\.com", "123abc@163.com...789xyz@qq.com") # ['163', 'qq']
ret6 = re.findall("\w+@(?:163|qq)\.com", "123abc@163.com...789xyz@qq.com") # ['123abc@163.com', '789xyz@qq.com']
轉義符
轉義的兩個功能
將一些普通符號賦予特殊功能 ?\d \w ...
將特殊符號取消其特殊功能 ?* . + ...
轉義功能
ret7 = re.findall("\(abc\)", "(abc)...") # ['(abc)']
轉義元字符
特殊模式 | 描述 |
---|---|
\d | 匹配任意一個數字(0-9)[0-9] |
\D | 匹配任意一個非數字字符[^0-9]/[^\d] |
\w | 匹配任意一個字母、數字或下劃線(單詞字符)[A-Za-z0-9_] |
\W | 匹配任意一個非字母、非數字、非下劃線字符[^A-Za-z0-9_]\[^\w] |
\s | 匹配任意一個空白字符(空格、制表符、換行符等)[ \f\n\r\t\v] |
\S | 匹配任意一個非空白字符[^ \f\n\r\t\v]\[\s] |
\b | 匹配單詞邊界 |
\B | 匹配非單詞邊界[^\b] |
\n | 匹配一個換行符 |
\r | 匹配一個回車符 |
\t | 匹配一個制表符 |
\v | 匹配一個垂直制表符 |
\f | 匹配一個換頁符 |
\\ | 匹配一個反斜杠 |
\0 | 匹配一個 NULL 字符 |
2 正則進階
元字符組合(常用)
.*?
.+?
text1 = '<12> <xyz> <!@#$%> <1a!#e2> <>'
ret1 = re.findall("<\d+>", text1) # ['<12>']
ret1 = re.findall("<\w+>", text1) # ['<12>', '<xyz>']
ret1 = re.findall("<.+>", text1) # ['<12> <xyz> <!@#$%> <1a!#e2> <>']
ret1 = re.findall("<.+?>", text1) # ['<12>', '<xyz>', '<!@#$%>', '<1a!#e2>']
ret1 = re.findall("<.*?>", text1) # ['<12>', '<xyz>', '<!@#$%>', '<1a!#e2>', '<>']
text2 = '''<12
><xyz><!@#$%><1a!#
e2><>
'''ret2 = re.findall("<.*?>", text2) # ['<!@#$%>', '<>']
ret2 = re.findall("<.*?>", text2, re.S) # ['<12\n>', '<x\n yz>', '<!@#$%>', '<1a!#\ne2>', '<>']
模式修正符
修正符 | re模塊提供的變量 | 描述 |
---|---|---|
i | re.I | 使模式對大小寫不敏感,也就是不區分大小寫 |
m | re.M | 使模式在多行文本中可以多個行頭和行位,影響^和$ |
s | re.S | 讓通配符,可以代表所有的任意原子(包括換行符\n在內) |
import reret = re.findall('.*?<span class="title">(.*?)</span>.*?<span class=\"rating_num\".*?>(.*?)</span>',s,re.S,
)
print(ret)
print(len(ret))
?re模塊的方法
re.Match對象對應兩個方法
ret.span() ?返回符合規則的字符串的出現位置,為元組
ret.group() ?返回第一個匹配字符串
函數 | 描述 |
---|---|
findall | 按指定的正則模式查找文本中符合正則模式的匹配項,以列表格式返回結果 |
search | 在字符串的任何位置查找首個符合正則模式的匹配項,存在返回re.Match對象,不存在返回None |
match | 判定字符串開始位置是否匹配正則模式的規則,匹配返回re.Match對象,不匹配返回None |
split | 按指定的正則模式來分割字符串,返回一個分割后的列表 |
sub/subn | 把字符串按指定的正則模式來查找符合正則模式的匹配項,并可以替換一個或多個匹配項成其他內容 |
compile | 將規則編譯,可以重復使用 |
有名分組
<name>
search和match的區別:
search在任何位置找,match從開頭找
ret3 = re.search("(?P<tel>1[3-9]\d{9}).*?(?P<email>\d+@qq\.com)", "我的手機號是18793437893,我的郵箱是123@qq.com")print(ret3.group()) ?# 18793437893,我的郵箱是123@qq.com
print(ret3.group("tel")) ?# 18793437893
print(ret3.group("email")) ?# 123@qq.com
compile編譯
s1 = "12 apple 34 peach 21 banana"
s2 = "18 apple 12 peach 33 banana"
reg = re.compile(r"\d+")print(reg.findall(s1))
print(reg.findall(s2))
二、bs4
bs4:Beautiful Soup是python的一個庫,主要功能是從網頁抓取數據。
需要安裝兩個庫
pip install bs4
pip install lxml
?基本使用順序如下所示:
# 調用bs4庫
from bs4 import BeautifulSoup# 讀文件
with open("第一階段-爬蟲\\JS逆向\\3-網頁數據解析\\bs4\\demo.html", "r", encoding="utf-8") as f:s = f.read()# 創建bs4對象
soup = BeautifulSoup(s, 'html.parser')# 或者使用如下方式
soup = BeautifulSoup(open("第一階段-爬蟲\\JS逆向\\3-網頁數據解析\\bs4\\demo.html", encoding="utf-8"), "html.parser")
1 四種對象
BeautifulSoup
Tag
NavigableString
Comment
主要使用BeautifulSoup和Tag對象?
from bs4 import BeautifulSoupwith open("第一階段-爬蟲\\JS逆向\\3-網頁數據解析\\bs4\\demo.html", "r", encoding="utf-8") as f:s = f.read()soup = BeautifulSoup(s, 'html.parser')# Tag查找標簽
# 如果查到,一定是一個Tag對象
print(soup.body)
print(type(soup.body))print(soup.div.a) # <a class="nav-login" href="https://accounts.douban.com/passport/login?source=movie" rel="nofollow">登錄/注冊</a>
print(soup.div.a.name) # aprint(soup.a["href"]) # https://accounts.douban.com/passport/login?source=movie
print(soup.a.attrs) # {'href': 'https://accounts.douban.com/passport/login?source=movie', 'class': ['nav-login'], 'rel': ['nofollow']}# 拿取文本
print(soup.a.string) # 登錄/注冊
print(soup.a.text) # 登錄/注冊print({link.text:link["href"] for link in soup.find_all("a")})
2 導航文檔樹
嵌套選擇
子節點、子孫節點
父節點、祖先節點
兄弟節點
導入文件并創建對象
from bs4 import BeautifulSoupwith open("第一階段-爬蟲\\JS逆向\\3-網頁數據解析\\bs4\\demo.html", "r", encoding="utf-8") as f:s = f.read()soup = BeautifulSoup(s, 'html.parser')
嵌套選擇
# 嵌套選擇
print(soup.head.title.text)
print(soup.body.a.text)
子節點、子孫節點
# 子節點、子孫節點
print(soup.p.contents) # p下所有子節點
print(soup.p.children) # 得到一個迭代器,包含p下所有子節點
print(soup.p.descendants) # 獲取子孫節點,p下所有標簽都會選擇出來
父節點、祖先節點
# 父節點、祖先節點
print(soup.a.parent) # 獲取a標簽的父節點
print(soup.a.parents) # 獲取a標簽所有祖先節點,父親、爺爺等等
兄弟節點
# 兄弟節點
print(soup.a.next_sibling) # 下一個兄弟
print(soup.a.next_sibling.next_sibling) # 下下一個兄弟,以此類推
print(soup.a.previous_sibling) # 上一個兄弟
print(soup.a.previous_sibling.previous_sibling) # 上上一個兄弟
3 搜索文檔樹
fand_all()
name參數(標簽名過濾):字符串、正則表達式、列表、方法
字符串:即標簽名關鍵字參數(屬性過濾)
文本參數(文本過濾)
find()
與find_all()的區別:find()只查找第一個
參數完全一樣
find_parents() ?找所有父親標簽
find_parent() ?找父親標簽
優點:只包含兄弟標簽
find_next_siblings() ?找下邊所有兄弟標簽
find_next_sibling() ?找下邊一個兄弟標簽
find_previous_siblings() ?找上邊所有兄弟標簽
find_previous_sibling() ?找上邊一個兄弟標簽
find_all_next() ?找下邊所有相同標簽
find_next() ?找下邊相同標簽
這里使用find_all()方法舉例,其余搜索使用方法幾乎相同
name參數
? 標簽名
# name參數:標簽名
ret1 = soup.find_all(name="a") # 查找所有a標簽
print(ret1)
? 正則表達式
# name參數:正則表達式
ret2 = soup.find_all(name=re.compile("^a"))
print(ret2)
? 列表
# name參數:列表
ret3 = soup.find_all(name=["a", "b"])
print(ret3)
? 函數(精致過濾)
# name參數:函數(精致過濾)# 表示擁有class和id兩個屬性的標簽
def has_class_has_id(tag):return tag.has_attr("class") and tag.has_attr("id")print(soup.find_all(name=has_class_has_id))
關鍵字參數
ret4 = soup.find_all(href="https://img1.doubanio.com/cuphead/movie-static/pics/apple-touch-icon.png"
)
ret4 = soup.find_all(attrs={"href": "https://img1.doubanio.com/cuphead/movie-static/pics/apple-touch-icon.png"}
)
ret4 = soup.find_all(href=re.compile("^https://"), class_="download-android", id="id1")
print(ret4)
文本參數
ret5 = soup.find_all(string="豆瓣")
ret5 = soup.find_all(string=re.compile("豆瓣")) # 查找含有"豆瓣"的文本
ret5 = soup.find_all(string=re.compile("豆瓣"), limit=1) # 查找含有"豆瓣"的文本,只取第一個
print(ret5)
4 CSS選擇器
與CSS中選擇器用法相同
select()方法或者在搜索中使用from lxml import etree selector = etree.HTML(源碼) ?# 將源碼轉換為能被XPath匹配的格式 ret = selector.xpath(表達式) ?# 返回為一列表
例:soup.select("body a")
from bs4 import BeautifulSoup
import resoup = BeautifulSoup(open("第一階段-爬蟲\\JS逆向\\3-網頁數據解析\\正則\\豆瓣top250.html", encoding="utf-8"), "html.parser")items = soup.find_all(class_="item")
print(len(items))for item in items:title = item.find(class_="title").stringrating_num = item.find(class_="rating_num").stringstar = item.find(class_="star").find_all("span")[-1].stringprint(title, rating_num, star)
三、xpath
xpath:一種小型的查詢語言,屬于lxml庫模塊
使用方式:
from lxml import etree
selector = etree.HTML(源碼) ?# 將源碼轉換為能被XPath匹配的格式
ret = selector.xpath(表達式) ?# 返回為一列表
1 路徑表達式
表達式 | 描述 | 實例 | 解析 |
---|---|---|---|
/ | 從根節點選取 | /body/div[1] | 選取根節點下的body下的第一個div標簽 |
// | 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置 | //a | 選取文檔中所有的a標簽 |
./ | 從當前節點再次進行xpath | ./a | 選取當前節點下的所有a標簽 |
@ | 選取屬性 | //@class | 選取所有的class屬性 |
2 謂語
是放在方括號[]里,用來查找某個特定的節點或者包含某個指定值的節點
路徑表達式 | 結果 |
---|---|
/ul/li[1] | 選取屬于ul元素的第一個li元素 |
/ul/li[last()] | 選取屬于ul元素的最后一個li元素 |
/ul/li[last-1] | 選取屬于ul元素的倒數第二個li元素 |
/ul/li[position()<3] | 選取最前面的兩個屬于ul元素的子元素的li元素 |
//a[@title] | 選取所有擁有名為title屬性的a元素 |
//a[@title='xx'] | 選取所有擁有title屬性,并且屬性值為'xx'的a元素 |
//a[@title>10] > < >= <= != | 選取所有擁有title屬性,并且屬性值為大于10的a元素 |
/body/div[@price>35.00] | 選取body下price元素值大于35的div節點 |
3 通配符
xpath的通配符可以選取未知節點
通配符 | 描述 |
---|---|
* | 匹配任何元素節點 |
@* | 匹配任何屬性節點 |
node() | 匹配任何類型節點 |
4 其他用法
表達式 | 結果 |
---|---|
//xx[@id="" and @class=""] | 獲取滿足兩者要求的元素 |
//xx | //xx | 獲取若干路徑的元素 |
//xx[id/@class=""] | 獲取xx標簽中包含或者對應id/class的元素 |
//xx[n] | 獲取xx標簽的第n個元素,索引從1開始,last()獲取最后一個 |
//xx[starts-with(@id, "ll")] | 獲取xx標簽中id屬性中以ll開頭的,class類似 |
//xx[contains(@id, "ll")] | 獲取xx標簽中id屬性中包含ll的,class類似 |
//xx/text() | 獲取xx標簽中的文本值 |
//div/a/@href | 獲取a標簽中的href屬性值 |
//* | 獲取所有,例//*[@class="xx"]:獲取所有class為xx的標簽 |
?獲取節點內容轉換成字符串
c = tree.xpath('//li/a')[0]
result = etree.tostring(c, encoding='utf-8')
print(result.decode('utf-8'))
5 文件操作
文件句柄
open()方法
mode參數為對文件的操作,r只讀,w只寫,rw讀寫...
encoding表示編碼方式
絕對路徑
filer = open("D:/桌面/Python自學/第一階段-爬蟲/JS逆向/3-網頁數據解析/xpath/豆瓣top250.html",mode="r",encoding="utf-8",
)
相對路徑
file = open("/豆瓣top250.html", mode="r",encoding="utf-8",)
讀操作
方法 | 功能 |
---|---|
file.read() | 直接獲取文件內容,參數為整型數字,光標按字移動 |
file.readline() | 按行獲取文件內容,參數為整型數字,光標按字移動 |
file.readlines() | 獲取文件所有行,放在列表中,參數為整型數字,光標按行移動 |
通常以以下方式循環高效獲取數據:
for line in file:print(line)
寫操作?
覆蓋寫
會將源文件內容清空再存入
filew = open("write.txt",mode="w",encoding="utf-8",
)filew.write("")
filew.close()
追加寫
filew = open("write.txt",mode="a",encoding="utf-8",
)filew.write("")
filew.close()