初窥Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。
还是先推荐几个学习的教程:Scrapy 0.25文档 Scrapy快速入门教程 这些教程里面有关于Scrapy的安装,创建项目,爬取实例等等,如果一个全新的东西扔给你首先要看文档,初看文档我也是蒙蒙的,后来一层一层的去摸索才大概懂了个皮毛。我们就试着将之前的爬虫福利改写成用Scrapy框架的爬虫,在实践中学习。 战斗吧 Scrapy!
安装Scrapy
如果配置好了pip或者easy_install 可以直接pip install scrapy (从https://pip.pypa.io/en/latest/installing.html 安装 pip)
还需要从 http://sourceforge.net/projects/pywin32/ 安装 pywin32 (注:此处要注意了,这里pywin32的版本要跟你python的完全一致,比如你在64位系统安装的32位的python2.7 那么你也需要安装2.7 32位的pywin32) 否则遇到:Scrapy [twisted] CRITICAL:Unhandled error in Deferred
新建项目
因为我们要重写之前的项目,我们新建一个scrapy项目,命名为rosi: scrapy startproject rosi
可以看到目录里面包含:
1 rosi/
2 scrapy.cfg #项目的配置文件
3 rosi/ #该项目的Python模块,代码全在这里面
4 __init__.py
5 items.py #放多个model的地方
6 pipelines.py #顾名思义 管道,处理items结果的地方
7 settings.py #配置文件
8 spiders/ #爬虫代码
9 __init__.py
10 ...
好了。。。说了这么多废话,接下来让我们深入基层!新建rosi项目,然后在rosi/rosi/spiders下面新建rosi_spider.py
import scrapy
class RosiSpider(scrapy.spiders.Spider):
name = "rosi" #爬虫名字 唯一
allowed_domains = ["baidu.com"] #白名单
start_urls = ["http://www.baidu.com"] #爬取起始页面
def parse(self,response):#回调函数
print response.url
上面的代码就是一直简单的爬虫,默认爬取了百度首页。你如果问我,怎么爬取的,什么原理,怎么会爬取了,我只能这么回答你:我表达不出来,因为我也是刚学现在还一团浆糊,我现在只明白怎么用,至于原理,我想等我用的熟了,需要去更深的应用的时候我就会懂了,如果能看的下去可以去看看源码。。。不过我可以引用官方文档中的话来回答你:Scrapy为start_urls属性中的每个url都创建了一个Request对象,并将parse方法最为回调函数(callback)赋值给了Request。Request对象经过调度,执行生成scrapy.http.Response对象并返回给parse方法。
执行该爬虫:scrapy crawl rosi
我们既然知道了返回的是response,我们可以试着将里面我们需要的东西匹配读取保存下来,比如文字,比如图片。在Scrapy中呢他拥有自己的Selectors。使用了一种基于XPath和css的机制。深入的东西还是看官方文档:Selector文档 简单介绍介绍几个官方文档的例子:
/html/head/title
: 选择HTML文档中 <head>
标签内的 <title>
元素/html/head/title/text()
: 选择上面提到的 <title>
元素的文字//td
: 选择所有的 <td>
元素//div[@class="mine"]
: 选择所有具有 class="mine"
属性的 div
元素Selector有4个基本方法:
xpath()
: 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。css()
: 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.extract()
: 序列化该节点为unicode字符串并返回list。re()
: 根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。这里可以自行尝试一下利用XPath取出百度首页的title文字等等等等。
好了,重点来了。Scrapy中的BaseSpider爬虫类只能抓取start_urls中提供的链接,而利用Scrapy提供的crawlSpider类可以很方便的自动解析网页上符合要求的链接,从而达到爬虫自动抓取的功能。要利用crawSpider和BaseSpider的区别在于crawSpider提供了一组Rule对象列表,这些Rule对象规定了爬虫抓取链接的行为,Rule规定的链接才会被抓取,交给相应的callback函数去处理。
在rules中通过SmglLinkExtractor提取希望获取的链接。比如:
1 rules = (
2 Rule(SgmlLinkExtractor(allow = ('detail_\d{4}_\d{5}\.html')),callback = 'parse_image',follow=True),
3 )
这里要解释下,Rule就是一组对象列表,在这里我们设置要过滤的地址。SmglLinkExtractor的主要参数:
我们尝试着从首页得到符合规则的rosi跳转页面:
1 import scrapy
2 from scrapy.contrib.spiders import CrawlSpider,Rule
3 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
4 class RosiSpider(CrawlSpider):
5 name = "rosi"
6 allowed_domains = ["5442.com"]
7 start_urls = ["http://www.5442.com/tag/rosi.html"]
8 rules = (Rule(SgmlLinkExtractor(allow=('rosi/[\d]+\.html', )),callback='parse_href',),)
9 def parse_href(self,response):#注意 回调函数不要命名为parse 否则出bug
10 print response.url
得到的结果如下:
天杀的,明明是1-25页好不好,怎么只有这么几个,上面说了如果不设置follow的话默认为false,所以访问了这个就不继续了,我们设置为True就对了。
我们还是要分析一下这个流程。我们从起始页面:http://www.5442.com/tag/rosi.html 我们需要得到符合条件为tag/rosi/[0-9]+/.html的所有页面,然后访问这些页面得到所有图片集的地址如:http://www.5442.com/meinv/20150904/27062.html和http://www.5442.com/meinv/20150904/27062_2.html,分析可得[0-9_]+\.html。这样我们就得到了所有包含我们需要下载图片url的地址,我们就可以根据XPath得到图片url进行下载。所以我们的爬虫Rule是这样的:
1 import scrapy,re,urllib2
2 from scrapy.http import Request
3 from scrapy.contrib.spiders import CrawlSpider,Rule
4 from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
5 from scrapy.selector import Selector
6 from scrapydemo.items import *
7
8 class RosiSpider(CrawlSpider):
9 name = "rosi"
10 number = 0
11 allowed_domains = ["5442.com"]
12 start_urls = ["http://www.5442.com/tag/rosi.html"]
13 rules = (Rule(SgmlLinkExtractor(allow=('rosi/[\d]+\.html', )),follow=True),
14 Rule(SgmlLinkExtractor(allow=('[0-9_]+\.html', )),callback='parse_img',follow=True)
15 )
第一条Rule我们得到了rosi的所有页面的信息,在访问这些页面的时候我们并不需要进行处理,所以我们不需要回调函数,然后我们从这些页面信息中提取出了所有的图片集页面,然后我们将图片集页面的返回值response给回调函数进行处理:
1 def parse_img(self,response):
2 #print response.url
3 sel = Selector(response)
4 src = sel.xpath("//div[@class='arcBody']//p[@id='contents']//a//img/@src").extract()
5 for item in src:
6 self.saveimg(item)
7
8 def saveimg(self,url):
9 savePath = '%d.jpg'%(self.number)
10 print url
11 self.number += 1
12 try:
13 u = urllib2.urlopen(url)
14 r = u.read()
15 downloadFile = open(savePath,'wb')
16 downloadFile.write(r)
17 u.close()
18 downloadFile.close()
19 except:
20 print savePath,'can not download.'
可能我们要问了,这就完了? items.py 和 pipeline.py咋没用上呢。那就来谈谈这两个:
Items
爬取的主要目标就是从非结构性的数据源提取结构性数据,例如网页。 Scrapy提供 Item类来满足这样的需求。Item
对象是种简单的容器,保存了爬取到得数据。 其提供了 类似于词典(dictionary-like) 的API以及用于声明可用字段的简单语法。
1 import scrapy
2
3 class Product(scrapy.Item):
4 name = scrapy.Field()
5 price = scrapy.Field()
6 stock = scrapy.Field()
他就是一个model,我们可以在回调函数中通过XPath得到内容 然后新建一个Item对象,赋值给他,
1 def parse_href(self,response):
2 items = []
3 item = Product()
4 item["name"] = "xxx"
5 item["price"] = "xxx"
6 items.append(item)
7 return items
注意,这里我们返回了一个items!!!当Item在Spider中被收集之后,它将会被传递到Item Pipeline,一些组件会按照一定的顺序执行对Item的处理。每个item pipeline组件(有时称之为“Item Pipeline”)是实现了简单方法的Python类。他们接收到Item并通过它执行一些行为,同时也决定此Item是否继续通过pipeline,或是被丢弃而不再进行处理。
以下是item pipeline的一些典型应用:
我们可以在pipelines.py中编写自己的itempipeline方法。你必须实现process_item(self,item,spider)方法。更多内容 看官方文档。。。
让我们来看一下以下这个假设的pipeline,它为那些不含税(price_excludes_vat
属性)的item调整了price
属性,同时丢弃了那些没有价格的item:
1 from scrapy.exceptions import DropItem
2
3 class PricePipeline(object):
4
5 vat_factor = 1.15
6
7 def process_item(self, item, spider):
8 if item['price']:
9 if item['price_excludes_vat']:
10 item['price'] = item['price'] * self.vat_factor
11 return item
12 else:
13 raise DropItem("Missing price in %s" % item)
以下pipeline将所有(从所有spider中)爬取到的item,存储到一个独立地 items.jl
文件,每行包含一个序列化为JSON格式的item:
1 import json
2
3 class JsonWriterPipeline(object):
4
5 def __init__(self):
6 self.file = open('items.jl', 'wb')
7
8 def process_item(self, item, spider):
9 line = json.dumps(dict(item)) + "\n"
10 self.file.write(line)
11 return item
好了,今天就到这儿吧。。。其实我现在也蒙蒙的,接下来就是在实际应用中去提升了,毕竟熟能生巧!!战斗吧 Scrapy!