Python3网络爬虫开发实战(4)数据的存储

一、文本文件存储

1. os 文件 mode

python open() 函数用于打开一个文件,创建一个 file 对象,相关的方法才可以调用它进行读写。

open(name[, mode[, buffering]])
模式描述
t文本模式 (默认)。
x写模式,新建一个文件,如果该文件已存在则会报错。
b二进制模式。
+打开一个文件进行更新(可读可写)。
U通用换行模式(不推荐)。
r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+打开一个文件用于读写。文件指针将会放在文件的开头。
rb+以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

2. TXT

with open('movies.txt', 'w', encoding='utf-8') as f:
    f.writelines(['名称', '类别', '时间'])

with open('movies.txt', 'w', encoding='utf-8') as f:
    f.write('名称')
    f.write('类别')
    f.write('时间')

3. JSON

json 数据要使用双引号包围起来,而不能使用单引号;

import json

# 读取数据
with open('data.json', encoding='utf-8') as f:
    json_data = json.loads(f.read())

json_data = json.load(open('data.json', encoding='utf-8'))

# 导出数据
with open('data.json', mode='w', encoding='utf-8') as f:
    f.write(json.dumps(json_data, indent=2, ensure_ascii=False))

json.dump(json_data, open('data.json', mode='w', encoding='utf-8'), indent=2, ensure_ascii=False)

4. CSV

import csv

# 存储数据
with open('data.csv', 'w') as csvfile:
    writer = csv.writer(csvfile, delimiter=',')
    writer.writerow(['1', '2', '3'])
    writer.writerow(['1', '2', '3'])
    writer.writerow(['1', '2', '3'])

with open('data.csv', 'w') as csvfile:
    writer = csv.DictWriter(csvfile, delimiter=',', fieldnames = ['id1', 'id2', 'id3'])
    writer.writeheader()
    writer.writerow({'id1': 1, 'id2': 2, 'id3': 3})
    writer.writerow({'id1': 1, 'id2': 2, 'id3': 3})
    writer.writerow({'id1': 1, 'id2': 2, 'id3': 3})

# 读取数据
with open('data.csv', 'r') as csvfile:
    reader = csv.DictReader(csvfile)
    for row in reader:
        print(row)

# 如果 w 模式中没有使用 newline='' 则会出现空行,不推荐使用,推荐使用 csv.DictReader
with open('data.csv', 'r') as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
        print(row)

二、数据库存储

1. SQLAlchemy

安装 SQLAlchemy 库:

pip install SQLAlchemy

SQLAlchemy 库相较于 pymysql 库来说,sqlalchemy还包括ORM,可以负责数据转换和类型处理;sqlalchemy ORM 使用 Python 类来表示数据库表,这使得查询和数据处理变得无比容易。此外,sqlalchemy 还支持高级查询和 SQL 语句生成,可以在处理复杂查询时更加便捷。

官网:SQLAlchemy Documentation — SQLAlchemy 2.0 Documentation

drivernameDatabase
mysqlMySQL
mysql+mysqldbMySQL
mysql+pymysqlMySQL
oracleOracle
oracle+cx_oracleOracle
mssql+pyodbcMicrosoft SQL Server
mssql+pymssqlMicrosoft SQL Server
sqliteSQLite
postgresqlPostgreSQL
postgresql+psycopg2PostgreSQL
postgresql+pg8000PostgreSQL
from sqlalchemy import URL, create_engine, text

url_object = URL.create(
    drivername="postgresql+pg8000",
    username="dbuser",
    password="kx@jj5/g",  # plain (unescaped) text
    host="pghost10",
    database="appdb",
)

engine = create_engine(url_object, echo=False)

with engine.connect() as conn:
     result = conn.execute(text("select 'hello world'"))
     print(result.all())
     # 加上 conn.commit() 表示提交 否则就是 rollback
     # conn.commit() 

在这里涉及到 SQL 语句部分:史上最全SQL基础知识总结(理论+举例)-CSDN博客

2. MongoDB

