j記錄學習--python網路爬蟲與資訊提取
The website is the API...要獲取網站內容,只要把網站當成API就可以了。
requests庫獲取網頁資訊---》Beautiful Soup解析提取到資訊的內容---》利用re庫正則表示式提取其中某部分的關鍵資訊----》Scrapy*網路爬蟲
網路爬蟲之規則-》requests庫
requests庫的介紹和使用
requests庫的安裝
pip install requests # doc命令列下輸入安裝即可,測試例子:
>>> import requests #匯入requests庫 >>> r = requests.get(url="http://www.baidu.com",timeout=30) #返回一個Python物件r >>> r.status_code 200 #狀態碼200 Ok >>> r.encoding = r.apparent_encoding # >>> r.text >>> type(r) #檢視r型別,返回的是一個物件 <class 'requests.models.Response'>
requests庫的7個常用方法
get方法:
r = requests.get(url)
r 是伺服器返回的一個包含伺服器所有資源的Response物件,requests.get(url)是Python構造的一個向伺服器請求資源的Request物件。
requests.get(url,params=None,**kwargs)
- url :要獲取的API/網頁的URL連結
- params:URL中的額外引數,可以是字典、位元組流格式,可選
- **kwargs:共有12個控制訪問的引數
Response物件的常用屬性
- r.status_code http請求的返回狀態,200是OK
- r.text http響應內容的字串
- r.encoding 從http Header中猜測的響應內容的編碼方式,若header沒有charset欄位,則預設為ISO-8859-1編碼,<meta charset='utf-8'>
- r.apparent_encoding 從內容分析出的響應內容編碼方式(備選編碼)這個更準確解析頁面的編碼
- r.content http響應內容的二進位制形式(如圖片是由二進位制儲存的,就可以通過r.content還原這圖片)
- r.headers http響應的響應頭
- r.raise_for_status http請求狀態碼不是200則會引發HTTPError異常
爬去網頁通用程式碼框架
網路連結有風險,所以需要進行異常處理。
requests庫的異常:
r.raise_for_status() #判斷r若果不是200,產生異常requests.HTTPError異常
爬去網頁通用程式碼框架:
import requests
def getHTMLText(url):
try:
r = requests.get(url,timeout=30)
r.raise_for_status() #請求不成功不是200,則引發HTTPError異常
r.encoding = r.apparent_encoding
return r.text
except:
return "異常了!"
if __name__ == "__main__":
url = "http://www.baidu.com"
print(getHTMLText(url))
HTTP協議和requests7個方法
http是超文字傳輸協議,是一個基於請求與響應模式的、無狀態的應用層協議。第一次請求和另一次請求沒有聯絡、工作於tcp協議之上。ip--tcp-http..http協議採用URL作為定位網路資源的標識,協議名://host[:port]/資源路徑
一個URL對應伺服器一個資源,http協議對資源的操作方法:每次操作都是獨立和無狀態的
和requests庫的get、post、put、、、7個方法是一一對應的!!!
理解PATCH和PUT的區別:
- PATCH是僅向URL提交區域性更新的欄位資料,其他不改的資料不用上傳
- PUT是向伺服器上傳的欄位會覆蓋原來伺服器的欄位,若果只想改變伺服器一個欄位用PATCH上傳該欄位即可,若果用PUT則需要把改變的欄位和伺服器原來的欄位全部上傳到對應欄位,否則會上除掉不上傳的欄位
PATCH優點是節省網路頻寬啊!需要改伺服器哪個欄位就上傳哪個欄位就可!
Requests庫的head方法:
>>> r = requests.head(url="http://www.jd.com",timeout=30) #可以以很少的網路流量獲取網站大體資訊
>>> r.headers #響應頭
{'Content-Type': 'text/html', 'Connection': 'keep-alive', ...}
>>> r.text #所以API內容是空的
''
Requests庫的post方法:控制引數是data向URLpost一個字典,自動編碼為form(表單)
>>> payload = {'key1':'value1','key2':'value2'} #post的是一個字典,則自動編碼為form表單
>>> r = requests.post(url='http://httpbin.org/post',data=payload,timeout=30)
>>> print(r.text) #返回json
{
......
"form": { #post的是字典或鍵值對,自動放在form表單下的
"key1": "value1",
"key2": "value2"
},
......
}
>>> print(r.json()) #返回的是一個Python字典
{... 'form': {'key1': 'value1', 'key2': 'value2'}, ......}
向URL post一個字串,自動編碼為data
>>> string = "skdkheh990" #post一個字串,自動編碼為data
>>> r = requests.post(url="http://httpbin.org/post",data = string)
>>> print(r.text) #返回json
{
......
"data": "skdkheh990", #post字串自動存在data欄位下
"form": {},
......
}
>>> r.json() #json()方法返回一個Python字典
{...... 'form': {}, 'url': 'http://httpbin.org/post', 'data': 'skdkheh990',......}
Requests庫的put方法:和post方法一樣,只不過會把原有的資料覆蓋掉的
Requests庫主要方法解析(7個)
r = requests.request(method,url,**kwargs),request方法是基礎方法
method:請求方式
- r = requests.request('GET',url,**kwargs)
- r = requests.request('POST',url,**kwargs)
- r = requests.request('PUT',url,**kwargs)
- r = requests.request('PATCH',url,**kwargs)
- r = requests.request('HEAD',url,**kwargs)
- r = requests.request('delete',url,**kwargs)
- r = requests.request('OPTIONS',url,**kwargs) #向伺服器獲取到伺服器和客戶端一些打交道的引數與獲取資源無直接相干
**kwargs:控制方為引數,都是可選項
1.params:是字典或位元組序列,作為引數增加到URL中,是get方法中使用
>>> kv = {'wd':'unittest'}
>>> r = requests.get(url='http://www.baidu.com',params=kv)
>>> r.status_code
200
>>> r.url
'http://www.baidu.com/?wd=unittest'
2.data:是字典、位元組序列或檔案物件,作為Request的內容,向伺服器提交資源時使用,post、put、patch、delete方法使用
>>> dic ={'key1':'value1','key2':'value2'} #
>>> r = requests.post(url='http://httpbin.org/post',data=dic)#post提交到伺服器不會顯示在URL上
>>> body='stringafasdf'
>>> r = requests.post(url="http://httpbin.org/post",data=body)
3.json:是json格式的資料,作為Request的內容,提交內容是json串時,用到json=。。。,post等方法使用
>>> json1 ={'key1':'value1'}
>>> r = requests.post(url="http://httpbin.org/post",json=json1)
>>> print(r.text)
{
"args": {},
"data": "{\"key1\": \"value1\"}",
"files": {},
"form": {},
"json": { #json=json1會把資料提交到伺服器的json域中
"key1": "value1"
},
......
}
post 控制引數用到data、json的區別:
- 提交的資料是字典或鍵值對,使用data=XXX,則提交到伺服器的form表單下;
- 提交的資料是字串,使用data=XXX,則提交到伺服器的data下;
- 提交的資料是json串時,使用json=XXX,則提交到伺服器的json域;
4.headers:字典格式,用於定製http請求頭
>>> header={'user-agent':'chrome/10'} #模擬瀏覽器向伺服器發起請求就是用headers
>>> r = requests.request('POST',url="http://httpbin.org/post",headers=header)
5.cookies:是字典或cookieJar格式,Request中的cookie
6.auth :是元組型別,用於http認證功能
7.files:是字典型別,用於向伺服器傳輸檔案
>>> fs = {'file':open(r'E:\test.txt','rb')}
>>> r = requests.post(url="http://httpbin.org/post",files = fs,timeout=30)
>>> print(r.text)
{
"args": {},
"data": "",
"files": { #向伺服器提交的檔案儲存到伺服器的files域中
"file": "hahfhadfhadsfhhsdflahlowej[of567890987654567890987654345678"
},
"form": {},
......
}
8.timeout:設定超時時間,秒為單位在設定時間內沒有返回內容則返回一個timeout異常
9.proxies:是字典型別,可以為我們爬取網頁設定訪問代理伺服器,可以增加登入認證
>>> pxs = {'http':'http"//user:[email protected]:1234','https':'https://10.10.10.1.4321'}#設定2個代理,一個是http訪問時使用的代理,另一個是https訪問時使用的代理
>>> r = requests.get(url='http://www.baidu.com',proxies=pxs) #可以隱藏使用者爬去網頁時原來的IP地址資訊
10.allow_redirects:Ture/False,預設是Ture,用於允不允許URL進行重定向
11.stream:Ture/False,預設是Ture,用於對獲取的內容是否立即下載
12.verify:Ture/False,預設是Ture,用於驗證SSL證書開關
13.cert:儲存本地SSL證書路徑的欄位
網路爬蟲中的Robots協議
網路爬蟲的尺寸
- 小規模,資料量小,爬取速度不明感,爬取網頁:requests庫(90%)
- 中規模,資料量規模較大,爬取速度敏感,爬取網站、系列網站:Scrapy庫
- 大規模,搜尋引擎,爬取速度關鍵,爬取全網:定製開發
網路爬蟲的限制
網站是通過根據請求頭中user-agent欄位是否是瀏覽器來判斷是否是爬蟲爬取資料,通過headers={...}更改使用者代理為瀏覽器就可以正常爬取資料了。
來源審查:判斷user-agent進行限制
檢查來訪http協議頭的user-agent域,只響應瀏覽器或友好爬蟲的訪問
釋出公告:Robots協議(網路爬蟲排除標準)
http://www.jd.com/robots.txt 京東網終的robots協議
robots協議是指:放在網站根目錄下,用來告知爬蟲哪些可以爬取哪些不能爬取資料。
爬取例子:
import requests
def getJd(url):
try:
header= {'user-agent':'Mozilla/5.0'} #定製請求頭,使用者代理
r = requests.get(url,headers=header,timeout=30)
r.raise_for_status() #不是200則爬出httperror
r.encoding = r.apparent_encoding
print(r.request.headers) #請求頭
print(r.headers) #響應頭
return r.text[:1000]
except:
return "異常了!"
if __name__ == "__main__":
url = "https://item.jd.com/4435312.html"
print(getJd(url))
圖片爬取:
import requests
#爬取網上的圖片、視訊、動畫等http://xxx.jpg/x.mp4/xxx.mp3
def gePicture(url,path):
try:
r = requests.get(url)
r.raise_for_status()
except:
return '出錯了'
else:
#with開啟一個檔案,然後把爬取到的內容(2進位制)寫進這個檔案中,r.content表示返回內容的2進位制形式
with open(path,'wb') as f:
#將返回的二進位制圖片資料寫進檔案中
f.write(r.content)
f.close()
if __name__ == "__main__":
url = "http://xxx.mpv"
path = r'D:\screenshot\girl1.jpg'
gePicture(url,path)
圖片爬取2:
import requests
import os
url = "https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1489415348&di=9d1919ad385f0b4afed19f83f1887e75&src=http://img.caixin.com/2014-03-24/1395633851186812.jpg"
root = "D://screen//"
path = root + url.split('/2014-03-24/')[-1]
try:
#資料夾不存在則新建一個
if not os.path.exists(root):
os.mkdir(root)
#檔案不存在則儲存,存在則列印‘已存在’
if not os.path.exists(path):
r = requests.get(url)
with open(path,'wb') as f :
f.write(r.content)
f.close()
print('檔案儲存成功!')
else:
print('檔案已經存在!')
except:
print('爬取失敗')
我們平時看到的人機互動方式,如:影象、文字框、點選按鈕等等,瀏覽器向後臺伺服器提交請求的時候其實都是以連結的形式來提交的,只要通過瀏覽器的解析知道提交給後臺的URL的形式那就可以用request庫模擬瀏覽器向伺服器做正式的提交。向網站提交資料時可以通過這種方式。
eg:在百度搜索框搜尋內容其實是:http://www.baidu.com?wd='unittest'方式提交的
網路上任何內容都有一個URL,任何內容都是用URL定義的,都可以通過URL對網路上內容進行操作。
網路爬蟲之規則-》Beautiful Soup庫
Beautiful Soup的介紹和安裝
Beautiful Soup庫可以將HTML、XML進行解析並且提取其中的相關資訊,可以將提供給它的任何格式進行爬取並且可以進行樹形解析。
Beautiful Soup庫的安裝
pip install beautifulsoup4
安裝成功後使用Beautiful Soup庫,先import進:
from bs4 import BeautifulSoup (從bs4導進BeautifulSoup這個類)
soup = BeautifulSoup('<p>data</p>','html.parser')
引數解析:
<p>data</p>:需要BeautifulSoup解析的xml或html程式碼
html.parser:對xml或html解析的解析器
使用例子:
>>> import requests
>>> r = requests.get(url='http://192.168.0.107/html5/girl.html')
>>> r.encoding = r.apparent_encoding
>>> demo = r.text #通過requests庫獲取API頁面
>>> from bs4 import BeautifulSoup #從bs4導進BeautifulSoup類
>>> soup = BeautifulSoup(demo,'html.parser') #html.paeser是解析demo的解析器(對demo進行html的解析)
>>> print(soup.prettify()) #呼叫prettify()方法,下面則是成功解析了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>
四大美女
...... ...... ...... ......
</body>
</html>
Beautiful Soup庫的基本元素
Beautiful Soup庫是解析xml和html的功能庫。html、xml大都是一對一對的標籤構成,所以Beautiful Soup庫是解析、遍歷、維護“標籤樹”的功能庫,只要提供的是標籤型別Beautiful Soup庫都可以進行很好的解析。<p id='pp'>...</p>:標籤Tag
p:標籤的name
屬性:成對存在
Beauti Soup庫的匯入
from bs4 import BeautifulSoup
import bs4
html文件 == 標籤樹 == BeautifulSoup類 可以認為三者是等價的
>>>from bs4 import BeautifulSoup
>>>soup = BeautifulSoup('<html>data</html>','html.parser')
>>>soup1=BeautifulSoup(open(r'D:\demo.html'),'html.parser')
簡單來說一個BeautifulSoup類對應一個html文件的全部內容。如上面的soup、soup1都對應一個html文件。Beautiful Soup庫的解析器
一般使用html.parser解析器就行了。
Beautiful Soup類的基本元素
- Tag標籤,最基本的資訊組織單元,分別用<></>開頭結尾,通過soup.<標籤>獲得
- Name 標籤名字,通過<標籤>.name獲取到
- Attribute 標籤的屬性,字典形式,通過<標籤>.attrs獲取
- Navigablestring 標籤對之間的字串,通過<標籤>.string獲取
- Comment 標籤內字串的註釋部分,一種特殊的Comment型別
通過 soup.標籤名 可獲得該標籤資訊
當存在多個一樣的標籤時,預設返回第一個標籤的資訊。
>>> soup=BeautifulSoup(demo,'html.parser')
>>> soup.title
<title>四大美女</title>
>>> soup.a
<a href="http://www.baidu.com" target="_blank"><img alt="" heigth="200" src="picture/1.png" title="貂蟬" width="150"/></a>
通過 soup.標籤.name 可獲取標籤的名字,以字串形式返回
>>> soup.a.name #獲取a標籤的名字
'a'
>>> soup.a.parent.name #獲取a標籤的上級標籤名字
'p'
>>> soup.a.parent.parent.name #獲取a標籤的上上級標籤的名字
'hr'
通過 soup.標籤.attrs 可獲得標籤的屬性,以字典形式返回>>> soup.a.attrs #獲取a標籤的屬性,字典返回
{'href': 'http://www.baidu.com', 'target': '_blank'}
>>> tag = soup.a.parent
>>> tag.name #獲取p標籤的屬性,字典返回
'p'
>>> tag.attrs
{'align': 'center'}
因為返回的是字典可以採用字典的方法對其進行資訊的提取。
>>> for i,j in soup.a.attrs.items(): #for迴圈遍歷字典
print(i,j)
href http://www.baidu.com
target _blank
>>> soup.a.attrs['href'] #獲取某個key的value值
'http://www.baidu.com'
>>> soup.a.attrs.keys() #獲取字典所有的keys
dict_keys(['href', 'target'])
>>> soup.a.attrs.values() #獲取字典所有values
dict_values(['http://www.baidu.com', '_blank'])
通過 soup.標籤.string 可以獲取標籤之間的文字,返回字串>>> soup.title.string #獲取title表之間的文字
'四大美女'
>>> soup.a.string #獲取a標籤之間的文字,沒有返回空
>>> soup.a
<a href="http://www.baidu.com" target="_blank"><img alt="" heigth="200" src="picture/1.png" title="貂蟬" width="150"/></a>
html中註釋部分通過soup.html或soup不能顯示出來,要檢視html的註釋怎麼辦?>>> newsoup = BeautifulSoup('<b><!--這是註釋!--></b><p>這是段落</p>','html.parser')
>>> newsoup.b.string
'這是註釋!'
>>> newsoup.p.string
'這是段落'
>>> type(newsoup.b.string)
<class 'bs4.element.Comment'>
>>> type(newsoup.p.string)
<class 'bs4.element.NavigableString'>
基於bs4庫的HTML的遍歷方法
HTML的基本格式樹形結構:
標籤樹的下行遍歷
屬性及說明 返回的是列表
- .contents 子節點的列表,將<tag>標籤所有兒子節點存入列表===》返回列表型別
- .children 子節點的迭代型別,與.content類似,用於迴圈遍歷兒子節點
- .descendants 子孫節點的迭代型別,包含所有子孫節點,用於迴圈遍歷 ===》迭代型別,只能用在for迴圈中
也就是說contents和children只獲得當前節點的下一層的節點資訊,而descendants可以獲得一個節點後續的所有節點資訊用於遍歷
>>> soup.head
<head>
<meta charset="utf-8">
<title>四大美女</title>
</meta></head>
>>> soup.head.contents #獲取head標籤下的兒子節點
['\n', <meta charset="utf-8">
<title>四大美女</title>
</meta>]
>>> len(soup.body.contents) #通過len函式獲取body標籤的兒子節點個數
3
>>> for i in soup.body.children: #遍歷body標籤的兒子節點
print(i)
>>> for i in soup.body.descendants: #遍歷body標籤所有的兒子、子孫節點
print(i)
上行遍歷
屬性及說明- .parent 當前節點的父親標籤
- .parents 節點先輩標籤的迭代型別,用於迴圈遍歷先輩節點。包含當前節點的直接父親、先輩父親。
>>> soup.a.parents
<generator object parents at 0x03944D80>
>>> for i in soup.a.parents:
if i is None: #對a標籤的所有先輩標籤進行列印
print(i)
else:
print(i.name)
p
hr
body
html
[document]
平行遍歷
平行遍歷必須是發生在同一個父親節點下的各節點間。
屬性及說明- .next_sibling 返回按照html文字順序的下一個平行節點標籤
- previous_sibling 返回按照html文字順序的上一個平行節點標籤
- .next_siblings 迭代型別,返回按照html文字順序的後續所有平行節點標籤
- .previous_siblings 迭代型別,返回按照html文字順序的前續所有平行節點標籤
迭代型別是需要通過迴圈遍歷一個個打印出來的
在標籤樹中樹形結構採用的是標籤來組織,但標籤之間的字串也構成了標籤的節點,也就是任何節點它的平行節點或兒子幾點是存在string型別的所以不能一致認為它們都是標籤。
>>> soup.p.next_sibling.next_sibling #p標籤的下一個再下一個平行節點
<h1 align="center"><img alt="girl" heigth="35" src=3deb48f371e4737f41f3a292cf578e0.jpg" width="20px">四大美女</img></h1>
>>> soup.p.previous_sibling #p標籤的上一個平行節點
'\n'
>>> soup.p.previous_sibling.previous_sibling #p標籤的上一個再上一個的平行節點
基於bs4庫的HTML格式化和編碼
prettify()方法可以格式化html程式碼>>> print(soup.a.prettify())
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">
新聞
</a>
bs4庫將任何讀入的html檔案或字串都轉換成“utf-8”編碼。Python3系列預設支援編碼是utf-8,所以中文時不需要進行Unicode轉碼。
資訊標記的三種形式
XML
<img src="chain.jpg" size="10">...</img> 有內容時用一對標籤表達
<img src="china.jpg" size="10"/> 空元素時縮寫形式,沒內容
<!--註釋-->
JSON
js對面向物件語言的表達形式,有型別的鍵值對。
對js等程式語言,可以把json直接當成程式語言的一部分,Python的或需要通過json庫的dump()和load()方法字典和json的轉換。
YAML
無型別的鍵值對,通過縮排表達所屬關係,和Python很像。
通過-號表達並列關係
name:
-liyue
-liyu
-test
|表示整塊資料,#註釋
key:value
key:#comment 註釋
-value1
-value2
key:
subkey:subvalue
資訊提取的一般方法
融合方法:結合形式解析與搜尋方法,提取關鍵資訊。
例子:提取html中所有的連結
1.搜尋到所有的a標籤
2.解析a標籤格式,提取href後的連結內容
>>> import requests
>>> r = requests.get(url='http://www.baidu.com',timeout=30)
>>> r.status_code
200
>>> r.encoding=r.apparent_encoding
>>> demo = r.text
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup(demo,'html.parser')
>>> for i in soup.find_all('a'): #find_all()方法獲取頁面中所有的a標籤
print(i.get('href')) #get()方法獲取url連結
http://news.baidu.com
http://www.hao123.com
http://map.baidu.com
http://v.baidu.com
http://tieba.baidu.com
基於bs4庫的HTML內容查詢方法
BeautifulSoup庫提供了一個方法
find_all(name,attrs,recursive,string,**kwargs)
引數都是精確搜尋,需要利用正則表示式模糊索索
import re
re.compilt()
這個方法可以在soup的變數中查詢資訊,返回的是列表型別,儲存查詢的結果
name:對標籤名稱的檢索字串
>>> for i in soup.find_all(True): #true則返回所有標籤
print(i.name)
html
head
meta
meta
meta
link
title
body
>>> soup.find_all(['a','b']) #返回a、b標籤,同一個列表中
返回以b開頭的標籤:需要利用正則表示式庫 re>>> for i in soup.find_all(re.compile('b')): #返回以b開頭的標籤,模糊匹配
print(i.name)
body
attrs:對標籤屬性值的檢索字串,可以標註屬性檢索,屬性值需要屬性名稱='屬性值'>>> soup.find_all('p',target='_blank') #查詢p標籤,而且屬性值是_blank的
[]
>>> soup.find_all(id='su') #查詢id屬性值是su的標籤
[<input class="bg s_btn" id="su" type="submit" value="百度一下"/>]
>>> soup.find_all(re.compile('link')) #查詢標籤包含link的標籤
[<link href="http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"><title>百度一下,你就知道</title></link>]
>>> soup.find_all(id=re.compile('link')) #查詢屬性名為id,值為包含link的標籤
[]
recursive:是否對子孫全部檢索,預設是Truestring:<>.....</>對標籤中的string檢索
>>> soup.find_all('a',recursive=False) #將recursive設為False則只在兒子節點檢索
[]
>>> soup.find_all(string='登入') #string檢索
['登入']
>>> soup.find_all(string='關於') #字串檢索,必須精確的檢索,通過正則表示式可以模糊索索
[]
>>> soup.find_all(string=re.compile('關於')) #通過正則表示式和find_all()函式可以很有效的通過字串檢索查該html或xml頁面所有待該頁面的該欄位
['關於百度']
find_all()函式的簡寫形式:
soup()====soup.find_all()
<tag>()====<a>.find_all()
(定向爬蟲、)網路爬蟲例項
需求:
輸入:需要爬取的URL
輸出:需要爬取到的資訊,並按一定排序輸出
方法:requests、BeautifulSoup
定向爬蟲:是指僅對指定的URL進行爬取,不擴充套件爬取
這類爬蟲只能獲取靜態頁面資訊,對於js動態生成的頁面資訊再說。通過原始碼可以判斷是否是js動態生成。
程式設計思路:
1.從網頁上獲取網頁內容---requests
2.提取網頁內容的有效資訊,並把資訊放進合適的資料結構中,這樣可以將資訊變成我們程式碼的一部分
3.利用資料結構輸出其中的資訊,並達到我們所要的資料結果
程式結構設計:
1.獲取網頁內容
getHTMLText()
2.獲取網路內容資訊並存儲到合適的資料結構中,列表
fillUnivList()
3.利用資料結構展示並輸出結果
printUnivList()
實現程式碼:
import requests
from bs4 import BeautifulSoup
import bs4
#獲取html頁面
def getHTMLText(url):
try:
r = requests.get(url,timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
#將html頁面放到list列表中
def fillUnivList(ulist,html):
soup = BeautifulSoup(html,'html.parser')
# 查詢tbody標籤然後,下行遍歷,只遍歷兒子節點(tr)。tr標籤是行,這裡是一所大學的資訊。
for tr in soup.find("tbody").children:
#tbody的兒子節點可能有tr標籤和字串型別(字串型別也是節點),這裡所有資訊都在tr標籤中,這裡需要過濾掉非標籤型別的資訊
#這裡需要import bs4,檢測迴圈遍歷tr的型別,若不是bs4庫定義的標籤型別則過濾掉
if isinstance(tr,bs4.element.Tag):
#對tr標籤中的td標籤做查詢,存為列表型別tds
#tds = tr.find_all("td")
tds = tr('td') #find_all()的簡寫
#將排名、大學名稱、大學排分加進ulist列表中
ulist.append([tds[0].string,tds[1].string,tds[3].string])
#將列表資訊打印出來,num表示將html頁面的多少個學校打印出來
def printUnivList(ulist,num):
#.format()方法格式化輸出
#表頭的列印
print("{:^10}\t{:^6}\t{:^10}".format("排名","學校名稱","總分"))
#大學資訊列印,格式和表頭一致
for i in range(num):
u = ulist[i]
print("{:^10}\t{:^6}\t{:^10}".format(u[0],u[1],u[2]))
print("Suc" + str(num))
#主函式
def main():
#將大學資訊放進ulist列表中
ulist = []
url = "http://www.zuihaodaxue.cn/zuihaodaxuepaiming2016.html"
#呼叫3個步驟對應的函式
#將URL轉換為html
html = getHTMLText(url)
#將html資訊提取後放在uinfo的變數中
fillUnivList(ulist,html)
#列印大學資訊
printUnivList(ulist,25) #這裡選取20所學校資訊
main()
中文輸出對齊問題
.format輸出中文字元和西文字元
中文輸出對齊問題在輸出是中英文混合的情況下都存在。
解決方案:
當中文字元寬度不夠時,系統預設採用西文字元填充。只要採用中文字元空格填充而不用西文空格填充,中文對齊問題就很很好解決了。
採用中文字元的空格填充採用chr(12288)表示一箇中文空格。
#將列表資訊打印出來
def printUnivList(ulist,num):
tplt = "{0:^10}\t{1:{3}^10}\t{2:^10}"
print(tplt.format("排名","學校名稱","總分",chr(12288)))
for i in range(num):
u = ulist[i]
print(tplt.format(u[0],u[1],u[2],chr(12288)))
print("Suc" + str(num))
網路爬蟲之實戰
正則表示式是什麼
- 正則表示式是用來簡潔表達一組字串的表示式。
- 正則表示式是通用的字串表達框架。
- 正則表示式是簡潔表達一組字串的表示式。
- 正則表示式是針對字串表達“簡潔”和“特徵”思想的工具。
- 正則表示式可以用來判斷某字串的特徵歸屬。
另外正則表示式在文字處理中十分常用:
- 正則表示式用來表達文字型別的特徵如,病毒、入侵等。
- 正則表示式可以表達一組字串,用於查詢替換。
- 正則表示式可以匹配字串的全部或一部分。
正則表示式的語法
正則表示式是由字元和操作符構成的。
正則表示式的常用操作符
常規例子:
P(Y|YT|YTH|YTHO)?N "PN","PYN","PYTN","PYTHN","PYTHON"
PYTHON+ "PYTHON","PYTHONN","PYTHONNN..."
PY[TH]ON "PYTON","PYHON"
PY[^TH]?ON "PYON","PYAON","PYXON"
PY{:3}N "PN","PYN","PYYN","PYYYN"
經典例子:
^[A-Za-z]+$ 由26個字母組成的字串
^[A-Za-z0-9]+$ 由26個字母和數字組成的字串
^-?\d+$ 整數形式的字串
^[0-9]*[1-9][0-9]*$ 正整數形式的字串
[1-9]\d{5} 中國境內郵政編碼,6位
[\u4e00-\u9fa5] 匹配中文字元 #採用utf8編碼約定中文字元取值範圍
\d{3}-\d{8}|\d{4}-\d{7} 國內電話號碼,010-68913536 11位啊
由字串來構造正則表示式:匹配IP地址的正則表示式,(IP地址分4段,每段是0-255)
精確的寫法:
0-99: [1-9]?\d
100-199:1\d{2}
200-249:2[0-4]\d
250-255:25[0-5]
ip地址:
(([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5]).){3}([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])
Re庫的基本使用
re庫是Python的的標準庫,主要用於字串匹配。
呼叫方式:
import re
正則表示式的表示型別
re庫採用raw string型別,也就是原生字串型別。
原生字串型別是指:斜槓\ 不會轉意。因為正則表示式中操作符有些是和斜槓一起構成的,使用原生字串就可以防止轉意了。
在字串前加一個小寫字母r就可以了。
如:
r"[1-9]?\d"
所以:
當正則表示式中包含轉意符號時,使用原生字串。
re庫的功能函式
函式的具體說明:
re.search(pattern,string,flags=0)
作用:
在一個字串中搜索匹配正則表示式的第一個位置,返回match物件。
引數說明:
- pattern:正則表示式的字串或原生字串
- string:需要和這個正則表示式匹配的字串
- flags:正則表示式使用時的控制標記
flags:正則表示式使用時的控制標記
- re.I 忽略正則表示是的大小寫,[a-z]可以匹配大寫
- re.M 正則表示式中的^操作符能夠將給定的字串的每行當做匹配的開始
- re.S 正則表示式中的.點操作符能夠匹配所有的字元,預設匹配除換行符外的所有字元
例子:
>>> import re #匯入re庫
>>> match = re.search(r'[1-9]\d{5}','GHF345326 GHF525300') #匹配郵政編碼,返回的是一個match物件
>>> if match:
print(match.group(0))
345326
re.match(pattern,string,flags=0)作用:
從一個字串的開始位置起匹配正則表示式,返回match物件。
引數:
同search
例子:
>>> import re
>>> match1 = re.match(r'[1-9]\d{5}','GHF535343')
>>> if match1:
print(match1.group(0))
#match()方法是從字串的起始位置開始匹配,開始字串不是,所有match物件為空。
>>> match1.group(0) #要對返回的match物件進行列印時,需要判斷match物件是否為空,不為空才能呼叫group(0)方法。
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
match1.group(0)
AttributeError: 'NoneType' object has no attribute 'group'
>>> match2 = re.match(r'[1-9]\d{5}','525300 GHT')
>>> if match2:
match2.group(0)
'525300'
re.findall(pattern,string,flags=0)作用:
搜尋字串,以列表的形式返回所有能夠匹配的子串。
引數同search
例子:
>>> import re
>>> ls =re.findall(r'[1-9]\d{5}','234323gg GHT838476ff535243')
>>> ls
['234323', '838476', '535243']
re.split(patttern,string,maxsplit=0,flags=0)作用:
將一個字串按照正則表示式匹配的結果進行分割,返回列表型別。
pattren\string\flags和search、match、findall方法一樣,
maxsplit:最大分割數,剩餘部分作為最後一個元素輸出
例子:
>>> import re
>>> ls = re.split(r'[1-9]\d{5}','FG123456JJJ213456 234567SDF')
>>> ls
['FG', 'JJJ', ' ', 'SDF'] #返回一個列表
>>> ls1 = re.split(r'[1-9]\d{5}','FJD324538HJH879045',maxsplit=1) #最大分割是1,所有隻分割1個,剩下的原路返回
>>> ls1
['FJD', 'HJH879045']
>>>
re.finditer(pattern,string,flags=0)作用:
搜尋字串,返回一個匹配結果的迭代型別,每個迭代元素都是match物件。
引數和search、match、findall方法一樣
例子:
>>> import re
>>> srting = 'ere123456kij234567pl345435'
>>> ls = re.finditer(r'[1-9]\d{5}',srting)
>>> type(ls)
<class 'callable_iterator'>
>>> for i in ls:
if i :
print(i.group(0))
123456
234567
345435
re.sub(pattern,repl,string,count=0,flags=0)作用:
在一個字串中替換所有匹配正則表示式的子串,返回被替換後的字串。
引數pattern、string、flags和search、match、findall的引數一樣。
repl:替換匹配字串的字串
count:匹配的最大替換次數
例子:
>>> import re
>>> re.sub(r'[1-9]\d{5}','repl111','GHT212123GTY345673',count=2)
'GHTrepl111GTYrepl111'
>>> re.sub(r'[1-9]\d{5}','+test+','GTH787878GHU898789')
'GTH+test+GHU+test+'
>>> re.sub(r'[1-9]\d{5}','+test+','GTH787878GHU898789',count=1)
'GTH+test+GHU898789'
>>>
以上通過.呼叫的方法使用re的方法是函式式用法:一次性操作
正則表示式還有另外一種方法:
面向物件用法:編譯後多次操作
>>> import re
>>> pat = re.compile(r'[1-9]\d{5}')
>>> rst = pat.search('GHT 525343')
>>> rst
<_sre.SRE_Match object; span=(4, 10), match='525343'>
>>> if rst:
print(rst.group(0))
525343
先使用compile()編譯成正則表示式,之後再呼叫search、match、findall、split、sub、finditer方法。
沒經過compile的字串只是正則表示式的一種表現形式,經過compile後的才是正則表示式。
優點:一次編譯可以多次使用該正則表示式進行匹配。
pat = compile(pattern,flags=0)
作用:
將一個正則表示式編譯成一個正則表示式物件。
引數說明:
pattern:正則表示式字串或原生字串;
flags:正則表示式使用時的控制標記;
re庫的match物件
re的search()、match()、finditer()返回的是一個match物件,search、match只返回匹配到的第一個字串,需要返回全部匹配的字串使用finditer,for迴圈全部打印出來。
match物件是:一次匹配的結果,它包含了很多匹配的相關資訊。
match物件的屬性
- .string 待匹配的的文字
- .re 匹配時使用的pattern物件(正則表示式)
- .pos 正則表達是搜尋文字的開始位置
- .endpos 正則表示式搜尋文字的結束位置
>>> import re
>>> i = re.search(r'[1-9]\d{5}','GTR343435')
>>> if i :
print(i.group(0))
343435
>>> i.string #待匹配的文字
'GTR343435'
>>> i.re #正則表示式
re.compile('[1-9]\\d{5}')
>>> i.pos #搜尋文字的開始位置
0
>>> i.endpos #搜尋文字的結束位置
9
match物件的方法- .group(0) 獲得匹配後的字串
- .start() 匹配字串在原字串的開始位置
- .end() 匹配字串在原字串的結束位置
- .span() 返回(.start(),.end())元組結構
>>> i.start() #匹配字串在原字串的開始位置
3
>>> i.end() #匹配字串在原字串的結束位置
9
>>> i.span() #返回一個元組,包括匹配字串在原字串的開始位置和結束位置
(3, 9)
>>> i.group(0) #返回匹配後的字串
'343435'
正則表示式的貪婪匹配和最小匹配
re庫預設採用貪婪匹配的方式,也就是返回匹配的最長項,如:
>>> import re
>>> i = re.search(r'py.*n','pyanbncndnfngn')
>>> if i:
i.group(0)
'pyanbncndnfngn' #預設採用貪婪匹配,返回最長的
如何輸出最小匹配呢?只要在*的後面增加一個?問號:
>>> ii = re.match(r'py.*?n','pyanbncndnfngn')
>>> if ii :
ii.group(0)
'pyan'
最小匹配操作符:當有操作符可以匹配不同長度時,我們都可以在操作符後面增加一個問號?來獲取最小匹配。
什麼時候採用BeautifulSoup庫?
什麼時候採用re庫?
什麼樣的網站適合定向爬蟲實現爬取?資料寫在html頁面中,可以通過定位到元素,一般的靜態頁面,但那種有js生成的頁面,原始碼沒有顯示頁面,不適合定向爬蟲。
淘寶定向爬蟲例項
從眾多的文字資訊中提取我們需要的資訊,使用正則表示式是最好的。
當不使用BeautifulSoup庫進行資訊提取,通過搜尋的方式提取資訊,使用re庫是最合適的。
新知識點:
- 使用r.encoding = "utf-8" 比使用r.encoding = r.apparent_encoding節約時間
- 字串中引入雙引號,需要在前面加反斜槓,r'\"view_price\"\:\"[\d\.]*\"'
- eval()函式能夠將字串最外層雙引號或單引號去掉
- for 迴圈遍歷range()是從0開始的,for i in plt:也可以的,但是下面也要再次遍歷tlt、重複了。使用range(len(list)),獲得列表的每個元素的序號,再通過下標可以得到列表元素了。
- continue語句只是結束本次迴圈,而不會終止迴圈的執行。break語句則是終止整個迴圈過程。
- {}定義槽函式,{:4}第一個位置長度為4,中間8,最後是16。參考程式碼。
- 字串的.format()函式使用
import requests
import re
import time
#獲取html頁面
def getHTMLText(url):
try:
r = requests.get(url,timeout = 30)
r.raise_for_status()
r.encoding = "utf-8" #可以節約時間
return r.text
except:
return ""
#對獲取的每一個頁面進行解析,ilt是結果的列表型別
def parsePage(ilt,html):
try:
#"view_price":"149.00"
#反斜槓\表示引入雙引號,獲取價格資訊儲存到plt中
plt = re.findall(r'\"view_price\"\:\"[\d\.]*\"',html)
#"raw_title":"2017春季新款雙肩包女韓版時尚pu揹包流蘇子母包百搭學院風書包"
#*?是最小匹配
tlt = re.findall(r'\"raw_title\"\:\".*?\"',html)
#獲取了商品的價格和資訊,下面對這兩個資訊關聯起來,儲存到我們要輸出的變數中
#plt\tlt列表長度一樣,元素位置一一對應的,同一個位置的元素是同一個商品的
for i in range(len(plt)):
#字串分割獲取商品的價格,eval()函式能夠將字串的最外層的雙引號或單引號去掉
price = eval(plt[i].split(':')[1])
#獲得商品名稱
title = eval(tlt[i].split(":")[1])
#將資訊需要輸出的列表中
ilt.append([price,title])
except:
print("")
#將解析後的資訊輸出
def printGoodsList(ilt):
#先設計一個列印模板tplt,希望列印什麼格式,
# {}定義槽函式,{:4}第一個位置長度為4,中間8,最後是16
tplt = "{:4}\t{:8}\t{:16}"
#列印輸出資訊的表頭
print(tplt.format("序號","價格","商品名稱"))
#定義一個輸出資訊計數器,商品序號
count = 0
#對所有的資訊進行輸出顯示
for i in ilt:
count +=1 #序號,價格,名稱
print(tplt.format(count,i[0],i[1]))
#定義主函式,記錄整個程式執行的過程
def main():
start_time = time.time()
#搜尋關鍵詞goods
goods = "書包"
#設定向下一頁爬取的深度,爬取頁數depth
depth = 2
#爬取的URL
start_url = "https://s.taobao.com/search?q=" + goods
#定義變數infoList 表示輸出結果
infoList = []
#因為每一個頁面URL不同,需要對每一個頁面進行單獨訪問和處理
for i in range(depth):
try: #使用try多獲取頁面進行異常判斷,如果某頁面解析出問題,可以跳過該頁面,往下繼續,不會造成出現解析錯誤,程式停止
#對每一個頁面的URL連結進行設計,因為淘寶每個頁面顯示44個商品
url = start_url + '&s=' + str(44*i)
html = getHTMLText(url) #獲取頁面內容
parsePage(infoList,html) #對獲取的頁面進行處理
except:
#continue語句只是結束本次迴圈,而不會終止迴圈的執行。break語句則是終止整個迴圈過程
continue
#將獲取的頁面資訊輸出
printGoodsList(infoList)
end_time = time.time()
print(end_time-start_time)
print(round(end_time-start_time))
main()
股票資料爬取例項
採用技術路線:
BeautifulSoup + re + requests + traceback(方便除錯)
注意的知識點:
share_reptile.py
import requests
import re
import traceback
from bs4 import BeautifulSoup
#獲得URL對應的頁面
def getHTMLText(url):
try:
r = requests.get(url,timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""
#獲得股票的資訊列表,lst是儲存所有股票的列表,存了所有股票資訊;
# stockURL是獲得股票列表的URL網站
def getStockList(lst,stockURL):
html = getHTMLText(stockURL) #獲取東方財富網的html
soup = BeautifulSoup(html,"html.parser")
a = soup.find_all('a') #通過find_all()獲得頁面的a標籤,a是一個列表
for i in a :
try: #程式碼中有很多不是股票連結的a標籤的,解析可能出現錯誤,需要採用try...except...框架
href = i.attrs["href"] #以s開頭中間是h或z後面是6個數字的正則
lst.append(re.findall(r'[s][hz]\d{6}',href)[0])#可以找到所有的股票URL
except:
continue
#獲得每一隻個股的股票資訊,lst是儲存所有股票的列表;stockURL是獲得股票列表的URL網站;股票資訊儲存的檔案路徑
def getStockInfo(lst,stockURL,fpath):
for i in lst:
url = stockURL + i + ".html"
html = getHTMLText(url)
try:
if html == "":
continue
infoDict = {}
soup = BeautifulSoup(html,"html.parser")
stockInfo = soup.find('div',attrs={'class':'stock-bets'})
name = stockInfo.find_all(attrs={'class':'bets-name'})[0]
infoDict.update({'股票名稱':name.text.split()[0]})
keyList = stockInfo.find_all('dt')
valueList = stockInfo.find_all('dd')
for i in range(len(keyList)):
key = keyList[i].text
value = valueList[i].text
infoDict[key] = value
with open(fpath,'a',encoding='utf-8') as f:
f.write(str(infoDict) + '\n')
except:
traceback.print_exc()
continue
#主函式
def main():
stock_list_url = 'http://quote.eastmoney.com/stocklist.html' #獲得股票列表
stock_info_url = 'https://gupiao.baidu.com/stock/' #獲取股票資訊的連結的主題部分
output_file = r'D:\gupaio_reptile.txt' #儲存的路徑
slist = [] #股票資訊,列表
getStockList(slist,stock_list_url) #獲得股票列表
getStockInfo(slist,stock_info_url,output_file) #獲得每一隻股票資訊,儲存到本地
main()
Scrapy爬蟲
Scrapy爬蟲框架的介紹、使用
Scrapy的安裝
在command命令列輸入:pip install scrapy