前言:
老早以前寫過一個用python的request包的爬蟲文章,那裏面我使用正則表達式去對信息進行篩選,今天我們來學習一種更簡便的解析數據、篩選信息的方法,利用Xpath,並且瞭解了基本語法後,我們來爬取一個IP池。
Xpath:
XPath,全稱 XML Path Language,即 XML 路徑語言,它是一門在XML文檔中查找信息的語言。XPath 最初設計是用來搜尋XML文檔的,但是它同樣適用於 HTML 文檔的搜索。它也是是 W3C XSLT 標準的主要元素,並且 XQuery 和 XPointer 都構建於 XPath 表達之上。因此,對 XPath 的理解是很多高級 XML 應用的基礎。
一些術語(可以參考XML的知識方便理解):
1. 在 XPath 中,有七種類型的節點:元素、屬性、文本、命名空間、處理指令、註釋以及文檔節點(或稱爲根節點)。XML 文檔是被作爲節點樹來對待的。樹的根被稱爲文檔節點或者根節點。
2. 基本值(或稱原子值,Atomic value)是無父或無子的節點。
3. 項目(Item)是基本值或者節點。
基本語法:
表達式 | 描述 |
nodename | 選擇這個節點名的所有子節點 |
/ | 從當前節點選擇直接子節點 |
// | 從當前節點選取子孫節點 |
. | 選擇當前節點 |
… | 選取當前節點的父節點 |
@ | 選取屬性 |
實例:
表達式 | 結果 |
node |
選取 node元素的所有子節點。 |
/node |
選取根元素 node。假如路徑起始於 / ,則此路徑始終代表到某元素的絕對路徑,就像Linux裏的路徑規則。 |
node/bit |
選取屬於 node 的子元素的所有 bit 元素。 |
//node |
選取所有 node 子元素,而不管它們在文檔中的位置。 |
node//bit |
選擇屬於 node 元素的後代的所有 bit 元素,而不管它們位於 node 之下的什麼位置。 |
//@lang |
選取名爲 lang 的所有屬性。 |
謂語(Predicates)
謂語用來查找某個特定的節點或者包含某個指定的值的節點。
謂語被嵌在方括號中。
表達式 | 結果 |
/bookstore/book[1] |
選取屬於 bookstore 子元素的第一個 book 元素。 |
/bookstore/book[last()] |
選取屬於 bookstore 子元素的最後一個 book 元素。 |
/bookstore/book[last()-1] |
選取屬於 bookstore 子元素的倒數第二個 book 元素。 |
/bookstore/book[position()<3] |
選取最前面的兩個屬於 bookstore 元素的子元素的 book 元素。 |
//title[@lang] |
選取所有擁有名爲 lang 的屬性的 title 元素。 |
//title[@lang=’eng’] |
選取所有 title 元素,且這些元素擁有值爲 eng 的 lang 屬性。 |
/bookstore/book[price>35.00] |
選取 bookstore 元素的所有 book 元素,且其中的 price 元素的值須大於 35.00。 |
/bookstore/book[price>35.00]/title |
選取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值須大於 35.00。 |
這裏感謝這位兄臺總結的表——https://cuiqingcai.com/2621.html
上手操作:
標籤補全:
首先我們在Pycharm的終端運行命令來安裝lxml,這裏爲了學習Xpath先安裝lxml
pip3 install lxml
我們可以先來簡單的小試牛刀
#在你的模塊裏導入指定的模塊屬性
from lxml import html
etree=html.etree
text='''
<html>
<head>
<title>ISHASH.COM</title>
</head>
<body>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
</ul>
</div>
'''
html=etree.HTML(text)
result=etree.tostring(html)
print(result.decode('UTF-8'))
輸出:
<html>
<head>
<title>ISHASH.COM</title>
</head>
<body>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
</ul>
</div>
</body></html>
註解:
上面的 etree.HTML() 是將字符串解析爲html文檔並對HTML文本進行自動修正。
而後面的 etree.tostring() 輸出修正後的結果,類型是bytes。
另外上面的道理讀取html文件也是可以的:
#在你的模塊裏導入指定的模塊屬性
from lxml import html
etree=html.etree
html=etree.parse('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('UTF-8'))
獲取所有節點:
//test.html
<!DOCTYPE html>
<html>
<head>
<title>ISHASH.COM</title>
</head>
<div>
<ul>
<li class="item-0"><a href="https://is-hash.com">first item</a></li>
<li class="item-1"><a href="https://baidu.com">second item</a></li>
</ul>
</div>
<table>
<tbody>
<tr>
<td>Look at me!</td>
<td>Or kill me!</td>
</tr>
</tbody>
</table>
</body></html>
//main.py
#在你的模塊裏導入指定的模塊屬性
from lxml import html
etree=html.etree
html=etree.parse('./test.html',etree.HTMLParser())
#'//'表示獲取當前節點的子孫節點,'*'表示通配符
#合起來則是獲取當前節點的所有節點,返回值是個列表
result=html.xpath('//*')
for item in result:
print(item)
//輸出
<Element html at 0x2892284b748>
<Element head at 0x2892285e9c8>
<Element title at 0x2892285ef88>
<Element body at 0x2892286a108>
<Element div at 0x28922b9ba48>
<Element ul at 0x28922b9bac8>
<Element li at 0x28922b9bb08>
<Element a at 0x28922b9bb48>
<Element li at 0x28922b9bb88>
<Element a at 0x28922b9ba88>
<Element table at 0x28922b9bbc8>
<Element tbody at 0x28922b9bc08>
<Element tr at 0x28922b9bc48>
<Element td at 0x28922b9bc88>
<Element td at 0x28922b9bcc8>
如果想要獲得所有tbody的節點,只要修改上面的xpath即可:
result=html.xpath('//tbody')
獲取所有子節點
還是在上面程序的基礎上進行修改,修改xpath裏的匹配規則
比如我想要獲得tbody節點下的tr的直接子節點
result=html.xpath('//tbody/tr')
如果是所有子孫的tr節點:
result=html.xpath('//tbody//tr')
根據屬性獲取
結合上面說的,加一個謂語即可
result=html.xpath('//li[@class="item-0"]')
如上所示,獲取所有 li節點 且要求具有屬性 class=”item-0″
獲取父節點
利用 .. 即可往上翻一層
例如獲取tr的父節點
result=html.xpath('//tr/..')
也可以用 節點軸——parent::* 來獲取父節點
result=html.xpath('//tr/parent::*')
◊獲取文本信息
其實更常用的操作還是獲取文本信息
我們用 XPath 中的 text() 方法可以獲取節點中的文本
例如上面的html文件中,有兩個td節點,裏面分別有一些字符串,我們現在來獲取他們
result=html.xpath('//td/text()')
輸出:
Look at me!
Or kill me!
另外,有的時候我們可以直接用 // 加 text() 的方式獲取,這樣可以爬到最全面的文本信息,但是可能會夾雜一些換行符等特殊字符。所以還是定位的越細緻越好。
屬性獲取
獲取li節點的屬性
result=html.xpath('//li/@class')
屬性多值獲取
例如上面的html文件中有這樣的一句
<li class="sp item-0"><a href="https://is-hash.com">first item</a></li>
我們要經過 li 節點去獲取內部的 first item 文本
這時就可以用到 contains() 函數了
result=html.xpath('//li[contains(@class,"sp")]//text()')
這樣只要li節點還有屬性 class,且屬性有值sp即可被匹配
多屬性獲取
當一個節點有多個屬性
<li class="sp item-0" name="item"><a href="https://is-hash.com">first item</a></li>
利用Xpath的運算符即可:
result=html.xpath('//li[contains(@class,"sp") and @name="item"]//text()')
下面是Xpath的常見運算符
運算符 | 描述 | 實例 | 返回值 |
---|---|---|---|
| | 計算兩個節點集 | //book | //cd | 返回所有擁有 book 和 cd 元素的節點集 |
+ | 加法 | 6 + 4 | 10 |
– | 減法 | 6 – 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等於 | price=9.80 | 如果 price 是 9.80,則返回 true。
如果 price 是 9.90,則返回 false。 |
!= | 不等於 | price!=9.80 | 如果 price 是 9.90,則返回 true。
如果 price 是 9.80,則返回 false。 |
< | 小於 | price<9.80 | 如果 price 是 9.00,則返回 true。
如果 price 是 9.90,則返回 false。 |
<= | 小於或等於 | price<=9.80 | 如果 price 是 9.00,則返回 true。
如果 price 是 9.90,則返回 false。 |
> | 大於 | price>9.80 | 如果 price 是 9.90,則返回 true。
如果 price 是 9.80,則返回 false。 |
>= | 大於或等於 | price>=9.80 | 如果 price 是 9.90,則返回 true。
如果 price 是 9.70,則返回 false。 |
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,則返回 true。
如果 price 是 9.50,則返回 false。 |
and | 與 | price>9.00 and price<9.90 | 如果 price 是 9.80,則返回 true。
如果 price 是 8.50,則返回 false。 |
mod | 計算除法的餘數 | 5 mod 2 | 1 |
插曲:
基本語法就先記錄到這裏了,感謝這位CSDN的帶翅膀的貓
另外這個谷歌插件也是很不錯——XPath Helper
按住shift選擇我們需要的內容,自動生成匹配規則
或者更簡單的,在審查元素裏找到你要的資源右擊,選擇Copy,就可以直接copy這個節點的xpath
爬取IP池:
下面的例子僅供參考
我們找到一個免費ip代理網站,這個其實還蠻好找的
我們先來確定網頁是靜態網頁還是動態:如果是靜態,那網頁源代碼裏的數據就是呈現給你的數據,即網頁的任何數據都是在源代碼中可以找到的,它的url地址對應的就是導航欄中的地址。
我們這個網站是個靜態網站,好,我直接把url記錄到python代碼中
在網頁中我們在審查元素的network裏(刷新一下)得到UA頭,來僞造我們的爬蟲是瀏覽器
import request;
base_url="https://www.馬賽克.com/free/inha/1/";
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
接下來我們使用requests模塊的get去發送請求
response=requests.get(base_url,headers=headers)
#接收數據
data=response.text
data.encode("UTF-8")
print(data)
一般沒問題的就會發現它成功把網頁源代碼打印了出來
好,現在咱們就要利用Xpath去解析數據,把需要的數據篩出來,首先我們先安裝parsel庫
pip install parsel
or easy_install parsel
(python -m pip install --upgrade
)
然後在代碼中導入
import parsel
對於該網站的html中,我們需要的數據就是 IP、IP端口、協議類型 ,這三個字段
最後我們要弄成這樣{“協議類型”:”IP:IP端口”}
每一個IP信息在審查元素中都是這樣:
故,利用這樣的Xpath即可得到每個tr標籤內的信息
#將data數據轉換成一個selector對象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
此時,parse_list就是一個列表,我們再用for循環遍歷,將每一個代理IP作爲一個字典存到一個集合中:
#一個空列表用來存儲所有的代理IP
proxies_list=[]
for tr in parse_list:
#代理IP的形式是一個類字典
proxies_dict={}
#提取協議類型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#將數據加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
好了,這一頁的代理IP就全被我們採集了:
import requests
import parsel
base_url="https://www.馬賽克.com/free/inha/1/";
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收數據
data=response.text
data.encode("UTF-8")
#解析數據
#將data數據轉換成一個selector對象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
#一個空列表用來存儲所有的代理IP
proxies_list=[]
for tr in parse_list:
#代理IP的形式是一個類字典
proxies_dict={}
#提取協議類型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#將數據加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_list)
print("獲取到的代理IP數量是",len(proxies_list),"個")
那要採集多頁呢?我們調動數據之後發現不同的頁的區別其實就是在URL中的最後數字的變化,好,我們再做個大循環,將剛纔的數據做到循環裏面:
import requests
import parsel
import time
# 一個空列表用來存儲所有的代理IP
proxies_list = []
for page in range(1,5):
#用{}預留一個接口,通過.format將頁數進行傳遞
base_url="https://www.馬賽克.com/free/inha/{}/".format(page)
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收數據
data=response.text
data.encode("UTF-8")
#解析數據
#將data數據轉換成一個selector對象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
for tr in parse_list:
#代理IP的形式是一個類字典
proxies_dict={}
#提取協議類型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#將數據加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_dict)
time.sleep(0.5)
print("獲取到的代理IP數量是",len(proxies_list),"個")
最後輸出:
......
......
{'HTTP': '58.253.153.221:9999'}
{'HTTP': '122.192.39.239:8118'}
{'HTTP': '115.218.215.101:9000'}
{'HTTP': '183.166.20.211:9999'}
{'HTTP': '163.204.244.7:9999'}
{'HTTP': '163.204.240.10:9999'}
{'HTTP': '117.95.232.210:9999'}
獲取到的代理IP數量是 60 個
好,接下來我們來對 proxies_list 進行篩選高質量的IP
再定義一個方法:
#定義一個檢測爬取到的IP的質量的方法
def check_IP(proxies_list):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
#定義一個列表來選出高質量的ip
can_use=[]
#使用代理IP訪問服務器從而檢查IP質量
for proxy in proxies_list:
try:
#使用代理IP訪問某站並要求0.1秒內給出響應(若超過0.1秒則會異常報錯)
response=requests.get('https://baidu.com',headers=headers,proxies=proxy,timeout=0.1)
#高質量的代理IP
if response.status_code==200:
can_use.append(proxy)
except Exception as e:
print(e)
return can_use
最後全部的代碼如下:
import requests
import parsel
import time
#定義一個檢測爬取到的IP的質量的方法
def check_IP(proxies_list):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
#定義一個列表來選出高質量的ip
can_use=[]
#使用代理IP訪問服務器從而檢查IP質量
for proxy in proxies_list:
try:
#使用代理IP訪問某站並要求0.1秒內給出響應(若超過0.1秒則會異常報錯)
response=requests.get('https://baidu.com',headers=headers,proxies=proxy,timeout=0.1)
#高質量的代理IP
if response.status_code==200:
can_use.append(proxy)
except Exception as e:
print(e)
return can_use
# 一個空列表用來存儲所有的代理IP
proxies_list = []
for page in range(1,5):
#用{}預留一個接口,通過.format將頁數進行傳遞
base_url="https://www.馬賽克.com/free/inha/{}/".format(page)
headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
response=requests.get(base_url,headers=headers)
#接收數據
data=response.text
data.encode("UTF-8")
#解析數據
#將data數據轉換成一個selector對象
html_data=parsel.Selector(data)
XpathSelect='//table[@class="table table-bordered table-striped"]/tbody/tr'
parse_list=html_data.xpath(XpathSelect)
for tr in parse_list:
#代理IP的形式是一個類字典
proxies_dict={}
#提取協議類型
http_type=tr.xpath("./td[4]/text()").extract_first()
IP = tr.xpath("./td[1]/text()").extract_first()
IP_Port = tr.xpath("./td[2]/text()").extract_first()
#將數據加入字典
proxies_dict[http_type]=IP+":"+IP_Port
proxies_list.append(proxies_dict)
print(proxies_dict)
time.sleep(0.5)
print("獲取到的代理IP數量是",len(proxies_list),"個")
#檢測代理IP可用性
can_use=check_IP(proxies_list)
print("能用的IP數量",len(can_use))
不錯吧,一共60個IP,56個質量不錯,
爬取到的數據可以存放到數據庫中,例如MongoDB數據庫(非關係型數據庫,無需建字段)
好了,IP池就搭建完成
博客:is-hash.com
商業轉載 請聯繫作者獲得授權,非商業轉載 請標明出處,謝謝