数据解析

基础知识

数据爬取流程

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()
posted @ 2019-06-27 10:06  言值  阅读(400)  评论(0编辑  收藏  举报