python爬虫知识----数据提取----Xpath和lxml、BeautifulScoup4、正则(二)

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())

猜你喜欢

转载自blog.csdn.net/qq_42231156/article/details/85486317
今日推荐