爬虫 结构化 数据 非结构化数据 XPath lxml 类库
背景
使用正则表达式比较费劲 可以使用xpath
先将 HTML文件 转换成 XML文档,然后用 XPath 查找 HTML 节点或元素。
什么是XPath?
XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言,可用来在 XML 文档中对元素和属性进行遍历。
W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
XPath 开发工具
开源的XPath表达式编辑工具:XMLQuire(XML格式文件可用)
Chrome插件 XPath Helper
Firefox插件 XPath Checker
安装lxml
python3.6 中安装 会安装比较新的lxml lxml-4.2.1 版本的, 里面没有etree版本
使用3.6 指定版本
直接使用 pip install lxml 就ok
现在使用好像是有的啦, 好哦哦奇怪
开始学习xpath
选取节点
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
下面列出了最常用的路径表达式:
在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:
外加说明:
bookstore/book 是找bookstore 的所有为book 的子节点 必须是儿子
bookstore//book 查找的是 bookstore下所有为book的节点
谓语(Predicates)
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
谓语 都在方括号中
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
实例
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
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 代码,简单示例:
# lxml_test.py
# 使用 lxml 的 etree 库
from lxml import etree
text = '''
<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a> # 注意,此处缺少一个 </li> 闭合标签
</ul>
</div>
'''
#利用etree.HTML,将字符串解析为HTML文档
html = etree.HTML(text)
# 按字符串序列化HTML文档
result = etree.tostring(html)
print(result)
输出结果
<html><body>
<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</body></html>
lxml 可以自动修正 html 代码,例子里不仅补全了 li 标签,还添加了 body,html 标签。
文件读取:
除了直接读取字符串,lxml还支持从文件里读取内容。我们新建一个hello.html文件:
<!-- hello.html -->
<div>
<ul>
<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"><span class="bold">third item</span></a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
再利用 etree.parse() 方法来读取文件。
# lxml_parse.py
from lxml import etree
# 读取外部文件 hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
输出结果与之前相同:
<html><body>
<div>
<ul>
<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">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
</body></html>
XPath实例测试
- 获取所有的
- 标签
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
print type(html) # 显示etree.parse() 返回类型
result = html.xpath('//li')
print result # 打印<li>标签的元素集合
print len(result)
print type(result)
print type(result[0])
输出结果:
<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
5
<type 'list'>
<type 'lxml.etree._Element'>
- 继续获取
- 标签的所有 class属性
- # xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/@class')
print result
运行结果
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
3. 继续获取
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a[@href="link1.html"]')
print result
result
[<Element a at 0x10ffaae18>]
获取
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
#result = html.xpath('//li/span')
#注意这么写是不对的:
#因为 / 是用来获取子元素的,而 <span> 并不是 <li> 的子元素,所以,要用双斜杠
result = html.xpath('//li//span')
print result
result
[<Element span at 0x10d698e18>]
获取
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a//@class')
print result
运行结果
['blod']
获取最后一个
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()]/a/@href')
# 谓语 [last()] 可以找到最后一个元素
print result
运行结果
['link5.html']
获取倒数第二个元素的内容
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()-1]/a')
# text 方法可以获取元素内容
print result[0].text
运行结果
fourth item
获取 class 值为 bold 的标签名
# xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//*[@class="bold"]')
# tag方法可以获取标签名
print result[0].tag
运行结果
span
使用xpath解析 内涵段子
import urllib.request
from lxml import etree
class NH(object):
def __init__(self):
self.page = 1
def get_data(self, li_list):
for q in li_list:
title = q.find_all("a")[0].string
print(title)
content_list = q.find_all("div", class_="f18 mb20")[0].strings
content_string = "".join(content_list).strip().replace(" ", "")
print(content_string)
print("==============")
data = dict()
data["title"] = title
data["content"] = content_string
yield data
def loadPage(self):
url = "https://www.neihan8.com/article/list_5_" + str(self.page) + ".html"
# print(url)
# url = "https://www.neihan8.com/article/list_5_1.html"
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
request = urllib.request.Request(url, headers=header)
response = urllib.request.urlopen(request)
text = response.read().decode("gb2312")
html = etree.HTML(text)
result = html.xpath("//ul[@class='piclist longList']/li")
for q in result:
text2 = etree.tostring(q)
html2 = etree.HTML(text2)
# 获取h4 下面的第一个a 里面的内容 string(.) 函数获取a 标签 内的所有文本
# 即便a 标签里面还有别的标签
title = html2.xpath("//li/h4/a")[0].xpath('string(.)')
# print(ele[0].text)
print(title)
content = html2.xpath("//li/div[@class='f18 mb20']")[0].xpath('string(.)').strip()
print(content)
print("=======================================")
if __name__ == '__main__':
instance = NH()
instance.loadPage()
上面在for 循环时候有问题 实现的复杂了点
就是xpath 解析时候
import urllib.request
from lxml import etree
class NH(object):
def __init__(self):
self.page = 1
def get_data(self, li_list):
for q in li_list:
title = q.find_all("a")[0].string
print(title)
content_list = q.find_all("div", class_="f18 mb20")[0].strings
content_string = "".join(content_list).strip().replace(" ", "")
print(content_string)
print("==============")
data = dict()
data["title"] = title
data["content"] = content_string
yield data
def loadPage(self):
url = "https://www.neihan8.com/article/list_5_" + str(self.page) + ".html"
# print(url)
# url = "https://www.neihan8.com/article/list_5_1.html"
header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
request = urllib.request.Request(url, headers=header)
response = urllib.request.urlopen(request)
text = response.read().decode("gb2312")
html = etree.HTML(text)
result = html.xpath("//ul[@class='piclist longList']/li")
for q in result:
# q.xpath("//h4/a")[0].xpath('string(.)') 直接这样是不对的
title = q.xpath(".//h4/a")[0].xpath('string(.)')
print(title) ## 主要是title和content 这个地方要使用相对 . 当前 的才行不能直接写
content = q.xpath(".//div[@class='f18 mb20']")[0].xpath('string(.)').strip()
print(content)
if __name__ == '__main__':
instance = NH()
instance.loadPage()
对比使用bs4中beautifulsoup 简单一些
使用xpath 和 bs4 中beautiful比较
BeautifulSoup是一个库,而XPath是一种技术,python中最常用的XPath库是lxml,因此,这里就拿lxml来和BeautifulSoup做比较吧
1 性能 lxml >> BeautifulSoupBeautifulSoup和lxml的原理不一样,BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多。而lxml只会局部遍历,另外lxml是用c写的,而BeautifulSoup是用python写的,因此性能方面自然会差很多。
2 易用性 BeautifulSoup >> lxml
BeautifulSoup用起来比较简单,API非常人性化,支持css选择器。lxml的XPath写起来麻烦,开发效率不如BeautifulSoup。
title = soup.select('.content div.title h3')
同样的代码用Xpath写起来会很麻烦
title = tree.xpath("//*[@class='content']/div[@class='content']/h3")
3 总结
需求比较确定,要求性能的场合用lxml,快速开发用BeautifulSoup
ps: BeautifulSoup4可以使用lxml作为parser了
最后说一点, 那个顺手在不考虑别的情况下就使用哪个xpath beautifulsoup
上一篇: XPath