Python手記-9:Python LXML庫XPath的爬取晉江書目

目錄

1. lxml庫

1.1 XPath簡介

1.2 XPath屬性匹配

1.3 節點軸選擇

2. 使用XPath爬取書單


1. lxml庫

lxml是功能最豐富且易於使用的庫,用於處理Python語言中的XML和HTML,還可實現WEB爬取,簡言之主要功能是解析和提取 HTML/XML 數據,官文參考:https://lxml.de/index.html,完整的PDF文檔下載鏈接https://lxml.de/lxmldoc-4.5.0.pdf,但是閱讀起來……em……怪自己不夠聰明的樣子。

Linux下安裝lxml庫:[root@chengyu ~]# pip3 install lxml

1.1 XPath簡介

XPath全稱XML Path Language,即XML路徑語言,可以快速的在XML和HTML文檔中搜索特定元素以及節點信息數據,XPath的主要目的是解決XML和JSON樹的節點,XPath 提供超過 100 個內建的函數,用於字符串值、數值、日期和時間比較、節點和 QName 處理、序列處理、邏輯值等等,官文參考https://www.w3.org/TR/xpath/all/

XPath 有七種類型的節點:元素、屬性、文本、命名空間、處理指令、註釋以及文檔(根)節點,XPath 使用路徑表達式在 XML 文檔中選取節點,節點是通過沿着路徑或者 step 來選取的,常用的規則如下(本節理論部分參考《Python 3 網絡爬蟲開發實戰》和https://www.runoob.com/xpath/xpath-tutorial.html):

表達式 描述
nodename 選取此節點的所有子節點
/ 從根節點選取
// 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置
. 選取當前節點
.. 選取當前節點的父節點
@ 選取屬性

下面以給出路徑表達式的一些實例:

路徑表達式 結果
booklist 選取booklist元素的所有子節點。
/booklist

選取根元素booklist。

註釋:假如路徑起始於正斜槓( / ),則此路徑始終代表到某元素的絕對路徑!

booklist/book 選取屬於booklist的子元素的所有book元素。
//book 選取所有book子元素,而不管它們在文檔中的位置。
booklist//book 選擇屬於booklist元素的後代的所有book元素,而不管它們位於booklist之下的什麼位置。
//@align 選取名爲align的所有屬性。

所有節點、指定節點、子節點、父節點獲取案例:

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : lxml_xpath.py
# @Project: Python Notes
# @Time : 2020/5/6 17:23:39

from lxml import etree
text = '''
<tr>
<td align="left"><a href="oneauthor.php?authorid=1322620" target="_blank">墨香銅臭</a></td>
<td align="left"><a href="onebook.php?novelid=3200611" target="_blank" title="簡介:爲你,所向披靡!標籤:">天官賜福</a></td>
<td align="center">原創-純愛-架空歷史-愛情</td>
<td align="center">輕鬆</td>
<td align="center"><font color="red">已完成</font></td>
<td align="right">1144742</td>
<td align="right">32416696320</td>
<td align="center">2017-06-16 18:01:18</td>
</tr>
'''
html = etree.HTML(text)
res = html.xpath('//*')  # 所有節點獲取
res_td = html.xpath('//td')  # 指定節點獲取
res_td_a = html.xpath('//td/a')  # 子節點獲取
res_up = html.xpath('//a[@href="oneauthor.php?authorid=1322620"]/parent::*/@align')  # 獲取父節點
print(res)
print(res_td)
print(res_td[1])
print(res_td_a)
print(res_up)

“Run”結果:

G:\Python3.8.2\python.exe "G:/pycharm/Python Notes/lxml_xpath.py"
[<Element html at 0x246a0deccc0>, <Element body at 0x246a11081c0>, <Element tr at 0x246a1108240>, <Element td at 0x246a1108300>, <Element a at 0x246a1108340>, <Element td at 0x246a11083c0>, <Element a at 0x246a1108400>, <Element td at 0x246a1108440>, <Element td at 0x246a1108480>, <Element td at 0x246a1108380>, <Element font at 0x246a11084c0>, <Element td at 0x246a1108500>, <Element td at 0x246a1108540>, <Element td at 0x246a1108580>]
[<Element td at 0x246a1108300>, <Element td at 0x246a11083c0>, <Element td at 0x246a1108440>, <Element td at 0x246a1108480>, <Element td at 0x246a1108380>, <Element td at 0x246a1108500>, <Element td at 0x246a1108540>, <Element td at 0x246a1108580>]
<Element td at 0x246a11083c0>
[<Element a at 0x246a1108340>, <Element a at 0x246a1108400>]
['left']

Process finished with exit code 0

1.2 XPath屬性匹配

  • 節點未知時,XPath 通配符可用來選取未知的 XML 元素:

通配符 描述
* 匹配任何元素節點
@* 匹配任何屬性節點
node() 匹配任何類型的節點
  • 常用路徑表達式,以及對應結果:

路徑表達式 結果
/booklist/* 選取booklist節點的所有子節點
//* 選取文檔中的所有節點
//title[@*] 選取所有帶有屬性的title節點
  • 常用運算符介紹

運算符

描述 實例 返回值
| 計算兩個節點集 //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

 

  • 通過在路徑表達式中使用“|”運算符,您可以選取若干個路徑,在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:

路徑表達式 結果
//book/title | //book/style 選取book元素的所有title和style元素。
//title | //style 選取文檔中的所有title和style元素。
/booklist/book/title | //style 選取屬於booklist元素的book元素的所有title元素,以及文檔中所有的style元素。
  •  過濾器表達式(Filter Expressions)由一個基本表達式和一個謂詞組成,謂詞是用方括號括起來的表達式,過濾器表達式的結果由基本表達式返回的項目組成,通過依次將謂詞應用於每個項目進行過濾,過濾器表達式返回的項目的順序與主表達式結果中的順序相同。

路徑表達式 結果
/booklist/book[1] 選取屬於booklist子元素的第一個book元素。
/booklist/book[last()] 選取屬於booklist子元素的最後一個book元素。
/booklist/book[last()-1] 選取屬於booklist子元素的倒數第二個book元素。
/booklist/book[position()<3] 選取最前面的兩個屬於booklist元素的子元素的book元素。
//title[@align] 選取所有擁有名爲align的屬性的title元素。
//title[@align='center'] 選取所有title元素,且這些元素擁有值爲style的 center屬性。
/booklist/book[style='輕鬆'] 選取booklist元素的所有book元素,且其中的style元素的值爲“輕鬆”。
/booklist/book[style='輕鬆']//title 選取booklist元素中的book元素的所有title元素,且其中的style元素的值爲“輕鬆”。
  • 案例:通過@符號進行屬性過濾,比如上面案例中選取align屬性值爲center的td節點:[@align="center"];通過text()方法獲取節點的文本;通過contains()方法實現多屬性值匹配;通過XPath函數,比如last()、position()等上下文函數獲取指定節點的文本,更多的函數參考https://www.w3school.com.cn/xpath/xpath_functions.asp

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : lxml_xpath.py
# @Project: Python Notes
# @Time : 2020/5/6 17:23:39

from lxml import etree
text = '''
<tr>
<td align="left" target="_blank"><a href="oneauthor.php?authorid=1322620" target="_blank">墨香銅臭</a></td>
<td align="left"><a href="onebook.php?novelid=3200611" target="_blank" title="簡介:爲你,所向披靡!標籤:">天官賜福</a></td>
<td align="center">原創-純愛-架空歷史-愛情</td>
<td align="center">輕鬆</td>
<td align="center"><font color="red">已完成</font></td>
<td align="right">1144742</td>
<td align="right red">32416696320</td>
<td align="center">2017-06-16 18:01:18</td>
</tr>
'''
html = etree.HTML(text)
# 單屬性匹配
result = html.xpath('//td[@align="left"]')
print(result)
result_text1 = html.xpath('//td[@align="center"]/text()')
print(result_text1)
# 選取a節點再獲取文本
result_text2 = html.xpath('//td[@align="left"]/a/text()')
print(result_text2)
# 使用//獲取文本
result_text3 = html.xpath('//td[@align="left"]//text()')
print(result_text3)
# 節點屬性獲取
result_con = html.xpath('//td/a/@href')
print(result_con)
# 屬性多值匹配,contains(a,b),a參數爲屬性名稱,b爲屬性值
result_dubcon = html.xpath('//td[contains(@align,"red")]/text()')
print(result_dubcon)
# 多屬性匹配
result_dubval = html.xpath('//td/a[@href="onebook.php?novelid=3200611" and @target="_blank"]/text()')
print(result_dubval)
# 按序選擇指定節點,這裏介紹last()、position()等上下文函數
result_order1 = html.xpath('//td[position()<3]/a/text()')
print(result_order1)
result_order2 = html.xpath('//td[last()]/text()')
print(result_order2)
result_order3 = html.xpath('//td[4]/text()')
print(result_order3)

“Run”結果:

G:\Python3.8.2\python.exe "G:/pycharm/Python Notes/lxml_xpath.py"
[<Element td at 0x1ed394b8340>, <Element td at 0x1ed394b8400>]
['原創-純愛-架空歷史-愛情', '輕鬆', '2017-06-16 18:01:18']
['墨香銅臭', '天官賜福']
['墨香銅臭', '天官賜福']
['oneauthor.php?authorid=1322620', 'onebook.php?novelid=3200611']
['32416696320']
['天官賜福']
['墨香銅臭', '天官賜福']
['2017-06-16 18:01:18']
['輕鬆']

Process finished with exit code 0

1.3 節點軸選擇

軸定義爲相對於當前節點的節點集,包括子節點、兄弟節點、祖先節點等。

軸名稱 結果
ancestor 選取當前節點的所有先輩(父、祖父等)。
ancestor-or-self 選取當前節點的所有先輩(父、祖父等)以及當前節點本身。
attribute 選取當前節點的所有屬性。
child 選取當前節點的所有子元素。
descendant 選取當前節點的所有後代元素(子、孫等)。
descendant-or-self 選取當前節點的所有後代元素(子、孫等)以及當前節點本身。
following 選取文檔中當前節點的結束標籤之後的所有節點。
namespace 選取當前節點的所有命名空間節點。
parent 選取當前節點的父節點。
preceding 選取文檔中當前節點的開始標籤之前的所有節點。
preceding-sibling 選取當前節點之前的所有同級節點。
self 選取當前節點。
# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : lxml_xpath.py
# @Project: Python Notes
# @Time : 2020/5/6 17:23:39

from lxml import etree
text = '''
<tr>
<td align="left"><a href="oneauthor.php?authorid=1322620" target="_blank">墨香銅臭</a></td>
<td align="left"><a href="onebook.php?novelid=3200611" target="_blank" title="簡介:爲你,所向披靡!標籤:">天官賜福</a></td>
<td align="center">原創-純愛-架空歷史-愛情</td>
<td align="center">輕鬆</td>
<td align="center"><font color="red">已完成</font></td>
<td align="right">1144742</td>
<td align="right red">32416696320</td>
<td align="center">2017-06-16 18:01:18</td>
</tr>
'''
html = etree.HTML(text)
# 節點軸選擇
result_ax = html.xpath('//td[1]/ancestor::*')
print(result_ax)
result_ax = html.xpath('//td[1]/ancestor::tr')
print(result_ax)
result_ax = html.xpath('//td[1]/attribute::*')
print(result_ax)
result_ax = html.xpath('//td[1]/child::a[@href="oneauthor.php?authorid=1322620"]')
print(result_ax)
result_ax = html.xpath('//td[1]/descendant::a')
print(result_ax)
result_ax = html.xpath('//td[1]/following::*[2]')
print(result_ax)
result_ax = html.xpath('//td[1]/following-sibling::*')
print(result_ax)

 “Run”結果:

G:\Python3.8.2\python.exe "G:/pycharm/Python Notes/lxml_xpath.py"
[<Element html at 0x141d0f5cb40>, <Element body at 0x141d12880c0>, <Element tr at 0x141d1288180>]
[<Element tr at 0x141d1288180>]
['left']
[<Element a at 0x141d1288200>]
[<Element a at 0x141d1288200>]
[<Element a at 0x141d1288240>]
[<Element td at 0x141d1288200>, <Element td at 0x141d1288180>, <Element td at 0x141d1288040>, <Element td at 0x141d1288280>, <Element td at 0x141d12882c0>, <Element td at 0x141d1288340>, <Element td at 0x141d1288380>]

Process finished with exit code 0

2. 使用XPath爬取書單

接下來去晉江文學城爬爬純愛書單,先看下鏈接:

http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0&collectiontypes=&searchkeywords=&page=1

http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0&collectiontypes=&searchkeywords=&page=2

http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0&collectiontypes=&searchkeywords=&page=3

肉眼可見的規律:?部分爲頁面值。

http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0&collectiontypes=&searchkeywords=&page=

level 0級別的XPath應用如下:

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : xpath_getbooks.py
# @Project: Python Notes
# @Time : 2020/5/7 11:28:46

import requests
import csv
from lxml import etree

# 加上headers用來告訴網站這是通過一個瀏覽器進行的訪問
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/81.0.4044.122 Safari/537.36'}
# 初始化csv文件
with open('JJbooks.csv', 'w', newline='', encoding='utf-8') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['序號', '作者', '作品', '類型', '風格', '字數', '作品積分', '發表時間'])
    count = 1
    # page起始值爲1,爬取10頁

    for page in range(10):
        url = 'http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0' \
              '&collectiontypes=&searchkeywords=&page=' + str(page)
        # python獲得網頁源代碼編碼方式
        code = requests.get(url).encoding
        # print(code)
        # 獲取網頁源碼
        res = requests.get(url, headers=headers).text
        # F12獲得網頁實際編碼:charset = gb18030
        res = res.encode('ISO-8859-1').decode('gb18030')
        data_all = etree.HTML(res)
        # XPath 是一門在 XML 文檔中查找信息的語言,XPath 可用來在 XML 文檔中對元素和屬性進行遍歷
        author = data_all.xpath('//td[position()=1]/a/text()')
        bookname = data_all.xpath('//td[2]/a/text()')
        p_booktype = data_all.xpath('//td[position()=3]/text()')
        # 去除列表空格和換行
        booktype = [x.strip() for x in p_booktype if x.strip() != '']
        p_style = data_all.xpath('//td[@align and position()=4]/text()')
        style = [x.strip() for x in p_style if x.strip() != '']
        wordnum = data_all.xpath('//td[@align and position()=6]/text()')
        points = data_all.xpath('//td[@align and position()=7]/text()')
        publishTime = data_all.xpath('//td[@align="center" and position()=8]/text()')
        for i in range(len(bookname)):
            writer.writerow([count, author[i], bookname[i], booktype[i], style[i], wordnum[i], points[i],
                             publishTime[i]])
            count += 1

    # 注意csvfile.close()縮進問題,這應該是與for同級別,否則可能造成:ValueError: I/O operation on closed file.
    csvfile.close()

“Run”結果生成1000條書目:

level 1改進版利用節點軸來獲取數據:

# -*- coding: utf-8 -*-
# @Author : ChengYu
# @File : xpath_getbooks2.py
# @Project: Python Notes
# @Time : 2020/5/7 16:13:08

import requests
import csv
from lxml import etree

# 加上headers用來告訴網站這是通過一個瀏覽器進行的訪問
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/81.0.4044.122 Safari/537.36'}
# 初始化csv文件
with open('JJbooks.csv', 'w', newline='', encoding='utf-8') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['序號', '作者', '作品', '類型', '風格', '進度', '字數', '作品積分', '發表時間'])
    count = 1
    # page起始值爲1,爬取10頁

    for page in range(1, 10, 1):
        url = 'http://www.jjwxc.net/bookbase.php?fw=0&ycx=0&xx=2&mainview=0&sd=0&lx=0&fg=0&bq=-1&sortType=0&isfinish=0' \
              '&collectiontypes=&searchkeywords=&page=' + str(page)
        # python獲得網頁源代碼編碼方式
        code = requests.get(url).encoding
        # print(code)
        # 獲取網頁源碼
        res = requests.get(url, headers=headers).text
        # F12獲得網頁實際編碼:charset = gb18030
        res = res.encode('ISO-8859-1').decode('gb18030')
        data_all = etree.HTML(res)
        # XPath 是一門在 XML 文檔中查找信息的語言,XPath 可用來在 XML 文檔中對元素和屬性進行遍歷
        result_ax = data_all.xpath('//tr[position()>1]/descendant::*/text()')
        result_ax = [x.strip() for x in result_ax if x.strip() != '']

        for i in range(1, int(len(result_ax)/8), 1):
            bookitem = result_ax[8*(i-1):8*i]
            content = [i + (page-1)*99] + bookitem
            print(content)
            writer.writerow(content)

    # 注意csvfile.close()縮進問題,這應該是與for同級別,否則可能造成:ValueError: I/O operation on closed file.
    csvfile.close()

 

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