1. 什么是Xpath:是在xml和html文档中查找信息的一门语言,可对xml和html中的元素和属性进行遍历。
2.Xpath开发工具:chrome插件Xpath Helper。firefox插件Xpath Checker。在浏览中的拓展程序中下载,,用于直接在浏览中查找节点下的元素,验证代码xpath的准确性。
3. Xpath语法:
3.1 选取节点:
表达式 | 描述 | 实例 | 结果 |
nodename | 选取此节点的所有字节点 | div | 即选取div下的所有字节点 |
/ | 如果是在最前面,是根节点下的直接字元素,没有返回null,如果不是在最前面,则是选中某个节点下的直接子元素,没有返回null | /htm | 即根节点下的hmtl直接子元素 |
// | 从全局查找所有的的节点 | /div | 查看页面所有的div |
@ | 选某个节点的某个属性 | //img[@src] | 选取所有img节点中有src属性的img节点 |
3.2 谓语:
路径表达式 | 描述 |
/div/span[1] | 选取div下的第一个span元素,默认从1开始,不是从0开始。 |
/div/span[last()] | 选取div下的最后一个span元素。 |
/div/span[postion()<3] | 选取div下的前2个span元素。 |
//div[@id] | 从所有div中选取拥有id属性的div元素。 |
//div[@id=10] | 从所有div中选取拥有id属性等于10的div元素。 |
//div[contains(@class,'box')] | 从所有div中选取class中包含.box属性的div |
3.3 通配符:
通配符 | 描述 | 实例 | 结果 |
* | 匹配任意节点 | /body/* | 匹配body下所有子元素 |
@* | 匹配节点的任何属性 | //div[@*] | 匹配所有div中带任意属性的div。 |
3.4 运算符:
运算符 | 描述 | 实例 | 返回值 |
---|---|---|---|
| | 计算两个节点集 | //book | //cd | 返回所有拥有 book 和 cd 元素的节点集 |
+ | 加法 | 6 + 4 | 10 |
- | 减法 | 6 - 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 8 div 4 | 2 |
= | 等于 | price=9.80 | 如果 price 是 9.80,则返回 true。 如果 price 是 9.90,则返回 false。 |
!= | 不等于 | price!=9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 false。 |
< | 小于 | price<9.80 | 如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 false。 |
<= | 小于或等于 | price<=9.80 | 如果 price 是 9.00,则返回 true。 如果 price 是 9.90,则返回 false。 |
> | 大于 | price>9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.80,则返回 false。 |
>= | 大于或等于 | price>=9.80 | 如果 price 是 9.90,则返回 true。 如果 price 是 9.70,则返回 false。 |
or | 或 | price=9.80 or price=9.70 | 如果 price 是 9.80,则返回 true。 如果 price 是 9.50,则返回 false。 |
and | 与 | price>9.00 and price<9.90 | 如果 price 是 9.80,则返回 true。 如果 price 是 8.50,则返回 false。 |
mod | 计算除法的余数 | 5 mod 2 | 1 |
4. lxml库:是xml、html的解析器:主要是提取和解析xml、html数据。安装:pip install lxml。
#lxml的三种解析方式:
from lxml import etree
#1. 解析字符串
text="""
<div>字符串标签</div>
"""
def parse_text(): #解析字符串
htmlElement=etree.HTML(text) #将"""<div></div>"""解析为<html><body><div>字符串标签</div></body></html>标签对象,并添加html和body标签。
print(etree.tostring(htmlElement,encoding="utf-8").decode("utf-8")) #tostring是默认解析为bytes类型,需要先encoding编码为utf-8在解码为utf-8
#2. 默认xml解析器解析文件
def parse_file1(): #解析文件, 默认xml解析器解析文件
htmlElement=etree.parse("1.html") #将1.html文件解析为标签对象,但是不会添加html和body标签,etree.parse(),默认解析xml文件,对于不标准的html标签无法解析,会报错
print(etree.tostring(htmlElement,encoding="utf-8").decode("utf-8")) #tostring是默认解析为bytes类型,需要先encoding编码为utf-8在解码为utf-8
#3. html解析器解析文件
def parse_file2(): #解析文件,
parser=etree.HTMLParser(encoding="utf-8") #html解析器解析文件
htmlElement=parser("2.html",parser=parser) #将1.html文件解析为标签对象,但是不会添加html和body标签。参数parser默认解析xml文件,
#而etree.HTMLParser(encoding="utf-8")是解析,即如果解析文件中存在html中不标准的标签,xml解析器会报错,html解析器可解析出来,不会报错。
print(etree.tostring(htmlElement,encoding="utf-8").decode("utf-8")) #tostring是默认解析为bytes类型,需要先encoding编码为utf-8在解码为utf-8
if __name__=="__main__":
parse_text()
parse_file1()
parse_file2()
5. xpath和lxml结合使用。
#xpath和lxml结合使用
from lxml import etree
parser=etree.HTMLParser(encoding="utf-8") #用的是html解析器,而不是xml解析器,避免html一些标签会在xml解析器中报错。
html=etree.parse("1.html",parser=parser)
#1. 获取文件1.html里面的所有tr,xpath语法: //tr
trs1=html.xpath("//tr") #返回的是列表。
for tr in trs1:
print(etree.tostring(tr,encoding="utf-8").decode("utf-8")) #tr是bytes类型,需要先编码,在解码。
#2. 获取文本1.html里的第二个tr, xpath语法://tr[2]
tr=html.xpath("//tr[2]")[0] #返回的是列表。
print(etree.tostring(tr,encoding="utf-8").decode("utf-8")) #tr是bytes类型,需要先编码,在解码。
#3. 获取文本1.html里所有tr的class='box'的tr:xpath语法: //tr[@class='box']
trs2=html.xpath("//tr[@class='box']")
for tr in trs2:
print(etree.tostring(tr,encoding="utf-8").decode("utf-8")) #tr是bytes类型,需要先编码,在解码。
#4. 获取文本1.html里所有tr的class包含'box'的tr:xpath语法: //tr[contains(@class,'box')]
trs3=html.xpath("//tr[contains(@class,'box')]")
for tr in trs3:
print(etree.tostring(tr,encoding="utf-8").decode("utf-8")) #tr是bytes类型,需要先编码,在解码。
#5. 获取文本1.html里所有a标签的href属性的值(xpath语法://a/@herf),不是获取所有a标签中拥有href属性的a标签(xpath语法://a[@href])。
trs5=html.xpath("//a/@href")
for tr in trs5:
print(tr) #tr是a标签的href的值,是字符串类型,不需要解码。
#6. 获取文本1.html里所有tr里面的文本内容。
tr6=html.xpath("//tr[postion()>1]") #获取table表中除了表头第一行的所有tr标签信息。
poslists=[] #列表用于存放所有文本信息
for tr in trs6:
herf=tr.xpath(".//a/@herf")[0] #.//表示获取当前tr下所有a标签的herf的值组成的列表的第1个列表元素,如果是//a/@herf就会默认是从全局而不是从tr下查找a标签。
fullurl="www.baidu.com"+herf
title=tr.xpath(".//td[1]//text()")[0] #表示查找tr下第一个td下的所有子孙文本组成的列表中的第一个列表元素值
address=tr.xpath(".//td[2]/text()")[0] #表示查找tr下第二个td下的直接文本组成的列表中的第一个列表元素值
pos={
"title":title,
"herf":herf,
'fullurl':fullurl,
"address":address
}
poslists.append(pos)
print(poslists)
案例1:爬取电源网的即将上映的数据:
#python的requests的xpath和lmxl实现爬取豆瓣网的即将上映的影片信息
import requests
from lxml import etree
#1. 发送请求获取数据
url="https://movie.xxx.com/cinema/later/chongqing/"
headers={ #避免被反爬虫,需要headers中有user-agent和referer信息
"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Referer":"https://movie.douban.com/cinema/later/chongqing/"
}
res=requests.get(url,headers=headers)
text=res.text #返回的res.text默认解码处理的unicode类型,res.content是返回bytes类型。
print(res)
#2. 对数据信息的处理
html=etree.HTML(text)
divs=html.xpath("//div[@id='showing-soon']")[0] #查找到页面div的id='showing-soon'的所有div标签中的第一个。
movies=[]
divitem=divs.xpath("./div[@class='item']") #查找divs下直接子元素中class='item'的div
for item in divitem:
href=item.xpath("./a/@href")[0] #获取item标签下直接子元素a的href的值
imgsrc=item.xpath(".//img/@src")[0] #获取item标签下子孙元素img的src的值
title=item.xpath("./div[@class='intro']//a[1]/text()")
movie={
"href":herf,
"imgsrc":imgsrc,
"title":title
}
movies.append(movie)
print(movies)
拓展:python字符串的api:
str="Python字符串"
str.startswith("P") #判断str字符串是否以'P'开头。
str.replace("Python",'py').strip() #str字符串的替换replace(),以及去除前后空格strip()
for index,item in enumerate(list): #遍历列表以及其下标。
6. BeautifulSoup4库:和lxml一样,是hmtl/xml解析器。区别:lxml只会遍历局部,而beautifulSoup4是基于html dom的,会载入整个文档,解析整个DOM树,且beautifulSoup4用于解析html比较简单,API人性化,支持css选择器。
解析工具 | 解析速度 | 使用难度 |
beautifulSoup4 | 最慢 | 最简单 |
lxml | 快 | 简单 |
正则 | 最快 | 最难 |
6.1 安装:pip install bs4。
6.2 中文文档:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/。
6.3 使用:
from bs4 import BeautifulSoup
html="""
<div>字符串类型的标签</div>
"""
bs=BeautifulSoup(html,"lxml") #参数html是要解析的字符串标签。'lxml'是指定为lxml解析器,不写,默认的BeautifulSoup解析(即py标准解析器'html.parser')
解析器 | 使用方法 | 优势 | 劣势 |
python标准库 | BeautifulSoup(html,"hmtl.parser") | 速度适中,容错力强,py内置,不需安装 | py3.2版本以前容错差 |
lxml html | BeautifulSoup(html,"lxml") | 速度快,容错力强,推荐使用。 | 需要安装c语言库 |
lxm xml | BeautifulSoup(html,['lxml','xml']) | 速度快,唯一支持xml的解析器 | 需要安装c语言库 |
html5lib | BeautifulSoup(html," html5lib") | 最好的容错性,以浏览器方式解析 | 速度慢,不依赖外部拓展 |
注意:容错--指的是出现问题,自动修改的能力,容错力越强,越不容易报错。
6.4 bs.find()和bs.find_all()的使用:
#encoding:utf-8
from bs4 import BeautifulSoup
html="""
<table>
<tr>
<td>1</td>
</tr>
<tr class="tr_style">
<td>职位1</td>
<td><a href="www.baidu.com"></a></td>
</tr>
<tr>
<td>职位2</td>
<td><a class="test_a" id="test">15</a></td>
</tr>
</table>
"""
bs=BeautifulSoup(html,"lxml")
#1. 查找出bs中所有的tr标签,返回时tr标签组成的列表
trs1=bs.find("tr") #查找第一个满足条件的标签,就返回
trs1=bs.find_all("tr") #查找所有满足条件的标签,第一个是标签名,第二个参数是属性/个数筛选。
#for tr in trs1:
#print(type(tr)) #返回的是tag类型,不是str类型
#print(tr)
#2. 查找指定格个数(指定位置)的tr
trs2=bs.find_all("tr",limit=2) #limit参数表示从1开始最多获取多少个tr,返回组成的列表
tr=trs2[1] #表示从trs2中获取第二个tr标签
#3. 获取所有class='tr_style'的tr标签
trs3=bs.find_all("tr",attrs={'class':"tr_style"}) #attrs是属性选择器
#4. 查看id='test'且class="test_a"的a标签
alist=bs.find_all("a",id='test',class_='test_a') #返回列表,注意class是关键字,所有要用class_表示标签的class名。或用attrs={'id':"test","class":"test_a"}。
#5. 获取所有a标签的href值
alists=bs.find_all("a") #获取bs下所有的a标签
for a in alists:
href=a["href"] #遍历获取a的href值,或href=a.attrs['href']
#6. 获取所有文本的信息
trs6=bs.find_all("tr")[1:] #列表,筛选除了第一个外剩余的
for tr in trs6:
tds=tr.find_all("td")
text=tds[0].string #tds[0]是获取第1个td标签,.string是获取某个标签下的直接文本内容,但是注意,如果标签内的内容存在换行,则无法获取,可以通过tds[0].contents获取tds[0]表示下的直接所有子内容,返回包括换行符组成的列表。tds[0].children获取tds[0]表示下的直接所有子内容组成的迭代器,可遍历。
infos=list(tr.strings) #strings获取tr下所有子孙标签中所有的文本信息,返回生成器,包括空白字符和换行字符,通过list()转换为列表。
infos=list(tr.stripped_strings) #strings获取tr下子孙标签中所有的文本信息,返回生成器,不包括空白字符和换行字符,通过list()转换为列表。
infos=tr.get_text() #get_text获取tr下所有子孙标签中的所有文本信息,返回字符串,不是返回生成器。
6.5 bs.select()的使用:
#encoding:utf-8
#select()方法查找,即完全支持css选择器。
from bs4 import BeautifulSoup
html="""
<table>
<tr>
<td>1</td>
</tr>
<tr class="tr_style">
<td>职位1</td>
<td><a href="www.baidu.com"></a></td>
</tr>
<tr>
<td>职位2</td>
<td><a class="test_a" id="test">15</a></td>
</tr>
</table>
"""
bs=BeautifulSoup(html,'lxml')
#1. 通过标签名查找
trs1=bs.select("tr") #返回列表
#2. 通过类名查找
trs2=bs.select(".tr_style")
#3. 通过id查找
trs3=bs.select("#test")
#4. 组合选择器查找
trs4=bs.select("table .tr_style a")
#5. 通过属性查找
trs5=bs.select("a[href='www.baidu.com']")
print(trs5)
7. 正则:import re ,不需要下载,python库自带的。
7.1 正则API------------reg.match():查询字符串的开头字符是否满足正则条件,不满足会报错。
import re
text='hello'
#1. 匹配某个字符串
ret=re.match('he',text) #从text字符串中从头开始匹配出'he'字符,返回<re.Match object; span=(0, 2), match='he'>,或者返回None.
#print(ret.group()) #group()返回到匹配到的字符串'he'
#2. 匹配任意字符: '.',但是不能匹配换行'\n'
ret=re.match('.',text) #从text字符串中从头开始匹配出任意第一个字符,
#print(ret.group()) #group()返回到匹配到的字符串'h'
#3. 匹配任意数字(0-9): \d
text='102aasd'
ret=re.match('\d',text)
#print(ret.group())
#4. 匹配任意的非数字: \D
text='102aasd'
ret=re.match('\D',text)
#print(ret.group())
#5. 匹配空白字符 (\n,\t,\r,空格 ):\s
text=' '
ret=re.match('\s',text)
#print(ret.group())
#6. 匹配 [a-zA-z0-9_]:\w
text='adssa '
ret=re.match('\w',text)
#print(ret.group())
#7. 匹配除了[^a-zA-z0-9_]之外其他的:\W ,注意:^表示非。
text='adssa '
ret=re.match('\W',text)
#print(ret.group())
#8. 组合方式的匹配,满足[]中一项即可:[],注意[]匹配一个字符。
text='adssa '
ret=re.match('[a1]',text) #匹配text以a或1开头的字符。
#print(ret.group())
#9. 匹配0或多个字符:*
text='adssa '
ret=re.match('\d*',text) #匹配0或多个数字字符,没有匹配到返回空字符。
#print(ret.group())
#10. 匹配至少1个或多个字符:+
text='adssa '
ret=re.match('\d+',text)
#print(ret.group())
#11. 匹配0个或至多1个字符:?
text='adssa '
ret=re.match('\d?',text)
#print(ret.group())
#12. 匹配m个字符:{m}
text='adssa '
ret=re.match('\d{2}',text)
#print(ret.group())
#13. 匹配m~n个字符:{m,n}
text='adssa '
ret=re.match('\d{2,4}',text)
#print(ret.group())
#14. 验证url:
text='https://www.cnblogs.com/jianzhenghui/p/9974884.html '
ret=re.match('(http|https|ftp)://[^\s]+',text)
#print(ret.group())
#15.匹配以某个字符开始:^
text='https '
ret=re.match('^h',text) #匹配以'h'开始的字符,而match也是只匹配text开始位置。
ret=re.search('h',text) #从整个text中去匹配'h'字符
ret=re.search('^h',text) #从整个text中去匹配以'h'字符开始的字符
#print(ret.group())
#16.匹配以某个字符结束:$
text='[email protected]'
ret=re.match('\[email protected]$',text) #从整个text中去匹配以'h'字符开始的字符
#print(ret.group())
#17. 贪婪模式与非贪婪模式:
text="<h1>标题</h1>"
ret=re.match('<.+>',text) #贪婪模式:尽可能多的匹配,返回'<h1>标题</h1>'
ret=re.match('<.+?>',text) #非贪婪模式:尽可能少的匹配,返回'<h1>'
#print(ret.group())
#18. 匹配0-100: 不允许出现09这样
text="1"
ret=re.match('[1-9]\d?$|100$',text) #贪婪模式:尽可能多的匹配,返回'<h1>标题</h1>'
print(ret.group())
#19. 正则转义字符,将某些特殊的符号转义为普通的字符:\
text="hello world $1991"
ret=re.search("\$\d+",text) #转义$符号,返回 $1991,注意,'\n'匹配时,需要转义'\\\\n'
print(ret.group())
#20. 原生转义字符,将字符串全部识别为普通字符串,不识别为特殊字符:r''
text="hello world $1991"
ret=re.search(r("$\d+"),text)
print(ret.group())
7.2 正则API------------reg.group():分组
#. 分组,即正则中一个圆括号算是一个分组,最外层默认算是一个圆括号分组:()
text="apple price is $99,orange price is $19"
ret=re.search(r(".*(\d+).*(\d+)"),text) #一共有一个最外层分组'apple price is $99,orange price is $19'和2个子分组'$99'、'$19'
print(ret.group()) #相当于ret.group(0),返回最外层的一个分组,'apple price is $99,orange price is $19'。
print(ret.group(1)) #第二个分组 ,返回'$99'
print(ret.group(2)) #第三个分组 ,返回'$19'
print(ret.group(1)) #第二个,第三个分组 ,返回('$99','$19')
print(ret.groups()) #返回除了最外层分组外,所有的子分组('$99','$19')
7.3 正则API------------reg.search():查询整个字符串中满足条件的字符。
7.4 正则API------------reg.findall():查询整个字符串中所有满足条件的字符,返回列表。
7.5 正则API------------reg.sub(re,str,text,count):将text字符串中的str字符替换为re字符,从头开始替换count个,不写,默认替换全部。
7.6 正则API------------reg.compile():将对于常用的正则表达式,可以通过compile缓存起来,下次直接使用,提高效率。
#如:匹配23.05这个数字
text="apple is 23.05"
r=re.compile('\d\.+\d?')
ret=re.search(r,text)
print(ret.group())