转载

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

Scrapy+Flask+Mongodb+Swift 开发全攻略(1)

好的,这期开始之前,我们先要干两件事。 第一件事是找到spiders文件夹里的dbmeizi_scrapy.py。 打开他,上一篇教程里,这个爬虫文件是这么写的。

def parse(self, response):  liResults = Selector(response).xpath('//li[@class="span3"]')  for li in liResults:   for img in li.xpath('.//img'):    item = MeiziItem()    item['title'] = img.xpath('@data-title').extract()    item['dataid'] = img.xpath('@data-id').extract()    item['datasrc'] = img.xpath('@data-src').extract()    item['startcount'] = 0    yield item 

现在我们需要改成这样.

def parse(self, response):  liResults = Selector(response).xpath('//li[@class="span3"]')  for li in liResults:   for img in li.xpath('.//img'):    item = MeiziItem()    item['title'] = img.xpath('@data-title').extract()[0]    item['dataid'] = img.xpath('@data-id').extract()[0]    item['datasrc'] = img.xpath('@data-src').extract()[0]    item['startcount'] = 0    yield item 

why?

很简单,因为用extract这个方法得到的是一个数组,而我们的每一个字段实际上是一个string而非一个array,如果不取第一个值,那么存入mongodb之后,title这个key对应的value是一个数组,这会导致我们将mongodb里的数据转换成json之后需要在客户端再进行分解。很麻烦。

第二件事,是删除我们上一个爬虫爬取的数据。

如图:

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

ok,重新运行我们的爬虫,scrapy crawl dbmeiziSpider,现在,check一下数据库里的内容,是不是以前的每个字段对应的内容已经从数组变成了string了。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

开始编写服务器

激动人心的时刻要开始了,我们要从iOS程序员变成一个菜鸟级别的server端选手。不过能用自己编写的iOS客户端从自己写的server下载数据,也挺爽的,不是么?

在编写服务器端的时候确保你用pip安装了下面几个库。

1.pymongo

2.Flask

我们的服务端代码如下。

from flask import Flask, request import json from bson import json_util from bson.objectid import ObjectId import pymongo app = Flask(__name__) mongoClient = pymongo.MongoClient('localhost', 27017) db = mongoClient['dbmeizi'] def toJson(data):  return json.dumps(data, default=json_util.default) @app.route('/meizi/', methods=['GET']) def findmeizi():  if request.method == 'GET':   lim = int(request.args.get('limit', 10))   off = int(request.args.get('offset'),0)   results = db['meizi'].find().skip(off).limit(lim)   json_results= []   for result in results:    json_results.append(result)   return toJson(json_results) if __name__ == '__main__':  app.run(debug=True) 

以上代码就是我们的服务端代码,只有短短28行,python的强大之处也在于此。

好的,我来一行一行的解释一下。

前面5行就是import各种我们需要的库,如果后面你使用python server.py运行的时候提示错误,很可能是你的机子上缺少上述的库。

app = Flask(__name__)这句话就是利用Flask的构造方法生成一个Flask实例,name是什么?简单来说,你创建的任何python文件(.py),都会有一个内置属性,叫做__name__,他有两个用途,如果你在命令行状态下直接运行`python .py`的时候,这个时候这个python文件里的__name__就是__main__,如果你是在别的python文件里import *.py,那么这个name的东西就是这个Python文件的文件名。so,这个东西常常用来判断,你是在import还是直接在命令行里运行这个文件。

所以,上一行,我们生成了一个Flask实例并且把这个实例赋给了app这个变量。

 mongoClient = pymongo.MongoClient('localhost', 27017) db = mongoClient['dbmeizi']

这两句很简单,就是用pymongo这个第三方库,打开我们的mongodb数据库,并且拿到我们的dbmeizi这个database。

def toJson(data):     return json.dumps(data, default=json_util.default)

这句话,我们定义了一个函数,用来把mongodb里的数据转换为json格式。用来返回给我们的ios客户端。

@app.route('/meizi/', methods=['GET'])

这句话的意思就是Flask的一种写法,意思就是当我们发起了一个request,并且这个request的方法是get,url是"localhost:5000/meizi/"这种的的时候,我们就执行findmeizi()这个方法。

def findmeizi(): if request.method == 'GET':  lim = int(request.args.get('limit', 10))  off = int(request.args.get('offset'),0)  results = db['meizi'].find().skip(off).limit(lim)  json_results= []  for result in results:   json_results.append(result)  return toJson(json_results) 

