Bootstrap

第三章 网页数据的解析提取

XPath的使用

全称为XML Path Language,用于在XML文档中查找信息,同样适用于HTML文档的搜索

XPath的常用规则

表达式描述
nodename选取此节点的所有子节点
/从当前节点选取直接子节点
//从当前节点选取子孙节点
.选取当前节点
. .选取当前节点的父节点
@选取属性

例:

//title[@lang='eng']

表示选择所有名称为title,同时 属性lang的值为eng的节点

基本使用

from lxml import etree
text='''
<div>
	<ul>
		<li class="item-0"><a href="link1.heml">first item</a></li>
		<li class="item-1"><a href="link2.heml">second item</a></li>
		<li class="item-inactive"><a href="link3.heml">third item</a></li>
		<li class="item-1"><a href="link4.heml">fourth item</a></li>
		<li class="item-0"><a href="link5.heml">fifth item</a>
	</ul>
</div>
'''
html=etree,HTML(text)
result=etree.totring(html)
print(result.decode('utf-8'))

读取文件解读

from lxml import etree

html=etree.prase('./test.html',etree.HTMLParser())
result=etree.tostring(html)
print(result.decode('utf-8'))

所有文件

from lxml import etree
html= etree.parse('./test.heml',etree.HTMLParse())
result=html.xpath('//*')
print(result)

指定节点名称

from lxml import etree
html=etree.parse('./test.html',etree.HTMLParse())
result=html.xpath('//li')
print(result)

子节点

/用于获取直接子节点

from lxml import etree
html=etree.parse('./test.html',etree.HTMLParse())
result=html.xpath('//li/a')
print(result)

//用于获取子孙节点

 from lxml import etree
 html=etree.parse('./test.html',etree.HTMLParse())
 result=htmlx.path('//li//a')
 print(result)

父节点

. .用于查找父节点

 from lxml import etree
 html=etree.parse('./test.html',etree.HTMLParser())
 result=html.xpath('//a[@href="link4.html"]/../@class')
 print(result)

也可以通过parent::来获取父节点

from lxml import etree
html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//a[@href="link4.html"]/parent::*/@class')

属性匹配

在选取节点的时候,还可以使用@符合实现属性过滤

from lxml import etree
html=etree.parse('.test.html',etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]')

文本获取

用XPath中的text方法可以获取节点中的文本

from lxml import etree

html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li[@class"item=0"]//text()')
print(result)

属性获取

from lxml import etree

html=etree.parse('./test.html',etree.HTMLParser())
result=html.xpath('//li//a/@href')
print(result)

属性多值匹配

当节点属性拥有多个值时需要用到contains方法

from lxml impot etree
text='''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class,"li")]/a/text()')
print(result)

多属性匹配

from lxml import etree
text='''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class , "li") and @name="item"]/a/text()')
print(result)

其中的and其实是XPath中的运算符

运算符描述实例返回值
orage=19 or age=20如果age是19,则返回true。如果age是21,则返回false
andage>19 and age<21如果age是20,则返回true。如果age是18,则返回false
mod计算除法的余数5 mod 21
I计算两个节点集//bookI//cd返回所有拥有book和cd元素的节点集
+加法6+410
-减法6-42
*乘法6 * 424
div除法8 div 42
=等于age=19如果age是19,则返回true。如果age是20,则返回false
!=不等于age!=19如果age是18,则返回true。如果age是119,则返回false
<小于age<19如果age是18,则返回true。如果age是19,则返回false
<=小于等于age<=19如果age是19,则返回true。如果age是20,则返回false
>大于age>19如果age是20,则返回true。如果age是19,则返回false
>=大于等于age>=19如果age是19,则返回true。如果age是18,则返回false

按序选择

在选择节点时,某些属性可能同时匹配了多个节点,但我们只需要其中的某一个。这时可以使用往中括号中传入索引的方法获取特定次序的节点

from lxml import etree

