逆向爬虫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)