首先安装 MongoDB,然后安装 PyMongo 库:MongoDB 的安装 | 静觅 (cuiqingcai.com);(异步库 motor

pip install pymongo

MongoDB 是一个基于分布式文件存储的开源数据库系统,其内容的存储形式类似于 Json 对象;在 MongoDB 中,每条数据都有一个 _id 属性作为唯一标识,如果没有显示指明该属性,那么 MongoDB 会自动产生一个 ObjectID 类型的 _id 属性,insert 方法会在执行后返回 _id 值;MongoDB 教程 | 菜鸟教程 (runoob.com)

import pymongo

# 连接 MongoDB
client = pymongo.MongoClient(host="localhost", port=27017)
client = pymongo.MongoClient('mongodb://localhost:27107/')

# 指定名为 test 的数据库
db = client.test
db = client['test']

# 指定名为 students 的集合
collection = db.students
collection = db['students']

# 插入数据,insert 方法会在执行后返回 ObjectID 类型的 _id 值;
student_1 = {'name':'张三', 'age': 14}
student_2 = {'name':'张三', 'age': 14}

collection.insert_one(student_1)
collection.insert_one(student_2)
collection.insert_many([student_1, student_2])

# 查询数据,返回的数据是 字典型 数据
collection.find_one({'name': '张三'})
collection.find({'age': { $eq: 25}}) # 返回 cursor 类型数据,相当于一个生成器 

from bson.objectid import ObjectID
collection.find_one({'_id': ObjectID('88888888')})

# 更新
condition = {'name': 'Kevin'}
student = collection.find_one(condition)
student['age'] = 26
collection.update(condition, student) # 可以不用 $set
## {'ok': 1, ...}

## $set 只会更新 student 字典内存在的字段,如果原先还有其他字段,则既不会更新,也不会删除;
collection.update_one(condition, {'$set': student})
collection.update_many(condition, {'$set': student}) 

# 删除
collection.delete_one({'name': '张三'})
collection.delete_many({'name': '张三'})
## {'ok': 1, ...}

# 计数
collection.find().count()

# 排序 ASCENDING 和 DESCENDING
collection.find().sort('name', pymongo.ASCENDING) 

# 偏移位置
collection.skip(2)

# 限制结果个数
collection.limit(2)

条件操作符:

操作符描述示例
$eq等于{ age: { $eq: 25 } }
$ne不等于{ age: { $ne: 25 } }
$gt大于{ age: { $gt: 25 } }
$gte大于等于{ age: { $gte: 25 } }
$lt小于{ age: { $lt: 25 } }
$lte小于等于{ age: { $lte: 25 } }
$in在指定的数组中{ age: { $in: [25, 30, 35] } }
$nin不在指定的数组中{ age: { $nin: [25, 30, 35] } }
$and逻辑与,符合所有条件{ $and: [ { age: { $gt: 25 } }, { city: "New York" } ] }
$or逻辑或,符合任意条件{ $or: [ { age: { $lt: 25 } }, { city: "New York" } ] }
$not取反,不符合条件{ age: { $not: { $gt: 25 } } }
$nor逻辑与非,均不符合条件{ $nor: [ { age: { $gt: 25 } }, { city: "New York" } ] }
$exists字段是否存在{ age: { $exists: true } }
$type字段的 BSON 类型{ age: { $type: "int" } }
$all数组包含所有指定的元素{ tags: { $all: ["red", "blue"] } }
$elemMatch数组中的元素匹配指定条件{ results: { $elemMatch: { score: { $gt: 80, $lt: 85 } } } }
$size数组的长度等于指定值{ tags: { $size: 3 } }
$regex匹配正则表达式{ name: { $regex: /^A/ } }
$text进行文本搜索{ $text: { $search: "coffee" } }
$where使用 JavaScript 表达式进行条件过滤{ $where: "this.age > 25" }
功能操作符:

![[Pasted image 20240725150419.png]]

3. Redis

首先可以按照下面链接安装 Redis,安装好 Redis 数据库后,还需要安装好 redis-py 库,即用来操作 Redis 的 Python 包

  1. Redis 库的安装 | 静觅 (cuiqingcai.com)
  2. Redis 安装 | 菜鸟教程 (runoob.com)
  3. Python redis 使用介绍 | 菜鸟教程 (runoob.com)
pip install redis

Redis 是一个基于内存的,高效的键值型非关系型数据库,存取效率极高,而且支持多种数据结构,使用起来也非常简单;鉴于 Redis 的便捷性和高效性,在维护代理池,账号池,ADSL拨号代理池,Scrapy-Redis 分布式架构有着重要应用

from redis import StrictRedis, ConnectionPool

# 连接 Redis
pool = ConnectionPool(host='localhost', port=6379, db=0, password='foobared')
redis = StrictRedis(connection_pool=pool)

redis = StrictRedis(host='localhost', port=6379, db=0, password='foobared')

1) 键操作

