天天看點

Python爬蟲 XPath文法和lxml子產品

XPath文法和lxml子產品

什麼是XPath?

xpath(XML Path Language)是一門在XML和HTML文檔中查找資訊的語言,可用來在XML和HTML文檔中對元素和屬性進行周遊。

XPath開發工具

  1. Chrome插件XPath Helper。
  2. Firefox插件Try XPath。

XPath文法

選取節點:

XPath 使用路徑表達式來選取 XML 文檔中的節點或者節點集。這些路徑表達式和我們在正常的電腦檔案系統中看到的表達式非常相似。

表達式 描述 示例 結果
nodename 選取此節點的所有子節點 bookstore 選取bookstore下所有的子節點
/ 如果是在最前面,代表從根節點選取。否則選擇某節點下的某個節點 /bookstore 選取根元素下所有的bookstore節點
// 從全局節點中選擇節點,随便在哪個位置 //book 從全局節點中找到所有的book節點
@ 選取某個節點的屬性 //book[@price] 選擇所有擁有price屬性的book節點
. 目前節點 ./a 選取目前節點下的a标簽

謂語:

謂語用來查找某個特定的節點或者包含某個指定的值的節點,被嵌在方括号中。

在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:

路徑表達式 描述
/bookstore/book[1] 選取bookstore下的第一個子元素
/bookstore/book[last()] 選取bookstore下的倒數第二個book元素。
bookstore/book[position()<3] 選取bookstore下前面兩個子元素。
//book[@price] 選取擁有price屬性的book元素
//book[@price=10] 選取所有屬性price等于10的book元素

通配符

*表示通配符。

