爬虫基础-数据存储

注:本文章为学习过程中对知识点的记录,供自己复习使用,也给大家做个参考,如有错误,麻烦指出,大家共同探讨,互相进步。
借鉴出处:
该文章的路线和主要内容:崔庆才(第2版)python3网络爬虫开发实战


1、Text文本文件存储

def open(file, mode='r', buffering=None, encoding=None, errors=None, newline=None, closefd=True)

  • file表示要保存或者打开的文件路径。
  • mode是一个可选字符串,指定文件的模式打开。
  • encoding指定文件的编码方式。
  • buffering用于设置缓冲策略的可选整数。传递0以关闭缓冲(仅在二进制模式下允许),传递1以选择行缓冲(仅在文本模式下可用),以及大于1的整数表示固定大小块缓冲区的大小。
    当没有缓冲参数时给定,默认缓冲策略的工作方式如下:二进制文件以固定大小的块缓冲;缓冲区的大小使用试探法选择,试图确定底层设备的“块大小”并返回io.DEFAULT_BUFFER_size。在许多系统上,缓冲区通常为4096或8192字节长。“交互式”文本文件(isatty()返回True的文件)使用行缓冲。其他文本文件使用上述策略用于二进制文件。
  • errors是一个可选字符串,指定编码错误的方式此参数不应在二进制模式下使用。通过“strict”用于在存在编码错误时引发ValueError异常(默认值“无”具有相同的效果),或传递“忽略”以忽略错误。(请注意,忽略编码错误可能会导致数据丢失。)
  • newline换行符控制通用换行符的工作方式(它仅适用于文本模式)。它可以是None、“”、“\n”、“\r”和“\r\n”。
  • 如果closefd为False,则底层文件描述符将保持打开状态当文件关闭时。当给定文件名时,此操作无效并且在这种情况下必须为True。

但常用的三个参数就是file、mode、encoding了。
mode的几种模式:

  • r:以只读方式打开一个文件,默认模式。
  • rb:以二进制方式打开一个文件,例如音频、图片、视频等。
  • r+:以读写方式打开一个文件,既可以读文件又可以写文件。
  • rb+:以二进制读写方式打开一个文件,即可以读也可以写,读写都是二进制数据。
  • w:以写入的方式打开一个文件。如果该文件已存在,则将其覆盖;否则创建文件(下面几个都是)。
  • wb:以二进制写入方式打开一个文件。
  • w+:以读写方式打开一个文件。
  • wb+:以二进制读写格式打开一个文件。
  • a:以追加方式打开一个文件。若文件已存在,文件指针会放在文件结尾;否则创建文件来写入。(下面几个都是)。
  • ab:以二进制追加方式打开一个文件。
  • a+:以读写方式打开一个文件。
  • ab+:以二进制追加方式打开一个文件。

每回调用open方法后,要用close()方法关闭文件对象。
为了简化方法,采用with as语法。当with控制块语句结束时,文件会自动关闭,意味着不需要在调用close方法。

with open('movies.txt', 'w', encoding='utf-8') as file:
	file.write(f'名称':{name}\n}
	file.write(f'类别':{categories}\n}
	file.write(f'上映事件':{published_at}\n}
	file.write(f'评分':{score}\n}

2、JSON文件存储

在JSON对象中用’[]'包围的内容相当于数组,数组中的每个元素都可以是任意类型,数据结构类型为[“java”,“javascript”,“vb”,…]。‘{}’包围的内容相当于对象,数据结构类型是{key1:value1,key2:value2,…}
JSON可以有对象和数组两种形式自由组合,能够嵌套无限次,并且结构清晰,是数据交换的极佳实现方式。
JSON库中的loads方法将JSON文本字符串转为JSON对象,反过来通过dumps方法将JSON对象转换为文本字符串。
loads
输入:

import json
str = '''[{"name":"Bob"},{"name":"Tom"}]'''
print(type(str))
data = json.loads(str)
print(type(data))
print(data)
print(data[1].get('name'))

输出:

<class 'str'>
<class 'list'>
[{'name': 'Bob'}, {'name': 'Tom'}]
Tom

