数据解析
目录
基础知识
数据爬取流程
1. 指定url
2. 基于 requests 模块发起请求
3. 获取响应中的数据
4. 数据解析
5. 进行持久化存储
实现数据解析的三种方式
1. 正则解析
- re.I : 忽略大小写
- re.M : 多行匹配
- re.S : 单行匹配
- re.sub(正则表达式, 替换内容, 字符串)
2. bs4 解析
3. xpath 解析
数据解析原理
1. 实现标签定位
2. 将标签中存储的文本内容或着相关的属性值进行提取, 存储
bs4 数据解析
1. 环境安装
- pip install bs4
- pip install lxml
2. bs4 解析原理
- 实例化一个BeautifulSoup对象, 必须把即将被解析的页面源码加载到该对象中
- 调用该对象中相关的属性或者方法进行标签定位和内容的提取
3. 如何实现一个BerutifulSoup对象
- 本地加载
soup = BeautifulSoup('本地文件', 'lxml')
- 网络加载
soup = BeautifulSoup('字符串类型或字节类型', 'lxml')
相关的属性和方法
1. 定位标签
- soup.tagName # 只能找到第一个符合要求的标签, 返回的永远是一个单数,是源码
2. 获取属性
- soup.tagName.attrs # 获取标签所有的属性和属性值, 返回一个字典
- soup.tagName.attrs['href'] # 获取href属性
- soup.tagName['href'] # 简写形式
3. 获取文本
- soup.tagName.string # 获取标签中直系的文本内容, 返回字符串.
- soup.tagName.text # 获取标签下的所有的文本内容, 返回字符串.
- soup.tagName.get_text() # 获取标签下的所有的文本内容
【注意】如果标签还有标签,那么string获取到的结果为None,而其它两个,可以获取文本内容
4. find: 基于属性定位实现标签定位, 返回的永远是一个单数
- soup.find('a') # 找到第一个符合要求的标签
- soup.find('a', title='xxx')
- soup.find('a', class_='xxx') # python关键字要添加下划线
- soup.find('a', id='xxx')
5. find_all: 找到所有符合要求的标签, 返回的永远是一个列表
- soup.fing_all('a')
- soup.find_all(['a', 'b']) # 找到所有a和b标签
- soup.find('a', limit=2) # 限制前两个
6. select: 使用选择器定位标签, 返回的是列表(需要通过下标提取指定的对象)
- soup.select('#div')
- 常见的选择器:
- 标签选择器(a), 类选择器(.), id选择器(#)
- 层级选择器
- 单层级: div > ul > li
- 多层级: div li
xpath数据解析
1. 特点: 通用性比较强
2. 安装
pip install lxml
3. 解析原理
1) 实例化一个etree对象, 且将解析的页面源码加载到该对象中
2) 使用该对象中的xpath方法, 结合xpath表达式进行标签定位和数据解析提取
4. etree对象的实例化
- 本地加载
tree = etree.parse('filepath')
tree.xpath('xpath表达式')
- 网络加载
tree = etree.HTML(网页内容字符串)
tree.xpath('xpath表达式')
常用xpath表达式
1. 属性定位
//div[@class='song'] # 找到class属性值为song的div标签
2. 层级&索引定位 (索引从1开始)
# 找到class属性值为tang的div的直系子标签ul下的第二个子标签li下的直系子标签a
// div[@class='tang']/ul/li[2]/a
3. 逻辑运算
//a[@href='' and @class='du'] # 找到href属性值为空且class属性值为du的a标签
4. 模糊匹配
//div[contains(@class, "ng")]
//div[starts-with(@class, "ta")]
5. 获取文本
1) /text() 表示获取某标签下的直系文本内容
//div[@class='song']/p[1]/text()
2) //text() 表示获取某个标签的文本内容和所有子标签下的文本内容
//div[@class='tang']//text()
6. 获取属性值
//div[@class='tang']//li[2]/a/@href
注意:
/ -> 从根标签开始实现层级定位
// -> 丛任意位置实现标签的定位
基于标签的层级实现定位, 返回的永远是一个列表
xpath示例内容
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>测试bs4</title>
</head>
<body>
<div>
<p>百里守约</p>
</div>
<div class="song">
<p>李清照</p>
<p>王安石</p>
<p>苏轼</p>
<p>柳宗元</p>
<a href="http://www.song.com/" title="赵匡胤" target="_self">
<span>this is span</span>
宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱</a>
<a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
<img src="http://www.baidu.com/meinv.jpg" alt="" />
</div>
<div class="tang">
<ul>
<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村</a></li>
<li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山</a></li>
<li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君</a></li>
<li><a href="http://www.sina.com" class="du">杜甫</a></li>
<li><a href="http://www.dudu.com" class="du">杜牧</a></li>
<li><b>杜小月</b></li>
<li><i>度蜜月</i></li>
<li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘</a></li>
</ul>
</div>
</body>
</html>
处理编码的方式
response = requests.get(url=url, headers=headers)
1. response.encoding = 'utf-8'
2. text.encode('iso-8859-1').decode('gbk') # 通用性比较强
相关案例
import requests
from lxml import etree
url = 'http://pic.netbian.com/4kdongwu/'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]/ul/li')
for li in li_list:
img_name = li.xpath('./a/b/text()')[0]
img_name = img_name.encode('iso-8859-1').decode('gbk')
print(img_name)
处理频繁请求问题
- 问题:
对一个网站进行大量请求发送时, 会报出这样的错误:
HTTPConnectionPool(host:xx) Max retries exceeded with url.
- 原因:
1. 每次数据传输前客户端要和服务器建立TCP连接. 为节省传输消耗, 默认为keep-alive, 即连接一次, 传输多次. 然而如果连接迟迟不断开, 则连接池满后则无法产生新的连接对象, 导致请求无法发送.
2. ip被封
3. 请求频率太频繁
- 解决:
1. 求头中Connection的值改为close, 表示每次请求成功后断开连接
2. 更换请求ip
3. 每次请求之间使用sleep进行等待间隔
案例
urllib 模块爬取图片
from urllib import request
import requests
import os
import re
url = 'https://www.qiushibaike.com/pic/'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
# 创建一个文件夹来存储下载好的所有图片
if not os.path.exists("./qiutu"):
os.mkdir("./qiutu")
# 使用正则方式进行数据解析
ex = '<div class="thumb">.*?<img src="(.*?)" alt.*?</div>'
img_src = re.findall(ex, page_text, re.S)
for src in img_src:
src = "http:" + src
name = src.split('/')[-1]
img_path = './qiutu/' + name
request.urlretrieve(src, img_path)
print("下载完成")
print("下载数量: ", len(img_src))
利用 bs4 进行数据分析爬取小说
import requests
import os
from bs4 import BeautifulSoup
url = 'http://www.shicimingju.com/book/sanguoyanyi.html'
# http://www.shicimingju.com/book/sanguoyanyi/1.html
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
# 实例化一个BeautifulSoup对象
soup = BeautifulSoup(page_text, 'lxml')
# 解析数据, 获得标签, 为一个列表
li = soup.select(".book-mulu > ul > li")
f = open('./sanguo.txt', 'w', encoding='utf-8')
for ls in li:
# 获取解析数据中的URL地址
story_url = 'http://www.shicimingju.com/book/' + ls.a['href']
# 获取文本
title = ls.a.string
story_text = requests.get(url=story_url, headers=headers).text
# 再一次实例化BeautifulSoup对象, 与上一次解析内容不同, 对象也不同
story_soup = BeautifulSoup(story_text, 'lxml')
div_tag = story_soup.find('div', class_='chapter_content')
content=div_tag.text
f.write(title +':' +content + '\n')
f.close()
print('下载完成')
利用xpath解析爬取58同城二手房信息
import requests
from lxml import etree
url = 'https://bj.58.com/ershoufang/?PGTID=0d100000-0000-196c-4591-e33593cbb706&ClickID=4'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
# 将html文档转换成一个etree对象,调用对象中的方法查找指定的节点
tree = etree.HTML(page_text)
# 使用xpath对url_conten进行解析, 从网络上获取的数据, 为列表
ls = tree.xpath('//ul[@class="house-list-wrap"]/li')
all_data = []
for li in ls:
name = li.xpath('./div[2]/h2/a/text()')[0]
detail_url = li.xpath('./div[2]/h2/a/@href')[0]
if 'https:' not in detail_url:
detail_url = "https:" + detail_url
price = li.xpath('./div[3]//text()')
price = ''.join(price)
# 获取详情页的页码原数据 ,提取解析出房屋概况
detail_page_text = requests.get(url=detail_url, headers=headers).text
tree = etree.HTML(detail_page_text)
desc = tree.xpath('//div[@id="generalSituation"]//text()')
desc = "".join(desc)
dic = {
"name": name,
"price":price,
"desc": desc
}
all_data.append(dic)
print(all_data,len(all_data))
爬取全国城市
import requests
from lxml import etree
url = 'https://www.aqistudy.cn/historydata/'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
# 或的关系
all_city_name = tree.xpath("//div[@class='hot']//div[2]/ul/li/a/text() | //div[@class='all']/div[2]/ul/div[2]/li/a/text()")
print(all_city_name)
爬取梨视频, 正则解析链接地址
import requests
from lxml import etree
import re
url = 'https://www.pearvideo.com/category_3'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
ls = tree.xpath('//div[@class="category-top"]//li/div/a/@href')
all_url = []
for i in ls:
detail_url = "https://www.pearvideo.com/"+ i
all_url.append(detail_url)
ll = []
for l_url in all_url:
d_page_text = requests.get(url=l_url, headers=headers).text
mp_url = re.findall(r'srcUrl="(.*?)",vdoUrl', d_page_text, re.S)[0]
contents = requests.get(url=mp_url, headers=headers).content
mp_title = mp_url.split("/")[-1]
with open("./{}".format(mp_title), "wb") as f:
f.write(contents)
print(mp_title, 'ok')
print("完成")
爬取站长之家的免费简历
import requests
from lxml import etree
import os
url = "http://sc.chinaz.com/jianli/free.html"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"
}
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
ls = tree.xpath('//div[@id="main"]/div/div/a/@href')
if not os.path.exists("./muban"):
os.mkdir("./muban")
for l_url in ls:
detail_text = requests.get(url=l_url, headers=headers).text
detail_tree = etree.HTML(detail_text)
detail_ls = detail_tree.xpath('//div[@id="down"]/div[2]/ul/li[1]/a/@href')
if 'http://' in detail_ls[0]:
contents = requests.get(url=detail_ls[0], headers=headers).content
name = detail_ls[0].split("/")[-1]
print(name)
with open("./muban/{}.rar".format(name), "wb") as f:
f.write(contents)
f.close()