方  法作  用参数说明示  例示例说明示例结果
exists(name)判断一个键是否存在name:键名redis.exists(‘name’)是否存在 name 这个键True
delete(name)删除一个键name:键名redis.delete(‘name’)删除 name 这个键1
type(name)判断键类型name:键名redis.type(‘name’)判断 name 这个键类型b’string’
keys(pattern)获取所有符合规则的键pattern:匹配规则redis.keys(‘n*’)获取所有以 n 开头的键[b’name’]
randomkey()获取随机的一个键randomkey()获取随机的一个键b’name’
rename(src, dst)重命名键src:原键名;dst:新键名redis.rename(‘name’, ‘nickname’)将 name 重命名为 nicknameTrue
dbsize()获取当前数据库中键的数目dbsize()获取当前数据库中键的数目100
expire(name, time)设定键的过期时间,单位为秒name:键名;time:秒数redis.expire(‘name’, 2)将 name 键的过期时间设置为 2 秒True
ttl(name)获取键的过期时间,单位为秒,1 表示永久不过期name:键名redis.ttl(‘name’)获取 name 这个键的过期时间1
move(name, db)将键移动到其他数据库name:键名;db:数据库代号move(‘name’, 2)将 name 移动到 2 号数据库True
flushdb()删除当前选择数据库中的所有键flushdb()删除当前选择数据库中的所有键True
flushall()删除所有数据库中的所有键flushall()删除所有数据库中的所有键True

2) 字符串操作

方  法作  用参数说明示  例示例说明示例结果
set(name, value)给数据库中键名为 name 的 string 赋予值 valuen ame:键名;value:值redis.set(‘name’, ‘Bob’)给 name 这个键的 value 赋值为 BobTrue
get(name)返回数据库中键名为 name 的 string 的 valuename:键名redis.get(‘name’)返回 name 这个键的 valueb’Bob’
getset(name, value)给数据库中键名为 name 的 string 赋予值 value 并返回上次的 valuename:键名;value:新值redis.getset(‘name’, ‘Mike’)赋值 name 为 Mike 并得到上次的 valueb’Bob’
mget(keys, *args)返回多个键对应的 value 组成的列表keys:键名序列redis.mget([‘name’, ‘nickname’])返回 name 和 nickname 的 value[b’Mike’, b’Miker’]
setnx(name, value)如果不存在这个键值对,则更新 value,否则不变name:键名redis.setnx(‘newname’, ‘James’)如果 newname 这个键不存在,则设置值为 James第一次运行结果是 True,第二次运行结果是 False
setex(name, time, value)设置可以对应的值为 string 类型的 value,并指定此键值对应的有效期n ame:键名;time:有效期;value:值redis.setex(‘name’, 1, ‘James’)将 name 这个键的值设为 James,有效期为 1 秒True
setrange(name, offset, value)设置指定键的 value 值的子字符串name:键名;offset:偏移量;value:值redis.set(‘name’, ‘Hello’) redis.setrange (‘name’, 6, ‘World’)设置 name 为 Hello 字符串,并在 index 为 6 的位置补 World11,修改后的字符串长度
mset(mapping)批量赋值mapping:字典或关键字参数redis.mset({‘name1’: ‘Durant’, ‘name2’: ‘James’})将 name1 设为 Durant,name2 设为 JamesTrue
msetnx(mapping)键均不存在时才批量赋值mapping:字典或关键字参数redis.msetnx({‘name3’: ‘Smith’, ‘name4’: ‘Curry’})在 name3 和 name4 均不存在的情况下才设置二者值True
incr(name, amount=1)键名为 name 的 value 增值操作,默认为 1,键不存在则被创建并设为 amountname:键名;amount:增长的值redis.incr(‘age’, 1)age 对应的值增 1,若不存在,则会创建并设置为 11,即修改后的值
decr(name, amount=1)键名为 name 的 value 减值操作,默认为 1,键不存在则被创建并将 value 设置为 - amountname:键名;amount:减少的值redis.decr(‘age’, 1)age 对应的值减 1,若不存在,则会创建并设置为11,即修改后的值
append(key, value)键名为 key 的 string 的值附加 valuekey:键名redis.append(‘nickname’, ‘OK’)向键名为 nickname 的值后追加 OK13,即修改后的字符串长度
substr(name, start, end=-1)返回键名为 name 的 string 的子字符串name:键名;start:起始索引;end:终止索引,默认为1,表示截取到末尾redis.substr(‘name’, 1, 4)返回键名为 name 的值的字符串,截取索引为 1~4 的字符b’ello’
getrange(key, start, end)获取键的 value 值从 start 到 end 的子字符串key:键名;start:起始索引;end:终止索引redis.getrange(‘name’, 1, 4)返回键名为 name 的值的字符串,截取索引为 1~4 的字符b’ello’

