掌握Xpath语法:XML文档遍历与节点选择的实用技巧
Xpath语法

Xpath 是一门用于在 XML 文档中查找信息的语言。它可以用于在 XML 文档中对元素和属性进行遍历。Xpath 是 W3C XSLT 标准的主要元素。
1,节点关系
在下面的例子中,body 是 h1 元素的父节点,而 h1 是 body 节点的子节点,子节点的数量可以是零个或者多个。
小白
小黑
在上面的例子里,h1 与 h2 属于同胞节点,这些同胞节点具备相同的父节点。
2,节点的选择
节点是通过路径或者step来选取的,如下表所示
3,使用的技巧
在一般的爬虫实战里,Xpath 路径能够通过谷歌浏览器复制得到,也能够通过火狐浏览器复制得到,如下图
新手可以多多尝试自己写 Xpath 路径。因为有时复制获取的 Xpath 路径会过长,而自己写的可能会更简洁。
例子:
在# //*[@id="qiushi_tag_120129862"]这个范围内,找到 div[1] 中的 a[2],然后找到它的 h2 部分。
# 通过代码即可得到用户id:
import requests
from lxml import etree
headers = {
“User-Agent”是“Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36”。此 User-Agent 表明使用的是 Windows NT 6.1 系统且为 64 位,浏览器内核为 WebKit,具体版本是 537.36,呈现方式类似 Gecko,浏览器是 Chrome 53.0.2785.143 版本,同时使用的是 Safari 537.36 的相关部分。
}
url 等于 'http://www.qiushibaike.com/text/'
res 是通过 requests.get 方法获取的,获取的对象是 url,并且设置了 headers=headers 这个参数
etree.HTML(res.text) 被赋值给了 selector
id 等于通过 selector 选取的内容,该内容为 //*[@id="qiushi_tag_118732380"]/div[1]/a[2]/h2 中的文本。
print(id)
# 注意:通过/text()可以获取标签中的文字信息。
# 结果为:
# ["姑苏云琪"]
几种解析方式的性能对比
通过爬取获取豆瓣图书 TOP250 的数据,接着把这些数据存储到 CSV 格式中。
以前的例子中,爬取的数据有两种处理方式,一是直接输出到屏幕,二是存储到 txt 文档中。这两种格式不利于数据的存储,导致数据显示较为混乱。因此,这里采用 CSV 格式来存储数据,因为 CSV 是存储表格数据的常用文件格式,Excel 和很多应用都支持 CSV 格式。
(1)要爬取的内容为豆瓣图书top250的信息,如下图所示
通过浏览得知,一共有 10 页,每页有 25 本书的信息。前 4 页的网址分别是:第一页的网址;第二页的网址;第三页的网址;第四页的网址。
所以只需要更改start=后面的数据就可以构造10页的网址
需要爬取的信息包含:书名;书本的链接;作者;出版社;出版日期;评分;评价
使用 Python 里的 CSV 库,将爬取到的内容存储到本地的 CSV 文本里。
具体的代码如下:
# -*- encoding:utf8 -*-
# 爬取豆瓣图书TOP250,爬取的数据存储到CSV文件中
from lxml import etree
import requests
import csv
wt 在 python 中是以文本写的方式打开的。它只能用于写文件。如果文件不存在,它会创建该文件。
打开名为“douban_top250.csv”的文件,以写入模式进行操作,将其赋值给变量 fp 。
writer = csv.writer(fp)
上面的代码是用于处理 CSV 文件的。下面是向 CSV 文件写入表头信息的操作,这些表头信息相当于每一列的列名。
writer 写入了以下内容:('name', 'url', 'author', 'publisher', 'date', 'price', 'rate', 'comment')
# 创建url,range的第三个参数是步长
urls 等于由多个字符串组成的列表,这些字符串是通过对 format 函数的调用生成的。format 函数的参数 i 依次取 0 到 250 之间以 25 为步长的数值,即 i 分别为 0、25、50、75、100、125、150、175、200、225。生成的字符串为 "https://book.douban.com/top250?start={}".format(i),每个字符串都代表一个特定的 URL 地址。
# 请求头,用来模拟浏览器
headers = {
"Mozilla/5.0 表示用户代理的类型,其适用于 Windows NT 10.0 操作系统且为 64 位(WOW64),浏览器的内核是 WebKit/537.36,呈现方式类似 Gecko,浏览器的版本是 Chrome/63.0.3239.132,渲染引擎是 Safari/537.36"
}
res 发起了对 "http://www.baidu.com" 的请求,并且在请求中携带了 headers 这个头部信息。
# # lxml库的etree解析html
使用 etree.HTML 函数对 res.text 进行处理并将结果赋值给 selector 。
# print type(selector)
for url in urls:
使用 requests 库发送 GET 请求,请求的 URL 为指定的 url,请求头为 headers,获取的响应结果赋值给 res
# lxml库的etree解析html
etree.HTML 被用于获取 res.text 的内容,获取后的内容被赋值给 selector 。
获取的是一页中所有书本的信息,每本信息都在类为 item 的 tr 之下。
infos 等于通过选择器选取的("//tr[@class='item']")内容
for info in infos:
通过 info.xpath('td/div/a/@title')获取到的第一个元素赋值给 name
通过 info.xpath('td/div/a/@href') 获取到的第一个元素赋值给 url
获取的是书本的相关信息,其中包含作者、出版社以及出版日期等。
book_infos 等于 info.xpath('td/p/text()')[0]
# 作者
author 等于 book_infos 以斜杠“/”进行分割后的第一个部分
# 出版社
publisher 是通过对 book_infos 进行字符串分割(以“/”为分隔符)后取最后一个元素再往前推第三个元素得到的。
# 出版日期
date 是通过对 book_infos 进行分割操作(以 '/' 为分隔符)后得到的结果中的倒数第二个元素。
# 价格
价格等于书信息通过斜杠“/”分割后的最后一个元素
# 书本的评分
rate 等于 info.xpath('td/div/span[2]/text()')[0]
# 下面的评论
comments 是通过 info.xpath('td/p/span/text()') 获取到的
如果 comments 的长度不为 0,就把 comments 的第 1 个元素给 comment;否则,就把“空”赋值给 comment。
如果 comments 的长度不为 0,那么 comment 就等于 comments[0];如果 comments 的长度为 0,那么 comment 就等于“空”。
writer 将数据写入一行,这行数据包含 name 经 utf-8 编码后的内容、url 经 utf-8 编码后的内容、author 经 utf-8 编码后的内容、publisher 经 utf-8 编码后的内容、date 经 utf-8 编码后的内容、price 经 utf-8 编码后的内容、rate 经 utf-8 编码后的内容以及 comment 经 utf-8 编码后的内容。
# 关闭
fp.close()
我不知道该在何处更改写入的编码格式,于是在最后的写入操作中,为每个写入的字符串都指定了字符为“utf-8”,这样导致代码显得十分臃肿。我在网上查找了几种方式,但都没有找到答案。
第二个问题在于,在进行写入操作即将结束时会出现报错,报出的错误让人摸不着头脑,这致使仅仅写入了大约 155 条数据。
'ascii' 编码无法解码位置 0 处的字节 0xe7:该序数不在范围(128)内。
有一个事,写入的 csv 文件用 excel 打开会出现乱码。可以用记事本打开 csv 文件,在另存为时,选择右下角的编码格式为“utf-8”,这样用 excel 打开就不会是乱码了。
数据存储到excel中
使用 python 的第三方库 xlwt 便能够将数据写入到 excel 里。要安装它的话,可以通过 pip install xlwt 来进行操作。
通过下列代码可以将数据写入到excel中:
# 导入写入excel的库文件
import xlwt
# 创建工作簿,并指定编码格式,默认编码格式为ascii
创建一个名为 book 的对象,它是通过 xlwt.Workbook()函数创建的,并且设置编码为"utf-8"。
# 创建工作表
book 添加了一个名为“sheet1”的工作表,这个工作表被赋值给了 sheet 。
在相应单元格写入数据,将(1,1)这个位置在第二行第二列写入小白。
sheet.write(1,1,"小白")
# 保存到文件
book.save("test.xls")
具体的结果如下,在第二行第二列写入了小白
综合示例(二)——爬取起点中文网小说信息
如下图所示
第一页的网址如下所示:
感觉有点长,点击下一页跳转。只有最后的 page=后面的数字会发生变化,而前面中间的那一段文字似乎是用于控制分类的。经过一番删减,发现……
页面大小为 20,站点 ID 为 1,发布标志为 0,隐藏字段为 0,当前页码为 1。
pubflag 的值为 0,hiddenField 的值为 0,且 page 的值为 1。
page=1
上面网址跳转的都是第一页的书本信息,所以只需改变最后的 page 页数,就能访问后面的一系列信息。
需要爬取的信息包含:小说的名字,作者是谁,小说属于何种类型,是否已经完成,对小说的介绍以及字数的多少。
具体实现如下:存在一个问题,即小说的字数无法获取。在源代码上显示的是一些框,这些框可能是动态显示的,目前不知道该如何获取。不过,将其写入到 excel 中是比较容易理解的。
# -*- encoding:utf8 -*-
# 爬取起点中文网小说信息,存储到excel表中
import requests
import xlwt,time
from lxml import etree
from bs4 import BeautifulSoup
headers = {
User-Agent 为 Mozilla/5.0 且运行在 Windows NT 10.0 系统且为 WOW64 架构,其浏览器内核是 AppleWebKit/537.36 ,呈现方式类似 Gecko ,版本是 Chrome/63.0.3239.132 ,整体的渲染引擎是 Safari/537.36 。
}
# 初始化列表,用来存入爬虫数据
all_info_list = []
# 定义获取爬虫信息的函数
def get_info(url):
res = requests.get(url,headers=headers)
selector = etree.HTML(res.text)
# 先找到包含所有书本信息的标签,依次循环
infos 等于通过选择器选取到的("//ul[@class='all-img-list cf']/li")这部分内容。
for info in infos:
# 书名
通过 info.xpath 方法找到 div[2]/h4/a 下的文本内容,然后取第一个结果赋值给 title 。
# 作者
author 等于 info.xpath("div[2]/p[1]/a[1]/text()")[0]
# 类型
从 info 中通过 XPath 表达式 "div[2]/p[1]/a[2]/text()" 提取出的第一个元素即为 style1
从 info 中通过 XPath 表达式“div[2]/p[1]/a[3]/text()”获取到的第一个元素赋值给 style2
style = style1+" & "+style2
print style
# 状态,连载还是完结
通过 info.xpath('div[2]/p[1]/span/text()') 可以获取到 complete,其值为该路径下的第一个元素
# 书本的介绍
通过 xpath 表达式 'div[2]/p[2]/text()' 从某个对象中获取信息,然后取第一个元素并去除其两端的空白字符,将结果赋值给 introduce 变量。
# # 字数获取不到,算了
通过 info.xpath('div[2]/p[3]/span/text()') 获取到的内容中,第一个元素赋值给了 word 。
# print word
# 每一本书的信息
info_list 包含 introduce。
# 把每本书的信息拼接到总的列表中
将 info_list 添加到 all_info_list 中。
time.sleep(1)
pass
对“https://www.qidian.com/all?page=1”进行信息获取操作。
# 程序入口
if __name__=="__main__":
# 创建要爬取的链接
urls 等于由“https://www.qidian.com/all?page={}”这个字符串与 range(1,500)范围内的每个整数 i 进行格式化后组成的列表,即 urls 是由多个形如“https://www.qidian.com/all?page={i 的具体值}”的字符串组成的列表,其中 i 依次取 1 到 499 这些整数。
for url in urls:
get_info(url)
'title'、'author'、'style'、'complete' 和 'introduce' 都在 header 之中。
“书名”“作者”“风格”“状态”“介绍”构成了 header 。
# 创建工作簿
创建一个名为 book 的对象,该对象是通过使用 xlwt.Workbook()函数创建的,并且设置编码为"utf-8"。
# 在工作簿中创建工作表
book 新增了一个名为“Sheet1”的工作表,该工作表由 sheet 表示。
for i in range(len(header)):
# 循环写入每一列的标题,从第一列开始
sheet.write(0,i,header[i])
i 等于 1,这意味着从第二行开始,因为第一行已经有列的标题了。
for list in all_info_list:
j = 0
每个元素在 all_info_list 中都是一个列表,这些列表里包含的都是我们所爬取的数据。
for data in list:
sheet.write(i,j,data)
j += 1
i += 1
book.save("xiaoshuo.xls")
pass
部分结果如下图所示:
网页的页数有好几千页。如果要进行爬取,爬取大概百来页就可以了。因为如果爬取过多,时间会很长。像我之前爬取了 499 页的数据,就花费了比较长的时间。