一、使用 urllib3 實現 HTTP 請求#
1. 生成請求#
- 通過 request 方法生成請求,原型如下
urllib3.request(method,url,fields=None,headers=None,**urlopen_kw)
參數 | 說明 |
---|---|
method | 接收 string。表示請求的類型,如 "GET"(通常使用)、"HEAD"、"DELETE" 等,無默認值 |
url | 接收 string。表示字符串形式的網址。無默認值 |
fields | 接收 dict。表示請求類型所帶的參數。默認為 None |
headers | 接收 dict。表示請求頭所帶參數。默認為 None |
**urlopen_kw | : 接收 dict 和 python 中的數據類型的數據,依據具體需求及請求的類型可添加的參數,通常參數賦值為字典類型或者具體數據 |
code: |
import urllib3
http = urllib3.PoolManager()
rq = http.request('GET',url='http://www.pythonscraping.com/pages/page3.html')
print('伺服器響應碼:', rq.status)
print('響應實體:', rq.data)
2. 處理請求頭#
傳入 headers 參數可通過定義一個字典類型實現,定義一個包含 User-Agent 信息的字典,使用瀏覽器為火狐和 chrome 瀏覽器,操作系統為 "Window NT 6.1;Win64; x64",向網站 "http://www.tipdm/index.html" 發送帶 headers 參數的 GET 請求,hearders 參數為定義的 User-Agent 字典
import urllib3
http = urllib3.PoolManager()
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)
3.Timeout 設置#
為防止因網路不穩定等原因丟包,可在請求中增加 timeout 參數設置,通常為浮點數,可直接在 url 後設置該次請求的全部參數,也可以分別設置這次請求的連接與讀取 timeout 參數,在 PoolManager 實例中設置 timeout 參數可應用至該實例的全部請求中
直接設置
http.request('GET',url='',headers=head,timeout=3.0)
#超過3s的話超時終止
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head,timeout=urllib3.Timeout(connect=1.0,read=2.0))
#連接超過1s,讀取超過2s終止
應用至該實例的全部請求中
import urllib3
http = urllib3.PoolManager(timeout=4.0)
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)
#超過4s超時
4. 請求重試設置#
urllib3 庫可以通過設置 retries 參數對重試進行控制。默認進行 3 次請求重試,並進行 3 次重定向。自定義重試次數通過賦值一個整型給 retries 參數實現,可通過定義 retries 實例來定制請求重試次數及重定向次數。若需要同時關閉請求重試及重定向則可以將 retries 參數賦值為 False,僅關閉重定向則將 redirect 參數賦值為 False。與 Timeout 設置類似,可以在 PoolManager 實例中設置 retries 參數控制全部該實例下的請求重試策略。
應用至該實例的全部請求中
import urllib3
http = urllib3.PoolManager(timeout=4.0,retries=10)
head = {'User-Agent':'Window NT 6.1;Win64; x64'}
http.request('GET',url='http://www.pythonscraping.com/pages/page3.html',headers=head)
#超過4s超時 重試10次
5. 生成完整 HTTP 請求#
使用 urllib3 庫實現向http://www.pythonscraping.com/pages/page3.html 生成一個完整的請求,該請求應當包含連結、請求頭、超時時間和重試次數設置。
注意編碼方式 utf-8
import urllib3
#發送請求實例
http = urllib3.PoolManager()
#網址
url = 'http://www.pythonscraping.com/pages/page3.html'
#請求頭
head = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.56'}
#超時時間
tm = urllib3.Timeout(connect=1.0,read=3.0)
#重試次數和重定向次數設置,生成請求
rq = http.request('GET',url=url,headers=head,timeout=tm,redirect=4)
print('伺服器響應碼:', rq.status)
print('響應實體:', rq.data.decode('utf-8'))
伺服器響應碼: 200
響應實體: <html>
<head>
<style>
img{
width:75px;
}
table{
width:50%;
}
td{
margin:10px;
padding:10px;
}
.wrapper{
width:800px;
}
.excitingNote{
font-style:italic;
font-weight:bold;
}
</style>
</head>
<body>
<div id="wrapper">
<img src="../img/gifts/logo.jpg" style="float:left;">
<h1>Totally Normal Gifts</h1>
<div id="content">Here is a collection of totally normal, totally reasonable gifts that your friends are sure to love! Our collection is
hand-curated by well-paid, free-range Tibetan monks.<p>
We haven't figured out how to make online shopping carts yet, but you can send us a check to:<br>
123 Main St.<br>
Abuja, Nigeria
</br>We will then send your totally amazing gift, pronto! Please include an extra $5.00 for gift wrapping.</div>
<table id="giftList">
<tr><th>
Item Title
</th><th>
Description
</th><th>
Cost
</th><th>
Image
</th></tr>
<tr id="gift1" class="gift"><td>
Vegetable Basket
</td><td>
This vegetable basket is the perfect gift for your health conscious (or overweight) friends!
<span class="excitingNote">Now with super-colorful bell peppers!</span>
</td><td>
$15.00
</td><td>
<img src="../img/gifts/img1.jpg">
</td></tr>
<tr id="gift2" class="gift"><td>
Russian Nesting Dolls
</td><td>
Hand-painted by trained monkeys, these exquisite dolls are priceless! And by "priceless," we mean "extremely expensive"! <span class="excitingNote">8 entire dolls per set! Octuple the presents!</span>
</td><td>
$10,000.52
</td><td>
<img src="../img/gifts/img2.jpg">
</td></tr>
<tr id="gift3" class="gift"><td>
Fish Painting
</td><td>
If something seems fishy about this painting, it's because it's a fish! <span class="excitingNote">Also hand-painted by trained monkeys!</span>
</td><td>
$10,005.00
</td><td>
<img src="../img/gifts/img3.jpg">
</td></tr>
<tr id="gift4" class="gift"><td>
Dead Parrot
</td><td>
This is an ex-parrot! <span class="excitingNote">Or maybe he's only resting?</span>
</td><td>
$0.50
</td><td>
<img src="../img/gifts/img4.jpg">
</td></tr>
<tr id="gift5" class="gift"><td>
Mystery Box
</td><td>
If you love suprises, this mystery box is for you! Do not place on light-colored surfaces. May cause oil staining. <span class="excitingNote">Keep your friends guessing!</span>
</td><td>
$1.50
</td><td>
<img src="../img/gifts/img6.jpg">
</td></tr>
</table>
</p>
<div id="footer">
© Totally Normal Gifts, Inc. <br>
+234 (617) 863-0736
</div>
</div>
</body>
</html>
二、使用 requests 庫實現 HTTP 請求#
import requests
url = 'http://www.pythonscraping.com/pages/page3.html'
rq2 = requests.get(url)
rq2.encoding = 'utf-8'
print('響應碼:',rq2.status_code)
print('編碼:',rq2.encoding)
print('請求頭:',rq2.headers)
print('實體:',rq2.text)
解決字符編碼問題#
需要注意的是,當 requests 庫猜測錯時,需要手動指定 encoding 編碼,避免返回的網頁內容解析出現亂碼。手動指定的方法並不靈活,無法自適應對應爬取過程中不同網頁的編碼,而使用 chardet 庫比較簡便靈活,chardet 庫是一個非常優秀的字符串∕文件編碼檢測模塊。
chardet 庫使用 detect 方法檢測給定字符串的編碼,detect 方法常用的參數及其說明如下
參數 | 說明 |
---|---|
byte_str | 接收 string。表示需要檢測編碼的字符串。無默認值 |
import chardet
chardet.detect(rq2.content)
輸出:100% 的概率是用 ascii 碼編碼的
完整代碼
import requests
import chardet
url = 'http://www.pythonscraping.com/pages/page3.html'
head={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.56'}
rq2 = requests.get(url,headers=head,timeout=2.0)
rq2.encoding = chardet.detect(rq2.content)['encoding']
print('實體:',rq2.content)
三、解析網頁#
chrome 開發者工具各面板功能如下
1. 元素面板#
在爬蟲開發中,元素面板主要用來查看頁面元素所對應的位置,比如圖片所在位置或文字連結所對應的位置。面板左側可看到當前頁面的結構,為樹狀結構,單擊三角符號即可展開分支。
2. 源代碼面板#
切換至源代碼面板(Sources)
3. 網路面板#
切換至網路面板(Network),需先重新加載頁面,點擊某資源,將在中間顯示該資源的頭部信息、預覽、響應信息、Cookies 和花費時間詳情,如圖所示。
四、使用正則表達式解析網頁#
1. Python 正則表達式:尋找字符串中的姓名和電話號碼#
正則表達式是一種可以用於模式匹配和替換的工具,可以讓用戶通過使用一系列的特殊字符構建匹配模式,然後把匹配模式與待比較字符串或文件進行比較,根據比較對象中是否包含匹配模式,執行相應的程序。
rawdata = “555-1239Moe Szyslak(636) 555-0113Burns, C.Montgomery555-6542Rev. Timothy Lovejoy555 8904Ned Flanders636-555-3226Simpson,Homer5553642Dr. Julius Hibbert ”
試試
import re
string = '1. A small sentence - 2.Anthoer tiny sentence. '
print('re.findall:',re.findall('sentence',string))
print('re.search:',re.search('sentence',string))
print('re.match:',re.match('sentence',string))
print('re.match:',re.match('1. A small sentence',string))
print('re.sub:',re.sub('small','large',string))
print('re.sub:',re.sub('small','',string))
輸出:
re.findall: ['sentence', 'sentence']
re.search: <re.Match object; span=(11, 19), match='sentence'>
re.match: None
re.match: <re.Match object; span=(0, 19), match='1. A small sentence'>
re.sub: 1. A large sentence - 2.Anthoer tiny sentence.
re.sub: 1. A sentence - 2.Anthoer tiny sentence.
常用廣義化符號
1、英文句號 “.”:能代表除換行符 “\n” 任意一個字符;
string = '1. A small sentence - 2.Anthoer tiny sentence. '
re.findall('A.',string)
輸出:['A ', 'An']
2、字符類 “[]”:被包含在中括號內部,任何中括號內的字符都會被匹配;
string = 'small smell smll smsmll sm3ll sm.ll sm?ll sm\nll sm\tll'
print('re.findall:',re.findall('sm.ll',string))
print('re.findall:',re.findall('sm[asdfg]ll',string))
print('re.findall:',re.findall('sm[a-zA-Z0-9]ll',string))
print('re.findall:',re.findall('sm\.ll',string))
print('re.findall:',re.findall('sm[.?]ll',string))
輸出:
re.findall: ['small', 'smell', 'sm3ll', 'sm.ll', 'sm?ll', 'sm\tll']
re.findall: ['small']
re.findall: ['small', 'smell', 'sm3ll']
re.findall: ['sm.ll']
re.findall: ['sm.ll', 'sm?ll']
3. 量化符號 "{}": 可以被匹配多少次
print('re.findall:',re.findall('sm..ll',string))
print('re.findall:',re.findall('sm.{2}ll',string))
print('re.findall:',re.findall('sm.{1,2}ll',string))
print('re.findall:',re.findall('sm.{1,}ll',string))
print('re.findall:',re.findall('sm.?ll',string)) # {0,1}
print('re.findall:',re.findall('sm.+ll',string)) # {0,}
print('re.findall:',re.findall('sm.*ll',string)) # {1,}
輸出:
re.findall: ['smsmll']
re.findall: ['smsmll']
re.findall: ['small', 'smell', 'smsmll', 'sm3ll', 'sm.ll', 'sm?ll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']
re.findall: ['small', 'smell', 'smll', 'smll', 'sm3ll', 'sm.ll', 'sm?ll', 'sm\tll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']
re.findall: ['small smell smll smsmll sm3ll sm.ll sm?ll', 'sm\tll']
ps:貪婪規則,儘可能匹配多的
完整代碼#
import pandas as pd
rawdata = '555-1239Moe Szyslak(636) 555-0113Burns, C.Montgomery555-6542Rev. Timothy Lovejoy555 8904Ned Flanders636-555-3226Simpson,Homer5553642Dr. Julius Hibbert'
names = re.findall('[A-Z][A-Za-z,. ]*',rawdata)
print(names)
number = re.findall('\(?[0-9]{0,3}\)?[ \-]?[0-9]{3}[ \-]?[0-9]{4}',rawdata)
print(number)
pd.DataFrame({'Name':names,'TelPhone':number})
輸出:
五、使用 Xpath 解析網頁#
XML 路徑語言(XML Path Language),它是一種基於 XML 的樹狀結構,在數據結構樹中找尋節點,確定 XML 文檔中某部分位置的語言。使用 Xpath 需要從 lxml 庫中導入 etree 模塊,還需使用 HTML 類對需要匹配的 HTML 對象進行初始化(XPath 只能處理文檔的 DOM 表現形式)。HTML 類的基本語法格式如下。
1. 基本語法#
lxml.etree.HTML(text, parser=None, *, base_url=None)
參數 | 說明 |
---|---|
text | 接收 str。表示需要轉換為 HTML 的字符串。無默認值 |
parser | 接收 str。表示選擇的 HTML 解析器。無默認值 |
base_url | 接收 str。表示設置文檔的原始 URL,用於在查找外部實體的相對路徑。默認為 None |
若 HTML 中的節點沒有閉合,etree 模塊也提供自動補全功能。調用 tostring 方法即可輸出修正後的 HTML 代碼,但是結果為 bytes 類型,需要使用 decode 方法轉成 str 類型。 |
Xpath 使用類似正則的表達式來匹配 HTML 文件中的內容,常用匹配表達式如下。
表達式 | 說明 |
---|---|
nodename | 選取 nodename 節點的所有子節點 |
/ | 從當前節點選取直接子節點 |
// | 從當前節點選取子孫節點 |
. | 選取當前節點 |
.. | 選取當前節點的父節點 |
@ | 選取屬性 |
2. 謂語#
Xpath 中的謂語用來查找某個特定的節點或包含某個指定的值的節點,謂語被嵌在路徑後的方括號中,如下。
表達式 | 說明 |
---|---|
/html/body/div[1] | 選取屬於 body 子節點下的第一個 div 節點 |
/html/body/div[last()] | 選取屬於 body 子節點下的最後一個 div 節點 |
/html/body/div[last()-1] | 選取屬於 body 子節點下的倒數第二個 div 節點 |
/html/body/div[positon()<3] | 選取屬於 body 子節點下的下前兩個 div 節點 |
/html/body/div[@id] | 選取屬於 body 子節點下的帶有 id 屬性的 div 節點 |
/html/body/div[@id="content"] | 選取屬於 body 子節點下的 id 屬性值為 content 的 div 節點 |
/html/body/div[xx>10.00] | 選取屬於 body 子節點下的 xx 元素值大於 10 的節點 |
3. 功能函數#
Xpath 中還提供部分功能函數進行模糊搜索,有時對象僅掌握了其部分特徵,當需要模糊搜索該類對象時,可使用功能函數來實現,具體函數如下。
功能函數 | 示例 | 說明 |
---|---|---|
starts-with | //div[starts-with(@id,”co”)] | 選取 id 值以 co 開頭的 div 節點 |
contains | //div[contains(@id,”co”)] | 選取 id 值包含 co 的 div 節點 |
and | //div[contains(@id,”co”)andcontains(@id,”en”)] | 選取 id 值包含 co 和 en 的 div 節點 |
text() | //li[contains(text(),”first”)] | 選取節點文本包含 first 的 div 節點 |
4. 谷歌開發者工具使用#
谷歌開發者工具提供非常便捷的複製 xpath 路徑的方法
eg:爬取知乎熱榜完整代碼
試了一下爬取知乎熱榜,需要登錄所以可以自己登錄然後獲取 cookie
import requests
from lxml import etree
url = "https://www.zhihu.com/hot"
hd = { 'Cookie':'你的Cookie', #'Host':'www.zhihu.com',
'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36'}
response = requests.get(url, headers=hd)
html_str = response.content.decode()
html = etree.HTML(html_str)
title = html.xpath("//section[@class='HotItem']/div[@class='HotItem-content']/a/@title")
href = html.xpath("//section[@class='HotItem']/div[@class='HotItem-content']/a/@href")
f = open("zhihu.txt",'r+')
for i in range(1,41):
print(i,'.'+title[i])
print('連結:'+href[i])
print('-'*50)
f.write(str(i)+'.'+title[i]+'\n')
f.write('連結:'+href[i]+'\n')
f.write('-'*50+'\n')
f.close()
爬取結果
六、數據存儲#
1. 以 json 格式存儲#
import requests
from lxml import etree
import json
#上面代碼略
with open('zhihu.json','w') as j:
json.dump({'title':title,'hrefL':href},j,ensure_ascii=False)
存儲結果(ps: 經過文件格式化處理)