text='''
<div>
	<ul>
		<li class="item-0"><a href="link1.heml">first item</a></li>
		<li class="item-1"><a href="link2.heml">second item</a></li>
		<li class="item-inactive"><a href="link3.heml">third item</a></li>
		<li class="item-1"><a href="link4.heml">fourth item</a></li>
		<li class="item-0"><a href="link5.heml">fifth item</a>
	</ul>
</div>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/a/text()')
print(result)
result=html.xpath('//li[last()]/a/text()')
print(result)
result=html.xpath('//li[position()<3]/a/text()')
print(result)
result=html.xpath('//li[last()-2]/a/text()')

节点轴选择

from lxml import etree

text='''
<div>
	<ul>
		<li class="item-0"><a href="link1.heml">first item</a></li>
		<li class="item-1"><a href="link2.heml">second item</a></li>
		<li class="item-inactive"><a href="link3.heml">third item</a></li>
		<li class="item-1"><a href="link4.heml">fourth item</a></li>
		<li class="item-0"><a href="link5.heml">fifth item</a>
	</ul>
</div>
'''
html=etree.HTML(text)
result=html.xpath('//li[1]/ancestor::*')	#调用了ancestor轴,可以获取所有祖先节点 
print(result)
result=html.xpath('//li[1]/ancestor::div')
print(result)
result=html.xpath('//li[1]/attribute::*')	#调用了attribute轴,可以获取所有属性值
print(result)
result=html.xpath('//li[1]//child::a[@href="link1.html"]')	#调用了child轴,可以获取所有直接子节点
print(result)
result=html.xpath('//li[1]//descendant::span')	#调用了descendant轴,可以获取所有子孙节点
print(result)
result=html.xpath('//li[1]//following::*2')	#调用了following轴,可以获取当前节点之后的所有节点
print(result)
result=html.xpath('//li[1]//following::sibling*')	#调用了following-sibling轴,可以获取当前节点之后的所有同级节点
print(result)

Beautiful Soup

其在解析时是依赖解析器的,推荐使用LXML解析器

from bs4 import BeautifulSoup
soup=BeautifulSoup('<p>Hello</p>','lxml')
print(soup.p.string)

pip3 install beautifulsoup4

基本使用

html="""
<html><head><title>The Dormouse's story </title></head>
<body>
<p class="title" name="dormouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters;and their names were
<a href="http:://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>
<a href="http:://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http:://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.prettify())
print(soup.title.string)

其中调用的prettify方法可以把要解析的字符串以标准的缩进格式输出
而调用soup.titlt.string则是输出HTML中title节点的文本内容

节点选择器

html="""
<html><head><title>The Dormouse's story </title></head>
<body>
<p class="title" name="dormouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters;and their names were
<a href="http:://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>
<a href="http:://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http:://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)

提取信息

获取名称

print(soup.title.name)

获取属性

print(soup.p.attrs)
print(soup.p.attrs['name'])	#调用attrs属性
print(soup.p['name'])

获取内容

print(soup.p.string)

嵌套选择

print(soup.head.title)
print(type(soup.head.title))
print(soup.head.title.string)

关联选择

子节点和子孙节点

选取节点之后,如果想要获取它的直接子节点,可以调用contents属性

html="""
<html>
	<head>
		<title>The Dormous's story</title>
	</head>
	<body>
		<p class="story">
			Once upon a time there were three little sisters;and their names were
			<a herf="http://example.com/elsie" class="sister" id="link1">
				<span>Elsie</span>
			</a>
			<a herf="http://example.com/lacie" class="sister" id="link2">
				<span>Lacie</span>
			</a>
			and
			<a href="http://example.com/tillie" class="sister" id="link3">
				<span>Tillie</span>
			and they lived at the bottom of a well
			
print(soup.p.contents)

同样,我们可以调用child属性

print(soup.p.children)
for i,child in enumerate(soup.p.children):
	print(i,child)

要得到所有的子孙节点,则可以调用descendants属性

print(soup.p.descendants)
for i,child in enumerate(soup.p.descendants):
	print(i,child)

父节点和祖先节点

获取某个节点元素的父节点,可以调用parent属性

print(soup.p.parent)

获取某个节点的所有祖先节点,可以调用parents属性:

print(type(soup,a.parents))
print(list(enumerate(soup.a.parents)))

兄弟节点

next_sibling和previous_sibling分别用于获取节点的下一个和上一个兄弟节点。而next_siblings和previous_siblings分别返回后面和前面的所有兄弟节点

提取信息

print(type(soup.a.next_sibling))
print(soup.a.next_sibling)
print(soup.a.next_sibling.string)
print(type(soup.a.parent))
print(soup.a.parent)
print(list(soup.a.parent)[0])
print(list(soup.a.parent)[0].attrs['class'])

方法选择器

find_all

find_all查询所有符合条件的元素
API:

find_all(name,attrs,recursive,text,**kwargs)

name参数:

html='''
<div class="panel">
	<div class="panel-heading">
		<h4>Hello</h4>
	</div>
	<div class="panel-body">
		<ul class="list" id="list-1",name="elements">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
			<li class="element">Jar</li>
		</ul>
		<ul class="list list-small" id="list-2">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
		</ul>
	</div>
