在之前的项目中已经可以正常运行出scrapy框架下的爬虫程序,但是如果换一个项目换一个爬取任务,要活学活用还需要进行针对scrapy是如何运行的进行深入的学习.
目录:
基本概念
具体从代码中解析
item
pipelines
spiders
可以从这里学习详细的结构说明:
http://www.tuicool.com/articles/fiyIbq
基本概念
Scrapy框架主要由五大组件组成,调试器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。
如下图.
Scrapy运行流程
- 首先,引擎从调度器中取出一个链接(URL)用于接下来的抓取
- 引擎把URL封装成一个请求(Request)传给下载器,下载器把资源下载下来,并封装成应答包(Response)
- 然后,爬虫解析Response
- 若是解析出实体(Item),则交给实体管道进行进一步的处理。
- 若是解析出的是链接(URL),则把URL交给Scheduler等待抓取
具体解析:
参照项目 meizitu 源代码在:
https://github.com/luyishisi/WebCrawlers/tree/master/scrapy_code/meizitu
item.py : 定义提取的数据结构:
在定义这部分时,要考虑项目目标是为了爬取妹子图网的图片,因此需要的结构有:
- url : 页面路径 ;
- name:页面名字 ;
- tags : 页面标签(用于图片分类)
- image_urls = 图片的地址 ;
- images = 图片的名字
item代码如下:
代码语言:javascript复制import scrapy
class MeizituItem(scrapy.Item):
url = scrapy.Field()
name = scrapy.Field()
tags = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
关于field
Field对象指明了每个字段的元数据(metadata)。
您可以为每个字段指明任何类型的元数据。Field 对象对接受的值没有任何限制。也正是因为这个原因,文档也无法提供所有可用的元数据的键(key)参考列表。Field 对象中保存的每个键可以由多个组件使用,并且只有这些组件知道这个键的存在
关于items.的实例化
可从抓取进程中得到这些信息, 比如预先解析提取到的原生数据,items 提供了盛装抓取到的数据的*容器* , 而Item Loaders提供了构件*装载populating*该容器。
在用于下面例子的管道功能时.在spiders中使用了item的实例化:代码如下:
代码语言:javascript复制def parse_item(self, response):
#l=用ItemLoader载入MeizituItem()
l = ItemLoader(item=MeizituItem(), response=response)
#名字
l.add_xpath('name', '//h2/a/text()')
#标签
l.add_xpath('tags', "//div[@id='maincontent']/div[@class='postmeta clearfix']/div[@class='metaRight']/p")
#图片连接
l.add_xpath('image_urls', "//div[@id='picture']/p/img/@src", Identity())
#url
l.add_value('url', response.url)
return l.load_item()
最终, 当所有数据被收集起来之后, 调用 ItemLoader.load_item() 方法, 实际上填充并且返回了之前通过调用 add_xpath(), add_css(), and add_value() 所提取和收集到的数据的Item.
pipeline.py : (管道.用于保存数据)
需要导入settings配置文件,根据你保存的内容需要不同的包,例如,保存文本数据往往需要json包,本项目保存的是图片,则导入os包用于设定保存路径等.最重要的是要导入requests包.用于发送请求给图片的url,将返回的应答包进行保存.
本部分至少需要重写图片下载类,返回的是item对象.
这部分的特性是:
- 避免重新下载最近已经下载过的数据
- 指定存储数据位置
- 将所有下载的图片转换成通用的格式(JPG)和模式(RGB)
- 缩略图生成
- 检测图像的宽/高,确保它们满足最小限制
典型的工作流程如下:
- 在一个爬虫(在spiders.py中),你抓取一个项目,把其中图片的URL放入
file_urls
组内。 l.add_xpath('image_urls', "//div[@id='picture']/p/img/@src", Identity() - 项目从爬虫(在spiders.py中)内返回,进入项目管道(到pipeline.py中)。 return l.load_item()
- 当项目进入
Pipeline
,file_urls || image_urls
组内的URLs将被Scrapy的调度器和下载器(这意味着调度器和下载器的中间件可以复用)安排下载(将url请求发送到下载器)。项目会在这个特定的管道阶段保持“locker”的状态,直到完成文件的下载(或者由于某些原因未完成下载)。 - 当文件下载完后,另一个字段(
files
)将被更新到结构中。这个组将包含一个字典列表,其中包括下载文件的信息,比如下载路径、源抓取地址(从file_urls
组获得)和图片的校验码(checksum)。files
列表中的文件顺序将和源file_urls
组保持一致。如果某个图片下载失败,将会记录下错误信息,图片也不会出现在files
组中。
附录:
为了启用 media pipeline,你首先需要在项目中添加它setting.
对于 Images Pipeline, 使用:
代码语言:javascript复制<span class="n">ITEM_PIPELINES</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'scrapy.pipeline.<span style="color: #ff0000;">images</span>.<span style="color: #ff0000;">Images</span>Pipeline'</span><span class="p">:</span> <span class="mi">1</span><span class="p">}</span>
对于 Files Pipeline, 使用:
代码语言:javascript复制<span class="n">ITEM_PIPELINES</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'scrapy.pipeline.<span style="color: #ff0000;">files</span>.<span style="color: #ff0000;">Files</span>Pipeline'</span><span class="p">:</span> <span class="mi">1</span><span class="p">}</span>
因此配置文件如下:
代码语言:javascript复制BOT_NAME = 'meizitu'
SPIDER_MODULES = ['meizitu.spiders']
NEWSPIDER_MODULE = 'meizitu.spiders'
#载入ImageDownLoadPipeline类
#为了启用一个Item Pipeline组件,你必须将它的类添加到 ITEM_PIPELINES 配置
#分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过pipeline,
TEM_PIPELINES = {'meizitu.pipelines.ImageDownloadPipeline': 1}
#图片储存,第一个meizitu是项目名称,imagedown部分则是类的名字.
IMAGES_STORE = '.'
pipeline代码如下:
代码语言:javascript复制# -*- coding: utf-8 -*-
#图片下载部分(自动增量)
import requests
from meizitu import settings
import os
#导入重写图片下载类
class ImageDownloadPipeline(object):
def process_item(self, item, spider):
#每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item (或任何继承类)对象,
# 或是抛出 DropItem 异常,被丢弃的item将不会被之后的pipeline组件所处理
if 'image_urls' in item:#如果‘图片地址’在项目中
images = []#定义图片空集
dir_path = '%s/%s' % (settings.IMAGES_STORE, spider.name)
#建立目录名字和项目名称一致
if not os.path.exists(dir_path):
os.makedirs(dir_path)
#根据item字典进行查询
for image_url in item['image_urls']:
us = image_url.split('/')[3:]
image_file_name = '_'.join(us)
file_path = '%s/%s' % (dir_path, image_file_name)
images.append(file_path)
#如果这个文件存在则跳过
if os.path.exists(file_path):
continue
#进行图片文件写入,wb模式打开文件,然后requests.get获取图片流,
with open(file_path, 'wb') as handle:
response = requests.get(image_url, stream=True)
for block in response.iter_content(1024):
#获取的流如果有不存在的,则使用break结束,如果没有一次结束则进行写入
if not block:
break
handle.write(block)
item['images'] = images
return item
Spiders
Spider类定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接)以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider就是定义爬取的动作及分析某个网页(或者是有些网页)的地方。
对spider来说,爬取的循环类似下文:
- 以初始的URL初始化Request,并设置回调函数。 当该request下载完毕并返回时,将生成response,并作为参数传给该回调函数。
spider中初始的request是通过调用
start_requests()
来获取的。start_requests()
读取start_urls
中的URL, 并以parse
为回调函数生成Request
。 - 在回调函数内分析返回的(网页)内容,返回
Item
对象、dict、Request
或者一个包括三者的可迭代容器。 返回的Request对象之后会经过Scrapy处理,下载相应的内容,并调用设置的callback函数(函数可相同)。 - 在回调函数内,您可以使用 选择器(Selectors) (您也可以使用BeautifulSoup, lxml 或者您想用的任何解析器) 来分析网页内容,并根据分析的数据生成item。
- 最后,由spider返回的item将被存到数据库(由某些 Item Pipeline 处理)或使用 Feed exports 存入到文件中。
虽然该循环对任何类型的spider都(多少)适用,但Scrapy仍然为了不同的需求提供了多种默认spider。
分析代码:
导入选择器,itemloader等.重写类,从start_urls开始爬取
代码语言:javascript复制# -*- coding: utf-8 -*-
import scrapy
from scrapy.selector import Selector
from scrapy.contrib.loader import ItemLoader, Identity
from meizitu.items import MeizituItem
class MeiziSpider(scrapy.Spider):
name = "meizi"
allowed_domains = ["meizitu.com"]
start_urls = (
'http://www.meizitu.com/',
)
def parse(self, response):
#sel是页面源代码,载入scrapy.selector
sel = Selector(response)
#每个连接,用@href属性
for link in sel.xpath('//h2/a/@href').extract():
#请求=Request(连接,parese_item)
request = scrapy.Request(link, callback=self.parse_item)
yield request#返回请求
#获取页码集合
pages = sel.xpath('//*[@id="wp_page_numbers"]/ul/li/a/@href').extract()
print('pages: %s' % pages)#打印页码
if len(pages) > 2:#如果页码集合>2
page_link = pages[-2]#图片连接=读取页码集合的倒数第二个页码
page_link = page_link.replace('/a/', '')#图片连接=page_link(a替换成空)
request = scrapy.Request('http://www.meizitu.com/a/%s' % page_link, callback=self.parse)
yield request#返回请求
def parse_item(self, response):
#l=用ItemLoader载入MeizituItem()
l = ItemLoader(item=MeizituItem(), response=response)
#名字
l.add_xpath('name', '//h2/a/text()')
#标签
l.add_xpath('tags', "//div[@id='maincontent']/div[@class='postmeta clearfix']/div[@class='metaRight']/p")
#图片连接
l.add_xpath('image_urls', "//div[@id='picture']/p/img/@src", Identity())
#url
l.add_value('url', response.url)
return l.load_item()
阿萨德 阿斯顿
原创文章,转载请注明: 转载自URl-team
本文链接地址: scrapy笔记六 scrapy运行架构的实例配合解析
Related posts:
- Scrapy-笔记一 入门项目 爬虫抓取w3c网站
- Scrapy笔记四 自动爬取网页之使用CrawlSpider
- Scrapy笔记五 爬取妹子图网的图片 详细解析
- Scrapy笔记零 环境搭建与五大组件架构
- 基于百度IP定位的网站访问来源分析的python实战项目–实践笔记二–调百度地图将经纬信息可视化呈现
- scrapy学习笔记十一 scrapy实战效率测评