目錄
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爬取書單
接下來去晉江文學城爬爬純愛書單,先看下鏈接:
肉眼可見的規律:?部分爲頁面值。
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()