Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
Scrapy构架
下图显示Scrapy的结构和组件,箭头表示框架内数据流情况。
Scrapy框架结构和组件Scrapy中的数据流由执行引擎控制,具体流程如下:
- 引擎获取初始请求从爬虫开始抓取数据。
- 引擎在调度器中调度请求,并要求抓取下一个请求。
- 调度器将下一个请求返回给引擎。
- 引擎将请求发送到下载器,通过下载器中间件。
- 一旦页面完成下载,下载器会生成响应并将其发送到引擎,通过下载中间件。
- 引擎从下载器接收响应并将其发送到爬虫进行处理,通过爬中间件。
- 爬虫处理响应,并将抓取的项目和新的请求返回到引擎,通过爬虫中间件。
- 引擎将处理过的项目发送到项目管道,然后将处理过的请求发送到调度器,并要求可能的下一个请求爬取。
- 重复以上流程直到调度器没有更多的请求。
接下来我们通过项目实例来看看它是如何运行的。
安装Scrapy
使用pip安装:
pip install scrapy
pip install Twisted-17.9.0-cp35-cp35m-win_amd64.whl
注:当前操作系统是64位的,环境是Python3.5,所以选择下载Twisted-17.9.0-cp35-cp35m-win_amd64.whl
安装完后再次运行:
pip install scrapy
创建项目
图灵社区:主要以出版计算机、数学统计、科普等图书,并授权销售正版电子书的在线社区。是我比较喜欢的出版社之一,有兴趣的可以看看上面的书籍,质量都不错。
这里我们以爬取图灵社区图书信息作为项目先切换到要放置项目的目录文件夹,在命令行下输入:
scrapy startproject ituring
目录结构
该命令会创建包含下面内容的ituring目录:
- scrapy.cfg:项目的配置文件
- items.py:项目中的item文件
- middlewares.py:项目中的中间件文件
- pipelines.py:项目中的pipelines文件
- settings.py:项目的设置文件
创建爬虫文件
进入项目文件夹ituring/ituring,使用如下命令:
cd ituring
创建爬虫程序并设定允许爬取的域名地址:
scrapy genspider ituring_spider
在spiders目录下会新创建ituring_spider.py
文件,具体代码如下:
import scrapy
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider'
allowed_domains =
start_urls =
def parse(self, response):
pass
定义Item
Item 是保存爬取到的数据的容器,本质上就是Python字典数据类型,提供了额外保护机制来避免拼写错误导致的未定义字段错误。每个自定义的Item类都继承scrapy.item类,字段定义类型scrapy.Field类属性。
打开items.py
文件开始在Item中定义相应字段,对IturingItem类进行修改:
import scrapy
class IturingItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field() # 标题
link = scrapy.Field() # 链接
img = scrapy.Field() # 图片
up_count = scrapy.Field() # 推荐数
read_count = scrapy.Field() # 阅读数
price = scrapy.Field() # 价格
编写爬虫
Spider是用户编写用于从单个网站(或者一些网站)爬取数据的类。可以存在多个Spider类,但name
属性的值(即爬虫名称)必须唯一,start_urls
作为初始的URL,可以对其进行重写,一旦重写则start_urls
立即失效。
通过检查元素发现书籍在a
标签下的href
属性值里,并且因为是相对地址,还需要拼接成绝对地址。
下面开始我们的第一个爬虫,打开ituring/ituring/spiders
目录下的ituring_spider.py
:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬虫名称
allowed_domains = # 允许爬取的域名地址
start_urls = # 初始URL
def parse(self, response):
"""
初始URl默认使用该解析方法
"""
for book in response.xpath('//div[@class="book-img"]/a/@href'):
url = response.urljoin(book.extract()) # 从列表页获取书籍URL链接
yield scrapy.Request(url, callback=self.parse_book_info) # 发送给调度器,回调函数使用parse_book_info
def parse_book_info(self, response):
"""
解析图书详情页信息
"""
item = IturingItem() # 定义Item
item['title'] = response.xpath('//div[@class="book-title"]/h2/text()').extract_first()
item['link'] = response.url
item['img'] = response.xpath('//div[@class="book-img"]/a/img/@src').extract_first()
item['up_count'] = response.xpath('//*[@id="toggle-vote"]/span[1]/text()').extract_first()
item['read_count'] = response.xpath('//*[@id="book-fav-vote"]/div/span[1]/text()').extract_first()
item['price'] = response.xpath('//dl/dd/span[@class="price"]/text()').extract_first()
yield item
启动爬虫,抓取数据
到这里,我们的爬虫程序基本雏形已经完成,现在可以运行我们的爬虫程序,在命令行下输入:
scrapy crawl ituring_spider
启动爬虫后,将得到类似以下的输出信息:
可以看到我们的爬虫程序已经爬取到详情页信息数据,但仍然有问题数据里多了很多无用的字符例如
\r
、\n
、¥
。
项目管道(Item Pipeline)
当Spider最终处理的Item之后,会被传递到项目管道,管道按顺序进行执行处理,在这里我们可以定义处理清除无用字符串的Pipeline。另外需要判断进入管道的Item是否有需要处理的字段,因为我们也许有很多的Item进入到管道里。通过管道将Item里字段多余的无用字符删除掉,达到清理数据的效果。
打开pipelines.py
文件,看到默认的管道类:
class IturingPipeline(object):
def process_item(self, item, spider):
return item
处理后的Item最终需要return
,下面开始自定义我们的管道吧!
class StripPipeline(object):
"""
清除无用字符
"""
def process_item(self, item, spider):
if item['price']:
item['price'] = item['price'].replace(' ', '').replace('\r', '').replace('\n', '').replace('¥', '')
if item['title']:
item['title'] = item['title'].replace(' ', '').replace('\r', '').replace('\n', '')
return item
翻页爬取
第一页 最后一页打开ituring_spider.py
文件,添加翻页代码:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬虫名称
allowed_domains = # 允许爬取的域名地址
start_urls = # 初始URL
def parse(self, response):
"""
初始URl默认使用该解析方法
"""
for book in response.xpath('//div[@class="book-img"]/a/@href'):
url = response.urljoin(book.extract()) # 从列表页获取书籍URL链接
yield scrapy.Request(url, callback=self.parse_book_info) # 发送给调度器,回调函数使用parse_book_info
next_page = response.xpath('//div/ul/li[@class="PagedList-skipToNext"]/a/@href')
if next_page:
url = response.urljoin(next_page.extract_first()) # 下一页的链接
yield scrapy.Request(url, callback=self.parse)
def parse_book_info(self, response):
"""
解析图书详情页信息
"""
item = IturingItem() # 定义Item
item['title'] = response.xpath('//div[@class="book-title"]/h2/text()').extract_first()
item['link'] = response.url
item['img'] = response.xpath('//div[@class="book-img"]/a/img/@src').extract_first()
item['up_count'] = response.xpath('//*[@id="toggle-vote"]/span[1]/text()').extract_first()
item['read_count'] = response.xpath('//*[@id="book-fav-vote"]/div/span[1]/text()').extract_first()
item['price'] = response.xpath('//dl/dd/span[@class="price"]/text()').extract_first()
yield item
配置settings
不管是管道还是中间件,都需要到setting文件里面进行设置启用,这步可以可以在编写完管道或中间件后进行。
设置存放在settings.py
文件,打开后编辑添加配置信息:
- 激活管道
管道对应的值越大则越后通过,这里可以看到两个管道,默认管道和处理无用字符的管道。
ITEM_PIPELINES = {
'ituring.pipelines.IturingPipeline': 300,
'ituring.pipelines.StripPipeline': 400,
}
- 设置延时
因为爬虫程序设计爬取到多页面,为防止对服务器造成影响和可能被kill掉,需要添加每次请求延时时间。
DOWNLOAD_DELAY = 3
保存数据
最简单存储爬取的数据的方式是使用Feed exports,其自带的类型有:
- JSON
- JSON lines
- CSV
- XML
你也可以通过FEED_EXPORTERS
设置扩展支持的属性,也可以存储到数据库里,数据库推荐使用MongoDB。
在启动爬虫的命令后面加上-o file_name.json
将对爬取的数据进行序列化并采用JSON格式存储,生成文件file_name.json
文件。
scrapy crawl ituring_spider -o items.json
最后打开items.json文件查看数据发现title标题数据中文乱码,打开settings.py
文件配置FEED_EXPORTERS
导出的编码方式:
# 设置输出格式
# FEED_EXPORT_ENCODING = 'utf-8'