注意:JSON字符串的表示需要用双引号,否则loads方法会解析失败。同时还有load方法,与loads功能一致,只不过load接收的是文件操作对象,loads接收的是JSON字符串
dumps
输入:

import json
data = [{'name':'张三'},{'name':'Tom'}]
with open('data.json','w',encoding='utf-8') as file:	
	# indent缩进字符的个数;ensure_ascii=False表示不让中文转Unicode字符
    file.write(json.dumps(data,indent=2,ensure_ascii=False))

输出:

# data.json
[
  {
    "name": "张三"
  },
  {
    "name": "Tom"
  }
]

注意:dumps同样有dump方法,与load使用方式一致。

3、CSV文件存储

CSV比Excel文件更加简洁,XLS文本是电子表格,包含文本、数值、公式和格式等内容,CSV中不包含这些,就是以特定字符作为分隔符的纯文本,结构简单清晰。所以,有时候使用CSV来存储数据是比较方便的。
写入
输入:

import csv
with open('data.csv','w',encoding='utf-8') as csvFile:
    # writer初始化csv写入对象;delimiter用于传列与列之间的分隔符
    writer = csv.writer(csvFile, delimiter=',')
    writer.writerow(['id','name'])
    # writerow写入单行数据
    writer.writerow(['10001','Tom'])
    writer.writerow(['10002','Jack'])
    # writerows写入多行数据
    writer.writerows([['10003','Jerry'],['10004','Bob']])

输出:

# data.csv
id,name

10001,Tom

10002,Jack

10003,Jerry

10004,Bob

但一般情况下,爬虫爬取的都是结构化的数据,我们一般会用字典表示这种数据。csv库提供了字典的写入方式,如下:
输入:

import csv
with open('data.csv','w',encoding='utf-8') as csvFile:
    fieldnames = ['id', 'name']
    # DictWriter初始化csv写入对象并定义好字段;delimiter用于传列与列之间的分隔符
    writer = csv.DictWriter(csvFile, fieldnames=fieldnames, delimiter=';')
    # writeheader方法写入fieldnames头信息
    writer.writeheader()
    # writerow写入单行数据
    writer.writerow({'id':'10001','name':'Tom'})
    writer.writerow({'id':'10002','name':'Jack'})
    # writerows写入多行数据
    writer.writerows([{'id':'10003','name':'Jerry'},{'id':'10004','name':'Bob'}])

输出:

# data.csv
id;name

10001;Tom

10002;Jack

10003;Jerry

10004;Bob

同时,通过pandas库DataFrame对象的to_csv方法将数据写入CSV文件中。
安装pandas库pip install pandas
输入:

import pandas as pd
data = [
    {'id':'10001','name':'Tom'},
    {'id':'10002','name':'Jack'}
]
df = pd.DataFrame(data)
df.to_csv('data.csv', index=False)

输出:

# data.csv
id,name
10001,Tom
10002,Jack

读取
采用csv中的reader函数进行读取csv文件。
输入:

import csv
with open('data.csv','r',encoding='utf-8') as csvFile:
    reader = csv.reader(csvFile)
    for row in reader:
        print(row)

输出:

['id', 'name']
['10001', 'Tom']
['10002', 'Jack']

也可以用read_csv方法将数据从CSV文件中读取出来。
输入:

import pandas as pd
df = pd.read_csv('data.csv')
print(df)
# 转换成列表或元组
data = df.values.tolist()
print(data)

输出:

      id  name
0  10001   Tom
1  10002  Jack
[[10001, 'Tom'], [10002, 'Jack']]

4、MySQL存储

关系型数据库是基于关系模型的数据库,而关系模型是通过二维表来保存的,所以关系型数据库中数据的存储方式就是行列组成的表,每一列代表一个字段、每一行代表一条记录。表可以看作某个实体的集合,实体之间存在的联系需要通过表与表之间的关联关系体现,例如主键和外键的关联关系。由多个表组成的数据库,就是关系型数据库。
关系型数据库有多种,例如SQLite、MySQL、Oracle、SQL Server、DB2等。
安装连接库pip install pymysql
输入:

