網(wǎng)絡(luò)爬蟲提取
[TOC]
1. Beautiful Soup庫入門
bs庫的安裝
win平臺下横媚,cmd輸入
pip install beautifulsoup4
beautifulsoup的基本結(jié)構(gòu)
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'html.parser')
BeautifulSoup庫的基本元素
對于bs庫的理解
? BeautifulSoup庫是解析先改、遍歷、維護(hù)”標(biāo)簽樹“的功能庫翎冲,什么是標(biāo)簽樹呢,HTML語言是標(biāo)記語言捐祠,通過不同的標(biāo)簽將內(nèi)容分割為不同的結(jié)構(gòu)眶熬,因此HTML的文本可以通過標(biāo)簽生成一顆標(biāo)簽樹,bs庫通過對這個樹進(jìn)行不同的操作勃黍。
? 一個標(biāo)簽宵统,例如<p>..</p>``<a>..</a>
等是以一個標(biāo)簽:Tag的形式出現(xiàn),p
a
是tag的名稱Name覆获,而后面跟的鍵值對是它的屬性Attributes马澈,一個HTML文檔 = 一顆標(biāo)簽樹 = 一個BeautifulSoup類實例 ,它對應(yīng)的是HTML/XML文檔的全部內(nèi)容
bs庫解析器
soup = BeautifulSoup('<html>data</html>','html.parser')
bs支持多種不同的解析器分別為
解析器 | 使用方法 | 條件 |
---|---|---|
bs4的HTML解析器 | BeautifulSoup('<html>data</html>','html.parser') | 安裝bs4庫 |
lxml的HTML解析器 | BeautifulSoup('<html>data</html>','lxml') | pip install lxml |
lxml的XML解析器 | BeautifulSoup('<html>data</html>','xml') | pip install lxml |
html5lib的解析器 | BeautifulSoup('<html>data</html>','html5lib') | pip install html5lib |
BeautifulSoup類的基本元素
基本元素 | 說明 |
---|---|
Tag | 標(biāo)簽弄息,最基本信息組織單元<></> |
Name | 標(biāo)簽名字,比如p痊班,a,格式<tag>.name |
Attributes | 標(biāo)簽屬性摹量,字典形式格式<tag>.attrs |
NavigableString | 標(biāo)簽內(nèi)非屬性的字符串<>和</>中間的字符串涤伐,格式:<tag>.string |
Comment | 標(biāo)簽內(nèi)字符串的注釋部分,特殊Comment類型 |
下面來說明一下這些都是怎么用的
-
Tag
? 因為不同的標(biāo)簽都有不同的名字所以任何存在與HTML語法中的標(biāo)簽都可以用soup.<tag>訪問獲得缨称,如果存在多個<tag>對應(yīng)內(nèi)容時废亭,返回第一個
? 比如,soup為經(jīng)過處理的bs類
soup = BeautifulSoup(demo, 'html.parser')
如果需要訪問title標(biāo)簽具钥,輸入
soup.title
如果需要訪問a標(biāo)簽
soup.a tag = soup.a
這樣就取得了一個Tag豆村,后續(xù)可以對這個Tag做其他操作
-
Name
每個Tag都有自己的名字,通過<tag>.name獲取骂删,字符串類型
>>> soup.a.name 'a' >>> soup.a.parent.name #雙親的名字 'p' >>> soup.a.parent.parent.name 'body'
-
Attributes
? 一個<tag>可以有多個屬性或者0個掌动,字典類型四啰,使用
<tag>.attrs
可以以字典形式顯示所有屬性,由于是字典類型粗恢,可以通過Dict['name']
取到對應(yīng)的值>>> tag = soup.a >>> tag.attrs {'id':'link1','class':['py1'],'href':'xxx'} >>> tag.attrs['class'] ['py1'] >>> tag.attrs['href'] 'xxx' #注意是字符串形式
-
NavigableString
? 代表標(biāo)簽內(nèi)非屬性字符串柑晒,注意,NavigableString可以跨越多個層次眷射,即匙赞,如果string外面有兩個標(biāo)簽,取最外面的標(biāo)簽<tag>.string取到的是兩個標(biāo)簽夾著的中間字符串妖碉。
-
Comment
Comment是一種特殊類型的String涌庭,代表注釋
>>> type(soup.b.string) #b中間是Comment,<!--...--> <class 'bs4.element.Comment'>
Beautiful Soup庫的3種遍歷方式
? 對于標(biāo)簽樹欧宜,一共有3種遍歷方式坐榆,分別是:上行遍歷,下行遍歷冗茸,平行遍歷
下行遍歷
下行遍歷有三種屬性
屬性 | 說明 |
---|---|
.contents | 子結(jié)點的列表席镀,將<tag>所有兒子節(jié)點存入列表 |
.children | 子結(jié)點的迭代類型,與.contents類似夏漱,用于循環(huán)遍歷兒子結(jié)點 |
.descendants | 子孫結(jié)點的迭代類型豪诲,包含所有子孫結(jié)點,用于循環(huán)遍歷 |
BeautifulSoup類是根節(jié)點挂绰,<head></head>標(biāo)簽單獨作為一例屎篱,中間包含title標(biāo)簽,<body></body>中間包含<p><a>標(biāo)簽等扮授。
注:在.contents屬性中,'\n'也是作為單獨的一個兒子節(jié)點被存入
下行遍歷的結(jié)構(gòu)
-
遍歷兒子節(jié)點
for child in soup.body.children: print(child)
-
遍歷子孫節(jié)點
for child in soup.body.descendants: print(child)
上行遍歷
屬性 | 說明 |
---|---|
.parent | 結(jié)點的父親標(biāo)簽 |
.parents | 結(jié)點先輩標(biāo)簽的迭代類型专肪,用于循環(huán)遍歷先輩結(jié)點 |
注:soup類本身的父結(jié)點為空刹勃,所以如果進(jìn)行迭代,要進(jìn)行判斷
上行遍歷的結(jié)構(gòu)
for parent in soup.a.parents:
if parent is None: #判斷是否為最頂結(jié)點
print(parent)
else:
print(parent.name)
平行遍歷
屬性 | 說明 |
---|---|
.next_sibling | 返回按照HTML文本順序的下一個平行結(jié)點標(biāo)簽 |
.previous_sibling | 返回按照HTML文本順序的上一個平行結(jié)點標(biāo)簽 |
.next_siblings | 迭代類型(迭代器)嚎尤,返回按照HTML文本順序的后續(xù)平行結(jié)點 |
.previous_siblings | 迭代類型(迭代器)荔仁,返回按照HTML文本順序的前驅(qū)平行結(jié)點 |
注:平行遍歷是發(fā)生在同一父結(jié)點下的各結(jié)點
平行遍歷的結(jié)構(gòu)
-
遍歷后續(xù)結(jié)點
for sibling in soup.a.next_siblings: print(sibling)
-
遍歷前驅(qū)結(jié)點
for sibling in soup.a.previous_siblings: print(sibling)
bs4的prettify方法
? bs4庫的prettify()方法為HTML文本<>及其內(nèi)容增加換行符
prettify方法可用于標(biāo)簽或者BeautifulSoup類型,格式為
<tag>.prettify()
2. 信息組織與提取方法
信息標(biāo)記的三種形式
XML JSON YAML
XML
XML全稱為eXtensible Markup Language芽死,是一種標(biāo)記語言
<img src="china.jpg" size="10">...</img>
帶名稱和屬性的標(biāo)簽乏梁,中間還有非標(biāo)簽元素
<img src="china.jpg" size="10" />
空元素書寫形式
<!-- This is a comment-->
注釋
JSON
JavaScript Object Notation 有類型的鍵值對 key:value
比如:
"name":"哈爾濱工業(yè)大學(xué)"
而一個關(guān)鍵字對應(yīng)多個值時
"name":["哈爾濱工業(yè)大學(xué)","西大直街"]
如果出現(xiàn)鍵值對的嵌套
"name":{
"newName":"圣馬家溝職業(yè)學(xué)院",
"oldName":"哈爾濱工業(yè)大學(xué)"
}
YAML
YAML Ain`t Markup Language 無類型鍵值對, key:value僅用字符串就可以表達(dá)關(guān)系
name:哈爾濱工業(yè)大學(xué)
利用縮進(jìn)表達(dá)所屬關(guān)系
name:
newName:圣馬家溝職業(yè)學(xué)院
oldName:哈爾濱工業(yè)大學(xué)
-表達(dá)并列關(guān)系
name:
-哈爾濱工業(yè)大學(xué)
-西大直街
| 表達(dá)整塊數(shù)據(jù)关贵,如果數(shù)據(jù)很多的話遇骑,# 表示注釋
text: | #introduction
balabalabala...
舉個例子區(qū)分:
描述一個學(xué)生小明
XML:
<person>
<firstname>Ming</firstname>
<lastname>Xiao</lastname>
<address>
<street>西大直街</street>
<city>哈爾濱</city>
</address>
<prof>CS</prof><prof>SE</prof>
</person>
JSON
"person":{
"firstName":"Ming",
"lastName":"Xiao",
"address":{
"street":"西大直街"
"city":"哈爾濱"
},
"prof":["CS","SE"]
}
YAML
person:
firstname:Ming
lastName:Xiao
address:
street:西大直街
city:哈爾濱
prof:
-CS
-SE
三種信息標(biāo)記比較:
XML 最早的標(biāo)記語言,拓展性好揖曾,繁瑣
JSON 信息有類型落萎,適合程序處理亥啦,較XML簡潔(什么叫有類型,JSON中每個關(guān)鍵字有""標(biāo)記练链,”“叫做類型翔脱,”xxx“叫做鍵key
YAML 信息無類型,單純?yōu)樽址焦模勺x性好
信息提取的一般方法
方法一:完整解析信息的標(biāo)記形式届吁,在提取關(guān)鍵信息
? 需要標(biāo)記解析器,例如bs4庫的標(biāo)簽樹绿鸣,信息提取準(zhǔn)確但是提取過程很繁瑣疚沐,速度慢
方法二:無視標(biāo)記形式,直接搜索關(guān)鍵信息
? 需要文本查找函數(shù)枚驻,提取過程簡潔速度快濒旦,但是提取的結(jié)果準(zhǔn)確性與信息內(nèi)容有關(guān)
基于bs4庫的HTML查找方法
<>.find_all方法
<>.find_all方法的實現(xiàn)為
<>.find_all(name, attrs, recursive, string, **kw)
函數(shù)返回的是一個列表LIST類型,存儲查找的結(jié)果再登,注意:此時列表中的是Tag類型實例尔邓,其包含name、string等元素
- name:對標(biāo)簽名稱的檢索字符串
對一個標(biāo)簽搜索
>>> soup.find_all('a')
對多個標(biāo)簽搜索
>>> soup.find_all(['a','b'])
也可以輸入?yún)?shù)True
和正則表達(dá)式锉矢,True返回的結(jié)果是所有的標(biāo)簽
>>> for tag in soup.find_all(True):
print(tag.name)
>>> import re
>>> for tag in soup.find_all(re.complie('b')):
print(tag.name)
body
b
-
attrs:對標(biāo)簽屬性值的檢索字符串梯嗽,可標(biāo)注屬性檢索
參數(shù)輸入可以是單個的鍵值對中的值,也可以是鍵值對沽损,也可以是正則表達(dá)式
>>> soup.find_all('p','course') #鍵值對中的值 >>> soup.find_all(id='link1') #鍵值對形式灯节,注意此時name可以省略,如果attrs參數(shù)不是鍵值對绵估,要加上name炎疆,否則函數(shù)會將attrs識別為name >>> soup.find_all(id=re.complie('link'))
-
recursive:是否對子孫全部檢索,默認(rèn)為True
通過
recursive=False``recursive=True
設(shè)置 -
string:對標(biāo)簽中字符串區(qū)域檢索国裳,檢索結(jié)果為字符串
支持正則表達(dá)式
>>> soup.find_all(string='Python')
<>.find_all()的等價寫法
<tag>(...) <--> <tag>.find_all(...)
soup(...) <--> soup.find_all(...)
主要是因為這個方法使用頻率很高
<>.find_all的擴(kuò)展方法
方法 | 說明 |
---|---|
<>.find() | 搜索值返回一個結(jié)果 |
<>.find_parents() | 在先輩節(jié)點中搜索形入,返回列表類型 |
<>.find_parent() | 在先輩節(jié)點中返回一個結(jié)果 |
<>.find_next_siblings() | 在后續(xù)平行節(jié)點中搜索,返回列表類型 |
<>.find_next_sibling() | 在后續(xù)平行節(jié)點中返回一個結(jié)果 |
<>.find_previous_siblings() | 在前序平行節(jié)點中搜索缝左,返回列表 |
<>.find_previous_sibling() | 在前序平行節(jié)點中返回一個結(jié)果 |
至于為什么沒有對于子孫節(jié)點的搜索亿遂,是因為find_all() 和 find()函數(shù)就是這樣的功能
3. 正則表達(dá)式庫入門(re)
? RE,regular expression渺杉, regex蛇数,正則表達(dá)式是用來簡潔表達(dá)一組字符串的表達(dá)式,是一種通用的字符串表達(dá)框架是越。
? 在re庫中耳舅,首先使用正則表達(dá)式規(guī)則寫出字符串,然后通過re庫的編譯是字符串轉(zhuǎn)換為正則表達(dá)式特征倚评,再將特征與字符串匹配挽放。
正則表達(dá)式regex(string) => p = re.compile(regex)(編譯) => 特征p
正則表達(dá)式的語法
正則表達(dá)式常用操作符
操作符 | 說明 | 實例 |
---|---|---|
. | 表示任何單個字符 | |
[ ] | 字符集绍赛,對單個字符給出取值范圍 | [abc]表示abc,[a-z]表示a到z單個字符 |
[^ ] | 非字符集辑畦,對單個字符給出排除范圍 | [^abc]表示非a或b或c的單個字符 |
* | 前一個字符0次或無限次擴(kuò)展 | abc*表示ab吗蚌、abc、abcccc |
+ | 前一個字符1次或無限次擴(kuò)展 | abc+表示abc纯出、abcc蚯妇、abccc |
? | 前一個字符0次或1次擴(kuò)展 | abc暂筝?表示ab箩言、abc |
| | 左右表達(dá)式任意一個 | abc|edf表示abc、def |
{m} | 擴(kuò)展前一個字符m次 | ab{2}表示abb |
{m,n} | 擴(kuò)展前一個字符m至n次(含n) | ab{1,2}表示abc焕襟、abbc |
^ | 匹配字符串的開頭 | ^abc表示abc且在一個字符串的開頭 |
$ | 匹配字符串的結(jié)尾 | abc$表示abc且在一個字符串的結(jié)尾 |
( ) | 分組標(biāo)記陨收,內(nèi)部只能使用|操作符 | (abc)表示abc,(abc|def)表示abc鸵赖、def 沒有或 |
\d | 數(shù)字务漩,等價0-9 | |
\w | 單詞字符,等價[A-Za-z0-9_] |
正則表達(dá)式的經(jīng)典實例
由26個字母組成的字符串 ^[A-Za-z]+$
由26個字母和數(shù)字組成的字符串 ^[A-Za-z0-9]+$
整數(shù)形式的字符串 ^-?\d+$
正整數(shù)形式的字符串 ^[0-9]*****[1-9][0-9]*****$
郵政編碼 [1-9]\d{5}
匹配中文字符 [\u4e00-\u9fa5]
匹配IP地址的正則表達(dá)式
0-99 :[1-9]?\d
100-199 :1\d{2}
200-249 :2[0-4]\d
250-255 :25[0-5]
表示為:(0-99|100-199|200-249|250-255.){3}(0-99|100-199|200-249|250-255)
Re庫介紹
? 正則表達(dá)式使用raw string類型(原生字符串類型)它褪,表示為r'text'饵骨。當(dāng)然也可以使用string類型表達(dá),但是\
需要表示為\\
Re庫的主要功能函數(shù)
函數(shù) | 說明 |
---|---|
re.search() | 在一個字符串中搜索匹配正則表達(dá)式的第一個位置茫打,返回match對象 |
re.match() | 從一個字符串的開始位置匹配正則表達(dá)式居触,返回match對象 |
re.findall() | 搜索字符串,列表類型返回全部匹配子串 |
re.split() | 將一個字符串按照正則表達(dá)式匹配結(jié)果進(jìn)行分割老赤,返回列表類型 |
re.finditer() | 搜索字符串轮洋,返回匹配的迭代類型,每個迭代元素都是match對象 |
re.sub() | 在一個字符串中替換所有匹配子串抬旺,返回替換后字符串 |
-
re.search(pattern, string, flags=0)
pattern:正則表達(dá)式的字符串或原生字符串表示
string:待匹配字符串
flags:正則表達(dá)式使用時控制標(biāo)記
標(biāo)記有一下幾種
常用標(biāo)記 說明 re.I re.IGNORECASE h忽略正則表達(dá)式的大小寫弊予,[A-Z]能夠匹配小寫字符 re.M re.MULTILINE 正則表達(dá)式中的^操作符能夠?qū)⒔o定字符串的每行當(dāng)作匹配開始 re.S re.DOTALL 正則表達(dá)式中的.操作符能夠匹配所有字符,默認(rèn)的匹配是除換行外的所有字符 re.match(pattern, string, flags=0)
re.findall(pattern, string, flags=0)
-
re.split(pattern, string, maxsplit=0, flags=0)
maxsplit:最大分割數(shù)嚷狞,剩余部分作為最后一個元素輸出
re.finditer(pattern, string, flags=0)
-
re.sub(pattern, repl, string, count=0, flags=0)
repl:替換匹配字符串的字符串
count:匹配最大替換次數(shù)
Re庫的另一個等價用法
? 除了函數(shù)式用法外块促,還有一種面向?qū)ο蟮挠梅ㄈ傺撸聪染幾g出正則表達(dá)式對象床未,在對這個對象進(jìn)行操作
其中生成編譯對象的用法為
regex = re.complie(pattern, flags=0)
regex為編譯后生成的正則表達(dá)式對象,pattern為正則字符串或原生字符串表示振坚。flags為正則表達(dá)式使用時的控制標(biāo)記薇搁。通過調(diào)用regex對象(也可以是其他名字)的方法實現(xiàn)搜索,具體方法的名字與上面功能函數(shù)相同渡八。
上面很多的函數(shù)都返回了match對象啃洋,接下來簡單介紹match對象
Match對象介紹
Match對象是一次匹配的結(jié)果传货,包含很多匹配的信息
Match對象的屬性
屬性 | 說明 |
---|---|
.string | 待匹配的文本 |
.re | 匹配時使用的pttern對象(正則表達(dá)式) |
.pos | 正則表達(dá)式搜索文本的開始位置 |
.endpos | 正則表達(dá)式的結(jié)束位置 |
Match對象的方法
方法 | 說明 |
---|---|
.group(0) | 獲得匹配后的字符串 |
.start() | 匹配字符串在原始字符串的開始位置 |
.end() | 匹配字符串在原始字符串的結(jié)束位置 |
.span() | 返回(開始位置,結(jié)束位置) |
以"BIT100081 TSU10084"為例
>>> import re
>>> m = re.search(r'[1-9]\d{5}', "BIT100081 TSU100084")
>>> m.string
'BIT100081 TSU100084'
>>> m.re
re.complie('[1-9]\\d{5}') #正則表達(dá)式對象
>>> m.endpos
19
>>> m.group(0)
'100081'
>>> m.start()
3
>>> m.end()
9
>>> m.span()
(3, 9)
Re庫的貪婪匹配和最小匹配
Re庫默認(rèn)采用貪婪匹配, 即輸出匹配最長的子串,如果要輸出最短的字符串,則采用最小匹配,在匹配符后加上?
最小匹配操作符
操作符 | 說明 |
---|---|
*? | 前一個字符0次或無限次擴(kuò)展,最小匹配 |
+? | 前一個字符1次或無限次擴(kuò)展,最小匹配 |
?? | 前一個字符0次或1次擴(kuò)展,最小匹配 |
{m,n}? | 擴(kuò)展前一個字符m至n次,最小匹配 |