</div>
'''
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')))

返回的列表元素都是tag类型的,所以可以进行嵌套查询

for ul in soup,find_all(name='ul'):
	print(ul.find_all(name='li'))

attrs参数:

print(soup.find_all(attrs={'id'='list-1'}))
print(soup.find_all(attrs={'name'='elements'}))

对于一些常用的属性,例如id和class,我们可以不用attrs传递

print(soup.find_all('id'='list-1'))
print(soup.find_all('class_'='elements'))

python中class是一个关键字,所以需要在其后加一个_

text参数:
可以用来匹配节点的文本,其传入形式可以是字符串,也可以是正则表达式对象

import re
html='''
<div class="panel">
	<div class="panel-body">
		<a>Hello,this is a link</a>
		<a>Hello,this is a link,too</a>
	</div>
</div>
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.find_all(text=re.compile('link')))
find

find方法返回单个元素,而fand_all返回元素列表

html='''
<div class="panel">
	<div class="panel-heading">
		<h4>Hello</h4>
	</div>
	<div class="panel-body">
		<ul class="list" id="list-1",name="elements">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
			<li class="element">Jar</li>
		</ul>
		<ul class="list list-small" id="list-2">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
		</ul>
	</div>
</div>
'''
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'xlml')
print(soup.find('name'='ul'))
print(type(soup.find('name'='ul')))
print(soup.find('class_'='list'))

其他查询方法:

  • find_parents和find_parent:前者返回所有祖先节点,后者返回直接父节点
  • find_next_siblings和find_next_sibling:前者返回后面的所有兄弟节点,后者返回后面第一个兄弟节点
  • find_previous_siblings和find_previous_sibling:前者返回前面的所有兄弟节点,后者返回前面第一个兄弟节点
  • find_all_next和find_next:前者返回节点后所有符合条件的节点,后者返回后面第一个符合条件的节点
  • find_all_previous和find_previous:前者返回节点前所有符合条件的节点,后者返回前面第一个符合条件的节点

CSS选择器

使用CSS选择器只需要调用select方法,传入相应的CSS选择器即可

html='''
<div class="panel">
	<div class="panel-heading">
		<h4>Hello</h4>
	</div>
	<div class="panel-body">
		<ul class="list" id="list-1",name="elements">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
			<li class="element">Jar</li>
		</ul>
		<ul class="list list-small" id="list-2">
			<li class="element">Foo</li>
			<li class="element">Bar</li>
		</ul>
	</div>
</div>
'''
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
嵌套选择
for ul in soup.select('ul'):
	print(ul.select('li'))
获取属性
for ul in soup.select('ul'):
	print(ul['id'])
	print(ul.atrrs['id'])
获取文本
for li in soup.select('li')
	print(li.get_text())
	print(li.string)

pyquery

准备工作

pip3 install pyquery

初始化

用pyquery库解析HTML文本的时候需要将其初始化为一个PyQuery对象

字符串初始化

html='''
<div>
	<ul>
		<li class='item-0'>first item</li>
		<li class='item-1'><a href='link2.html'>second item</a></li>
		<li class='item-0 active'><a href='link3.html'><span class='bold'>third item</span></a></li>
		<li class='item-1 active'><a href='link4.html'>fouth item</a></li>
		<li class='item-0'><a href='link5.heml'>fifth item</a></li>
	</ul>
</div>
'''
from  pyquery import PyQuery as pq
doc=pq(html)
print(pq('li'))

URL初始化

from pyquery as pq
doc=pq(url='www.baidu.com')
print(doc('title'))

文件初始化

from pyquery as pq
doc=pq(filename='demo.html')
print(doc('li'))

基本CSS选择器

html='''
<div id='container'>
	<ul class='list'>
		<li class='item-0'>first item</li>
		<li class='item-1'><a href='link2.html'>second item</a></li>
		<li class='item-0 active'><a href='link3.html'><span class='bold'>third item</span></a></li>
		<li class='item-1 active'><a href='link4.html'>fouth item</a></li>
		<li class='item-0'><a href='link5.heml'>fifth item</a></li>
	</ul>
</div>
'''
print(doc('#container .list li'))
print(type(doc('#container .list li')))