3) 列表操作

方  法作  用参数说明示  例示例说明示例结果
rpush(name, *values)在键名为 name 的列表末尾添加值为 value 的元素,可以传多个name:键名;values:值redis.rpush(‘list’, 1, 2, 3)向键名为 list 的列表尾添加 1、2、33,列表大小
lpush(name, *values)在键名为 name 的列表头添加值为 value 的元素,可以传多个name:键名;values:值redis.lpush(‘list’, 0)向键名为 list 的列表头部添加 04,列表大小
llen(name)返回键名为 name 的列表的长度name:键名redis.llen(‘list’)返回键名为 list 的列表的长度4
lrange(name, start, end)返回键名为 name 的列表中 start 至 end 之间的元素name:键名;start:起始索引;end:终止索引redis.lrange(‘list’, 1, 3)返回起始索引为 1 终止索引为 3 的索引范围对应的列表[b’3’, b’2’, b’1’]
ltrim(name, start, end)截取键名为 name 的列表,保留索引为 start 到 end 的内容name:键名;start:起始索引;end:终止索引ltrim(‘list’, 1, 3)保留键名为 list 的索引为 1 到 3 的元素True
lindex(name, index)返回键名为 name 的列表中 index 位置的元素name:键名;index:索引redis.lindex(‘list’, 1)返回键名为 list 的列表索引为 1 的元素b’2’
lset(name, index, value)给键名为 name 的列表中 index 位置的元素赋值,越界则报错name:键名;index:索引位置;value:值redis.lset(‘list’, 1, 5)将键名为 list 的列表中索引为 1 的位置赋值为 5True
lrem(name, count, value)删除 count 个键的列表中值为 value 的元素name:键名;count:删除个数;value:值redis.lrem(‘list’, 2, 3)将键名为 list 的列表删除两个 31,即删除的个数
lpop(name)返回并删除键名为 name 的列表中的首元素name:键名redis.lpop(‘list’)返回并删除名为 list 的列表中的第一个元素b’5’
rpop(name)返回并删除键名为 name 的列表中的尾元素name:键名redis.rpop(‘list’)返回并删除名为 list 的列表中的最后一个元素b’2’
blpop(keys, timeout=0)返回并删除名称在 keys 中的 list 中的首个元素,如果列表为空,则会一直阻塞等待keys:键名序列;timeout:超时等待时间,0 为一直等待redis.blpop(‘list’)返回并删除键名为 list 的列表中的第一个元素[b’5’]
brpop(keys, timeout=0)返回并删除键名为 name 的列表中的尾元素,如果 list 为空,则会一直阻塞等待keys:键名序列;timeout:超时等待时间,0 为一直等待redis.brpop(‘list’)返回并删除名为 list 的列表中的最后一个元素[b’2’]
rpoplpush(src, dst)返回并删除名称为 src 的列表的尾元素,并将该元素添加到名称为 dst 的列表头部src:源列表的键;dst:目标列表的 keyredis.rpoplpush(‘list’, ‘list2’)将键名为 list 的列表尾元素删除并将其添加到键名为 list2 的列表头部,然后返回b’2’

4) 集合操作

