保存到json中
- 在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()
- 在setting.py中配置pipelines
ITEM_PIPELINES = {
# 'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
'ArticleSpider.pipelines.JsonWhithEncondingPipeline': 2,
'ArticleSpider.pipelines.ArticleImagesPipeline': 1,
# 'scrapy.pipelines.images.ImagesPipeline': 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
- 将日期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
- 在mysql中创建数据表
- 安装 mysqlclient 下载地址
- 编写对应的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,
}
通过连接池实现异步
- 在setting中配置
MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"
- 编写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机制
- 在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(",")
)
- 在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