import pymysql
# 与mysql建立连接,填写mysql的host,port,user,password
db = pymysql.connect(host='127.0.0.1',port=3306, user='root', password='password')
# 获取mysql的操作游标,利用游标执行sql语句
cursor= db.cursor()
sql1 = 'CREATE DATABASE IF NOT EXISTS TestDataBase;'
sql2 = 'USE TestDataBase;'
sql3 = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL ,name VARCHAR(255) NOT NULL , primary key (id));'
# execute执行语句
cursor.execute(sql1)
cursor.execute(sql2)
cursor.execute(sql3)
data = [{'id':'100001','name':'Tom'},{'id':'100002','name':'Jerry'},{'id':'100003','name':'Jack'}]
# 插入数据
sql4 = 'INSERT INTO students(id, name) values(%s ,%s)'
try:
    for i in range(len(data)):
        cursor.execute(sql4,(data[i].get("id"),data[i].get("name")))
    # 提交到数据库
    db.commit()
except EOFError as e:
    print(e)
    # 如果执行异常,用rollback回滚
    db.rollback()
# 更新数据
sql5 = 'UPDATE students SET name=%s WHERE id=%s;'
try:
    cursor.execute(sql5,('Bob','100001'))
    db.commit()
except EOFError as e:
    print(e)
    db.rollback()
# 删除数据
sql6 = 'DELETE FROM students where name=%s;'
try:
    cursor.execute(sql6,('Jerry'))
    db.commit()
except EOFError as e:
    print(e)
    db.rollback()
# 查询数据
sql7 = 'SELECT * FROM students;'
try:
    cursor.execute(sql7)
    # 如果先fetchone再fetchall,那fetchall就查询不到第一条数据了
    # one = cursor.fetchone()
    alls = cursor.fetchall()
    for row in alls:
        print(row)
except EOFError as e:
    print(e)
    db.rollback()
db.close()

其他知识点:
事务的4个属性:

属性解释
原子性事务是一个不可分割的工作单位,一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作
一致性事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态。
隔离性一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰
持久性持续性也称持久性,指一个事务一旦提交,它对数据库中数据做的改变就应该是永久性的。接下来的其他操作或故障不应该对数据有任何影响。即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态。

5、MongoDB文档存储

  • 键值存储数据库:Redis、Voldmort、Oracle BDB等。
  • 列存储数据库:Cassandra、HBase、Riak等。
  • 文档型数据库:CouchDB、MongoDB等。
  • 键值存储数据库:Redis、Voldmort、Oracle BDB等。
  • 图形数据库:Neo4J、InfoGrid、Infinite Graph等。
    MongoDB事由C++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容的存储形式类似JSON对象。它的字段值可以包含其他文档、数组及文档数组,非常灵活。
    安装pip install pymongo
    输入:
import pymongo
'''建立连接'''
client = pymongo.MongoClient(host='localhost',port=27017)
# 或者这种方式client = pymongo.MongoClient('mongodb://localhost:27017')
'''指定数据库'''
db = client.test
# 或者这种方式db = client['test']
'''指定集合'''
collection = db.students
# 或者这种方式collection = db['students']
'''插入数据'''
student1 = {'id':'100001','name':'Tom'}
result = collection.insert(student1)
# 每条数据都有一个_id属性作为唯一标识。如果没有显式指明该属性,那么MongoDB会自动产生一个Object类型的_id属性,insert方法会在执行后返回_id值。
print(result)
'''插入多条数据'''
student2 = {'id':'100002','name':'Jerry'}
result = collection.insert([student1,student2])
# PyMongo3.x版本中推荐使用insert_one和insert_many方法,继续使用insert也没问题。
print(result)
'''查询'''
result = collection.find_one({'name':'Tom'})
# find_one查询一条数据;find查询所有数据。返回的是一个字典类型。
print(result)
'''计数'''
count = collection.find().count()
print(count)
'''排序'''
#ASCENDING升序,DESCENDING降序
results = collection.find().sort('name', pymongo.ASCENDING)
print([result['name'] for result in results])
'''偏移'''
# skip(2)偏移2个位置即忽略前两个元素,获取第三个及以后的元素
results = collection.find().sort('name', pymongo.ASCENDING).skip(2)
print([result['name'] for result in results])
# limit(2)指定获取前两个
# 数据库中数据量偏大时,最好不要使用大偏移来查询数据,可能导致内存溢出。
results = collection.find().sort('name', pymongo.ASCENDING).skip(2).limit(2)
print([result['name'] for result in results])
# 数据量偏大时,根据id查询,这里需要记录好上次查询的_id
from bson.objectid import ObjectId
collection.find({'_id':{'$gt':ObjectId('2738192731720a1902c38103d')}})
'''更新'''
student = collection.find_one({'id':'100001'})
student['name'] = 'Bob'
result = collection.update({'id':'100001'},student)
print(result)
'''删除'''
result = collection.remove({'name':'Tom'})
print(result)
'''还有find_one_and_delete\find_one_and_replace\find_one_and_update\create_index\create_indexes\drop_index等方法,实际使用时,再根据官方提供的api操作即可'''