这个方法就是我们的http server监测到用户发起get请求,并且URL是形如'http://127.0.0.1:5000/sightings/?offset=0&limit=3'的时候,我们取出limit这个值,赋给lim这个变量,然后取出offset这个值,赋给off。

然后呢?利用我们的db(就是刚才利用pymongo获取的mongodb实例),取出‘meizi’这个collection,skip(off)的意思就是跳过前面多少行,limit(lim)表示从数据库取出多少个值。

整句话的意思就是,从meizi这个collection里跳过前off个值,取后面的lim个值。

现在取到的数据都在results变量里,我们遍历results,放入json_results这个数组里,然后把数组转换成json格式返回给客户端。

我们运行一下试试。

python server.py

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

perfect!

数据已经返回给浏览器了。

这时候我们编写一个简单的iOS客户端,验证一下。

我们建立一个swift的iOS程序,用cocoapods安装下列库。

platform :ios, '8.0' use_frameworks! target 'HotGirls' do pod 'Alamofire' pod 'Kingfisher' end target 'HotGirlsTests' do end

注意,cocoapods务必升级到最新版,要不然安装swift的第三方库会出现问题。

我写了一个超简单的iOS客户单,纯验证下服务端是否有效。

class ViewController: UIViewController {  @IBOutlet weak var mTableView: UITableView!  var imageURLStringArray:NSMutableArray = NSMutableArray()  override func viewDidLoad() {   super.viewDidLoad()   Alamofire.request(.GET, "http://localhost:5000/meizi/?offset=0&limit=10")    .responseJSON { (_, _, JSON, _) in    let resultArray:NSArray = JSON as! NSArray     for dict in resultArray{      self.imageURLStringArray.addObject(dict["datasrc"] as! String)     }    print(self.imageURLStringArray)    self.mTableView.reloadData()   }   // Do any additional setup after loading the view, typically from a nib.  }  override func didReceiveMemoryWarning() {   super.didReceiveMemoryWarning()   // Dispose of any resources that can be recreated.  } } 
extension ViewController:UITableViewDelegate, UITableViewDataSource{ func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {  var cell:ImageTableViewCell = tableView.dequeueReusableCellWithIdentifier("ImageViewCellID") as! ImageTableViewCell  var imageURL:NSString = imageURLStringArray[indexPath.row] as! NSString  cell.meiziImageView.kf_setImageWithURL(NSURL(string: imageURL as String)!)  return cell } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {  return imageURLStringArray.count } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {  return 250.0 } 

运行一下。

哈哈,大功告成。数据返回正确。

github地址: https://github.com/zangqilong198812/MeiziServer

但是,这样就结束了么?

远远没有,爬虫完全是个半成品,服务端简直就是个玩笑,客户端什么炫酷特效交互都没有,做这样的东西简直是打自己的脸。

现在,万里长征只开始了一小步。

1.爬虫如何自己定时运行?

2.Mongodb如何避免插入重复数据?

3.Server如何提供多个接口。

4.如何Put,delete,Post,Get

5.POP,AsyncDisplaykit,collectionviewlayout,Custom Transition,Bézier curve,Private Cocoapods,Continuous integration,Unit Test.

现在,到了我展现真正实力的时候了。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

请允许我小小的装一下13

我们经过之前的学习,掌握了哪些内容呢?

1.学会了如何用python的scrapy框架来趴一些数据。

2.学会了把爬虫爬来的数据存入mongodb。

3.学会了怎么用Flask写一个简单的接口。

那么,从现在开始,我们会把注意力集中在如何写一个iOS app上。(主要原因是我这个server端菜??还在看服务端的资料,所以只能先做app了)。

先看一张图。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

我们的app大概长这个样子。

关注我微博的人应该知道我前段时间做了一个card效果,主要也是为了写这个界面,因为card那个效果应该是整个页面最复杂的了。饭要一口一口吃吃,我们今天先讲一个简单地,就是图中显示PASSES的那个圆形view。

这个动画加载的顺序大概是。

1.首先是一个圆形的环的形成动画。

2.然后是数字的滚动效果。

3.细看的话,数字滚动的速度是不同的,十位数滚动的稍慢。个位数略快一些。那么首先脑子里的想法就是这肯定不能用一个label来显示,用两个label来分别显示个位和十位比较好做动画。

ok,我们先来做最简单的圆环形成的动画。

那很简单,用CAShapelayer来做strokeEnd动画就行了。

我们新建一个类,继承自UIView,然后改名叫StartView.

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

然后呢,给这个类添加一个属性。

var progressLayer: CAShapeLayer = CAShapeLayer()

然后重写我们的init方法。

加入下列代码

progressLayer.lineWidth = 2         progressLayer.fillColor = UIColor.clearColor().CGColor         progressLayer.strokeColor = UIColor.whiteColor().CGColor         progressLayer.path = UIBezierPath(ovalInRect: self.bounds).CGPath         self.layer.addSublayer(progressLayer)

现在我们的StarView类是这样的。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

然后呢,我们先看看效果,在Viewcontroller里添加一个我们刚刚写的StarView,运行模拟器。看看效果。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

很好,已经有一个圆环了。

刚才我说过,要做出画出圆环的那种动画要使用一个叫做strokeEnd的动画。为什么呢?

我早期在一篇blog中讲过,CABasicAnimation的keypath来源于何处。

其实keypath就是来源于CALayer的所有属性,也就是说,layer的所有属性都能放到keypath里做动画,而CAShapeLayer有一个叫做strokeEnd的属性,专门是用来检测CAShapeLayer的path属性是否被赋值,如果赋值的话就画出path的路径。so,我们就可以用这个strokeEnd属性做一些绘画的动画。

直接看代码。

func startDrawCircleAnimation(){  var pathAnimation:CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")  pathAnimation.fromValue = 0  pathAnimation.toValue = 1  pathAnimation.duration = 0.5  progressLayer.addAnimation(pathAnimation, forKey: "pathAnimation")    } 

这里我定义了一个函数,专门用来执行这个画圆环的动画,然后把动画时长设置为0.5.

然后我们来运行一下。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

确实是执行了画圆环的操作,但是问题来了。绘画的起点并不正确,起点是在三点钟方向,而我们的原型是需要在12点钟方向开始画。

问题在哪呢?

问题就在progressLayer.path = UIBezierPath(ovalInRect: self.bounds).CGPath 这句话上。

因为这个函数就是默认创建一个起点在三点钟方向的oval(圆形),所以我们通过这个函数创建的圆环是没有办法指定起点的。

现在我们有两个解决方案,第一个是让我们的layer逆时针方向做一个90度的transform,那么我们的子layer也逆时针旋转了九十度,刚好从3点钟方向旋转到了12点钟方向。

但是这种解决方案并不合适。因为会留下很多后遗症。

比如说,你如果后期需要在cashapelayer加一些东西的话,所有的子layer全部会旋转。

所以我们需要改变progressLayer的path的创建方式。

var radius:CGFloat = CGRectGetWidth(self.bounds)/2.0         var center = CGPointMake(radius, radius)         var startAngle = -M_PI_2         var endAngle = M_PI_2*3.0         var circlePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: true)         progressLayer.path = circlePath.CGPath

我们用画弧线的方法画一个圆,因为这种方法可以指定startAngle和endAngle。运行一下,看看。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

好的,已经能正确显示了。

接下来是我们的滚动Label。

这种能上下滚动的label最好的方法就是创建一个scrollView,然后在scrollview中添加十个label,label的内容就是数字0-10.

ok,我们新建一个类。

然后加入如下代码。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

新建这个enum的原因就是我们的十位数和个位数弹跳的速率不同,所以我们在初始化的时候加入了一个type,用来区别创建的是十位数label还是个位数label。

然后就是创建了一个scrollview,在竖直的方向添加了十个label。

并且设置了一下每个label的字体,颜色等等属性。

然后创建一个开始动画的方法。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

然后我们运行一下。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

好的,label可以滚动了。那么其实问题也来了。

这两个scroll的滚动速度是一样的,而我们的需求是十位数的速度要慢一点。

可能熟悉scrollView的同学会知道scrollView有一个属性叫做decelerationRate 可以改变滑动速率。但是这个属性有两个问题,1是只有在pageEnable开启的情况下才能生效,2是无法很细致的改动。

所以我们用另一个方法,用UIView的animation方法。

代码如下

if scrollType == .SingleDigitType{  UIView.animateWithDuration(0.85, animations: { () -> Void in   self.singleDigitsScroll.contentOffset = CGPointMake(0, CGRectGetHeight(self.bounds) * CGFloat(num))  }) }else {  UIView.animateWithDuration(1, animations: { () -> Void in   self.singleDigitsScroll.contentOffset = CGPointMake(0, CGRectGetHeight(self.bounds) * CGFloat(num))  }) } 

相信不用我做过多解释也能看懂。就是更改scrollView的contentoffset。

ok,核心组件已经完成。那么我们把它组装起来。

最后运行效果如下。

Scrapy+Flask+Mongodb+Swift开发全攻略(2)

最后项目地址在这: https://github.com/zangqilong198812/HotGirls

正文到此结束
Loading...