xpath 和 css selector 方式的内容提取介绍
在目标网页中,找到相应的元素,右键检查元素,看到元素的代码信息,找到自己需要的,进行右击,这时候有两种方式可以获得标签的位置的具体描述方式:
- 使用 copy selector
- 使用 copy XPath
图示:
这两种复制的路径有什么区别(以tr为例)?
copy XPath复制出来的路径:
/html/body/section/section/section/article/table[1]/tbody/tr[1]
copy selector复制出来的:
body > section > section > section > article >
table.table.table-striped.table-top20 > tbody > tr:nth-child(1)
这两种不同的路径描述方式,使用copy selector复制出来的路径叫做 CSS Selector,使用copy XPath复制出来的叫做XPath。
总之:
- XPath的路径是按照:谁,在哪,第几个的选择方式
- CSS Selector是按照:谁,在哪,第几个,长啥样来选择
XPath (XML Path Language) 是一门在 XML 文档中查找信息的语言(并不是解析器!是一种语言格式),可用来在 XML 文档中对元素和属性进行遍历。
一般xpath要配合lxml解析器来使用。
XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
*表示通配符。匹配任意节点
@*匹配节点中的任何属性
使用“ | ”运算符,选取若干个路径。
注意:
/ 和 // 的区别: / 代表只获取直接子节点; //获取子孙节点。
一般// 用的比较多。
./表示当前节点下,//表示全网页。
XPath语法——选取节点:
路径表达式类似这种:
/html/body/div[4]/div[2]/div/div[2]/div[14]/a
或者
/html/body/div[@class=”content”] ,
其中第一个路径叫绝对路径,其中每个‘/’就是一个节点。
第二个路径中的[@class=”content”] 是为了在多个相同标签中定位到一个标签。
使用方法:
f12审查元素进去,然后直接在你需要爬的网页上面,找到你想要提取的对应地点,然后点击copy,copy xpath。
复制出来的xpath语言是这样的:
//*[@id="app"]/div/div/div/dl/dd[1]/div/div/div[1]/p[2]
那么怎么知道复制出来的xpath正不正确呢?
推荐两个xpath的插件。
在Chrome或Firefox浏览器中,有这样两个关于xpath的插件。可以快速找到对应网页的xpath路径。
- Chrome插件 XPath Helper
- Firefox插件 XPath Checker
插件使用效果:
当然你也可以直接在这个地方输入,ctrl+F,然后输入:
最后要注意的就是:使用.xpath() 的时候,其返回值是一个列表list,取值时需要注意。
BeautifulSoup解析网页后,开始提取需要的内容,常用:Css Selector的方式。
BeautifulSoup本身不支持XPath表达式,可以用Css Selector的方式。
BeautifulSoup库有三种方法来查找元素。
- findall() 查找所有节点
- find() 查找单个
- selsect() 根据css的选择器Selector来查找
现在推荐的,就是第三种方式,根据css的选择器Selector来查找。
#app > div > div.tags-panel > ul > li:nth-child(1) > ul > li:nth-child(2)
同样也可以在浏览器里面验证你的CSS Selector路径是否正确?
ctrl+F 然后搜索自动匹配一下,找对了就会高亮。
BeautifulSoup结合Css Selector的爬虫提取例子:
import requests
from bs4 import BeautifulSoup
url = 'https://maoyan.com/films'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser') #也可用lxml
# 获取文本,由于select()方法获得是list类型,必须要先获取到确定的元素,才能确定文本内容
a1 = soup.select('body > div.header > div > div.city-container > div.city-selected')[0].get_text()
print('Selector方式获取文本内容:',a1)
a2 = soup.select('.city-name')[0].get_text() # 按照标签名,id名,类名查找
print('按标签名,id名,类名查询:',a2)
a3 = soup.select('div .js-geo-city') # 组合查找名为jsxxx的div。用空格分开
print('组合查找:',a3)
# 查img的src,a标签中的href属性
a4 = soup.select('div.movie-item > a > div > img:nth-of-type(2)')[0]['data-src']
print('找img的src属性:',a4)
# 重复li取其中一项:
a5 = soup.select('div.channel-detail.channel-detail-orange')[0].get_text()
print('重复li取其中一项:',a5)
# 批量查询,比如table中多组td,div中多组li
a6 = soup.select('body > div.header > div > div.nav > ul > li')
for li in a6:
# print(li)
a = li.find('a')['href']
# print(a)
wz = li.text
print('批量查找:',wz)
# 与find方法对比-多但是copy快。
a3111 = soup.find('div', class_='city-name').text
print('与find方法对比:',a3111)
else:
print('error', response.status_code)
常用的就是如下的了:
- 获取某个节点的文字:比如p标签外面的文字,标题
- 获取某个节点的属性值:比如a标签的href,img的src。
- 获取某个指定名称的点的文本内容或者属性:比如某个class='city-name’的div的内容。
- 批量获取列表型内容,比如table的多个td,div的多个li。
除了BeautifulSoup解析器以外,还有 lxml 也是python的一个解析库,而且解析效率非常高,支持HTML或XML的解析,支持使用Xpath1.0语法来解析代码。
主要的功能是如何解析和提取 HTML/XML 的数据。
注意:lxml和上面说的两种,xpath和css selector不是同一个类别。这是和BeautifulSoup一个级别的解析器。
使用 pip 安装:pip install lxml
代码使用的时候,主要是lxml的etree库来解析:
from lxml import etree
利用lxml来 解析HTML代码,在解析HTML代码的时候,如果HTML代码不规范,他会自动的进行补全。
比如:
# 先安装lxml: pip install lxml
from lxml import etree # 用lxml来解析HTML代码-自动补全
text='''
<div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>....
'''
# 利用 etree.HTML 把字符串解析成 HTML 文件
html = etree.HTML(text)
# 按字符串序列化HTML文档
result = etree.tostring(html).decode()
print(result )
运行结果:
输出后,会补全了li标签,还添加了body,html标签。
因为lxml支持xpath,而且lxml快,xpath获得也非常方便,所以,我们
一般采用 “lxml+xpath” 的方式 来快速的定位特定元素以及节点信息。
( lxml+Selector也可以,但是推荐用xpath)
使用xpath语法。应该使用Element.xpath方法。来执行xpath的选择。
示例代码如下:
trs = html.xpath("//tr[position()>1]")
xpath函数 返回来的永远是一个列表。
获取某个标签的属性:
href = html.xpath("//a/@href") # 获取a标签的href值
获取文本,是通过xpath中的text()函数。
示例代码如下:
address = tr.xpath("./td[4]/text()")[0]
在某个标签下,再执行xpath函数,获取这个标签下的子孙元素,那么应该在斜杠之前加一个点,代表是在当前元素下获取。
示例代码如下:
address = tr.xpath("./td[4]/text()")[0]
xpath解析器下元素下标从1开始。
1.修正HTML代码,补齐标签
from lxml import etree
html=etree.HTML(text) #Lxml库解析数据,为Element对象
result=etree.tostring(html) #此时可以自动修正HTML代码,补齐标签等等
2.读取本地的HTML文件
读取本地的HTML文件使用parse()方法
from lxml import etree
html=etree.parse(r"文件路径")
result=etree.tostring(html,pretty_print=True)
3.解析在线请求回来的HTML源码
使用requests获取HTML源码后,
html=etree.HTML(res.text)
result = etree.tostring(html).decode() #实现对其的解析
再次提醒:使用.xpath() 的时候,其返回值是一个列表list
# xpath函数返回的是一个列表
str ="""
<div class="wrapper">
<i class="iconfont icon-back" id="back">马麻花</i>
<a href="/" id="channel">新浪社会</a>
<ul id="nav">
<li><a href="http://domestic.firefox.sina.com/" title="国内">国内</a></li>
<li><a href="http://world.firefox.sina.com/" title="国际">国际</a></li>
<li><a href="http://mil.firefox.sina.com/" title="军事">军事</a></li>
<li><a href="http://photo.firefox.sina.com/" title="图片">图片</a></li>
<li><a href="http://society.firefox.sina.com/" title="社会">社会</a></li>
<li><a href="http://ent.firefox.sina.com/" title="娱乐">娱乐</a></li>
<li><a href="http://tech.firefox.sina.com/" title="科技">科技</a></li>
<li><a href="http://sports.firefox.sina.com/" title="体育">体育</a></li>
<li><a href="http://finance.firefox.sina.com/" title="财经">财经</a></li>
<li><a href="http://auto.firefox.sina.com/" title="汽车">汽车</a></li>
</ul>
<i class="iconfont icon-liebiao" id="menu">张三丰</i>
</div>
"""
from lxml import etree
html=etree.HTML(str) #创建一个html对象
result1=html.xpath("//a/text()") #获取所有a标签下的所有内容
print(result1)
result2=html.xpath("//a/@href") #获取所有a标签下的所有内容
print(result2)
result3=html.xpath("//li/a/text()") #获取标签下的内容
print(result3)
result4=html.xpath('//a[@id="channel"]/text()') #获取带属性的标签的内容
print(result4)
result5=html.xpath("//li[position()<4]/a/text()") #选前三项
print(result5)
result6=html.xpath("//li[1]/a/text()") #第一个
print(result6)
result7=html.xpath("//li[last()]/a/text()") #最后一个last()-1倒数第二项
print(result7)
result8=html.xpath("//i[contains(@class,'iconfont')]/text()") #class类中包含iconfont的取出来
print(result8)
效果:
补充几种常见情况:
遇到相同的字符开头的多个标签?
<li class="tag-1">需要的内容1</li>
<li class="tag-2">需要的内容2</li>
<li class="tag-3">需要的内容3</li>
想同时爬取时,不需要构造多个Xpath路径,通过 starts-with()便可以获取多个标签内容。
starts-with()如下:
contents = selector.xpath('//li[starts-with(@class,"tag")]/text() ')
当遇到标签套标签情况时,想同时获取文本内容:
<div class="red">需要的内容1
<h1>需要的内容2</h1>
</div>>
可以使用string(.)方法如下:
from lxml import etree
html2 = '''
<div class="red">需要的内容1
<h1>需要的内容2</h1>
</div>>
'''
selector = etree.HTML(html2)
content1 = selector.xpath('//div[@class="red"]')[0]
content2 = content1.xpath('string(.)')
print(content2)
string(.)方法可用于标签套标签情况。
- xpath 和 css selector 是同一类,都是用来定位元素的两种不同的路径。其中xpath比css selector更高效一些。BeautifulSoup和Lxml是同一类,都是解析网页的解析器。
- 对于网页解析器BeautifulSoup,它本身不支持XPath表达式,所以可以用Css Selector的方式。
- 对于网页解析器Lxml,它支持xpath表达式,也支持Css Selector的方式,推荐使用xpath的方式。
最后说两句:
lxml只支持xpath1.0,不支持xpath2.0,Xpath2.0支持正则匹配,而1.0不支持,但是目前很多功能用不到。(但是,我还真没找到关于XPATH 2.0的教程或项目实例。截止2019/11月。现在市面上的网页基本上都还是xpath1的。)
其他参考网站:
https://www.cnblogs.com/jjb1997/p/11234638.html
https://www.jianshu.com/p/1660003f40c8
https://www.cnblogs.com/yiyea/p/11446981.html
https://www.cnblogs.com/wsg-python/articles/10182177.html
https://www.cnblogs.com/liudi2017/articles/9158390.html
https://www.cnblogs.com/zhangxinqi/p/9210211.html#_label16