方  法作  用参数说明示  例示例说明示例结果
sadd(name, *values)向键名为 name 的集合中添加元素name:键名;values:值,可为多个redis.sadd(‘tags’, ‘Book’, ‘Tea’, ‘Coffee’)向键名为 tags 的集合中添加 Book、Tea 和 Coffee 这 3 个内容3,即插入的数据个数
srem(name, *values)从键名为 name 的集合中删除元素name:键名;values:值,可为多个redis.srem(‘tags’, ‘Book’)从键名为 tags 的集合中删除 Book1,即删除的数据个数
spop(name)随机返回并删除键名为 name 的集合中的一个元素name:键名redis.spop(‘tags’)从键名为 tags 的集合中随机删除并返回该元素b’Tea’
smove(src, dst, value)从 src 对应的集合中移除元素并将其添加到 dst 对应的集合中src:源集合;dst:目标集合;value:元素值redis.smove(‘tags’, ‘tags2’, ‘Coffee’)从键名为 tags 的集合中删除元素 Coffee 并将其添加到键为 tags2 的集合True
scard(name)返回键名为 name 的集合的元素个数name:键名redis.scard(‘tags’)获取键名为 tags 的集合中的元素个数3
sismember(name, value)测试 member 是否是键名为 name 的集合的元素name:键值redis.sismember(‘tags’, ‘Book’)判断 Book 是否是键名为 tags 的集合元素True
sinter(keys, *args)返回所有给定键的集合的交集keys:键名序列redis.sinter([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的交集{b’Coffee’}
sinterstore(dest, keys, *args)求交集并将交集保存到 dest 的集合dest:结果集合;keys:键名序列redis.sinterstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的交集并将其保存为 inttag1
sunion(keys, *args)返回所有给定键的集合的并集keys:键名序列redis.sunion([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的并集{b’Coffee’, b’Book’, b’Pen’}
sunionstore(dest, keys, *args)求并集并将并集保存到 dest 的集合dest:结果集合;keys:键名序列redis.sunionstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的并集并将其保存为 inttag3
sdiff(keys, *args)返回所有给定键的集合的差集keys:键名序列redis.sdiff([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的差集{b’Book’, b’Pen’}
sdiffstore(dest, keys, *args)求差集并将差集保存到 dest 集合dest:结果集合;keys:键名序列redis.sdiffstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的差集并将其保存为 inttag3
smembers(name)返回键名为 name 的集合的所有元素name:键名redis.smembers(‘tags’)返回键名为 tags 的集合的所有元素{b’Pen’, b’Book’, b’Coffee’}
srandmember(name)随机返回键名为 name 的集合中的一个元素,但不删除元素name:键值redis.srandmember(‘tags’)随机返回键名为 tags 的集合中的一个元素Srandmember (name)

5) 有序集合操作

方  法作  用参数说明示  例示例说明示例结果
zadd(name, *args, **kwargs)向键名为 name 的 zset 中添加元素 member,score 用于排序。如果该元素存在,则更新其顺序name:键名;args:可变参数redis.zadd(‘grade’, 100, ‘Bob’, 98, ‘Mike’)向键名为 grade 的 zset 中添加 Bob(其 score 为 100),并添加 Mike(其 score 为 98)2,即添加的元素个数
zrem(name, *values)删除键名为 name 的 zset 中的元素name:键名;values:元素redis.zrem(‘grade’, ‘Mike’)从键名为 grade 的 zset 中删除 Mike1,即删除的元素个数
zincrby(name, value, amount=1)如果在键名为 name 的 zset 中已经存在元素 value,则将该元素的 score 增加 amount;否则向该集合中添加该元素,其 score 的值为 amountname:键名;value:元素;amount:增长的 score 值redis.zincrby(‘grade’, ‘Bob’, -2)键名为 grade 的 zset 中 Bob 的 score 减 298.0,即修改后的值
zrank(name, value)返回键名为 name 的 zset 中元素的排名,按 score 从小到大排序,即名次name:键名;value:元素值redis.zrank(‘grade’, ‘Amy’)得到键名为 grade 的 zset 中 Amy 的排名1
zrevrank(name, value)返回键为 name 的 zset 中元素的倒数排名(按 score 从大到小排序),即名次name:键名;value:元素值redis.zrevrank (‘grade’, ‘Amy’)得到键名为 grade 的 zset 中 Amy 的倒数排名2
zrevrange(name, start, end, withscores= False)返回键名为 name 的 zset(按 score 从大到小排序)中 index 从 start 到 end 的所有元素name:键值;start:开始索引;end:结束索引;withscores:是否带 scoreredis.zrevrange (‘grade’, 0, 3)返回键名为 grade 的 zset 中前四名元素[b’Bob’, b’Mike’, b’Amy’, b’James’]
zrangebyscore (name, min, max, start=None, num=None, withscores=False)返回键名为 name 的 zset 中 score 在给定区间的元素name:键名;min:最低 score;max:最高 score;start:起始索引;num:个数;withscores:是否带 scoreredis.zrangebyscore (‘grade’, 80, 95)返回键名为 grade 的 zset 中 score 在 80 和 95 之间的元素[b’Bob’, b’Mike’, b’Amy’, b’James’]
zcount(name, min, max)返回键名为 name 的 zset 中 score 在给定区间的数量name:键名;min:最低 score;max:最高 scoreredis.zcount(‘grade’, 80, 95)返回键名为 grade 的 zset 中 score 在 80 到 95 的元素个数2
zcard(name)返回键名为 name 的 zset 的元素个数name:键名redis.zcard(‘grade’)获取键名为 grade 的 zset 中元素的个数3
zremrangebyrank (name, min, max)删除键名为 name 的 zset 中排名在给定区间的元素name:键名;min:最低位次;max:最高位次redis.zremrangebyrank (‘grade’, 0, 0)删除键名为 grade 的 zset 中排名第一的元素1,即删除的元素个数
zremrangebyscore (name, min, max)删除键名为 name 的 zset 中 score 在给定区间的元素name:键名;min:最低 score;max:最高 scoreredis.zremrangebyscore (‘grade’, 80, 90)删除 score 在 80 到 90 之间的元素1,即删除的元素个数

6) 散列操作

方  法作  用参数说明示  例示例说明示例结果
hset(name, key, value)向键名为 name 的散列表中添加映射name:键名;key:映射键名;value:映射键值hset(‘price’, ‘cake’, 5)向键名为 price 的散列表中添加映射关系,cake 的值为 51,即添加的映射个数
hsetnx(name, key, value)如果映射键名不存在,则向键名为 name 的散列表中添加映射name:键名;key:映射键名;value:映射键值hsetnx(‘price’, ‘book’, 6)向键名为 price 的散列表中添加映射关系,book 的值为 61,即添加的映射个数
hget(name, key)返回键名为 name 的散列表中 key 对应的值name:键名;key:映射键名redis.hget(‘price’, ‘cake’)获取键名为 price 的散列表中键名为 cake 的值5
hmget(name, keys, *args)返回键名为 name 的散列表中各个键对应的值name:键名;keys:键名序列redis.hmget(‘price’, [‘apple’, ‘orange’])获取键名为 price 的散列表中 apple 和 orange 的值[b’3’, b’7’]
hmset(name, mapping)向键名为 name 的散列表中批量添加映射name:键名;mapping:映射字典redis.hmset(‘price’, {‘banana’: 2, ‘pear’: 6})向键名为 price 的散列表中批量添加映射True
hincrby(name, key, amount=1)将键名为 name 的散列表中映射的值增加 amountname:键名;key:映射键名;amount:增长量redis.hincrby(‘price’, ‘apple’, 3)key 为 price 的散列表中 apple 的值增加 36,修改后的值
hexists(name, key)键名为 name 的散列表中是否存在键名为键的映射name:键名;key:映射键名redis.hexists(‘price’, ‘banana’)键名为 price 的散列表中 banana 的值是否存在True
hdel(name, *keys)在键名为 name 的散列表中,删除键名为键的映射name:键名;keys:键名序列redis.hdel(‘price’, ‘banana’)从键名为 price 的散列表中删除键名为 banana 的映射True
hlen(name)从键名为 name 的散列表中获取映射个数name:键名redis.hlen(‘price’)从键名为 price 的散列表中获取映射个数6
hkeys(name)从键名为 name 的散列表中获取所有映射键名name:键名redis.hkeys(‘price’)从键名为 price 的散列表中获取所有映射键名[b’cake’, b’book’, b’banana’, b’pear’]
hvals(name)从键名为 name 的散列表中获取所有映射键值name:键名redis.hvals(‘price’)从键名为 price 的散列表中获取所有映射键值[b’5’, b’6’, b’2’, b’6’]
hgetall(name)从键名为 name 的散列表中获取所有映射键值对name:键名redis.hgetall(‘price’)从键名为 price 的散列表中获取所有映射键值对{b’cake’: b’5’, b’book’: b’6’, b’orange’: b’7’, b’pear’: b’6’}

4. Elasticsearch

首先要安装 Elasticsearch,安装完毕后,我们需要安装 Elasticsearch 库专门用来对接 Elasticsearch

  1. Elasticsearch 的安装 | 静觅 (cuiqingcai.com)
  2. Elasticsearch 教程 - 菜鸟教程 (cainiaojc.com)
pip install elasticsearch

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

from elasticsearch import Elasticsearch

# 连接 Elasticsearch 默认连接本地9200端口
es = Elasticsearch(
	hosts:"localhost",
	port:9200,
	verify_certs=True # 是否验证证书
)

# 创建索引
es.indices.create(index='news', ignore=400) # ignore 为 400 忽略索引存在错误
## {'acknowledged':True, ...} acknowledged 表示创建成功

# 删除索引
es.indices.delete(index='news')
## {'acknowledged':True, ...} acknowledged 表示创建成功

# 插入数据 es.index() 可以不提供 id,会自动生成
es.index(index='test', id='1', body={"id": "1", "name": "小明"}) # 提倡使用下面的方法
es.index(index='test', id='2', doc={"name": "方天", "age": "23"})

# 插入数据 es.create() 必须提供 id,当索引中已存在具有相同 ID 的文档时,返回 409 响应。
es.create(index='test', id='3', doc={"name": "方天", "age": "23"})

# 更新数据 必须提供 id
es.update(index='test', id='3', doc={"name": "方天", "age": "23"})

# 查询数据
es.get(index='test',id='1')

1) 检索数据:利用 elasticsearch-analysis-ik 进行分词

Elasticsearch 具有一个强大的信息检索功能,对于中文来说,我们需要安装一个中文分词插件,如 elasticsearch-analysis-ik,这里我们可以使用 Elasticsearch 的命令行工具 elasticsearch-plugin 来安装这个插件:infinilabs/analysis-ik: 🚌 The IK Analysis plugin integrates Lucene IK analyzer into Elasticsearch and OpenSearch, support customized dictionary. (github.com)

elasticsearch-plugin install https://get.infini.cloud/elasticsearch/analysis-ik/8.4.1

安装完毕后,重新启动 Elasticsearch,会自动加载安装好的插件,然后我们定义一个 mapping,指定搜索分词器 search_analyzer 为 ik_smart (书上是 ik_max_word )

from elasticsearch import Elasticsearch

# 连接 Elasticsearch 默认连接本地9200端口
es = Elasticsearch(
	hosts:"localhost",
	port:9200,
	verify_certs=True # 是否验证证书
)


# 指定搜索分词器 search_analyzer 为 ik_smart (书上是 ik_max_word )
mapping = {
        "properties": {
            "content": {
                "type": "text",
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_smart"
            }
        }
}
es.indices.put_mapping(index='news', body=mapping)

然后我们可以使用 search 函数搭配 query 进行解锁;

# 基础查询 -> 查询 test 索引前 10 条数据 
es.search(index='test')

# 过滤字段查询 -> 只显示test索引的age字段信息
es.search(index='test',filter_path=['hits.hits._source.age'])

# 切片查询 -> 查询test索引中,从序号为1的位置查询两条数据
es.search(index='test', from_=1, size=2)

# 模糊查询(分词)-> 查询test索引中,age字段为20的数据
es.search(index='test', query={'match':{'age':20}})

# 模糊查询(不分词)-> 查询test索引中,name字段为杨晨的数据
es.search(index='test', query={'match_phrase':{'name':'杨晨'}})

# 精准单值查询 -> 查询test索引中,age为20的数据
es.search(index='test', query={'term':{'age':20}})

# 精准单值查询 -> 查询test索引中,name为杨晨的数据,查询中文,要在字段后面加上.keyword
es.search(index='test', query={'term':{'name.keyword':'杨晨'}})

# 精准多值查询 -> 查询test索引中,name为杨晨或小明的数据
es.search(index='test', query={'terms':{'name.keyword':['杨晨','小明']}})

# 多字段查询 -> 查询test索引中,name和about都为小美的数据
es.search(index='test',query={'multi_match':{'query':'小美',"fields":['name','about']}})

# 前缀查询 -> 查询test索引中,name字段前缀为小的数据
es.search(index='test',query={'prefix':{'name.keyword':'小'}})

# 通配符查询 -> 查询test索引中,name字段为杨*的数据
es.search(index='test',query={'wildcard':{'name.keyword':'杨?'}})

# 正则查询 -> 查询test索引中,name字段为杨*的数据
es.search(index='test',query={'regexp':{'name.keyword':'杨.'}})

# 多条件查询 -> 查询test索引中,name字段为小美,id字段为1的数据
es.search(index='test',query={'bool':{'must':{'term':{'name':'小美'},'term':{'id':'1'}}}})

# 存在字段查询 -> 查询test索引中,包含age字段的数据
es.search(index='test',query={'exists':{'field':'age'}})

# 范围查询 -> 查询test索引中,age字段大于20小于等于23的数据
es.search(index='test',query={'range':{'age':{'gt':20,'lte':23}}})

# Json字段查询 -> 查询test索引中,jsonfield1字段下json数据jsonfield2字段的数据包含'json'的数据
es.search(index='test',query={'nested':{'path':'jsonfield1','query':{'term':{'jsonfield1.jsonfield2':'json'}}}})

# 排序 -> 查询test索引中的数据,按照age字段降序
es.search(index='test', sort={'age.keyword':{'order':'desc'}})

2) dsl 语句:Search DSL — Elasticsearch DSL 8.14.0 documentation (elasticsearch-dsl.readthedocs.io)

5. RabbitMQ

首先需要安装 RabbitMQ,安装完毕后,还有需要安装一个操作 RabbitMQ 的库,pika

  1. RabbitMQ 的安装 | 静觅 (cuiqingcai.com)
  2. Installing on Windows | RabbitMQ
  3. Introduction to Pika — pika 1.3.2 documentation
pip install pika

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

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

在这里我们可以使用 RabbitMQ 作为消息队列的中间件来存储和转化信息,有了消息队列中间件后,以上各机制中的两个进程就可以独立执行,他们之间的通信则由消息队列实现;

  1. 声明队列:通过指定一些参数,创建消息队列;
  2. 生产内容:根据队列的连接信息连接队列,往队列中放入消息;
  3. 消费内容:根据队列的连接信息连接队列,从队列中取出消息;

1) 声明队列

无论是生产队列还是消费队列,都需要创建 channel 才可以操作队列,其中生产队列需要声明队列

import pika

QUEUE_NAME = 'scrape'
parameters = pika.ConnectionParameters(host='losthost')
connection = pika.BlockingConnection(parameters)

channel = connection.channel()

# 生产队列需要声明
channel.queue_declare(queue=QUEUE_NAME)

2) 生产内容

