前言
正则、bs4和xpath爬取都是为了可以数据解析(数据解析指获取页面的局部资源或者指定的资源,例如说想要获取古诗文网的某篇文章的文本或者想获取微博的某张图片,这些相比于之前的获取的整个网站而言,只是局部的数据,这就是数据解析)
正则还未搞懂(弄懂之后在评论补充),所以这篇笔记主要围绕bs4和xpath这两种方式讨论
数据解析思路
1标签定位
2提取标签或标签中的属性中存储的数据值
标签定位
定义:标签(tagName)、属性(atrrName)、属性值(atrrValue)
以文件路径解释如何进行标签定位
例如下面的路径,获取第二个文件
在bs4中 >表示的是一个层级 空格表示多个
('.work>bin>t2')[1] 或者('.work t2')[1]
注:bs4索引从0开始
在xpath中 /表示一个层级 //表示多个层级
('/work/bin/t2[2]') 或者('/work//t2[2]')
注:xpath中索引从1开始
一、bs4爬取局部资源
1.环境安装
安装BeautifulSoup4、lxml
2.bs4数据解析的原理
1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
3.实例化BeautifulSoup对象
导入包--实例化(注意:BeautifulSoup(文件名,'lxml') 第二个参数是固定的)
如何实例化BeautifulSoup对象:
1导入包
from bs4 import BeautifulSoup
2对象的实例化:(两种)
1.将本地的html文档中数据加载到该对象中
打开文件 fp=open()
BeautifulSoup(文件名fp,'lxml')
2.将互联网上获取的页面源码数据加载到该对象中
page_text=response.text
soup=BeautifulSoup(page_text,'lxml')
4.BeautifulSoup提供的用于数据解析的方法和属性
soup.tagName:返回的是文档中第一次出现的tagName标签(tagName是标签名字)
soup.find():
1.find('tagName'):返回的是文档中第一次出现的tagName标签
2.属性定位:
soup.find('tagName',class_/id/attr='属性内容')
soup.find_all('tagName'):返回符合要求的所有标签 (返回的是一个列表)
soup.select():
select('某种选择器(id,class,标签...选择器)'),返回的是一个列表
层级选择器:
>表示的是一个层级 空格表示多个层级
print(soup.select('.search>form>input')[0])
print(soup.select('.search input')[0]) 空格跨了一个层级
#print(soup.select('.search>form>input')[0]) #返回了<div class="search">下的<form>下的<input>的第一个标签
获取标签中的文本数据:soup.text/string/get_text() (两个属性一个方法)
其中这三种方式的区别:
text/get_text():可以获取某一个标签中所有的文本内容
string:只可以获取该标签下面直系的文本内容
获取标签中属性值:
soup.a['属性名称']
代码举例(以古诗文网--诗文--忆江南词三首 页面为例)
from bs4 import BeautifulSoup
if __name__ == '__main__':
fp=open('./poem.html','r',encoding='utf-8')
soup=BeautifulSoup(fp,'lxml')
#print(soup) 输出soup对象(即页面源代码内容
#print(soup.a) >><a href="/">古诗文网</a> 输出html中第一个a标签
#print(soup.div)
#print(soup.find('a')) >><a href="/">古诗文网</a> 和soup.a一样 输出第一个a标签
#print(soup.find('div'))
#print(soup.find('div',class_='tool')) #文档里有多个div标签 可以通过find方法定位到class='tool' 这个属性
#因为class是python的关键字 所以需要加下划线_ 来区分关键字
#print(soup.find_all('a')) #返回符合要求的所有标签
#print(soup.select('.contyishang')) #使用类选择器或者id选择器或者标签选择器 定位到对应标签(返回的是列表)
#print(soup.select('.son1>a')[3]) #想要返回<div class="son1">下的第4个a标签的内容(从0开始)
#print(soup.select('.search>form>input')[0]) #返回了<div class="search">下的<form>下的<input>的第一个标签
#print(soup.select('.search input')[0]) #跟上面的结果一样 但是空格是跨了多个层级的(因为中间还有一级form)
#print(soup.select('.son1>a')[3].text) #>>作者 获取了对应的文本内容
#print(soup.select('.son1>a')[3].string)
#print(soup.select('.son1>a')[3].get_text())
#对比
#print(soup.find('div',class_='right').text) #获取了<div class="son1">下面所有的文本内容
#print(soup.find('div', class_='right').string) #>>None 因为不是直系的 文本的标签是a 不是div 所以输出None
#print(soup.select('.search input')[0]['autocomplete']) #获取标签中的属性内容
5.实例:bs4实现获取诗词网的三国演义中的章节标题和章节内容
'''
需求分析:爬取三国演义小说所有的章节标题和章节内容
网址:https://www.shicimingju.com/book/sanguoyanyi.html (古诗词名句网)
思路:先爬取三国演义页面的内容获取章节标题和章节内容对应的地址--再通过地址获取章节内容
'''
import requests
from bs4 import BeautifulSoup
if __name__ == '__main__':
url='https://www.shicimingju.com/book/sanguoyanyi.html'
headers={
'User-Agent':'x' #x是网页User-Agent
}
response=requests.get(url=url,headers=headers)
response.encoding='utf-8'
page_text=response.text
#在首页中 解析出章节的标题和详情页的url
#设置数据解析
#1.实例化BeautifulSoup 对象,需要将页面源码数据加载到该对象中
soup=BeautifulSoup(page_text,'lxml')
#解析章节标题和详情页的url
# <a href="/book/sanguoyanyi/1.html">第一回·宴桃园豪杰三结义 斩黄巾英雄首立功</a>
# 观察可以发现:章节标题的文本内容和详情页的url都在<li>标签中的a标签中 href是属性(详情页的url)
#如何批量获取?find_all批量获取/select可以写一个层级选择器
li_list=soup.select('.book-mulu>ul>li')#获取了列表 接下来想获取的是li标签中的a标签
fp=open('./sanguo.text','w',encoding='utf-8')
for li in li_list:#li 对应的是 li_list 中的每一个列表元素
title=li.a.string
#detail_url=li.a['href'] #获取标签的属性:标签['属性']
#但是因为这个url并不完整 所以需要补充
#方法:在元素中点击url观察网址:https://www.shicimingju.com/book/sanguoyanyi/1.html
#需要补充前缀https://www.shicimingju.com
detail_url = 'https://www.shicimingju.com'+li.a['href']
#接下来对详情页发起请求 解析出章节内容
detail_response=requests.get(url=detail_url,headers=headers)
detail_response.encoding='utf-8'
detail_page_text=detail_response.text #detail_page_text存储的是详情页的页面数据
#在页面数据中 定位章节内容(文本信息) 检查--元素--p标签中--其中所有p标签在div class=chapter_content 中
detail_soup=BeautifulSoup(detail_page_text,'lxml')
div_tag=detail_soup.find('div',class_='chapter_content') #定位的div的标签
#解析到了章节内容
content=div_tag.text
#先打开文件 然后每次以追加的形式写入文件中
fp.write(title+':'+content+'\n')
print(title,"爬取成功")
二、xpath爬取局部资源
1.环境安装
下载lxml
2.xpath数据解析的原理
1.实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中 2.调用etree对象中的xpath结合着xpath表达式实现标签定位和内容的捕获
3.实例化etree对象
1.从lxml包中导入一个类etree
from lxml import etree
2.对象的实例化
1.将本地的html文档中的源码数据加载到etree对象中: 新版不能直接调用etree.parse()
parser = etree.HTMLParser(encoding="utf-8")
tree = etree.parse('filePath', parser=parser) #filePath是本地文档的存放路径
2.可以将从互联网上获取的源码数据加载到该对象中
parser = etree.HTMLParser(encoding="utf-8")
tree = etree.HTML(page_text, parser=parser)
4.xpath表达式和获取文本及属性
xpath表达式
/:表示从根节点开始定位,表示的是一个层级
//:1表示多个层级(相当于bs4中的空格)2可以表示从任意位置开始定位
属性定位:(模板)//tagName[@attrName="attrvalue"] #tagName标签 attrName属性名称 attrvalue属性值
索引定位:(模板)//tagName[@attrName="attrvalue"]/tagName[n] (n是第几个索引 注意:索引从1开始)
如何取文本:
索引定位/text()[0] (text对应的一定是直系的 否则返回的是空!!!)
索引定位//text()[0] (text对应的不一定是直系的 获取标签中所有的文本内容)
如何取属性:
索引定位/@attrName (==a/href)
代码举例(以古诗文网--诗文--忆江南词三首 页面为例)
from lxml import etree
if __name__ == '__main__':
#实例化好了一个etree对象 且将被解析的源码加载到了该对象中
parser = etree.HTMLParser(encoding="utf-8")
tree= etree.parse('./poem.html', parser=parser) #加解析器(参数)
#在前面加 /表示从根节点或者根目录开始遍历 html表示从页面开始
#r=tree.xpath('/html/head/meta')
#print(r)
#print(tree.xpath('/html//meta') ) #与上面的一样
#print(tree.xpath('//meta')) #所有的meta元素
#print(tree.xpath('//div[@class="son1"]')) #返回div 中class='son1' 的 结果可能有多个(返回的是列表)
#print(tree.xpath('//div[@class="son1"]/a')) #<div class='son1'>下的所有a标签 (有七个)
#print(tree.xpath('//div[@class="son1"]/a[3]')) #获取<div class='son1'>下的第3个 a标签 注意:索引从1开始
#获取定位的文本数据
#print(tree.xpath('//div[@class="son1"]/a[4]/text()')) #>>['作者']
#print(tree.xpath('//div[@class="son1"]/a[4]/text()')[0]) #>>作者 0是索引
# [0] 取列表中的第零个元素 以字符串形式返回
#获取属性
#print(tree.xpath('//div[@class="son1"]/a[4]/@href')) #>>['https://so.gushiwen.cn/authors/']
5.实例1:获取58同城二手房的房源信息
'''
url='https://fs.58.com/ershoufang/'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0'
}
response=requests.get(url=url,headers=headers)
response.encoding='utf-8'
page_text=response.text
with open('./58.html','w',encoding='utf-8')as fp:
fp.write(page_text)
'''
from lxml import etree
if __name__ == '__main__':
#先获取页面源码数据
#实例化etree对象 把页面源码数据加载到etree对象中
parser=etree.HTMLParser(encoding='utf-8')
tree=etree.parse('./58.html',parser=parser)
#因为要批量获取 所以先观察 发现 一个房源信息对应一段 其中所有房源信息在一个标签中<section class='list' ...>
#<section 标签>下面包含 div标签
div_list=tree.xpath('//section[@class="list"]/div') #列表存储的是房源信息对应的一批div标签对象
fp=open('./58.txt','w',encoding='utf-8')
for div in div_list:
#在当前的div标签中继续定位找到标题对应的h3标签
#结构div--a--(第二个div下)--(第一个div下)--(第一个div下)--h3
title=div.xpath('./a/div[2]/div[1]/div[1]/h3/text()')[0] #./表示从div开始
print(title)
fp.write(title+'\n')
总结:
1.获取网站页面
2.把页面源代码加载到实例化的bs对象或者etree对象中
3.对想要获取的资源进行标签定位(找到所在的位置,用文件夹来比喻就是找到路径)
4.获取标签或者属性中存储的值
(以三国演义来说先获取章节标题(文本),章节内容需要获取属性中的存放章节内容的地址,再通过获取到的地址,获取内容所在页面的位置)