之前的文章我们介绍了一下 Python 中正则表达式和 re 模块来做一个案例,爬取《糗事百科》的糗事并存储到本地。本章我们来看一下另一种爬取数据的方式 XPath。

我们在前面爬取《糗事百科》的时候处理 HTML 文档的时候发现会有些累人,还要对正则表达式非常熟悉爬起来才得心应手,那有没有更为方便的方法呢,答案当然是有的,我们可以先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。

什么是XML

  • XML 指可扩展标记语言(EXtensible Markup Language)
  • XML 是一种标记语言,很类似 HTML
  • XML 的设计宗旨是传输数据,而非显示数据
  • XML 的标签需要我们自行定义。
  • XML 被设计为具有自我描述性。
  • XML 是 W3C 的推荐标准

XML 和 HTML 的区别

数据格式 描述 设计目标
XML Extensible Markup Language (可扩展标记语言) 被设计为传输和存储数据,其焦点是数据的内容。
HTML HyperText Markup Language (超文本标记语言) 显示数据以及如何更好显示数据。
HTML DOM Document Object Model for HTML (文档对象模型) 通过 HTML DOM,可以访问所有的 HTML 元素,连同它们所包含的文本和属性。可以对其中的内容进行修改和删除,同时也可以创建新的元素。
XML文档示例
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <bookstore>
  3. <book category="cooking">
  4. <title lang="en">this is title</title>
  5. <content>hello world</>
  6. </book>
  7. </bookstore>
HTML DOM 模型示例

HTML DOM 定义了访问和操作 HTML 文档的标准方法,以树结构方式表达 HTML 文档。

什么是XPath?

XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。

XPath 开发工具

  1. 开源的XPath表达式编辑工具:XMLQuire(XML格式文件可用)
  2. Chrome插件 XPath Helper
  3. Firefox插件 XPath Checker

选取节点

XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。

下面列出了最常用的路径表达式:

表达式 描述
nodename 选取此节点的所有子节点。
/ 从根节点选取。
// 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
. 选取当前节点。
.. 选取当前节点的父节点。
@ 选取属性。

在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

路径表达式 结果
bookstore 选取 bookstore 元素的所有子节点。
/bookstore 选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!
bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//book 选取所有 book 子元素,而不管它们在文档中的位置。
bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
//@lang 选取名为 lang 的所有属性。

谓语(Predicates)

谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。

在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

路径表达式 结果
/bookstore/book[1] 选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[last()] 选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()-1] 选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[position()<3] 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
//title[@lang] 选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang=’eng’] 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
/bookstore/book[price>35.00] 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

选取未知节点

XPath 通配符可用来选取未知的 XML 元素。

