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条书目:

 

 

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