还有一些常用操作符,如比较符号、正则匹配 $ regex、类型判断$type、文本查询 $ text等,用到时百度即可。

6、Redis缓存存储

Redis是一个基于内存的、高效的键值型数据库,存取效率极高,而且支持多种数据存储结构,使用起来也非常简单。
安装pip install redis
输入:

from redis import StrictRedis
redis = StrictRedis(host='localhost',port=6379, db=0, password='password')
    ## 构建连接的其他方法
    # 1、ConnectionPool连接方式
    # from redis import StrictRedis,ConnectionPool
    # pool = ConnectionPool(host='localhost',port=6379, db=0, password='password')
    # redis = StrictRedis(connection_pool=pool)
    # 2、通过URL连接
    # url = 'redis://:redisredis@localhost:6379/0'
    # pool = ConnectionPool.from_url(url)
    # redis = StrictRedis(connection_pool=pool)
redis.set('name','Bob')
print(redis.get('name'))

输出:
b’Bob’
1、键的一些判断和操作方法
在这里插入图片描述
2、键值对存储的相关方法
在这里插入图片描述在这里插入图片描述
3、列表操作
在这里插入图片描述
4、集合操作
在这里插入图片描述
5、有序集合操作
在这里插入图片描述
6、散列操作
在这里插入图片描述

7、Elasticsearch搜索引擎存储

Elasticsearch是一个开源的搜索引擎,建议在一个全文搜索引擎库Lucene的基础之上。(Lucene拥有最先进、高性能和全功能搜索引擎功能的库,但也仅仅只是一个库。)Elasticsearch也是使用Java编写的,其内部使用Lucene实现索引和搜索,但它的目标是使全文检索变得简单,相当于Lucene的一层封装,它提供了一套简单一致的RESTful Api来帮助我们实现存储和检索。
特点:

  • 一个分布式的实时文档存储库,每个字段都可以被索引和检索;
  • 一个分布式的实时分析搜索引擎;
  • 能胜任上百个服务节点的扩展,并支持PB级别的结构化或者非结构化数据。

介绍几个概念:

  1. 节点和集群
    Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elasticsearch 实例。单个 Elasticsearch 实例称为一个节点(Node),一组节点构成一个集群(Cluster)
  2. 索引
    索引,即 Index,Elasticsearch 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。所以,Elasticsearch 数据管理的顶层单位就叫作索引,其实就相当于 MySQL、MongoDB 等中数据库的概念。
  3. 文档
    文档,即 Document。索引里面单条记录称为文档,许多条文档构成了一个索引。同一个索引里面的文档,不要求有相同的结构(Schema),但是最好保持一致,因为这样有利于提高搜索效率。
  4. 类型
    文档可以分组,比如 weather 这个索引里面,既可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫作类型(Type),它是虚拟的逻辑分组,用来过滤文档,类似 MySQL 中的数据表、MongoDB 中的 Collection。
    不同的类型应该有相似的结构。举例来说,id 字段不能在这个组中是字符串,在另一个组中是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如 products 和 logs)应该存成两个索引,而不是一个索引里面的两个类型(虽然可以做到)。
    根据规划,Elastic 6.x 版只允许每个索引包含一个类型,Elastic 7.x 开始将会将其彻底移除。
  5. 字段
    每个文档都类似一个 JSON 结构,它包含了许多字段,每个字段都有其对应的值,多个字段组成了一个文档,其实就可以类比 MySQL 数据表中的字段。

