python使用adbapi实现MySQL数据库的异步存储


Posted in Python onMarch 19, 2019

之前一直在写有关scrapy爬虫的事情,今天我们看看使用scrapy如何把爬到的数据放在MySQL数据库中保存。

有关python操作MySQL数据库的内容,网上已经有很多内容可以参考了,但都是在同步的操作MySQL数据库。在数据量不大的情况下,这种方法固然可以,但是一旦数据量增长后,MySQL就会出现崩溃的情况,因为网上爬虫的速度要远远高过往数据库中插入数据的速度。为了避免这种情况发生,我们就需要使用异步的方法来存储数据,爬虫与数据存储互不影响。

为了显示方便,我们把程序设计的简单一点,只是爬一页的数据。我们今天选择伯乐在线这个网站来爬取,只爬取第一页的数据。

首先我们还是要启动一个爬虫项目,然后自己建了一个爬虫的文件jobbole.py。我们先来看看这个文件中的代码

# -*- coding: utf-8 -*-
import io
import sys
import scrapy
import re
import datetime
from scrapy.http import Request
from urllib import parse
from ArticleSpider.items import JobboleArticleItem, ArticleItemLoader
from scrapy.loader import ItemLoader
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
 
class JobboleSpider(scrapy.Spider):
 """docstring for JobboleSpider"""
 name = "jobbole"
 allowed_domain = ["blog.jobbole.com"]
 start_urls = ['http://blog.jobbole.com/all-posts/']
 
 def parse(self, response):
 """
 1.获取列表页中的文章url
 """
 # 解析列表汇中所有文章url并交给scrapy下载器并进行解析
 post_nodes = response.css("#archive .floated-thumb .post-thumb a")
 for post_node in post_nodes:
 image_url = post_node.css("img::attr(src)").extract_first("")# 这里取出每篇文章的封面图,并作为meta传入Request
 post_url = post_node.css("::attr(href)").extract_first("")
 yield Request(url = parse.urljoin(response.url, post_url), meta = {"front_image_url":image_url}, callback = self.parse_detail)
 
 def parse_detail(self, response):
 article_item = JobboleArticleItem()
 # 通过ItemLoader加载Item
 # 通过add_css后的返回值都是list型,所有我们再items.py要进行处理
 item_loader = ArticleItemLoader(item = JobboleArticleItem(), response = response)
 item_loader.add_css("title", ".entry-header h1::text")
 item_loader.add_value("url", response.url)
 # item_loader.add_value("url_object_id", get_md5(response.url))
 item_loader.add_value("url_object_id", response.url)
 item_loader.add_css("create_date", "p.entry-meta-hide-on-mobile::text")
 item_loader.add_value("front_image_url", [front_image_url])
 item_loader.add_css("praise_nums", ".vote-post-up h10::text")
 item_loader.add_css("comment_nums", "a[href='#article-comment'] span::text")
 item_loader.add_css("fav_nums", ".bookmark-btn::text")
 item_loader.add_css("tags", "p.entry-meta-hide-on-mobile a::text")
 item_loader.add_css("content", "div.entry")
 
 article_item = item_loader.load_item()
 print(article_item["tags"])
 
 yield article_item
 pass

这里我把代码进行了简化,首先对列表页发出请求,这里只爬取一页数据,然后分析每一页的url,并且交给scrapy对每一个url进行请求,得到每篇文章的详情页,把详情页的相关内容放在MySQL数据库中。
这里使用itemloader来进行页面的解析,这样解析有个最大的好处就是可以把解析规则存放在数据库中,实现对解析规则的动态加载。但是要注意一点是使用itemloader中css方式和xpath方式得到的数据都是list型,因此还需要在items.py中再对相对应的数据进行处理。

接下来我们就来看看items.py是如何处理list数据的。

# -*- coding: utf-8 -*-
 
# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import datetime
import re
 
 
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, TakeFirst,Join
from ArticleSpider.utils.common import get_md5
 
 
def convert_date(value):
 try:
 create_date = datetime.datetime.strptime(create_date, "%Y/%m/%d").date()
 except Exception as e:
 create_date = datetime.datetime.now().date()
 return create_date
 
def get_nums(value):
 match_re = re.match(".*?(\d+).*", value)
 if match_re:
 nums = int(match_re.group(1))
 else:
 nums = 0
 
 return nums
 
def remove_comment_tags(value):
 # 去掉tags中的评论内容
 if "评论" in value:
 # 这里做了修改,如果返回"",则在list中仍然会占位,会变成类似于["程序员",,"解锁"]这样
 # return ""
 return None 
 else:
 return value
 
def return_value(value):
 return 
 