#表示ID,.表示类

查找节点

子节点

查找节点时,需要用到find方法

from pyquery import PyQuery as pq
doc=pq(html)
items=doc('.list')
print(type(items))
print(items)
lis=items.find('li')
print(type(lis))
print(lis)

find方法的查找范围是节点的所有子孙节点。如果只想查找子节点,那么可以用children方法

lis=items.children()
print(type(lis))
print(lis)

还可以进行筛选

lis=items.children('.active')
print(lis)

父节点

我们可以用parent方法获取某个节点的父节点

items=doc('.list')
container=items.parent()
print(type(contaier))
print(container)

还可以使用parents方法查找祖先节点

items=doc('.list')
parents=items.parents()
print(type(parents))
print(parents)

也可以筛选

parent=items.parents('.warp')
print(parent)

兄弟节点

li=doc('.list .item-0 .active')
print(li.siblings())

也可以进行筛选

li=doc('.list .item-0 .active')
print(li.siblings('.active'))

遍历节点

遍历节点需要调用items方法

lis=doc('li').items()
print(type(lis))
for li in lis:
	print(li,type(li))

获取属性

a=doc('.item-0.active a')
print(a,type(a))
print(a.attr('href'))

也可以调用attrs属性

print(a,.attr.href)

其返回结果只有一个,故当结果包含多个节点时,需要使用遍历

a=doc('a')

for item in a.items():
print(item.attr('href'))

获取文本

a=doc('.itemm-0 active a')
print(a)
print(a.text())

要获取节点内部的HTML文本,需要用html方法:

li=doc('.iten-0 active')
print(li)
print(li.html())

而其中text方法不需要遍历 ,会对所有节点取文本之后合并成一个字符串

节点操作

可以使用pyquery库对节点进行动态修改

addClass和removeClass
li=doc('.item-0 active')
print(li)
li.removeClass('active')
print(li)
li.addClass('actibe')
print(li)
attr、text和html
li=doc('.item-0.active')
print(li)
li.attr('name','link')
print(li)
li.text('change item')
print(li)
li.html('<span>changed item</span>')
print(li)
remove
wrap=doc('.wrap')
print(wrap.text())
wrap.find('p').remove()
print(warp.text())

伪类选择器

li=doc('li:first-child')	#选择文本中第一个li节点
print(li)
li=doc('li:last-child')	#最后一个li节点
print(li)
li=doc('li:nth-child(2)')	#第二个li节点
print(li)
li=doc('li:gt(2)')	#第三个li之后的li节点
print(li)
li=doc('li:nth-child(2n)')	#偶数位置的li节点
print(li)
li=doc('li:contains(second)')	#包含second文本的li节点
print(li)

parsel

准备工作

pip3 install parsel

初始化

我们一般会用parsel库里的Selector类声明一个Selector对象

html='''
<div id='container'>
	<ul class='list'>
		<li class='item-0'>first item</li>
		<li class='item-1'><a href='link2.html'>second item</a></li>
		<li class='item-0 active'><a href='link3.html'><span class='bold'>third item</span></a></li>
		<li class='item-1 active'><a href='link4.html'>fouth item</a></li>
		<li class='item-0'><a href='link5.heml'>fifth item</a></li>
	</ul>
</div>
'''
from parsel import Selector
selector=Selector(text=html)

之后我们可以使用css和xpath方法分别传入CSS选择器和XPath进行内容提取

items=selector.css('.item-0')
print(len(items),type(items),items)
items2=selector.xpath('//li[contains(@class,"item-0")]')
print(len(items2),type(item2),items2)

提取文本

需要进行遍历

items=selector.csss('.item-0')
for item in items:
	text=item.xpath('.//text()').get()
	print(text)

提取属性

result=selector.css('.item-0.active a::attr(href)').get()
print(result)
result=selector.xpath('//li[contains(@class,"item-0")and contains(@class,"active")]/a/@href').get()

正则提取

result=selector.css('.item-0').re('link.*')
print(result)
result=selector.css('.item-0').re_first('<span class="bold">(.*?)</span>')	#调用了re_first方法选择第一个符合条件的内容
print(result)
;