Python爬蟲進階——Xpath解析數據 並 爬取一個IP池

前言:

老早以前寫過一個用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

感謝w3school運算符

 


插曲:

基本語法就先記錄到這裏了,感謝這位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 parselpython -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

商業轉載 請聯繫作者獲得授權,非商業轉載 請標明出處,謝謝

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章