目录
一、Scrapy架构流程
1.简介
2.优势
3.架构流程图
4.组件
二、Scrapy爬虫步骤
三、案例(三国演义名著定向爬虫项目)
1.新建Scrapy项目
2.明确目标(items.py)
3、制作爬虫
4、存储数据
其中,绿色为数据流向。
大致整体流程如下:
只有当调度器中不存在任何request时, 整个程序才会停止。(注:对于下载失败的URL,Scrapy也会重新下载. )
1)新建项目(scrapy startproject xxx)
命令行输入:
scrapy startproject 项目名称
cd 项目名称
scrapy genspider 爬虫名称 爬取网址
2)明确目标(编写item.py)
明确你要抓取的目标
3)制作爬虫(爬虫名称.py)
制作爬虫, 开始爬取网页;
4)存储爬虫(pipelines.py)
设置管道存储爬取内容
运行时,命令行输入:
scrapy crawl 爬虫名称
运行并存储:scrapy crawl 爬虫名称 -o 文件名
项目名
|—— 项目名
| |—— _init_.py #包定义
| |—— items.py #模型定义
| |—— middlewares.py #中间件定义
| |—— pipelines.py #管道定义
| |—— settings.py #配置文件。编程方式控制的配置文件
| |—— spider
| |—— _init_.py #默认蜘蛛代码文件
|——— scrapy.cfg #运行配置文件。该文件存放的目录为根目录。模块名的字段定义了项目的设置
明确自己爬取的网站,需要爬取的信息,查看源代码,分析代码。
编辑items.py文件:这里明确爬取小说名称,章节名称和每个章节的内容
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
from scrapy.loader.processors import TakeFirst
class ScrapyprojectItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
class BookItem(scrapy.Item):
# 定义item类:
# 继承scrapy.item
# 所有字段都定义为 scrapy.Field() 不管是什么类型
# ItemLoader返回列表
# 输入输出处理器
name = scrapy.Field(output_processor=TakeFirst()) # 只提取列表中的第一个元素
content = scrapy.Field(output_processor=TakeFirst())
bookname = scrapy.Field(output_processor=TakeFirst())
设置settings.py
编写spiders/xxspider.py文件,我这里是spiders/book.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request
from scrapy.loader import ItemLoader
from ScrapyProject.items import BookItem
"""
Scrapy爬虫流程:
1. 确定start_urls起始URL
2. 引擎将起始的URL交给调度器(存储到队列, 去重)
3. 调度器将URL发送给Downloader,Downloader发起Request从互联网上下载网页信息(Response)
4. 将下载的页面内容交给Spider, 进行解析(parse函数), yield 数据
5. 将处理好的数据(items)交给pipeline进行存储
下载图书到本地修改的内容:
1). 请求图书详情页parse(self, response)函数的修改-ScrapyProject/ScrapyProject/spiders/book.py
2). 对章节详情页进行解析parse_chapter_detail函数的修改-ScrapyProject/ScrapyProject/spiders/book.py
3). 将采集的数据存储到文件中, pipeeline组件-ScrapyProject/ScrapyProject/pipelines.py
4). 设置文件中启动pipeline组件-ScrapyProject/ScrapyProject/settings.py
"""
class BookSpider(scrapy.Spider):
# 爬虫的名称,必须唯一
name = 'book'
base_url = 'http://www.shicimingju.com'
# 限制: 爬取的url地址必须是'shicimingju.com'
# allowed_domains = ['shicimingju.com']
# 起始的url地址, 可以指定多个, 有两种方式指定:
# 1). start_urls属性设置=[]
# 2). 通过start_requests生成起始url地址
start_urls = [
'http://www.shicimingju.com/book/sanguoyanyi.html',
# 'http://www.shicimingju.com/book/xiyouji.html',
# 'http://www.shicimingju.com/book/hongloumeng.html',
]
def parse(self, response):
"""
1). 如何编写好的解析代码呢? 使用Scrapy的交互式工具scrapy shell url
2). 如何处理解析后的数据? 通过yield返回解析数据的字典格式
3). 如何获取/下载小说章节详情页的链接并下载到本地?
"""
# 1). 获取所有章节的li标签
chapters = response.xpath('//div[@class="book-mulu"]/ul/li')
# 2). 遍历每一个li标签, 提取章节的详细网址和章节名称
for chapter in chapters:
# -). 创建ItemLoader对象, 将item对象和selector/response关联
l = ItemLoader(item=BookItem(), selector=chapter)
detail_url = chapter.xpath('./a/@href').extract_first()
# -). 根据xpath进行提取数据信息并填充到item对象的name属性中
l.add_xpath('name', './a/text()')
# -). 将数据信息(书籍名称)填充到item对象的bookname属性中
l.add_value('bookname', response.url.split('/')[-1].strip('.html'))
# print("item对象: ", item)
# 将章节详情页的url提交到调度器队列, 通过Downloader下载器下载并交给self.parse_chapter_detail解析器进行解析处理数据。
yield Request(url=self.base_url + detail_url,
callback=self.parse_chapter_detail,
# -). load_item获取item对象
meta={'item': l.load_item()}
)
def parse_chapter_detail(self, response):
# 1). .xpath('string(.)')获取该标签及子孙标签所有的文本信息;
# 2). 如何将对象转成字符串?
# - extract_first()/get()-转换一个对象为字符串
# - extract()/get_all()-转换列表中的每一个对象为字符串
item = response.meta['item']
content = response.xpath('.//div[@class="chapter_content"]')[0].xpath('string(.)').get()
item['content'] = content
yield item
其中:
book.py文件中:
两种做法实现:
方法一、实例化item对象
# 实例化item对象
item = BookItem()
获取信息并存储到item中:
name = chapter.xpath('./a/text()').extract_first()
bookname = response.url.split('/')[-1].strip('.html')
item['name'] = name
item['bookname'] = bookname
print('item对象: ' ,item)
章节详情页的url提交到调度器的队列,通过Downloader下载器下载并交给self.parse_detail解析器进行解析数据
yield Request(url=self.base_url+detail_url,
callback=self.parse_chapter_detail,
meta={'item':item}
)
方法二、使用ItemLoader
# 创建ItemLoader对象 用response/selector根据情况而定
l = ItemLoader(item=BookItem(), selector=chapters)
根据xpath提取数据信息,并填充到item对象的属性中
l.add_xpath('name','./a/text()')
l.add_value('bookname',response.url.split('/')[-1].strip('.html'))
填充数据有三种方法:
调用xpath选择器:add_xpath
调用css选择器:add_css
直接给字段赋值:add_value
章节详情页的url提交到调度器的队列,通过Downloader下载器下载并交给self.parse_detail解析器进行解析数据
yield Request(url=self.base_url+detail_url,
callback=self.parse_chapter_detail,
meta = {'item':l.load_item()} # l.load_item():获取item对象
)
通过pipeline(管道)存储,编辑pipelines.py文件
解析数据部分yield返回的数据,以item的方式传入pipeline中,实现信息的持久化存储。
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import os
import sqlite3
import scrapy
import sqlalchemy
from scrapy.exceptions import DropItem
from scrapy.pipelines.images import ImagesPipeline
from sqlalchemy import create_engine, Column, String, Float, Integer
from sqlalchemy.exc import IntegrityError, InvalidRequestError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from colorama import Fore
from ScrapyProject.settings import BASE_DIR
class ScrapyprojectPipeline(object):
def process_item(self, item, spider):
"""将章节内容写入对应的章节文件"""
# books/hongloumeng
dirname = os.path.join('books', item['bookname'])
if not os.path.exists(dirname):
os.makedirs(dirname) # 递归创建目录
name = item['name']
# 文件名相对路径用join方法拼接: Linux路径拼接符是/, Windows路径拼接符是\
filename = os.path.join(dirname, name)
# 写入文件时以‘w’的方式打开, 并指定编码格式为utf-8来写入中文.
with open(filename, 'w', encoding='utf-8') as f:
f.write(item['content'])
# print("写入文件%s成功" %(name))
return item
class SqlitePipeline(object):
"""用过sqlalchemy(ORM)将数据信息添加到数据库中"""
# 创建数据库模型, 用于做基类
Base = declarative_base()
# 类-数据库表(products)
class Product(Base):
__tablename__ = "products"
title = Column(String(100))
image = Column(String(200))
# 每个商品只能爬取一次
link = Column(String(200), unique=True, primary_key=True) # 商品详情页网址必须唯一
price = Column(Float)
sizes = Column(String(200))
stocks = Column(Integer)
def __repr__(self):
"""字符串友好展示"""
return "{}:{}".format(self.title, self.stocks)
def open_spider(self, spider): # 创建/链接数据库
# 创建数据库引擎. sqlite:///相对路径, sqlite:////绝对路径
engine = create_engine("sqlite:///" + 'shopify.db')
# 创建session类
Session = sessionmaker(bind=engine)
# 根据数据库引擎的配置,创建数据库表
self.Base.metadata.create_all(engine)
# 生成session会话, 用于缓存
self.session = Session()
def process_item(self, item, spider):
"""
sqlite3.IntegrityError, sqlalchemy.exc.IntegrityError
# 1). 数据库已经存在该商品的详情页链接, 接着判断商品的库存是否发生变化?
- 如果库存发生变化,
- 当前库存>原库存, 通知补货商品名称和补货的数量(邮件/微信/钉钉/discord), 同时更新产品目前的sizes和stocks - 补货(更新)
- 当前库存<=原库存, 只更新sizes和stock
- 如果库存无变化, 不做处理
# 2). 数据库不存在该商品的详情页链接,说明商品是上新产品, 通知商品产品,并将产品信息添加数据库中(添加)
"""
# 1). 数据库已经存在该商品的详情页链接, 接着判断商品的库存是否发生变化?unique/sqlite3.IntegrityError
# 如果库存发生变化, 当前库存/原库存
# 从当前数据库中找到该商品的信息(库存)
product = self.session.query(self.Product).filter(self.Product.link == item['link']).first()
if product:
old_stock = product.stocks
now_stock = item['stocks']
# print("原库存:", old_stock, type(old_stock))
# print("当前库存:", now_stock, type(now_stock))
# 当前库存>原库存, 通知补货商品名称和补货的数量(邮件/微信/钉钉/discord), 同时更新产品目前的sizes和stocks - 补货(更新)
# 当前库存<=原库存, 只更新sizes和stock
if now_stock > old_stock:
print(Fore.YELLOW + '[补货]: 商品%s补货数量为%s' %(product.title, now_stock-old_stock))
product.sizes = item['sizes']
product.stocks = item['stocks']
self.session.add(product)
self.session.commit()
else:
# 2). 数据库不存在该商品的详情页链接,说明商品是上新产品, 通知商品产品,并将产品信息添加数据库中(添加)
product = self.Product(title=item['title'],
image=item['image'],
link=item['link'],
price=item['price'],
sizes=item['sizes'],
stocks=item['stocks']
)
self.session.add(product)
self.session.commit()
print(Fore.GREEN + "新产品[%s]上架, 目前库存为[%s]" %(item['title'], item['stocks']))
return item
def close_spider(self, spider): # 关闭/断开数据库
self.session.close()
class MyImagesPipeline(ImagesPipeline):
"""自定义图片存储管道"""
def get_media_requests(self, item, info):
# 默认情况下下载多个图片的url
image_url = item['image'] # http://www.xxxx.com/xxx.png
yield scrapy.Request(image_url)
def item_completed(self, results, item, info):
# image_paths = [x['path'] for ok, x in results if ok]
# if not image_paths:
# raise DropItem("Item contains no images")
# item['image'] = image_paths[0]
return item
这样我们就实现了一个简单的scrapy爬虫框架的构建。