class ArticleItemLoader(ItemLoader):
 """docstring for AriticleItemLoader"""
 # 自定义ItemLoader
 default_output_processor = TakeFirst()
 
class ArticlespiderItem(scrapy.Item):
 # define the fields for your item here like:
 # name = scrapy.Field()
 pass
 
class JobboleArticleItem(scrapy.Item):
 """docstring for ArticlespiderItem"""
 title = scrapy.Field()
 create_date = scrapy.Field(
 input_processor = MapCompose(convert_date)
 )
 url = scrapy.Field()
 url_object_id = scrapy.Field(
 output_processor = MapCompose(get_md5)
 )
 # 这里注意front_image_url还是一个list,在进行sql语句时还需要处理
 front_image_url = scrapy.Field(
 output_processor = MapCompose(return_value)
 )
 front_image_path = scrapy.Field()
 praise_nums = scrapy.Field(
 input_processor = MapCompose(get_nums)
 )
 comment_nums = scrapy.Field(
 input_processor = MapCompose(get_nums)
 )
 fav_nums = scrapy.Field(
 input_processor = MapCompose(get_nums)
 )
 # tags要做另行处理,因为tags我们需要的就是list
 tags = scrapy.Field(
 input_processor = MapCompose(remove_comment_tags),
 output_processor = Join(",")
 )
 content = scrapy.Field()

首先我们看到定义了一个类ArticleItemloader,在这个类中只有一句话,就是对于每个items都默认采用list中的第一个元素,这样我们就可以把每个items中的第一个元素取出来。但是要注意,有些items我们是必须要用list型的,比如我们给ImagePipeline的数据就要求必须是list型,这样我们就需要对front_image_url单独进行处理。这里我们做了一个小技巧,对front_image_url什么都不错,因为我们传过来的front_image_url就是list型
在items的Field中有两个参数,一个是input_processor,另一个是output_processor,这两个参数可以帮助我们对items的list中的每个元素进行处理,比如有些需要用md5进行加密,有些需要用正则表达式进行筛选或者排序等等。

在进行mysql的pipeline之前,我们需要设计数据库,下面是我自己设计的数据库的字段,仅供参考

python使用adbapi实现MySQL数据库的异步存储

这里我把url_object_id作为该表的主键,由于它不会重复,所以适合做主键。

下面我们来看看数据库的pipeline。

# -*- coding: utf-8 -*-
 
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
import codecs
import json
from twisted.enterprise import adbapi
import MySQLdb
import MySQLdb.cursors
 
 
class MysqlTwistedPipeline(object):
 """docstring for MysqlTwistedPipeline"""
 #采用异步的机制写入mysql
 def __init__(self, dbpool):
 self.dbpool = dbpool
 
 @classmethod
 def from_settings(cls, settings):
 dbparms = dict(
 host = settings["MYSQL_HOST"],
 db = settings["MYSQL_DBNAME"],
 user = settings["MYSQL_USER"],
 passwd = settings["MYSQL_PASSWORD"],
 charset='utf8',
 cursorclass=MySQLdb.cursors.DictCursor,
 use_unicode=True,
 )
 dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
 
 return cls(dbpool)
 
 def process_item(self, item, spider):
 #使用twisted将mysql插入变成异步执行
 query = self.dbpool.runInteraction(self.do_insert, item)
 query.addErrback(self.handle_error, item, spider) #处理异常
 return item
 
 def handle_error(self, failure, item, spider):
 # 处理异步插入的异常
 print (failure)
 
 def do_insert(self, cursor, item):
 #执行具体的插入
 #根据不同的item 构建不同的sql语句并插入到mysql中
 # insert_sql, params = item.get_insert_sql()
 # print (insert_sql, params)
 # cursor.execute(insert_sql, params)
 insert_sql = """
 insert into jobbole_article(title, url, create_date, fav_nums, url_object_id)
 VALUES (%s, %s, %s, %s, %s)
 """
 # 可以只使用execute,而不需要再使用commit函数
 cursor.execute(insert_sql, (item["title"], item["url"], item["create_date"], item["fav_nums"], item["url_object_id"]))

在这里我们只是演示一下,我们只向数据库中插入5个字段的数据,分别是title,url,create_date,fav_nums,url_object_id。

当然你也可以再加入其它的字段。

首先我们看看from_settings这个函数,它可以从settings.py文件中取出我们想想要的数据,这里我们把数据库的host,dbname,username和password都放在settings.py中。实际的插入语句还是在process_item中进行,我们自己定义了一个函数do_insert,然后把它传给dbpool中用于插入真正的数据。

最后我们来看看settings.py中的代码,这里就很简单了。

MYSQL_HOST = "localhost"
MYSQL_DBNAME = "article_wilson"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"

