参考资料:
W3C关于Xpah的教程
阮一峰关于Xpath的文章
崔庆才关于lxml的博客
lxml python 官方文档
Beautiful Soup官方文档
崔庆才关于bs的博客
pyquery
网页解析可以用正则表达式解析,也可以用解析库解析,利用其强大的方法可以完成各种解析,比如XPath解析以及CSS选择器解析。
XPath全称为XML Path Language,即XML路径语言。是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。他是一个标准的函数库,也是W3C的一个标准。
它最初是用在搜寻XML文档的,但是它同样适用于HTML文档的检索。
简单说,xpath就是选择XML文件中节点的方法。 所谓节点(node),就是XML文件的最小构成单位,一共分成7种。
Xpath通过"路径表达式"(Path Expression)来选择节点。在形式上,"路径表达式"与传统的文件系统非常类似。其基本格式如下:
其中用/分割的成为步(step),步的语法如下:
轴名称::节点测试[谓语]
/html/body/form/input 选取所有input节点
//input 选取所有的input节点
规则 | 描述 |
---|---|
nodename | 选取此节点的所有子节点 |
@ | 选取属性 |
. | 选取当前节点。 |
… | 选取当前节点的父节点。 |
所谓"谓语条件",就是对路径表达式的附加条件。所有的条件,都写在方括号"[]" 中,表示对节点进行进一步的筛选。
示例如下:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()❤️] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
通过Xpath提供的轴选择器方法,可以获取子元素、兄弟元素、父元素、祖先元素等。
其中,轴可定义相对于当前节点的节点集。可用轴如下:
轴名称 | 结果 |
---|---|
ancestor | 选取当前节点的所有祖先节点。 |
ancestor-or-self | 选取当前节点的所有祖先节点以及当前节点本身。 |
attribute | 选取当前节点的所有属性。 |
child | 选取当前节点的所有子元素 。 |
descendant | 选取当前节点的所有后代元素。 |
descendant-or-self | 选取当前节点的所有后代元素以及当前节点本身。 |
following | 选取文档中当前节点的结束标签之后的所有节点。 |
namespace | 选取当前节点的所有命名空间节点。 |
parent | 选取当前节点的父节点。 |
preceding | 选取文档中当前节点的开始标签之前的所有节点。 |
preceding-sibling | 选取当前节点之前的所有同级节点。self选取当前节点。 |
示例如下:
例子 | 结果 |
---|---|
child::book | 选取所有属于当前节点的子元素的 book 节点。 |
attribute::lang | 选取当前节点的 lang 属性。 |
child::* | 选取当前节点的所有子元素 |
pip install lxml
首先我们使用 lxml 的 etree 库,然后利用etree.HTML 初始化,然后我们将其打印出来。在这里初始化库可以帮我们自动修正代码。
from lxml import etree
text = '''
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result)
我们同样可以使用etree.parase(),传入两个参数:
初始化完成后可以使用xpath获取节点信息,示例如下:
from lxml import etree
html = etree.parse('hello.html',etree.HTMLParaser())
print type(html)
result = html.xpath('//li')
print result
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'>
Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式。
相比与,lxml,BeautifulSoup是Python开发的,同时原理不同,BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多。而lxml只会局部遍历。
使用xpath 要求一定清楚文档层次结构,它通过元素和属性进行导航,可以使用绝对路径或相对路径查找,而beautifulsoup 不必清楚文档结构,可以直接找某些标签。
pip install beautifulsoup4
现在的项目中使用Beautiful Soup 4,不过它已经被移植到BS4了,也就是说导入时 我们需要 import bs4 。
Beautiful Soup在解析时需要依赖解析器,它支持的解析器如下表所示
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, “html.parser”) | Python的内置标准库执行速度适中文档容错能力强 | Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | 速度快文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, [“lxml-xml”])BeautifulSoup(markup, “xml”) | 速度快唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性以浏览器的方式解析文档生成HTML5格式的文档 | 速度慢不依赖外部扩展 |
推荐使用lxml解析器,他的效率更高。
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment 。
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象。
其代表的就是HTML或XML原生文档中的节点。
代表来包装tag中的字符串。
Comment 对象是一个特殊类型的 NavigableString 对象,其代表了文档中的注释及特殊字符串。
操作文档树最简单的方法就是告诉它你想获取的tag的name。
soup.body.b
# The Dormouse's story
提取信息方式有一下三种属性:
head_tag.string
# u'The Dormouse's story'
for string in soup.strings:
print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
for string in soup.stripped_strings:
print(repr(string))
# u"The Dormouse's story"
# u"The Dormouse's story"
这两个属性得到的都是当前节点的直接子节点,不同在于前者是直接以列表形式输出,可以利用索引获取元素;后者返回的是一个 list 生成器对象。如下实例所示
print soup.head.contents
#[The Dormouse's story ]
for child in soup.body.children:
print child
#The Dormouse's story
#,
返回的同样是生成器,但是是返回了所有子孙节点的生成器。
for child in head_tag.descendants:
print(child)
# The Dormouse's story
# The Dormouse's story
获取某个元素的父节点。
获取所有的祖先节点,返回的同样是列表生成器。
分别用来获取节点的下一个或者上一个兄弟元素。
分别返回后面和前面的兄弟节点,这里是所有兄弟节点,以生成器形式返回。
for sibling in soup.a.next_siblings:
print(repr(sibling))
# u',\n'
# Lacie
# u' and\n'
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。
参数格式如下:
find_all( name , attrs , recursive , text , **kwargs )
(1)name 参数
查找所有名字为 name 的tag,字符串对象会被自动忽略掉。其中可以传入的形式如下:
soup.find_all('b')
# [The Dormouse's story]
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b
soup.find_all(["a", "b"])
# [The Dormouse's story,
# Elsie,
# Lacie,
for tag in soup.find_all(True):
print(tag.name)
# html# head# title# body# p# b
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
soup.find_all(has_class_but_no_id)
# [The Dormouse's story
,
# Once upon a time there were...
,
# ...
]
(2)keyword 参数
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索。
soup.find_all(id='link2')
# [Lacie]
搜索指定名字的属性时可以使用的参数值包括:字符串 , 正则表达式 , 列表, True。
使用多个指定名字的参数可以同时过滤tag的多个属性。
soup.find_all(href=re.compile("elsie"), id='link1')
# [three]
class 在Python中是保留字,应通过 class_ 参数搜索有指定CSS类名的tag,其也可以接受不同类型的过滤器。
(3)attrs参数
attrs 参数定义一个字典参数来搜索包含特殊属性的tag,一般的id、name等属性也可以放在里面。
data_soup.find_all(attrs={"data-foo": "value"})# [foo!]
(4)string 参数
attrs通过 string 参数可以搜搜文档中的字符串内容,其可接受过滤器与name相同。其可以与其他参数混合使用。比如,下面代码用来搜索内容里面包含“Elsie”的a标签:
soup.find_all("a", string="Elsie")# [Elsie]
(4)limit 参数
attrs使用 limit 参数限制返回结果的数量。当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果。
(5)recursive 参数
若只想搜索tag的直接子节点,可以使用参数 recursive=False .
find_all() 几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法.。BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,下面两行代码是等价的:
soup.title.find_all(string=True)
soup.title(string=True)
它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果
除恶利用find_all(),在这里我们也可以利用soup.select()通过选择器筛选元素,返回类型是 list。
print soup.select('title')
#[The Dormouse's story ]
print soup.select('.sister')
#[, Lacie, ]
print soup.select('#link1')
#[]
组合原理和我们写css时候一样,如下
print soup.select('p #link1')
#[]
属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。
print soup.select('a[href="http://example.com/elsie"]')
#[]
这个我觉得用不上,不整理了。