通配符 描述 示例 結果
* 比對任意節點 /bookstore/* 選取bookstore下的所有子元素。
@* 比對節點中的任何屬性 //book[@*] 選取所有帶有屬性的book元素。

選取多個路徑:

通過在路徑表達式中使用“|”運算符,可以選取若幹個路徑。

示例如下:

//bookstore/book | //book/title
# 選取所有book元素以及book元素下所有的title元素      

運算符:

lxml庫

lxml 是 一個HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 資料。

lxml和正則一樣,也是用 C 實作的,是一款高性能的 Python HTML/XML 解析器,我們可以利用之前學習的XPath文法,來快速的定位特定元素以及節點資訊。

lxml python 官方文檔:​​http://lxml.de/index.html​​

需要安裝C語言庫,可使用 pip 安裝:pip install lxml

基本使用:

我們可以利用他來解析HTML代碼,并且在解析HTML代碼的時候,如果HTML代碼不規範,他會自動的進行補全。示例代碼如下:

# 使用 lxml 的 etree 庫
from lxml import etree 

text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此處缺少一個 </li> 閉合标簽
     </ul>
 </div>
'''

#利用etree.HTML,将字元串解析為HTML文檔
html = etree.HTML(text) 

# 按字元串序列化HTML文檔
result = etree.tostring(html) 

print(result)      

輸入結果如下:

<html><body>
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html">third item</a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
 </div>
</body></html>      

可以看到。lxml會自動修改HTML代碼。例子中不僅補全了li标簽,還添加了body,html标簽。

從檔案中讀取html代碼:

除了直接使用字元串進行解析,lxml還支援從檔案中讀取内容。我們建立一個hello.html檔案:

<!-- hello.html -->
<div>
    <ul>
         <li class="item-0"><a href="link1.html">first item</a></li>
         <li class="item-1"><a href="link2.html">second item</a></li>
         <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
         <li class="item-1"><a href="link4.html">fourth item</a></li>
         <li class="item-0"><a href="link5.html">fifth item</a></li>
     </ul>
 </div>      

然後利用​

​etree.parse()​

​方法來讀取檔案。示例代碼如下:

from lxml import etree

# 讀取外部檔案 hello.html
html = etree.parse('hello.html')
result = etree.tostring(html, pretty_print=True)

print(result)      

輸入結果和之前是相同的。

在lxml中使用XPath文法:

  1. 擷取所有li标簽:
from lxml import etree

 html = etree.parse('hello.html')
 print type(html)  # 顯示etree.parse() 傳回類型

 result = html.xpath('//li')

 print(result)  # 列印<li>标簽的元素集合      
  1. 擷取所有li元素下的所有class屬性的值:
from lxml import etree

 html = etree.parse('hello.html')
 result = html.xpath('//li/@class')

 print(result)      
  1. 擷取li标簽下href為​

    ​www.baidu.com​

    ​的a标簽:
from lxml import etree

 html = etree.parse('hello.html')
 result = html.xpath('//li/a[@href="www.baidu.com"]')

 print(result)      
  1. 擷取li标簽下所有span标簽:
from lxml import etree

 html = etree.parse('hello.html')

 #result = html.xpath('//li/span')
 #注意這麼寫是不對的:
 #因為 / 是用來擷取子元素的,而 <span> 并不是 <li> 的子元素,是以,要用雙斜杠

 result = html.xpath('//li//span')

 print(result)      
  1. 擷取li标簽下的a标簽裡的所有class:
from lxml import etree

 html = etree.parse('hello.html')
 result = html.xpath('//li/a//@class')

 print(result)      
  1. 擷取最後一個li的a的href屬性對應的值:
from lxml import etree

 html = etree.parse('hello.html')

 result = html.xpath('//li[last()]/a/@href')
 # 謂語 [last()] 可以找到最後一個元素

 print(result)      
  1. 擷取倒數第二個li元素的内容:
from lxml import etree

 html = etree.parse('hello.html')
 result = html.xpath('//li[last()-1]/a')

 # text 方法可以擷取元素内容
 print(result[0].text)      
  1. 擷取倒數第二個li元素的内容的第二種方式:
from lxml import etree

 html = etree.parse('hello.html')
 result = html.xpath('//li[last()-1]/a/text()')

 print(result)      

使用requests和xpath爬取電影天堂

示例代碼如下:

import requests
from lxml import etree

BASE_DOMAIN = 'http://www.dytt8.net'
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
    'Referer': 'http://www.dytt8.net/html/gndy/dyzz/list_23_2.html'
}

def spider():
    url = 'http://www.dytt8.net/html/gndy/dyzz/list_23_1.html'
    resp = requests.get(url,headers=HEADERS)
    # resp.content:經過編碼後的字元串
    # resp.text:沒有經過編碼,也就是unicode字元串
    # text:相當于是網頁中的源代碼了
    text = resp.content.decode('gbk')
    # tree:經過lxml解析後的一個對象,以後使用這個對象的xpath方法,就可以
    # 提取一些想要的資料了
    tree = etree.HTML(text)
    # xpath/beautifulsou4
    all_a = tree.xpath("//div[@class='co_content8']//a")
    for a in all_a:
        title = a.xpath("text()")[0]
        href = a.xpath("@href")[0]
        if href.startswith('/'):
            detail_url = BASE_DOMAIN + href
            crawl_detail(detail_url)
            break

def crawl_detail(url):
    resp = requests.get(url,headers=HEADERS)
    text = resp.content.decode('gbk')
    tree = etree.HTML(text)
    create_time = tree.xpath("//div[@class='co_content8']/ul/text()")[0].strip()
    imgs = tree.xpath("//div[@id='Zoom']//img/@src")
    # 電影海報
    cover = imgs[0]
    # 電影截圖
    screenshoot = imgs[1]
    # 擷取span标簽下所有的文本
    infos = tree.xpath("//div[@id='Zoom']//text()")
    for index,info in enumerate(infos):
        if info.startswith("◎年  代"):
            year = info.replace("◎年  代","").strip()

        if info.startswith("◎豆瓣評分"):
            douban_rating = info.replace("◎豆瓣評分",'').strip()
            print(douban_rating)

        if info.startswith("◎主  演"):
            # 從目前位置,一直往下面周遊
            actors = [info]
            for x in range(index+1,len(infos)):
                actor = infos[x]
                if actor.startswith("◎"):
                    break
                actors.append(actor.strip())
            print(",".join(actors))


if __name__ == '__main__':
    spider()      

chrome相關問題:

在62版本(目前最新)中有一個bug,在頁面302重定向的時候不能記錄FormData資料。這個是這個版本的一個bug。詳細見以下連結:​​https://stackoverflow.com/questions/34015735/http-post-payload-not-visible-in-chrome-debugger。​​