逆向爬虫14 Mongo入门
一、MongoDB和MySQL的区别
MongoDB
是一种 非关系型数据库
,存放任意形式的 json
格式数据;而 MySQL
是一种 关系型数据库
,只能存放事先定义好 字段
的表格数据。下面是 MongoDB
和 MySQL
对数据叫法不同的比较。
MySQL | MongoDB |
---|---|
数据库(文件夹) | 数据库(文件夹) |
表(文件,不同的表之间可以有关系) | 集合(文件,不同的集合之间没有关系) |
记录(表格的一行) | 文档(一个json格式数据) |
二、何时使用关系型?何时使用非关系型?
关系型数据库
比较合适用于需要对数据进行业务逻辑开发的场景,大量应用于Web全栈开发中,用户通过前端将信息提交给后端,后端再通过业务逻辑处理后,将需要存储的数据保存到 关系型数据库
中;当用户在前端申请查看信息后,后端从 关系型数据库
中读取数据,经业务逻辑处理后,发送给前端,再经过前端渲染后呈现在用户面前。在这个过程中,数据库中所存放的内容,严格的受到业务逻辑的控制,因此在创建数据库之前,就需要根据业务逻辑先确定每一张表的字段名,不同表之间的关系,这些关系确定好后就很少再会变动,除非业务逻辑需要。
非关系型数据库
相比于 关系型数据库
来说使用起来更为方便,即插即用。使用前无需事先确定字段,表与表关系等信息。数据格式只要满足 json
就可以存储。这对于需要存储大量不规则数据来说方便极了,爬虫所爬的每个网站的数据都是没有标准统一的,如果使用 关系型数据库
,在数据入库之前,就需要针对每一个网站需要爬取的不同信息,新建一张表。而 非关系型数据库
则无需这步操作,获取到数据后整理成 json
格式后即可以插入到数据库,十分方便。
因此,在只需要保存数据,且无需对数据进行业务逻辑处理的业务场景下,使用 非关系型数据库
更为合适,这也是我们要学习 MongoDB
的原因。
三、MongoDB的简单使用(了解)
MongoDB的安装这里就不赘述了,网上到处都有。这里直接进入正题。
show dbs 显示所有数据库(简写)
show databases 显示所有数据库(全写)
db 查看当前使用的数据库
use xxx 切换数据库
db.dropDatabase() 删除数据库
show collections 显示当前数据库中所有的集合
db.collection_name.insert({}) 往集合中插入一条数据{}中放json格式数据,如果集合不存在则创建,且没有大小限制
db.createCollection(name, {options}) 手动创建集合,可以增加options来限制集合,下面给个例子
db.createCollection("xxx_log", {capped:true, size:255}) capped:是否卷动, size:大小
上面这种创建方式特别适合用来存放日志类型的数据,当日志数据大于size大小后,会自动清除时间最早的数据,插入最新的数据
db.collection_name.isCapped() 判断集合是否有容量上限
四、MongoDB的增删改查
1. mongodb中常见的数据类型(了解)
Object ID: 主键ID
String: 字符串
Boolean: 布尔值
Integer: 数字
Double: 小数
Arrays: 数组
Object: 文档(关联其他对象) {sname: 李嘉诚, sage: 18, class: {cccc}}
Null: 空值
Timestamp: 时间戳
Date: 时间日期
2. mongodb添加数据
db.collection_name.insert({字段1:值1, 字段2:值2})
db.student.insert({name: '周杰伦', age: 18, hobby: ['哎哟,不错哦!', '耍酷']})
注意:如果集合不存在会自动创建
3. mongodb修改数据
3.1 update更新
db.collection_name.update({查询条件}, {待修改内容}, {multi: false, upsert: true})
multi 默认为false,只会修改一条数据
upsert 默认为true,待修改内容中如果存在新的数据则添加进去
db.student.update({name: '周杰伦'}, {$set: {title: '华语流行天王', age: 16}})
s e t 和 没 有 set和没有 set和没有set的区别:
$set只会修改当前给出的字段,其他内容保留
没有$set只会保留当前给出字段,其他内容删除
multi如果为true,必须使用$set,否则报错。
3.2 保存(save,了解)
db.collection_name.save({待保存的数据})
db.student.save({name: '王力宏', age: 188})
相当于新增了一条王力宏数据,如果待保存数据中包含已经存在数据的’_id’信息,则相当于update功能
4. mongodb删除数据
4.1 remove()
db.collection_name.remove({条件}, {justOne: true|false})
db.student.remove({name: '王力宏'}, {justOne: true})
4.2 deleteOne()
db.collection_name.deleteOne({条件})
db.student.deleteOne({name: '蔡依林'})
4.3 deleteMany()
db.collection_name.deleteMany({条件})
db.student.deleteMany({type: '歌手'})
5. mongodb查询数据
准备数据:
db.student.insert([
{name: "朱元璋", age:800, address:'安徽省凤阳', score: 160},
{name: "朱棣", age:750, address:'江苏省南京市', score: 120},
{name: "朱高炽", age:700, address:'北京紫禁城', score: 90},
{name: "李嘉诚", age:38, address:'香港xxx街道', score: 70},
{name: "麻花藤", age:28, address:'广东省xxx市', score: 80},
{name: "大老王", age:33, address:'火星第一卫星', score: -60},
{name: "咩咩", age:33, address:'开普勒225旁边的黑洞', score: -160}
])
5.1 普通查询
db.student.find() 查询所有
db.student.findOne() 查询一个
db.student.find({条件}) 条件查询
5.2 比较运算
等于:默认是等于判断,$eq
小于:$lt (less than)
小于等于:$lte (less than equal)
大于:$gt (greater than)
大于等于:$gte (greater than equal)
不等于:$ne (not equal)
db.student.find({age:28}) // 查询年龄为28的学生
db.student.find({age:{$eq:28}}) // 查询年龄为28的学生
db.student.find({age:{$gt:28}}) // 查询年龄大于28的学生
db.student.find({age:{$gte:28}}) // 查询年龄大于等于28的学生
db.student.find({age:{$lt:38}}) // 查询年龄小于38的学生
db.student.find({age:{$lte:38}}) // 查询年龄小于等于38的学生
db.student.find({age:{$ne:38}}) // 查询年龄不等于38的学生
5.3 逻辑运算符
-
and
$and: [条件1,条件2,条件3]
查询年龄等于33,并且,名字是'大老王'的学生
db.student.find({$and: [{age: {$eq:33}}, {name: '大老王'}]})
-
or
$or: [条件1,条件2,条件3]
查询名字叫'李嘉诚',或者,年龄大于100岁的学生
db.student.find({$or: [{name: '李嘉诚'}, {age: {$gt: 100}}]})
-
nor
$nor: [条件1,条件2,条件3]
查询年龄不等于38,并且,名字不叫'朱元璋'的学生
db.student.find({$nor: [{age: {$lt:38}}, {name: '朱元璋'}]})
5.4 范围运算符
使用 i n , in, in,nin判断数据是否在某个数组内
查询年龄是28或38的学生
db.student.find({age: {$in: [28, 38]}})
5.5 正则表达式
使用$regex进行正则表达式匹配
查询地址是北京的学生信息
db.student.find({address: {$regex: '^北京'}})
db.student.find({address: /^北京/})
5.6 自定义查询 (了解)
mongo shell是一个js的执行环境
使用 $where 写一个函数,返回满足条件的数据
查询年龄大于38岁的学生信息
db.student.find({$where: function(){return this.age > 38}})
5.7 skip 和 limit
db.student.find().skip(3).limit(3)
跳过3个,提取3个,类似limit 3,3 可以用来做分页
5.8 投影
投影可以控制最终查询的结果(字段筛选)
查询所有学生的数据的姓名,年龄,分数,但不显示它们的_id
db.student.find({}, {_id: 0, name: 1, age: 1, score: 1})
需要看的字段给1
注意,除了_id外,0,1不能共存
5.9 排序
sort({字段:1, 字段:-1})
1表示升序
-1表示降序
将所有学生的成绩按照降序排列
db.student.find().sort({score:-1})
5.10 统计数量
count(条件) 查询数量
统计年龄为33的学生个数
db.student.count({age: 33})
五、pymongo的使用
1. 增删改查操作
from audioop import add
from pymongo import MongoClient
def get_db(database, user=None, pwd=None):
client = MongoClient(host='localhost', port=27017) # 默认端口号:27017
# 如果有账号,则需要登录
# admin = client['admin']
# admin.authenticate(user, pwd)
# 如果没有设置用户名密码,直接切换就可以了
db = client[database] # use haha
return db
def add_one(database, table, data):
db = get_db(database)
result = db[table].insert_one(data)
return result
def add_many(database, table, data_list):
db = get_db(database)
result = db[table].insert_many(data_list)
return result
def upd(database, table, condition, data):
db = get_db(database)
result = db[table].update_many(condition, {'$set': data})
return result
def delete(database, table, condition):
db = get_db(database)
result = db[table].delete_many(condition)
return result
def query(database, table, condition):
db = get_db(database)
result = db[table].find(condition)
return list(result)
if __name__ == '__main__':
# 增加一条学生信息
ret = add_one('haha', 'student', {'name': '周杰伦', 'age': 18, 'address': '元宇宙', 'score': '无穷大'})
print(ret)
# 增加多条学生信息
data_list = [
{'name': '蔡依林', 'age': 17, 'address': '元宇宙', 'score': '无穷大大'},
{'name': '陈奕迅', 'age': 19, 'address': '元宇宙', 'score': '无穷小'},
{'name': '张学友', 'age': 20, 'address': '元宇宙', 'score': '无穷小小'}
]
ret = add_many('haha', 'student', data_list)
print(ret)
# 修改学生信息
ret = upd('haha', 'student', {"address": "元宇宙"}, {"score": 100})
print(ret)
# 删除学生信息
ret = delete('haha', 'student', {"address": "元宇宙"})
print(ret)
# 查询年纪大于33岁的学生信息
ret = query('haha', 'student', {"age": {"$gt": 33}})
for r in ret:
print(r)
# 查询北京学生的信息
ret = query('haha', 'student', {"address": {"$regex": "^北"}})
for r in ret:
print(r)
2. 抓取二手房信息
import requests
from lxml import etree
from mangodb import add_many
import pymysql
def get_page_source(url):
resp = requests.get(url)
page_source = resp.text
return page_source
def parse(html):
tree = etree.HTML(html)
li_list = tree.xpath('//ul[@class="sellListContent"]/li')
result = []
for li in li_list:
title = li.xpath('./div[1]/div[1]/a/text()')[0]
address = ' '.join(li.xpath('./div[1]/div[2]/div/a/text()'))
houseInfo = li.xpath('./div[1]/div[3]/div/text()')[0]
starInfo = li.xpath('./div[1]/div[4]/text()')[0]
tag = ' '.join(li.xpath('./div[1]/div[5]/span/text()'))
total_price = li.xpath('./div[1]/div[6]/div[1]/span/text()')[0] + '万元'
per_price = li.xpath('./div[1]/div[6]/div[2]/span/text()')[0]
dic = {
"title": title,
"address": address,
"houseInfo": houseInfo,
"starInfo": starInfo,
"tag": tag,
"total_price": total_price,
"per_price": per_price
}
result.append(dic)
return result
def save_to_mongo(data_list):
add_many('ershoufang', 'ershoufang', data_list)
print("一页保存完毕!")
def save_to_mysql(data_list):
try:
conn = pymysql.connect(
host='localhost',
port=3306,
user='root', # The first four arguments is based on DB-API 2.0 recommendation.
password="xxxxxx",
database='spider'
)
cursor = conn.cursor()
sql = """
insert into ershoufang(title, address, houseInfo, startInfo, tag, total_price, per_price) values
(%s, %s, %s, %s, %s, %s, %s)
"""
lst = (tuple(dic.values()) for dic in data_list)
cursor.executemany(sql, lst)
conn.commit()
print("一页保存完毕!")
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
if __name__ == '__main__':
for i in range(1, 31):
url = "https://bj.lianjia.com/ershoufang/pg{i}/"
page_source = get_page_source(url)
data_list = parse(page_source)
# save_to_mongo(data_list)
save_to_mysql(data_list)