06.保存数据

保存到json中

  1. 在pipelines.py中写入
import codecs
import json

# 用来配置保存到json文件
class JsonWhithEncondingPipeline(object):
    def __init__(self):
        self.file = codecs.open('article.json', 'w', encoding="utf-8")

    def process_item(self, item, spider):
        lines = json.dumps(dict(item), ensure_ascii=False) + "\n"
        self.file.write(lines)
        return item

    def spider_closed(self, spider):
        self.file.close()
  1. 在setting.py中配置pipelines
ITEM_PIPELINES = {
   # 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
   'ArticleSpider.pipelines.JsonWhithEncondingPipeline': 2,
   'ArticleSpider.pipelines.ArticleImagesPipeline': 1,
    # 'scrapy.pipelines.images.ImagesPipeline': 1,
}
  1. 也可以用JsonItemExporter
from scrapy.exporters import JsonItemExporter

class JsonExporterPipleline(object):
    #调用scrapy提供的json export导出json文件
    def __init__(self):
        self.file = open('articleexport.json', 'wb')
        self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
        self.exporter.start_exporting()

    def close_spider(self, spider):
        self.exporter.finish_exporting()
        self.file.close()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item

在setting.py中配置pipelines

ITEM_PIPELINES = {
   # 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
   'ArticleSpider.pipelines.JsonExporterPipleline': 2,
   'ArticleSpider.pipelines.ArticleImagesPipeline': 1,
    # 'scrapy.pipelines.images.ImagesPipeline': 1,
}

保存到mysql

  1. 将日期item转换格式
import datetime


        try:
            crate_date = datetime.datetime.strptime(crate_date, "%Y/%m/%d").date()
        except Exception as e:
            crate_date = datetime.datetime.now().date()
        article_item["crate_date"] = crate_date
  1. 在mysql中创建数据表
  2. 安装 mysqlclient 下载地址
  3. 编写对应的pipeline
import MySQLdb

class MysqlPipeline(object):
    #采用同步的机制写入mysql
    def __init__(self):
        self.conn = MySQLdb.connect('127.0.0.1', 'root', 'root', 'article_spider', charset="utf8", use_unicode=True)
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        insert_sql = """
            insert into jobbole_article(title, url, create_date, fav_nums)
            VALUES (%s, %s, %s, %s)
        """
        self.cursor.execute(insert_sql, (item["title"], item["url"], item["create_date"], item["fav_nums"]))
        self.conn.commit()

将MysqlPipeline配置到setting中

ITEM_PIPELINES = {
   # 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
   'ArticleSpider.pipelines.JsonExporterPipeline': 2,
   'ArticleSpider.pipelines.ArticleImagesPipeline': 1,
   'ArticleSpider.pipelines.MysqlPipeline': 1,
    # 'scrapy.pipelines.images.ImagesPipeline': 1,
}

通过连接池实现异步

  1. 在setting中配置
MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"
  1. 编写pipeline
from twisted.enterprise import adbapi
import MySQLdb.cursors

# mysql连接池 异步操作防止数据量大造成入库阻塞
class MysqlTwistedPipline(object):
    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) #处理异常

    def handle_error(self, failure, item, spider):
        #处理异步插入的异常
        print (failure)

    def do_insert(self, cursor, item):
        # 执行具体的插入
        insert_sql = """
                    insert into jobbole_article(title, url, create_date, fav_nums)
                    VALUES (%s, %s, %s, %s)
                """
        cursor.execute(insert_sql, (item["title"], item["url"], item["create_date"], item["fav_nums"]))

配置setting

ITEM_PIPELINES = {
   'ArticleSpider.pipelines.MysqlTwistedPipline': 1,
}

item loader机制

  1. 在items.py中
import scrapy
import datetime
import re
from scrapy.loader.processors import MapCompose, TakeFirst, Join
from scrapy.loader import ItemLoader


# 时间转化
def date_convert(value):
    try:
        create_date = datetime.datetime.strptime(value, "%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


# 去掉tag中提取的评论
def remove_comment_tags(value):
    if "评论" in value:
        return ""
    else:
        return value


def return_value(value):
    return value


class ArticleItemLoader(ItemLoader):
    # 自定义itemloder
    default_output_processor = TakeFirst()  # 相当于extract()[0]


class JobBoleArticleItem(scrapy.Item):
    # 标题
    title = scrapy.Field()
    # 日期
    create_date = scrapy.Field(
        input_processor=MapCompose(date_convert),
    )
    # url
    url = scrapy.Field()
    url_object_id = scrapy.Field()  # url是变长 可以md5定常加密保存
    # 标题图
    front_image_url = scrapy.Field(
        output_processor=MapCompose(return_value)  # 覆盖掉默认的default_output_processor
    )
    # 标题图地址
    front_image_path = scrapy.Field()
    # 点赞数
    praise_nums = scrapy.Field(
        input_processor=MapCompose(get_nums)
    )
    # 喜爱数
    fav_nums = scrapy.Field(
        input_processor=MapCompose(get_nums)
    )
    # 评论数
    comment_nums = scrapy.Field(
        input_processor=MapCompose(get_nums)
    )
    # 文章内容
    content = scrapy.Field()
    # 标签
    tags = scrapy.Field(
        input_processor=MapCompose(remove_comment_tags),
        output_processor=Join(",")
    )
  1. 在jobbole.py中
import scrapy
import re
from scrapy.http import Request
from urllib import parse
from ArticleSpider.items import JobBoleArticleItem, ArticleItemLoader
from ArticleSpider.utils.common import get_md5

class JobboleSpider(scrapy.Spider):
    name = 'jobbole'
    allowed_domains = ['blog.jobbole.com']
    start_urls = ['http://blog.jobbole.com/all-posts/']



    def parse(self, response):
        """
        1.获取文章列表中的文章url并交给scrapy下载解析
        2.获取下一页的url并交给scrapy进行下载,下载后再交给parse
        """
        # 解析文章所有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("")
            post_url = post_node.css("::attr(href)").extract_first("")
            print(post_url)  # 文章url
            # parse.urljoin(response.url, post_url)表示有绝对域名就直接使用,相对域名就拼接主域名
            yield Request(url=parse.urljoin(response.url, post_url), meta={"front_image_url": image_url}, callback=self.parse_detail)

        # 提取下一页并交给scrapy进行下载
        next_urls = response.css(".next.page-numbers::attr(href)").extract_first()
        if next_urls:
            yield Request(url=parse.urljoin(response.url, next_urls), callback=self.parse)

    # 提取文章的具体字段
    def parse_detail(self, response):
        #通过item loader加载item
        front_image_url = response.meta.get("front_image_url", "")  # 文章封面图
        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_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()
        yield article_item