文章目录
- 保存数据到数据库
- (一)数据保存至 MySQL 数据库
- 1. 安装 pymysql
- 2. 创建数据库与表
- 3. 实现 MySQLPipeline
- (二)数据保存至 MongoDB 数据库
- 1. 安装 pymongo
- 2. 实现 MongoDBPipeline
- (三)数据保存至 Redis 数据库
- 1. 安装 redis
- 2. 实现 RedisPipeline
- (四)项目实例——爬取360图片保存到数据库
- 1. 将数据保存到 MySQL 数据库
- 2. 将数据保存到 MongoDB 数据库
- 3. 运行爬虫
保存数据到数据库
在之前的章节中,曾讨论过将爬取到的数据导出到文件的相关话题,但在某些时候,我们希望将爬取到的数据存储到数据库中,这一章来学习使用 Item Pipeline 实现 Scrapy 爬虫和几种常用数据库的接口。
下面我们继续以爬取网站 [http://books.toscrape.com] 中的书籍信息为例,其中每一本书的信息包括:书名、价格、评价等级、产品编码、是否有货以及图片地址。
现在我们在第七节中所创建的项目基础上进一步完善,学习如何在爬取数据的过程中将书籍信息存储到各种数据库,这些数据库主要有:MySQL、MongoDB、Redis。
(一)数据保存至 MySQL 数据库
MySQL是一个应用极其广泛的关系型数据库,它是开源免费的,可以支持大型数据库,在个人用户和中小企业中成为技术首选。
1. 安装 pymysql
在 Python 中可以使用第三方库 pymysql 访问 MySQL 数据库,使用 pip 安装 pymysql:
$ sudo pip install pymysql
2. 创建数据库与表
使用客户端登录MySQL,创建一个供 Scrapy 使用的数据库,取名为 scrapy_db:
$ mysql -uroot -pqwe123
...
mysql> CREATE DATABASE scrapy_db CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
Query OK, 1 row affected (0.00 sec)
mysql> USE scrapy_db;
Database changed
接下来,创建存储书籍数据的表:
mysql> create table books(
-> name VARCHAR(256) NOT NULL,
-> price VARCHAR(20) NOT NULL,
-> rate INT,
-> availability VARCHAR(10),
-> img_url VARCHAR(256)
-> );
Query OK, 0 rows affected (0.01 sec)
3. 实现 MySQLPipeline
在项目文件夹下的 pipelines.py
中实现 MySQLPipeline
,代码如下:
python">import pymysql
class MySQLPipeline():
def open_spider(self, spider):
host = spider.settings.get('MYSQL_HOST')
port = spider.settings.get('MYSQL_PORT')
database = spider.settings.get('MYSQL_DATABASE')
user = spider.settings.get('MYSQL_USER')
password = spider.settings.get('MYSQL_PASSWORD')
self.db_connect = pymysql.connect(host=host, port=port, database=database, user=user, password=password, charset='utf8')
self.cursor = self.db_connect.cursor()
def close_spider(self, spider):
self.db_connect.commit()
self.db_connect.close()
def process_item(self, item, spider):
self.insert_db(item)
return item
def insert_db(self, item):
values = (
# item['upc'],
item['name'],
item['price'],
item['rate'],
item['availability'],
item['img_url'],
)
sql = 'INSERT INTO books VALUES (%s,%s,%s,%s,%s)'
self.cursor.execute(sql, values)
print("数据保存成功!")
在配置文件 settings.py 中指定我们所要使用的 MySQL 数据库,并启用 MySQLPipeline:
python">ITEM_PIPELINES = {
...
'scrapy_books.pipelines.MySQLPipeline': 600,
...
}
...
# MySQL数据库相关配置
MYSQL_HOST = '127.0.0.1'
MYSQL_DATABASE = 'scrapy_db'
MYSQL_PORT = 3306
MYSQL_USER = 'kevin'
MYSQL_PASSWORD = '20210401'
然后执行爬虫,程序运行结束后,我们就可以在数据库中看到保存的数据:
结果表明,我们成功地将1000条数据存储到了 MySQL 数据库。
上述代码中,同样是先执行全部的插入语句(INSERT INTO),最后一次性调用 commit 方法提交给数据库。或许在某些情况下,我们的确需要每执行一条插入语句,就立即调用 commit 方法更新数据库,如爬取过程很长,中途可能被迫中断,这样程序就不能执行到最后的 commit 。如果在上述代码的 insert_db 方法中直接添加 self.db_conn.commit()
,又会使程序执行慢得让人无法忍受。为解决以上难题,下面讲解另一种实现方法。
Scrapy 框架自身是使用另一个 Python 框架 Twisted 编写的程序,Twisted 是一个事件驱动型的异步网络框架,鼓励用户编写异步代码,Twisted 中提供了以异步方式多线程访问数据库的模块 adbapi
,使用该模块可以显著提高程序访问数据库的效率。下面我们就以这个模块改写 MySQLPipeline
:
python">class MySQLAsyncPipeline():
def open_spider(self, spider):
host = spider.settings.get('MYSQL_HOST')
port = spider.settings.get('MYSQL_PORT')
database = spider.settings.get('MYSQL_DATABASE')
user = spider.settings.get('MYSQL_USER')
password = spider.settings.get('MYSQL_PASSWORD')
self.dbpool = adbapi.ConnectionPool('pymysql', host=host, port=port,db=database, user=user, password=password, charset='utf8')
def close_spider(self, spider):
self.dbpool.close()
def process_item(self, item, spider):
self.dbpool.runInteraction(self.insert_db, item)
return item
def insert_db(self, tx, item):
values = (
# item['upc'],
item['name'],
item['price'],
item['rate'],
item['availability'],
item['img_url'],
)
sql = 'INSERT INTO books VALUES (%s,%s,%s,%s,%s)'
tx.execute(sql, values)
print("数据保存成功!")
上述代码解释如下:
adbapi.ConnectionPool()
方法可以创建一个数据库连接池对象,其中包含多个连接对象,每个连接对象在独立的线程中工作。adbapi 只是提供了异步访问数据库的编程框架,在其内部依然使用 pymysql、sqlite3 这样的库访问数据库。ConnectionPool()
方法的第一个参数就是用来指定使用哪个库访问数据库,其他参数在创建连接对象时使用。dbpool.runInteraction(insert_db, item)
以异步方式调用instert_db
函数,dbpool 会选择连接池中的一个连接对象在独立线程中调用insert_db
,其中参数 item 会被传给insert_db
的第二个参数,传给insert_db
的第一个参数是一个Transaction
对象,其接口与 Cursor 对象类似,可以调用 execute 方法执行 SQL 语句,insert_db
执行完后,连接对象会自动调用 commit 方法。
(二)数据保存至 MongoDB 数据库
MongoDB 是一个面向文档的非关系型数据库(NoSQL),它功能强大、灵活、易于拓展,近年来在多个领域得到广泛应用。
1. 安装 pymongo
在 Python 中可以使用第三方库 pymongo 访问 MongoDB 数据库,使用 pip 安装 pymongo:
$ sudo pip install pymongo
2. 实现 MongoDBPipeline
在项目文件夹下的 pipelines.py
中实现 MongoDBPipeline
,代码如下:
python">from pymongo import MongoClient
from scrapy import Item
class MongoDBPipeline():
def open_spider(self, spider):
db_uri = spider.settings.get('MONGODB_URI')
db_name = spider.settings.get('MONGODB_DB_NAME')
self.db_client = MongoClient(db_uri)
self.db = self.db_client[db_name] # 指定数据库
def close_spider(self, spider):
self.db_client.close()
def process_item(self, item, spider):
self.insert_db(item)
return item
def insert_db(self, item):
if isinstance(item, Item):
item = dict(item)
self.db.books.insert_one(item)
在配置文件 settings.py 中指定我们所要使用的 MongoDB 数据库,并启用 MongoDBPipeline:
python">ITEM_PIPELINES = {
...
'scrapy_books.pipelines.MongoDBPipeline': 700,
...
}
...
# MongoDB数据库相关配置
MONGODB_URI = 'mongodb://127.0.0.1:27017'
MONGODB_DB_NAME = 'scrapy_db'
然后执行爬虫,程序运行结束后,我们就可以在数据库中看到保存的数据:
(py3env) pyvip@VIP:~$ mongo
...
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
scrapy_db 0.000GB
student_system 0.000GB
> use scrapy_db
switched to db scrapy_db
> show collections
books
> db.books.find()
{ "_id" : ObjectId("6087e1c2870db595ab60e942"), "name" : "A Light in the Attic", "price" : "¥441.64", "rate" : 3, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e943"), "name" : "Tipping the Velvet", "price" : "¥458.45", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/26/0c/260c6ae16bce31c8f8c95daddd9f4a1c.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e944"), "name" : "Soumission", "price" : "¥427.40", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/3e/ef/3eef99c9d9adef34639f510662022830.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e945"), "name" : "Sharp Objects", "price" : "¥407.95", "rate" : 4, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/32/51/3251cf3a3412f53f339e42cac2134093.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e946"), "name" : "Sapiens: A Brief History of Humankind", "price" : "¥462.63", "rate" : 5, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/be/a5/bea5697f2534a2f86a3ef27b5a8c12a6.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e947"), "name" : "The Requiem Red", "price" : "¥193.22", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/68/33/68339b4c9bc034267e1da611ab3b34f8.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e948"), "name" : "The Dirty Little Secrets of Getting Your Dream Job", "price" : "¥284.42", "rate" : 4, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/92/27/92274a95b7c251fea59a2b8a78275ab4.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e949"), "name" : "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull", "price" : "¥152.96", "rate" : 3, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/3d/54/3d54940e57e662c4dd1f3ff00c78cc64.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94a"), "name" : "The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics", "price" : "¥192.80", "rate" : 4, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/66/88/66883b91f6804b2323c8369331cb7dd1.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94b"), "name" : "The Black Maria", "price" : "¥444.89", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/58/46/5846057e28022268153beff6d352b06c.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94c"), "name" : "Starving Hearts (Triangular Trade Trilogy, #1)", "price" : "¥119.35", "rate" : 2, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/be/f4/bef44da28c98f905a3ebec0b87be8530.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94d"), "name" : "Shakespeare's Sonnets", "price" : "¥176.25", "rate" : 4, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/10/48/1048f63d3b5061cd2f424d20b3f9b666.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94e"), "name" : "Set Me Free", "price" : "¥148.95", "rate" : 5, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/5b/88/5b88c52633f53cacf162c15f4f823153.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e94f"), "name" : "Scott Pilgrim's Precious Little Life (Scott Pilgrim #1)", "price" : "¥446.08", "rate" : 5, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/94/b1/94b1b8b244bce9677c2f29ccc890d4d2.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e950"), "name" : "Rip it Up and Start Again", "price" : "¥298.75", "rate" : 5, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/81/c4/81c4a973364e17d01f217e1188253d5e.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e951"), "name" : "Our Band Could Be Your Life: Scenes from the American Indie Underground, 1981-1991", "price" : "¥488.39", "rate" : 3, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/54/60/54607fe8945897cdcced0044103b10b6.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e952"), "name" : "Olio", "price" : "¥203.72", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/55/33/553310a7162dfbc2c6d19a84da0df9e1.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e953"), "name" : "Mesaerion: The Best Science Fiction Stories 1800-1849", "price" : "¥320.68", "rate" : 1, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/09/a3/09a3aef48557576e1a85ba7efea8ecb7.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e954"), "name" : "Libertarianism for Beginners", "price" : "¥437.89", "rate" : 2, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/0b/bc/0bbcd0a6f4bcd81ccb1049a52736406e.jpg" }
{ "_id" : ObjectId("6087e1c2870db595ab60e955"), "name" : "It's Only the Himalayas", "price" : "¥385.34", "rate" : 2, "availability" : true, "img_url" : "http://books.toscrape.com/media/cache/27/a5/27a53d0bb95bdd88288eaf66c9230d7e.jpg" }
Type "it" for more
结果表明,我们成功地将 1000 条数据存储到了 MongoDB 数据库。
(三)数据保存至 Redis 数据库
Redis是一个使用ANSI C编写的高性能Key-Value数据库,使用内存作为主存储,内存中的数据也可以被持久化到硬盘。
1. 安装 redis
在 Python 中可以使用第三方库 redis-py 访问 Redis 数据库,使用 pip 安装 redis-py:
$ sudo pip install redis
2. 实现 RedisPipeline
在项目文件夹下的 pipelines.py
中实现 RedisPipeline
,代码如下:
python">import redis
from scrapy import Item
class RedisPipeline:
def open_spider(self, spider):
db_host = spider.settings.get('REDIS_HOST')
db_port = spider.settings.get('REDIS_PORT')
db_index = spider.settings.get('REDIS_DB_INDEX')
self.db_conn = redis.StrictRedis(host=db_host, port=db_port, db=db_index)
self.item_i = 0
def close_spider(self, spider):
self.db_conn.connection_pool.disconnect()
def process_item(self, item, spider):
item['availability'] = str(item['availability'])
self.insert_db(item)
return item
def insert_db(self, item):
if isinstance(item, Item):
item = dict(item)
self.item_i += 1
self.db_conn.hmset('book:%s' % self.item_i, item)
在配置文件 settings.py 中指定我们所要使用的 Redis 数据库,并启用 RedisPipeline:
python">ITEM_PIPELINES = {
...
'scrapy_books.pipelines.RedisPipeline': 750,
...
}
...
# Redis数据库相关配置
REDIS_HOST = '127.0.0.1'
REDIS_PORT = 6379
REDIS_DB_INDEX = 0
然后执行爬虫,程序运行结束后,我们就可以在数据库中看到保存的数据:
(py3env) pyvip@VIP:~$ redis-cli
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> KEYS book:*
1) "book:280"
2) "book:432"
3) "book:313"
4) "book:38"
5) "book:542"
6) "book:411"
7) "book:459"
8) "book:482"
9) "book:864"
10) "book:784"
...
990) "book:19"
991) "book:136"
992) "book:502"
993) "book:96"
994) "book:405"
995) "book:651"
996) "book:78"
997) "book:35"
998) "book:296"
999) "book:386"
127.0.0.1:6379> HGETALL book:1
1) "name"
2) "A Light in the Attic"
3) "rate"
4) "3"
5) "price"
6) "\xef\xbf\xa5441.64"
7) "availability"
8) "True"
9) "img_url"
10) "http://books.toscrape.com/media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg"
127.0.0.1:6379> HGETALL book:2
1) "name"
2) "Tipping the Velvet"
3) "rate"
4) "1"
5) "price"
6) "\xef\xbf\xa5458.45"
7) "availability"
8) "True"
9) "img_url"
10) "http://books.toscrape.com/media/cache/26/0c/260c6ae16bce31c8f8c95daddd9f4a1c.jpg"
127.0.0.1:6379> HGETALL book:999
1) "name"
2) "1,000 Places to See Before You Die"
3) "rate"
4) "5"
5) "price"
6) "\xef\xbf\xa5222.49"
7) "availability"
8) "True"
9) "img_url"
10) "http://books.toscrape.com/media/cache/d7/0f/d70f7edd92705c45a82118c3ff6c299d.jpg"
结果表明,我们成功地将1000条数据存储到了 Redis 数据库。
(四)项目实例——爬取360图片保存到数据库
在前面的章节中,我们已经爬取360摄影美图并保存在了本地。下面我们在之前的基础上进一步将数据保存在数据库中。
1. 将数据保存到 MySQL 数据库
首先确保 MySQL 已经正确安装并且正常运行。新建一个数据库,名字还是 images360_db,SQL 语句如下所示:
$ mysql -uroot -pqwe123
...
mysql> CREATE DATABASE images360_db CHARSET=utf8;
新建一个数据表,包含 id、url、title、thumb
四个字段,SQL 语句如下所示:
mysql> CREATE TABLE images (
-> id VARCHAR(255) NULL PRIMARY KEY,
-> url VARCHAR(255) NULL,
-> title VARCHAR(255) NULL,
-> thumb VARCHAR(255) NULL
-> );
执行完 SQL 语句之后,我们就成功创建好了数据表。接下来就可以往表里存储数据了。 接下来我们实现一个 MySQLPipeline,代码如下所示:
python">import pymysql
class MySQLPipeline():
def open_spider(self, spider):
host = spider.settings.get('MYSQL_HOST')
port = spider.settings.get('MYSQL_PORT')
database = spider.settings.get('MYSQL_DATABASE')
user = spider.settings.get('MYSQL_USER')
password = spider.settings.get('MYSQL_PASSWORD')
self.db_connect = pymysql.connect(host=host, port=port, database=database, user=user, password=password, charset='utf8')
self.cursor = self.db_connect.cursor()
def close_spider(self, spider):
self.db_connect.commit()
self.db_connect.close()
def process_item(self, item, spider):
self.insert_db(item)
return item
def insert_db(self, item):
values = (
item['id'],
item['url'],
item['title'],
item['thumb'],
)
sql = 'INSERT INTO images VALUES (%s,%s,%s,%s)'
self.cursor.execute(sql, values)
print("数据保存成功!")
这里我们还需要几个 MySQL 的配置,我们在 settings.py 里添加几个变量,并启动 MySQLPipeline ,如下所示:
python">ITEM_PIPELINES = {
'image360.pipelines.ImagePipeline': 300,
'image360.pipelines.MySQLPipeline': 350,
}
# MySQL数据库相关配置
MYSQL_HOST = '127.0.0.1'
MYSQL_DATABASE = 'images360_db'
MYSQL_PORT = 3306
MYSQL_USER = 'kevin'
MYSQL_PASSWORD = '20210401'
这里分别定义了 MySQL 的地址、数据库名称、端口、用户名、密码。 这样,MySQLPipeline 的配置就完成了。
2. 将数据保存到 MongoDB 数据库
首先确保 MongoDB 已经正常安装并且正常运行。我们用一个 MongoPipeline 将信息保存到 MongoDB,在 pipelines.py 里添加如下类的实现:
python">from pymongo import MongoClient
from scrapy import Item
class MongoDBPipeline(object):
def open_spider(self, spider):
db_uri = spider.settings.get('MONGO_URI')
db_name = spider.settings.get('MONGO_DB_NAME')
self.db_client = MongoClient(db_uri) # 连接数据库
self.db = self.db_client[db_name] # 指定数据库
def close_spider(self, spider):
self.db_client.close()
def process_item(self, item, spider):
self.insert_db(item)
return item
def insert_db(self, item):
if isinstance(item, Item):
item = dict(item)
self.db.images.insert_one(item)
这里需要用到两个变量,MONGO_URI 和 MONGO_DB,即存储到 MongoDB 的链接地址和数据库名称。我们在 settings.py 里添加这两个变量,并启动 MongoDBPipeline ,如下所示:
python">ITEM_PIPELINES = {
'image360.pipelines.ImagePipeline': 300,
'image360.pipelines.MySQLPipeline': 350,
'image360.pipelines.MongoDBPipeline': 400,
}
# MongoDB数据库相关配置
MONGO_URI = 'mongodb://127.0.0.1:27017'
MONGO_DB_NAME = 'images360_db'
这样一个保存到 MongoDB 的 Pipeline 的就创建好了。这里最主要的方法是 process_item () 方法,直接调用 Collection 对象的 insert () 方法即可完成数据的插入,最后返回 Item 对象。
3. 运行爬虫
在与 scrapy.cfg
文件相同目录下,新建文件 bin.py
,编写如下内容:
python">from scrapy import cmdline
args = "scrapy crawl books".split()
cmdline.execute(args)
然后我们运行 bin.py
文件,执行爬虫。
程序运行结束之后,我们可以在 MySQL 数据库和 MongoDB 数据库中保存的图片信息:
上述文章内容如有错误,欢迎各位读者在评论区留言!