04-数据存储
数据存储
学习目标
- 掌握文本数据存储方式
- 掌握 json数据存储方式
- 熟悉表格数据存储
- 掌握mysql存储
- 掌握mongodb的储存
- 熟悉redis集合的使用
- 掌握数据去重方法
学习完如何对数据进行提取之后接下来就进入到我们数据保存的环节了,数据的存储多种多样,可以把存储大致分为文本数据存储和数据库数据存储,其中文本数据存储需要掌握TXT数据、json数据、csv表格数据;数据库数据储存到MySQL和MongoDB
一、文本文件数据存储
1.文本存储
文件打开模式
python中所有open()打开一个文件,文件的打开有很多模式:
- r:以只读方式打开文件,文件的指针将会放在文件的开头,这是默认模式。
- rb:以二进制只读方式打开一个文件,文件指针将会放在文件的开头。
- r+:以读写方式打开一个文件,文件指针将会放在文件的开头。
- rb+: 以二进制读写方式打开一个文件,文件指针将会放在文件的开头。
- w:以写入方式打开一个文件。如果该文件已存在,则将其瞿盖;如果该文件不存在,则创建新文件。
- wb:以二进制写入方式打开一个文件。如果该文件已存在,则将其覆盖;如果该文件不存在,则创建新文件。
- w+:以读写方式打开一个文件。如果该文件已存在,则将其覆盖;如果该文件不存在,则创建新文件。
- wb+:以二进制读写格式打开一个文件。如果该文件已存在,则将其覆盖;如果该文件不存在, 则创建新文件。
- a:以追加方式打开一个文件。如果该文件已存在,文件指针将会放在文件结尾。也就是说,新的内容将会被写入到已有内容之后;如果该文件不存在, 则创建新文件来写入。
- ab:以二进制追加方式打开一个文件。如果该文件已存在,则文件指针将会放在文件结尾。也就是说,新的内容将会被写入到己有内容之后;如果该文件不存在,则创建新文件来写入。
- a+:以读写方式打开一个文件。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式;如果眩文件不存在,则创建新文件来读写。
- ab+:以二进制追加方式打开一个文件。如果该文件已存在,则文件指针将会放在文件结尾;如果该文件不存在,则创建新文件用于读写。
2.以TXT文本形式存储
TXT 文本的操作非常简单,且其几乎兼容任何平台,但是它有个缺点,那就是不利于检索。
案列:
获取到知乎发现里的问题
网址:https://www.zhihu.com/explore
import requests
from bs4 import BeautifulSoup
url = 'https://www.zhihu.com/explore'
headers = {
'cookie': '',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS 10114) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
}
html = requests.get(url, headers=headers).text
soup = BeautifulSoup(html, 'lxml')
title_list = soup.select('div .css-1g4zjtl a')
# print(title_list)
for title in title_list:
print(title.get_text())
with open('data.txt', 'a', encoding='utf-8')as f:
f.write(title.get_text() + '\n')
二、json文件存储
JSON,全称为 JavaScript Object Notation, 也就是 JavaScript 对象标记,它通过对象和数组的组合来表示数据,构造简洁但是结构化程度非常高,是一种轻量级的数据交换格式。本节中,我们就来了解如何利用 Python 保存数据到 JSON 文件。
1.对象和数组
在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等,但是对象和数组是比较特殊且常用的两种类型,下面简要介绍一下它们。
- 对象:它在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2,…} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。
- 数组:数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 [“java”, “javascript”, “vb”, …] 的索引结构。在JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引用得多。同样,值的类型可以是任意类型。
所以,一个 JSON 对象可以写为如下形式:
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}, {
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
由中括号包围的就相当于列表类型,列表中的每个元素可以是任意类型,这个示例中它是字典类型,由大括号包围。
JSON 可以由以上两种形式自由组合而成,可以无限次嵌套,结构清晰,是数据交换的极佳方式。
2.json模块方法
方法 | 作用 |
---|---|
json.dumps() | 把python对象转换成json对象的一个过程,生成的是字符串。 |
json.dump() | 用于将dict类型的数据转成str,并写入到json文件中 |
json.loads() | 将json字符串解码成python对象 |
json.load() | 用于从json文件中读取数据。 |
3.json模块实际操作
案列:
获取到4399最新小游戏
网址:https://www.4399.com/flash/
import json
import requests
from lxml import etree
url = 'https://www.4399.com/flash/'
response = requests.get(url)
html = etree.HTML(response.content.decode('gbk'))
a_list = html.xpath('//ul[@class="n-game cf"]/li/a')
data_list = []
for a in a_list:
item = {}
item['href'] = a.xpath('./@href')[0]
item['title'] = a.xpath('./b/text()')[0]
data_list.append(item)
with open('data.json', 'w', encoding='utf-8')as file:
file.write(json.dumps(data_list))
可以看到,中文字符都变成了 Unicode 字符,这并不是我们想要的结果。
为了输出中文,还需要指定参数 ensure_ascii 为 False,另外还要规定文件输出的编码:
file.write(json.dumps(data_list, indent=2, ensure_ascii=False))
三、表格文件数据存储
CSV,全称为 Comma-Separated Values,中文可以叫作逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分隔。每条记录由字段组成,字段间的分隔符是其他字符或字符串,最常见的是逗号或制表符。不过所有记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。它比 Excel 文件更加简洁,XLS 文本是电子表格,它包含了文本、数值、公式和格式等内容,而 CSV 中不包含这些内容,就是特定字符分隔的纯文本,结构简单清晰。所以,有时候用 CSV 来保存数据是比较方便的。本节中,我们来讲解Python 读取和写入 CSV 文件的过程。
1.写入
这里先看一个最简单的例子:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
首先,打开 data.csv 文件,然后指定打开的模式为 w(即写入),获得文件句柄,随后调用 csv 库的writer 方法初始化写入对象,传入该句柄,然后调用 writerow 方法传入每行的数据即可完成写入。
如果想修改列与列之间的分隔符,可以传入 delimiter 参数,其代码如下:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile, delimiter=' ')
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
2.多行写入
调用 writerows 方法同时写入多行,此时参数就需要为二维列表,例如:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerows([['10001', 'Mike', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]])
3.字典写入
用字典来表示。在 csv 库中也提供了字典的写入方式,示例如下:
import csv
with open('data.csv', 'w') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'id': '10001', 'name': 'Mike', 'age': 20})
writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22})
writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21})
4.爬虫采集入库
案列:
采集B站上视频数据信息
网址:http://changs.ccgp-hunan.gov.cn/gp/noticeSerach.html?articleType=2&basicArea=gaoxin
import csv
import requests
with open('湘江.csv', 'a', encoding='utf-8', newline='')as f:
fieldnames = ['author', 'basicTitle', 'basicDatetime']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for i in range(1, 3):
url = 'http://changs.ccgp-hunan.gov.cn/gp/gpcategory/getNotice'
headers = {
# 'Host':'changs.ccgp-hunan.gov.cn',
# 'Cookie':'JSESSIONID=45823044-a192-4910-a217-62f1e148c34c',
# 'origin': 'http://changs.ccgp-hunan.gov.cn',
# 'Referer':'http://changs.ccgp-hunan.gov.cn/gp/noticeSerach.html?articleType=2&basicArea=gaoxin',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
data = {
"page": str(i),
"limit": "10",
"sidx": "",
"order": "",
"categoryId": "184",
"articleType": "2",
"projid": "",
"name": "",
"cgType": "0",
"buyerNm": "",
"buyerOrgNm": "",
"supplyNm": "",
"basicType": "1",
"basicDatetime": "2024-01-01",
"basicDatetimes": "2024-07-04",
"type": "0",
"basicArea": "gaoxin",
"categoryType": "0"
}
response = requests.post(url, headers=headers, data=data)
print(response.text)
# print(response.json())
for res in response.json()['list']['list']:
item = {}
item['author'] = res['author']
item['basicTitle'] = res['basicTitle']
item['basicDatetime'] = res['basicDatetime']
writer.writerow(item)
四、MySQL存储
关系型数据库是基于关系模型的数据库,而关系模型是通过二维表来保存的,所以它的存储方式就是行
列组成的表,每一列是一个字段,每一行是一条记录。表可以看作某个实体的集合,而实体之间存在联
系,这就需要表与表之间的关联关系来体现,如主键外键的关联关系。多个表组成一个数据库,也就是
关系型数据库。
关系型数据库有多种,如 SQLite、MySQL、Oracle、SQL Server、DB2 等。
1.准备工作
在开始之前,请确保已经安装好了 MySQL 数据库并保证它能正常运行,而且需要安装好 Py MySQL
库。如果没有安装找班主任老师拿包
下载地址;https://dev.mysql.com/downloads/mysql/
安装包:https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.9-winx64.zip
参考地址:https://blog.csdn.net/ychgyyn/article/details/84404217
2.连接数据库
这里,首先尝试连接一下数据库。假设当前的 MySQL 运行在本地,用户名为 root,密码为 root,运行端口为 3306。这里利用 PyMySQL 先连接 MySQL,然后创建一个新的数据库,名字叫作 spiders,
代码如下:
import pymysql
def db_connect():
# 打开数据库连接
db = pymysql.connect(host="localhost", user="root", password="root")
# 使用cursor()方法创建一个游标对象cursor
cursor = db.cursor()
# 使用execute()方法执行SQL查询
cursor.execute("SELECT VERSION()")
# 使用 fetchone() 方法获取单条数据
data = cursor.fetchone()
# data返回值为一个元组
print(data)
# 创建数据库
cursor.execute("CREATE DATABASE spiders DEFAULT CHARACTER SET utf8")
# 关闭数据库连接
db.close()
def main():
db_connect()
if __name__ == "__main__":
main()
3.创建数据表
import pymysql
def create_table():
db = pymysql.connect(host="localhost", user="root", password="root", db="spiders")
# 使用 cursor() 方法创建一个游标对象cursor
cursor = db.cursor()
# 使用预处理语句创建表
sql = '''
CREATE TABLE IF NOT EXISTS students (
id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
age INT NOT NULL,
PRIMARY KEY (id))
'''
try:
cursor.execute(sql)
print("CREATE TABLE SUCCESS.")
except Exception as ex:
print(f"CREATE TABLE FAILED,CASE:{ex}")
finally:
# 关闭数据库连接
db.close()
def main():
create_table()
if __name__ == "__main__":
main()
4.插入数据
import pymysql
id = '00001'
user = '柏汌'
age = 18
def insert_record():
db = pymysql.connect(host='localhost', user='root', password='root', db='spiders')
# 获取游标
cursor = db.cursor()
# SQL 插入语句
sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'
# 执行 SQL 语句
try:
cursor.execute(sql, (id, user, age))
# 提交到数据库执行
db.commit()
print('数据插入成功...')
except Exception as e:
print(f'数据插入失败: {e}')
# 如果发生错误就回滚
db.rollback()
finally:
# 关闭数据库连接
db.close()
if __name__ == '__main__':
insert_record()
5.爬虫数据采集入库
要求:获取到阿里招聘的数据信息
网址:https://talent-holding.alibaba.com/off-campus/position-list
import requests
import pymysql
class Baidu(object):
def __init__(self):
self.db = pymysql.connect(host="localhost", user="root", password="root", db="spiders16")
self.cursor = self.db.cursor()
self.url = 'https://talent-holding.alibaba.com/position/search'
self.headers = {
'cookie': 'cna=9EQNH8yVwF4CAa8A40LsVZX8; xlly_s=1; XSRF-TOKEN=ce3a8590-8d85-4e2c-9751-4aedd37b3750; prefered-lang=zh; SESSION=RTFENkJFRDhDODRCNEE3RjVGNzZEQUVEN0RBMkYxRDc=; tfstk=fN_-ArtXI-2ktmqhP_ZctWgZQFVceaCzhT5s-pvoAtBA_9Lhq6vhkXBfhwfltajvJtW8T7rU4ipdhtpWEWPFkpBhHpmkEU1Cp66--_YltZFdE6uhp6PyOp6CppjumPfPae8QIpUgS_P6cbC5p3TBcq129QiWNY47se8QI-mmR3z98OjtUurRGSdHtQiCd3gXGKRwde9WAxiX1K9BRe6BcnOyT2gIV39jKxcJKb9KJMdKMj07UX3KJZdbLKC8A4dGrQ3eB_pYCrQx73p1NK3QI4WDLppV5-qAgis5EIWLW-LF3M6B9tMbK35CDtdRERHeKa5lJLf8V85vuLBW2iFs4ICcy6IBD7a5MUpJjZKrO0LRAT7p4ghU_sT53GJwVoy2MabMvd-xh81cMLtOvT2qRLjOGtKGox0M8tSCdBBjegPNSNEu4Av9t0NYMDoeVIvU61OD7zV0jIpgimmEYnAq-0Q3MDiIQ2OvIS4sYD-DM; isg=BLW1a2MzP_L091tO8X_BKJDMxDFvMmlELXWE0jfezC5tDtIA9oZ4FLDIWNI4ToH8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}
self.params = {
"_csrf": "ce3a8590-8d85-4e2c-9751-4aedd37b3750"
}
def get_data(self, page): # 获取地址和User-Agent
data = {"channel":"group_official_site","language":"zh","batchId":"","categories":"","deptCodes":[],"key":"","pageIndex":page,"pageSize":10,"regions":"","subCategories":"","shareType":"","shareId":"","myReferralShareCode":""}
response = requests.post(url=self.url, params=self.params, headers=self.headers, json=data)
return response.json()
def parse_data(self, response):
print(response)
data_list = response["content"]['datas']
for node in data_list:
workLocations = ','.join(node['workLocations'])
name = node['name']
requirement = node['requirement']
self.save_data(workLocations, name, requirement)
def create_table(self):
# 使用预处理语句创建表
sql = '''
CREATE TABLE IF NOT EXISTS ali(
id int primary key auto_increment not null,
workLocations VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
requirement TEXT)
'''
try:
self.cursor.execute(sql)
print("CREATE TABLE SUCCESS.")
except Exception as ex:
print(f"CREATE TABLE FAILED,CASE:{ex}")
def save_data(self, workLocations, name, requirement):
# SQL 插入语句
sql = 'INSERT INTO ali(id, workLocations, name, requirement) values(%s, %s, %s, %s)'
# 执行 SQL 语句
try:
self.cursor.execute(sql, (0, workLocations, name, requirement))
# 提交到数据库执行
self.db.commit()
print('数据插入成功...')
except Exception as e:
print(f'数据插入失败: {e}')
# 如果发生错误就回滚
self.db.rollback()
def run(self):
self.create_table()
for i in range(1, 10):
response = self.get_data(i)
self.parse_data(response)
# 关闭数据库连接
self.db.close()
if __name__ == '__main__':
baidu = Baidu()
baidu.run()
五、MongoDB存储
1.MongoDB简介
MongoDB 是由 C++ 语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容存储形式类似JSON 对象,它的字段值可以包含其他文档、数组及文档数组,非常灵活。在这一节中,我们就来看看 Python 3 下 MongoDB 的存储操作。
常用命令:
-
查询数据库: show dbs
-
使用数据库: use 库名
-
查看集合: show tables/show collections
-
查询表数据: db.集合名.find()
-
删除表: db.集合名.drop()
2. 连接 MongoDB
连接 MongoDB 时,我们需要使用 PyMongo 库里面的 MongoClient。一般来说,传入 MongoDB 的 IP及端口即可,其中第一个参数为地址 host,第二个参数为端口 port(如果不给它传递参数,默认是27017)
import pymongo # 如果是云服务的数据库 用公网IP连接
client = pymongo.MongoClient(host='localhost', port=27017)
3. 指定数据库和表
import pymongo # 如果是云服务的数据库 用公网IP连接
client = pymongo.MongoClient(host='localhost', port=27017)
collection = client['students']
4.插入数据
插入数据。对于 students 这个集合,新建一条学生数据,这条数据以字典形式表示:
import pymongo # 如果是云服务的数据库 用公网IP连接
client = pymongo.MongoClient(host='127.0.0.1', port=27017)
collection = client['students']['stu']
# 插入单条数据
student = {'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male' }
result = collection.insert_one(student)
print(result)
# 插入多条数据
student1 = { 'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male' }
student2 = { 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male' }
collection.insert_many([student1, student2])
5.爬虫数据采集入库
要求:获取到爱奇艺视频电视剧数据信息,标题,播放地址,简介
网址:https://list.iqiyi.com/www/2/15-------------11-1-1-iqiyi--.html?s_source=PCW_SC
import requests
import pymongo
class Aqiyi(object):
def __init__(self):
self.client = pymongo.MongoClient(host='127.0.0.1', port=27017)
self.collection = self.client['spider']['aqy']
self.headers = {
'referer': 'https://list.iqiyi.com/www/2/15-------------11-1-1-iqiyi--.html?s_source=PCW_SC',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
'x-requested-with': 'XMLHttpRequest'
}
self.url = 'https://pcw-api.iqiyi.com/search/recommend/list'
def get_data(self, params):
response = requests.get(self.url, headers=self.headers, params=params)
return response.json()
def parse_data(self, data):
categoryVideos = data['data']['list']
for video in categoryVideos:
item = {}
item['title'] = video['title']
item['playUrl'] = video['playUrl']
item['description'] = video['description']
print(item)
self.save_data(item)
def save_data(self, item):
self.collection.insert_one(item)
def main(self):
for page in range(1, 2):
params = {
'channel_id': '2',
'data_type': '1',
'mode': '11',
'page_id': page,
'ret_num': '48',
'session': 'fc7d98794f15b224b169d328bf8f9f13',
'three_category_id': '15;must',
}
data = self.get_data(params)
self.parse_data(data)
if __name__ == '__main__':
yk = Aqiyi()
yk.main()
六、数据去重
1.爬虫去重的应用场景
- 防止发出重复的请求
- 防止存储重复的数据
2.爬虫数据去重实现的基本原理
根据给定的判断依据和给定的去重容器,将原始数据逐一进行判断,判断去重容器中是否有该数据,如果没有那就把该数据对应的判断依据添加去重容器中,同时标记该数据是不重复数据,如果有就不添加,同时标记该数据是重复数据。
3.临时去重容器和持久化去重容器
-
临时去重容器
指如利用list、set等编程语言的数据结构存储去重数据,一旦程序关闭或重启后,去重容器中的数据就被回收了。
优点:使用与实现简单方便;
缺点:但无法共享、无法持久化
-
持久化去重容器
指如利用 redis、mysql 等数据库存储去重数据。
优点:持久化、共享;
缺点:但使用与实现相对复杂
4.常用几种特殊的原始数据特征值计算
- 信息摘要 hash 算法(指纹)
- SimHash 算法 - 模糊文本
- 布隆过滤器方式 - 上亿级别的数据去重
5.Redis命令复习
- 进入数据库: redis-cli
- 切换数据库: select 下标
- 查看所有键: keys *
- 插入集合数据: sadd 键 值
- 查询集合数据: SMEMBERS 键
- 删除键: del 键
5.代码实战
获取芒果tv的数据信息,获取简介,演员,标题
import hashlib
import redis
import requests
import pymongo
class Mgtv(object):
def __init__(self):
self.client = pymongo.MongoClient(host='127.0.0.1', port=27017)
self.collection = self.client['spider']['mgtv']
self.red = redis.Redis()
self.headers = {
'referer': 'https://www.mgtv.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
'x-requested-with': 'XMLHttpRequest'
}
self.url = 'https://pianku.api.mgtv.com/rider/list/pcweb/v3'
def get_md5(self, val):
"""把目标数据进行哈希,用哈希值去重更快"""
md5 = hashlib.md5()
md5.update(str(val).encode('utf-8'))
# print(md5.hexdigest())
return md5.hexdigest()
def get_data(self, params):
response = requests.get(self.url, headers=self.headers, params=params)
return response.json()
def parse_data(self, data):
categoryVideos = data['data']['hitDocs']
for video in categoryVideos:
item = {}
item['story'] = video['story']
item['subtitle'] = video['subtitle']
item['title'] = video['title']
print(item)
self.save_data(item)
def save_data(self, item):
value = self.get_md5(item)
res = self.red.sadd('mg:filter', value)
if res:
self.collection.insert_one(item)
print('插入成功!!!!')
else:
print('数据重复!!!!')
def main(self):
for page in range(1, 2):
params = {
"allowedRC": "1",
"platform": "pcweb",
"channelId": "2",
"pn": page,
"pc": "80",
"hudong": "1",
"_support": "10000000",
"kind": "19",
"area": "10",
"year": "all",
"chargeInfo": "a1",
"sort": "c2"
}
data = self.get_data(params)
self.parse_data(data)
if __name__ == '__main__':
mg = Mgtv()
mg.main()
七、作业
目标:
获取搜狐视频电影一栏里的电视剧信息,提取名称、周播放数、主演,获取10个页面,将数据分别存储在mysql和MongoDB数据库,对数据进行去重,已经对请求网址实现去重
目标网址:https://so.tv.sohu.com/list_p1100_p2_p3_p4_p5_p6_p7_p8_p9_p102_p11_p12_p13_p14.html