---- 钢铁侠的知识库 2022.08.15
我们在写Python爬虫时,经常需要对网页提取信息,如果用传统正则表达去写会增加很多工作量,此时需要一种对数据解析的方法,也就是本章要介绍的Xpath表达式。
XPath,全称 XML Path Language,即 XML 路径语言,它是一门在 XML 文档中查找信息的语言。最初是用来搜寻 XML 文档的,但同样适用于 HTML 文档的搜索。所以在做爬虫时完全可以使用 XPath 做相应的信息抽取。
XPath 的选择功能十分强大,它提供了非常简洁明了的路径选择表达式。另外,它还提供超过 100 个内置函数,用于字符串、数值、时间的匹配以及节点、序列的处理等,几乎所有想要定位的节点都可以用 XPath 来选取。
下面介绍实战中常用的几个知识点,详细也可以看W3C介绍:https://www.w3school.com.cn/xpath/index.asp
| 表达式 | 描述 | 实例 | |
|---|---|---|---|
| nodename | 选取此节点的所有子节点 | xpath('//div') | 选取了div节点的所有子节点 |
| / | 从根节点选取 | xpath('/div') | 从根节点上选取div节点 |
| // | 选取所有当前节点,不考虑位置 | xpath('//div') | 选取所有的div节点 |
| . | 选取当前节点 | xpath('./div') | 选取当前节点下的div节点 |
| .. | 选取当前节点的父节点 | xpath('..') | 回到上一个节点 |
| @ | 选取属性 | xpath('//@calss') | 选取所有的class属性 |
谓语被嵌在方括号内,用来查找某个特定的节点或包含某个制定的值的节点
| 表达式 | 结果 |
|---|---|
| xpath('/body/div[1]') | 选取body下的第一个div节点 |
| xpath('/body/div[last()]') | 选取body下最后一个div节点 |
| xpath('/body/div[last()-1]') | 选取body下倒数第二个div节点 |
| xpath('/body/div[positon()❤️]') | 选取body下前两个div节点 |
| xpath('/body/div[@class]') | 选取body下带有class属性的div节点 |
| xpath('/body/div[@class="main"]') | 选取body下class属性为main的div节点 |
| xpath('/body/div[price>35.00]') | 选取body下price元素值大于35的div节点 |
通配符来选取未知的XML元素
| 表达式 | 结果 |
|---|---|
| xpath('/div/*') | 选取div下的所有子节点 |
| xpath('/div[@*]') | 选取所有带属性的div节点 |
使用“|”运算符可以选取多个路径
| 表达式 | 结果 |
|---|---|
| xpath('//div|//table') | 选取所有的div和table节点 |
使用功能函数能够更好的进行模糊搜索
| 函数 | 用法 | 解释 |
|---|---|---|
| starts-with | xpath('//div[starts-with(@id,"ma")]') | 选取id值以ma开头的div节点 |
| contains | xpath('//div[contains(@id,"ma")]') | 选取id值包含ma的div节点 |
| and | xpath('//div[contains(@id,"ma") and contains(@id,"in")]') | 选取id值包含ma和in的div节点 |
| text() | xpath('//div[contains(text(),"ma")]') | 选取节点文本包含ma的div节点 |
下面举一段HTML文本进行语法热身,代码如下
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# time: 2022/8/8 0:05
# author: gangtie
# email: 648403020@qq.com
from lxml import etree
text = '''
<div>
<ul id='ultest'>
<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"><span>fourth item</span></a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</ul>
</div>
'''
# 调用HTML类进行初始化,这样就成功构造了一个XPath解析对象。
# 利用etree.HTML解析字符串
page = etree.HTML(text)
print(type(page))
可以看到打印结果已经变成XML元素:
<class 'lxml.etree._Element'>
字符串利用etree.HTML解析成html格式:
print(etree.tostring(page,encoding='utf-8').decode('utf-8'))
```
<html><body><div>
<ul id="ultest">
<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"><span>fourth item</span></a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li></ul>
</div>
</body></html>
Process finished with exit code 0
```
经过处理可以看到缺失的</li>也自动补全了,还自动添加html、body节点。
通过绝对路径获取a标签的所有内容
a = page.xpath("/html/body/div/ul/li/a")
for i in a:
print(i.text)
```
first item
second item
third item
None
fifth item
```
查找所有li标签下的a标签内容
html = etree.HTML(text)
a = html.xpath("//a/text()")
print(a)
```
['first item', 'second item', 'third item', 'fifth item']
```
. 表示选取当前标签的节点。
我们先定位 ul 元素节点得到一个列表,打印当前节点列表得到第一个 ul,
接着打印 ul 节点的子节点 li,text()输出。
page = etree.HTML(text)
ul = page.xpath("//ul")
print(ul)
print(ul[0].xpath("."))
print(ul[0].xpath("./li"))
print(ul[0].xpath("./li/a/text()"))
```
[<Element ul at 0x234d16186c0>]
[<Element ul at 0x234d16186c0>]
[<Element li at 0x234d1618ac0>, <Element li at 0x234d1618b00>, <Element li at 0x234d1618b40>, <Element li at 0x234d1618b80>, <Element li at 0x234d1618bc0>]
['first item', 'second item', 'third item', 'fifth item']
```
.. 表示选取当前标签的父节点。
可以看到得到ul的上一级div
page = etree.HTML(text)
ul = page.xpath("//ul")
print(ul[0].xpath("."))
print(ul[0].xpath(".."))
```
[<Element ul at 0x1d6d5cd8540>]
[<Element div at 0x1d6d5cd8940>]
```
匹配时可以用@符号进行属性过滤
查找a标签下属性href值为link2.html的内容
html = etree.HTML(text)
a = html.xpath("//a[@href='link2.html']/text()")
print(a)
```
['second item']
```
last():查找最后一个li标签里的a标签的href属性
html = etree.HTML(text)
a = html.xpath("//li[last()]/a/text()")
print(a)
```
['fifth item']
```
contains:查找a标签中属性href包含link的节点,并文本输出
html = etree.HTML(text)
a = html.xpath("//a[contains(@href, 'link')]/text()")
print(a)
```
['first item', 'second item', 'third item', 'fifth item']
```
上面说完基本用法,接下来做几个实战案例练练手。
# -*-coding:utf8 -*-
# 1.请求并提取需要的字段
# 2.保存需要的数据
import requests
from lxml import etree
class DoubanBook():
def __init__(self):
self.base_url = 'https://book.douban.com/chart?subcat=all&icn=index-topchart-popular'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/104.0.0.0 Safari/537.36'
}
# 请求并提取需要的字段
def crawl(self):
res = requests.get(self.base_url, headers=self.headers)
lis = etree.HTML(res.text).xpath('//*[@id="content"]/div/div[1]/ul/li')
# print(type(lis))
books = []
for li in lis:
# print(etree.tostring(li,encoding='utf-8').decode('utf-8'))
# print("==================================================")
title = "".join(li.xpath(".//a[@class='fleft']/text()"))
score = "".join(li.xpath(".//p[@class='clearfix w250']/span[2]/text()"))
# list输出带有['\n 刘瑜 / 2022-4 / 广西师范大学出版社 / 82.00元 / 精装\n ']
publishing = "".join(li.xpath(".//p[@class='subject-abstract color-gray']/text()")).strip()
book = {
'title': title,
'score': score,
'publishing': publishing,
}
books.append(book)
self.save_data(books)
def save_data(self, datas):
with open('books.txt', 'w', encoding='utf-8') as f:
f.write(str(datas))
def run(self):
self.crawl()
if __name__ == '__main__':
DoubanBook().run()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 钢铁知识库
# email: 648403020@qq.com
import os
import requests
from lxml import etree
# 彼岸图片下载
class BiAn():
def __init__(self):
self.url = 'https://pic.netbian.com'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/104.0.0.0 Safari/537.36',
'cookie': '__yjs_duid=1_cb922eedbda97280755010e53b2caca41659183144320; Hm_lvt_c59f2e992a863c2744e1ba985abaea6c=1649863747,1660203266; zkhanecookieclassrecord=%2C23%2C54%2C55%2C66%2C60%2C; Hm_lpvt_c59f2e992a863c2744e1ba985abaea6c=1660207771; yjs_js_security_passport=1225f36e8501b4d95592e5e7d5202f4081149e51_1630209607_js'
}
# 如果目录不存在会报错
if not os.path.exists('BianPicture'):
os.mkdir('BianPicture')
# 请求拿到ul列表
def crawl(self):
res = requests.get(self.url, headers=self.headers)
res.encoding = 'gbk'
uls = etree.HTML(res.text).xpath('//div[@class="slist"]/ul[@class="clearfix"]/li')
# print(etree.tostring(uls,encoding='gbk').decode('gbk'))
# 循环拿到图片名、图片地址,拼接请求再次下载到图片
for ul in uls:
img_name = ul.xpath('.//a/b/text()')[0]
img_src = ul.xpath('.//a/span/img/@src')[0]
# print(img_name + img_src)
img_url = self.url + img_src
# 拼接后下载图片,转义Bytes
img_res = requests.get(img_url, headers=self.headers).content
img_path = "BianPicture/" + img_name + ".jpg"
data = {
'img_res': img_res,
'img_path': img_path
}
self.save_data(data)
# 数据保存逻辑
def save_data(self, data):
with open(data['img_path'], 'wb') as f:
f.write(data['img_res'])
# print(data)
def run(self):
self.crawl()
if __name__ == '__main__':
BiAn().run()

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: 钢铁知识库
# email: 648403020@qq.com
import os
import requests
from lxml import etree
class CityName():
def __init__(self):
self.url = 'https://www.aqistudy.cn/historydata/'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
}
# 判断目录是否存在
if not os.path.exists('city_project'):
os.mkdir('city_project')
def crawl(self):
res = requests.get(url=self.url, headers=self.headers).text
uls = etree.HTML(res).xpath('//div[@class="all"]/div[2]/ul/div[2]/li')
all_city_name = list()
for ul in uls:
city_name = ul.xpath('.//a/text()')[0]
# print(type(city_name))
all_city_name.append(city_name)
self.save_data(all_city_name)
def save_data(self, data):
with open('./city_project/city.txt', 'w') as f:
f.write(str(data))
def run(self):
self.crawl()
if __name__ == '__main__':
CityName().run()
经常使用chome的朋友都应该知道这功能,在 审查 状态下(快捷键ctrl+shift+i,F12),定位到元素(快捷键ctrl+shift+c) ,在Elements选项卡中,右键元素 Copy->Copy xpath,就能得到该元素的xpath了

为chome装上XPath Helper就可以很轻松的检验自己的xpath是否正确了。安装插件需要特别上网,安装好插件后,在chrome右上角点插件的图标,调出插件的黑色界面,编辑好xpath表达式,表达式选中的元素被标记为黄色

---- 钢铁侠的知识库 2022.08.15
以上就是利用XPath的所有用法,从常用语法,到案例练习都走了一遍。下一章 钢铁知识库 会继续介绍另一种好用的解析框架,Beautiful Soup,觉得有用点赞加关注,就当你送了红包666
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=oogrefjvrp08
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He