安装pip install elasticsearch

输入:

from elasticsearch import Elasticsearch
'''建立连接'''
es = Elasticsearch(['http://[username:password@]hostname:port'], verify_certs=True)
'''创建索引'''
# 合理利用ignore可以避免没必要的错误
result = es.indices.create(index='news', ignore=400)
print(result)
'''删除索引'''
result = es.indices.delete(index='news', ignore=[400,404])
print(result)
'''插入数据'''
data = {'title':'乘风破浪会有时,直挂云帆济沧海','url':'http://view.inew.com/asd098092sa8d90890890'}
#create需要指定id; es.index(index='news', body=data) --- index不需要指定id,会自动生成。
result = es.create(index='news', id=1, body=data)
print(result)
'''更新数据'''
data = {'title':'乘风破浪会有时,直挂云帆济沧海','url':'http://view.inew.com/asd098092sa8d90890890','date':'2022-10-23'}
#es.index(index='news',doc_type='politics',body=data, id=1)
result = es.update(index='news', body=data, id=1)
print(result)
'''删除数据'''
result = es.delete(index='news', id=1)
print(result)
'''查询数据'''
# 根据索引查询该索引下的所有数据
result = es.search(index='news')
dsl = {
    'query':{
        'match':{
            'title':'hello'
        }
    }
}
# 全文检索符合的字段
result = es.search(index='news', body=dsl)

8、RabbitMq消息队列存储(后续再补,现在用不到,待开发中扩展到mq时,再学习此模块)

在爬取过程中,可能需要一些进程间的通信机制:

  • 一个进程负责构造爬取请求,另一个进程负责执行爬取请求。
  • 某个数据爬取进程执行完毕,通知另外一个负责数据处理的进程开始处理数据。
  • 某个进程新建了一个爬取任务,通知另外一个负责数据爬取的进程开始爬取数据。

为了降低这些进程的耦合度,需要一个类似消息队列的中间件来存储和转发消息,实现进程间的通信。有了消息队列中间件之后,以上各机制中的两个进程就可以独立执行,它们之间的通信则由消息队列实现。

  • 一个进程根据需要爬取的任务,构造请求对象并放入消息队列,另一个进程从队列中取出请求对象并执行爬取。
  • 某个数据爬取进程执行完毕,就像消息队列发送消息,当另一个负责数据处理的进程监听到这类消息时,就开始处理数据。
  • 某个进程新建了一个爬取任务后,就向消息队列发送消息,当另一个负责数据爬取的进程监听到这类消息时吗,就开始爬取数据。

RabbitMQ介绍
RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议实现,其主要特点有面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全性。具有以下特点:

  • 可靠性:RabbitMQ通过一些机制保证可靠性,如持久化、传输确认、发布确认。
  • 灵活的路由:有Exchange将消息路由至消息队列。RabbitMQ已经提供了一些内置的Exchange来实现典型的路由功能;对于较复杂的路由功能,则将多个Exchange绑定在一起,或者通过插件机制实现自己的Exchange。
  • 消息集群:多个RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
  • 高可用:消息队列可以在集群中的机器上镜像存储,使得队列在部分节点出问题的情况下仍然可用。
  • 多种协议支持:RabbitMQ支持多种消息队列协议,如STOMP、MQTT。
  • 多语言客户端:RabbitMQ几乎支持所有常用语言。
  • 管理界面:RabbitMQ提供了一个易用的用户界面,使得用户可以监听和监管消息Broker的多个方面。
  • 跟踪机制:RabbitMQ提供了消息跟踪机制,如果消息异常,使用者就可以找出发生了什么。
  • 插件机制:RabbitMQ提供了许多插件,实现了多方面的扩展,用户也可以编写自己的插件。
    安装pip install pika

基本了解:
从本质上讲是一个生产者-消费者模型。

  • 生命队列:通过指定一些参数,创建消息队列。
  • 生产内容:生产者根据队列的连接信息连接队列,往队列中放入消息。
  • 消费内容:消费者根据队列的连接信息连接队列,从队列中取出消息。
'''声明一个队列'''
import pika
QUEUE_NAME = 'scrape'
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME)
  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值