Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
其最初是为了页面抓取(更确切来说,网络抓取)所设计的, 也可以应用在获取API所返回的数据(例如Amazon Associates Web Services) 或者通用的网络爬虫。
Scrapy构架
下图显示Scrapy的结构和组件,箭头表示框架内数据流情况。
Scrapy中的数据流由执行引擎控制,具体流程如下:
- 引擎获取初始请求从爬虫开始抓取数据。
- 引擎在调度器中调度请求,并要求抓取下一个请求。
- 调度器将下一个请求返回给引擎。
- 引擎将请求发送到下载器,通过下载器中间件。
- 一旦页面完成下载,下载器会生成响应并将其发送到引擎,通过下载中间件。
- 引擎从下载器接收响应并将其发送到爬虫进行处理,通过爬中间件。
- 爬虫处理响应,并将抓取的项目和新的请求返回到引擎,通过爬虫中间件。
- 引擎将处理过的项目发送到项目管道,然后将处理过的请求发送到调度器,并要求可能的下一个请求爬取。
- 重复以上流程直到调度器没有更多的请求。
接下来我们通过项目实例来看看它是如何运行的。
安装Scrapy
使用pip安装:
pip install scrapy
windows系统下安装会出现异常,需要到https://www.lfd.uci.edu/~gohlke/pythonlibs/下载Twisted库的whl文件,切换到文件目录后,在命令行下输入:
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 ituring.com.cn
在spiders目录下会新创建ituring_spider.py
文件,具体代码如下:
import scrapy
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider'
allowed_domains = ['ituring.com.cn']
start_urls = ['http://ituring.com.cn/']
def parse(self, response):
pass
定义Item
Item 是保存爬取到的数据的容器,本质上就是Python字典数据类型,提供了额外保护机制来避免拼写错误导致的未定义字段错误。每个自定义的Item类都继承scrapy.item类,字段定义类型scrapy.Field类属性。
根据我们要爬取网站的数据进行设定字段,从http://www.ituring.com.cn/book主页点击进入书籍,可以看到书籍详情数据,这里我们需要选取的数据有,标题、链接地址、书籍图片、推荐人数、阅读人数及图书价格。
打开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
立即失效。
在编写爬虫前我们先对网站进行分析,开始从http://www.ituring.com.cn/book主页获取每本书籍详情页链接,后面将要请求的request发送给调度器,接着使用编写的回调函数对详情页进行解析。
通过检查元素发现书籍在a
标签下的href
属性值里,并且因为是相对地址,还需要拼接成绝对地址。
进入详情页http://www.ituring.com.cn/book/1927使用谷歌浏览器插件 XPath Helper提取需要的字段的XPath进行调试(这里也可以用检查元素进行Copy XPath)。
下面开始我们的第一个爬虫,打开ituring/ituring/spiders
目录下的ituring_spider.py
:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬虫名称
allowed_domains = ['ituring.com.cn'] # 允许爬取的域名地址
start_urls = ['http://www.ituring.com.cn/book'] # 初始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
翻页爬取
在这之前我们还只是爬取单页的URL链接,而爬取多页则需要分析网页的翻页。通过点击下一页,发现原来的URL跳转到http://www.ituring.com.cn/book?tab=book&sort=hot&page=1,看出page参数是翻页页码,起始页是0。使用构造页码的方式可以遍历所有的页码页面,当没有获取到对应数据则停止。进一步分析发现最有一页没有下一页,而下一页则正是我们接下来要爬取的页面。所以可以通过判断当前页面是否有下一页,如果有则从“下一页”标签中的链接开始爬取,如果没有下一页则爬取完后停止程序。
打开ituring_spider.py
文件,添加翻页代码:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬虫名称
allowed_domains = ['ituring.com.cn'] # 允许爬取的域名地址
start_urls = ['http://www.ituring.com.cn/book'] # 初始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'