通配符 描述
* 匹配任何元素节点。
@* 匹配任何属性节点。
node() 匹配任何类型的节点。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
/bookstore/* 选取 bookstore 元素的所有子元素。
//* 选取文档中的所有元素。
//title[@*] 选取所有带有属性的 title 元素。

选取若干路径

通过在路径表达式中使用“|”运算符,您可以选取若干个路径。

在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

路径表达式 结果
//book/title | //book/price 选取 book 元素的所有 title 和 price 元素。
//title | //price 选取文档中的所有 title 和 price 元素。
/bookstore/book/title | //price 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

XPath的运算符

下面列出了可用在 XPath 表达式中的运算符:

这些就是XPath的语法内容,在运用到Python抓取时要先转换为xml。

lxml库

lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。

lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。

lxml python 官方文档:http://lxml.de/index.html

需要安装C语言库,可使用 pip 安装:pip install lxml (或通过wheel方式安装)

我们利用它来解析 HTML 代码,简单示例:

  1. from lxml import etree
  2.  
  3. text = '''
  4. <div>
  5. <ul>
  6. <li class="item-0"><a href="link1.html">first item</a></li>
  7. <li class="item-1"><a href="link2.html">second item</a></li>
  8. <li class="item-inactive"><a href="link3.html">third item</a></li>
  9. <li class="item-1"><a href="link4.html">fourth item</a></li>
  10. <li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 li 闭合标签
  11. </ul>
  12. </div>
  13. '''
  14.  
  15. # 利用etree.HTML,将字符串解析为HTML文档
  16. html = etree.HTML(text)
  17.  
  18. # 按字符串序列化HTML文档
  19. # html = etree.tostring(html).decode("utf8") # 不能正常显示中文
  20. html = etree.tostring(html, encoding="utf-8", pretty_print=True, method="html").decode("utf-8") # 可以正常显示中文
  21.  
  22. print(html)

运行结果如下:

  1. <html><body>
  2. <div>
  3. <ul>
  4. <li class="item-0"><a href="link1.html">first item</a></li>
  5. <li class="item-1"><a href="link2.html">second item</a></li>
  6. <li class="item-inactive"><a href="link3.html">third item</a></li>
  7. <li class="item-1"><a href="link4.html">fourth item</a></li>
  8. <li class="item-0">
  9. <a href="link5.html">fifth item</a> # 注意,此处缺少一个 li 闭合标签
  10. </li>
  11. </ul>
  12. </div>
  13. </body></html>

lxml 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。

文件读取:

除了直接读取字符串,lxml还支持从文件里读取内容。我们新建一个 index.html 文件:

  1. <div>
  2. <ul>
  3. <li class="item-0"><a href="link1.html">first item</a></li>
  4. <li class="item-1"><a href="link2.html">second item</a></li>
  5. <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
  6. <li class="item-1"><a href="link4.html">fourth item</a></li>
  7. <li class="item-0"><a href="link5.html">fifth item</a></li>
  8. </ul>
  9. </div>

再利用 etree.parse() 方法来读取文件。

  1. from lxml import etree
  2.  
  3. # 读取外部文件 hello.html
  4. html = etree.parse('./index.html', etree.HTMLParser()) # 指定解析器HTMLParser会根据文件修复HTML文件中缺失的如声明信息
  5. html = etree.tostring(html, encoding="utf-8", pretty_print=True, method="html").decode("utf-8")
  6.  
  7. print(html)

运行结果:

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
  2. <html><body><div>
  3. <ul>
  4. <li class="item-0"><a href="link1.html">first item</a></li>
  5. <li class="item-1"><a href="link2.html">second item</a></li>
  6. <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
  7. <li class="item-1"><a href="link4.html">fourth item</a></li>
  8. <li class="item-0"><a href="link5.html">fifth item</a></li>
  9. </ul>
  10. </div></body></html>

接下来我们看一下 XPath 的实力测试。

1. 获取所有的 <li> 标签

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. print(type(html)) # <class 'lxml.etree._ElementTree'>
  5.  
  6. result = html.xpath('//li')
  7.  
  8. print(result) # [<Element li at 0x109c66248>, <Element li at 0x109c66348>, <Element li at 0x109c66388>, <Element li at 0x109c663c8>, <Element li at 0x109c66408>]
  9. print(len(result)) #
  10. print(type(result)) # <class 'list'>
  11. print(type(result[0])) # <class 'lxml.etree._Element'>

2. 继续获取<li> 标签的所有 class属性

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//li/@class')
  5.  
  6. print(result) # ['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']

3. 继续获取<li>标签下hre 为 link1.html 的 <a> 标签

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//li/a[@href="link1.html"]')
  5.  
  6. print(result) # [<Element a at 0x10b324288>]

4. 获取<li> 标签下的所有 <span> 标签

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. # result = html.xpath('//li/span')
  5. # 注意这么写是不对的:因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
  6.  
  7. result = html.xpath('//li//span')
  8.  
  9. print(result) # [<Element span at 0x10a59b308>]

5. 获取 <li> 标签下的<a>标签里的所有 class

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//li/a//@class')
  5.  
  6. print(result) # ['bold']

6. 获取最后一个 <li> 的 <a> 的 href

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//li[last()]/a/@href')
  5. # 谓语 [last()] 可以找到最后一个元素
  6.  
  7. print(result) # ['link5.html']

7. 获取倒数第二个元素的内容

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//li[last()-1]/a')
  5.  
  6. # text 方法可以获取元素内容
  7. print(result[0].text) # fourth item

8. 获取 class 值为 bold 的标签名

  1. from lxml import etree
  2.  
  3. html = etree.parse('./index.html', etree.HTMLParser())
  4. result = html.xpath('//*[@class="bold"]')
  5.  
  6. # tag方法可以获取标签名
  7. print(result[0].tag) # span

XPath的更多用法参考:http://www.w3school.com.cn/xpath/index.asp

python lxml库的更多用法参考:http://lxml.de/

 
 

Python 爬虫从入门到进阶之路(十)的更多相关文章

  1. Python 爬虫从入门到进阶之路(八)

    在之前的文章中我们介绍了一下 requests 模块,今天我们再来看一下 Python 爬虫中的正则表达的使用和 re 模块. 实际上爬虫一共就四个主要步骤: 明确目标 (要知道你准备在哪个范围或者网 ...

  2. Python 爬虫从入门到进阶之路(二)

    上一篇文章我们对爬虫有了一个初步认识,本篇文章我们开始学习 Python 爬虫实例. 在 Python 中有很多库可以用来抓取网页,其中内置了 urllib 模块,该模块就能实现我们基本的网页爬取. ...

  3. Python 爬虫从入门到进阶之路(六)

    在之前的文章中我们介绍了一下 opener 应用中的 ProxyHandler 处理器(代理设置),本篇文章我们再来看一下 opener 中的 Cookie 的使用. Cookie 是指某些网站服务器 ...

  4. Python 爬虫从入门到进阶之路(九)

    之前的文章我们介绍了一下 Python 中的正则表达式和与爬虫正则相关的 re 模块,本章我们就利用正则表达式和 re 模块来做一个案例,爬取<糗事百科>的糗事并存储到本地. 我们要爬取的 ...

  5. Python 爬虫从入门到进阶之路(十二)

    之前的文章我们介绍了 re 模块和 lxml 模块来做爬虫,本章我们再来看一个 bs4 模块来做爬虫. 和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也 ...

  6. Python 爬虫从入门到进阶之路(十五)

    之前的文章我们介绍了一下 Python 的 json 模块,本章我们就介绍一下之前根据 Xpath 模块做的爬取<糗事百科>的糗事进行丰富和完善. 在 Xpath 模块的爬取糗百的案例中我 ...

  7. Python 爬虫从入门到进阶之路(十六)

    之前的文章我们介绍了几种可以爬取网站信息的模块,并根据这些模块爬取了<糗事百科>的糗百内容,本章我们来看一下用于专门爬取网站信息的框架 Scrapy. Scrapy是用纯Python实现一 ...

  8. Python 爬虫从入门到进阶之路(十七)

    在之前的文章中我们介绍了 scrapy 框架并给予 scrapy 框架写了一个爬虫来爬取<糗事百科>的糗事,本章我们继续说一下 scrapy 框架并对之前的糗百爬虫做一下优化和丰富. 在上 ...

  9. Python 爬虫从入门到进阶之路(五)

    在之前的文章中我们带入了 opener 方法,接下来我们看一下 opener 应用中的 ProxyHandler 处理器(代理设置). 使用代理IP,这是爬虫/反爬虫的第二大招,通常也是最好用的. 很 ...

  10. Python 爬虫从入门到进阶之路(七)

    在之前的文章中我们一直用到的库是 urllib.request,该库已经包含了平常我们使用的大多数功能,但是它的 API 使用起来让人感觉不太好,而 Requests 自称 “HTTP for Hum ...

随机推荐

  1. Fedora7 安装完全过程

    操作系统课要用Fedora7 安装配置真是一把辛酸泪呀..... 首先是找镜像文件(这个就找了好久.....): https://archives.fedoraproject.org/pub/arch ...

  2. 环境变量_JAVA_LAUNCHER_DEBUG,它能给你更多的JVM信息

    关于环境: 本文中的实战都是在docker容器中进行的,容器的出处请参照<在docker上编译openjdk8>一文,里面详细的说明了如何构造镜像和启动容器. 在上一篇文章<修改,编 ...

  3. 解决python -m pip install --upgrade pip 升级不成功问题

    1.使用命令出现. You are , however version is available. You should consider upgrading via the 'python -m p ...

  4. Cookie的应用——Servlet实现三天免登录

    1.工程结构: 2.Servlet的运用: (1)登录界面: protected void doGet(HttpServletRequest request, HttpServletResponse ...

  5. PHP 扩展开发初探

    什么是 PHP 扩展 通俗说,PHP 扩展是增强 PHP 语言功能的插件.PHP 提供了编程语言的语法,比如分支.循环.函数.类等,这些是 PHP 本身所提供的.在某些情况下需要在 PHP 语言的基础 ...

  6. [SpringBoot——Web开发(使用Thymeleaf模板引擎)]

    [文字只能描述片段信息,具体细节参考代码] https://github.com/HCJ-shadow/SpringBootPlus 引入POM依赖 <properties> <ja ...

  7. .netCore部署在IIS上遇到的问题(500.19,500.21错误)

    1.确保IIS功能都安装上了. 2.确保.netcore 的最新sdk已安装. 3.应用程序池改成无托管代码 4.500.19错误 错误原因,没有安装 DotNetCore.2.0.5-Windows ...

  8. JAVA设计模式-动态代理(Proxy)示例及说明

    在Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析文章的最后部分,我们提到了动态代理的概念,下面我们就简单了解一下动态代理. 一,概念 代理设计模式的目的就是在不直接操作对象的前 ...

  9. Audio Bit Depth Super-Resolution with Neural Networks

    Audio Bit Depth Super-Resolution with Neural Networks 作者:Thomas Liu.Taylor Lundy.William Qi 摘要 Audio ...

  10. Mysql 笔记(一)

    InnoDB存储引擎 mysql 存储引擎(好难用,看https://www.zybuluo.com/eqyun/note/27850) 简介 InnoDB是事务安全的MySQL存储引擎,从MySQL ...