在声明队列的时候,添加 durable 参数可以持久化

import pika
import requests
import pickle

MAX_PRIORITY = 100
TOTAL = 100
QUEUE_NAME = 'scrape'

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue=QUEUE_NAME, durable=True)

for i in range(1, TOTAL + 1):
    url = f'https://ssr1.scrape.center/detail/{i}'
    request = requests.Request('GET', url)
    print('re', request)
    channel.basic_publish(exchange='',
                          routing_key=QUEUE_NAME,
                          properties=pika.BasicProperties(
                              delivery_mode=2,
                          ),
                          body=pickle.dumps(request))
    print(f'Put request of {url}')

3) 消费内容

import pika
import pickle
import requests

MAX_PRIORITY = 100
QUEUE_NAME = 'scrape3'

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost'))
channel = connection.channel()


session = requests.Session()


def scrape(request):
    try:
        response = session.send(request.prepare())
        print(f'success scraped {response.url}')
    except requests.RequestException:
        print(f'error occurred when scraping {request.url}')


while True:
    method_frame, header, body = channel.basic_get(
        queue=QUEUE_NAME, auto_ack=True)
    if body:
        print(f'Get {body}')
        request = pickle.loads(body)
        print(request)
        scrape(request)

更具体的介绍在 Introduction to Pika — pika 1.3.2 documentationPython3WebSpider/RabbitMQTest: RabbitMQ Test (github.com)

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值