其实这里是和pipeline中的代码是想对应的,别忘了把在settings.py中把pipeline打开。

ITEM_PIPELINES = {
 # 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
 # 'ArticleSpider.pipelines.JsonWithEncodingPipeline': 1
 
 # # 'scrapy.pipelines.images.ImagePipeline': 1,
 # 'ArticleSpider.pipelines.JsonExporterPipleline': 1
 # 'ArticleSpider.pipelines.ArticleImagePipeline': 2
 # 'ArticleSpider.pipelines.MysqlPipeline': 1
 'ArticleSpider.pipelines.MysqlTwistedPipeline': 1
}

好了,现在我们可以跑一程序吧。

scrapy crawl jobbole

下面是运行结果的截图

python使用adbapi实现MySQL数据库的异步存储

好了,以上就是今天的全部内容了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python continue语句用法实例
Mar 11 Python
Python使用函数默认值实现函数静态变量的方法
Aug 18 Python
python基础知识小结之集合
Nov 25 Python
Python整型运算之布尔型、标准整型、长整型操作示例
Jul 21 Python
python学习——内置函数、数据结构、标准库的技巧(推荐)
Apr 18 Python
Python类中方法getitem和getattr详解
Aug 30 Python
在python shell中运行python文件的实现
Dec 21 Python
np.random.seed() 的使用详解
Jan 14 Python
最新2019Pycharm安装教程 亲测
Feb 28 Python
python初步实现word2vec操作
Jun 09 Python
Keras设置以及获取权重的实现
Jun 19 Python
.img/.hdr格式转.nii格式的操作
Jul 01 Python
python异步存储数据详解
Mar 19 #Python
利用Python半自动化生成Nessus报告的方法
Mar 19 #Python
python实现手机销售管理系统
Mar 19 #Python
Python使用修饰器进行异常日志记录操作示例
Mar 19 #Python
python学生管理系统学习笔记
Mar 19 #Python
Python操作rabbitMQ的示例代码
Mar 19 #Python
Python Matplotlib实现三维数据的散点图绘制
Mar 19 #Python
You might like
PHP中simplexml_load_string函数使用说明
2011/01/01 PHP
使用PHP免费发送定时短信的实例
2016/10/24 PHP
几种设置表单元素中文本输入框不可编辑的方法总结
2013/11/25 Javascript
Egret引擎开发指南之编译项目
2014/09/03 Javascript
Javascript aop(面向切面编程)之around(环绕)分析
2015/05/01 Javascript
详解JavaScript中的表单验证
2015/06/16 Javascript
使用CoffeeScrip优美方式编写javascript代码
2015/10/28 Javascript
jQuery each函数源码分析
2016/05/25 Javascript
jQuery实现公告新闻自动滚屏效果实例代码
2016/07/14 Javascript
JS实现给对象动态添加属性的方法
2017/01/05 Javascript
详解Vue.use自定义自己的全局组件
2017/06/14 Javascript
微信小程序获取手机号授权用户登录功能
2017/11/09 Javascript
JavaScript设计模式之原型模式分析【ES5与ES6】
2018/07/26 Javascript
Bootstrap Table列宽拖动的方法
2018/08/15 Javascript
vue中npm包全局安装和局部安装过程
2019/09/03 Javascript
Vue.js组件通信之自定义事件详解
2019/10/19 Javascript
深入了解Vue.js 混入(mixins)
2020/07/23 Javascript
解决Vue大括号字符换行踩的坑
2020/11/09 Javascript
Python比较两个图片相似度的方法
2015/03/13 Python
实例解析Python的Twisted框架中Deferred对象的用法
2016/05/25 Python
Python错误: SyntaxError: Non-ASCII character解决办法
2017/06/08 Python
Python实现数据可视化看如何监控你的爬虫状态【推荐】
2018/08/10 Python
Django框架model模型对象验证实现方法分析
2019/10/02 Python
10行Python代码计算汽车数量的实现方法
2019/10/23 Python
同步和异步有何异同,在什么情况下分别使用他们
2013/04/09 面试题
美术国培研修感言
2014/02/12 职场文书
计算机系本科生求职信
2014/05/31 职场文书
2014年小学工作总结
2014/11/26 职场文书
2014年档案室工作总结
2014/12/01 职场文书
放弃遗产继承公证书
2015/01/26 职场文书
求职简历自我评价范文
2015/03/10 职场文书
陪护人员误工证明
2015/06/24 职场文书
创业计划书之DIY自助厨房
2019/09/06 职场文书
SQL Server连接查询的实用教程
2021/04/07 SQL Server
解决SpringCloud Feign传对象参数调用失败的问题
2021/06/23 Java/Android
Java获取e.printStackTrace()打印的信息方式
2021/08/07 Java/Android