好的,這期開始之前,我們先要幹兩件事。第一件事是找到spiders資料夾裡的dbmeizi_scrapy.py。開啟他,上一篇教程裡,這個爬蟲檔案是這麼寫的。
1 2 3 4 5 6 7 8 9 10 |
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 |
現在我們需要改成這樣.
1 2 3 4 5 6 7 8 9 10 |
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之後需要在客戶端再進行分解。很麻煩。
第二件事,是刪除我們上一個爬蟲爬取的資料。
如圖:
ok,重新執行我們的爬蟲,scrapy crawl dbmeiziSpider,現在,check一下資料庫裡的內容,是不是以前的每個欄位對應的內容已經從陣列變成了string了。
開始編寫伺服器
激動人心的時刻要開始了,我們要從iOS程式設計師變成一個菜鳥級別的server端選手。不過能用自己編寫的iOS客戶端從自己寫的server下載資料,也挺爽的,不是麼?
在編寫伺服器端的時候確保你用pip安裝了下面幾個庫。
1.pymongo
2.Flask
我們的服務端程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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檔案裡的__name__就是__main__,如果你是在別的python檔案裡import *.py,那麼這個name的東西就是這個Python檔案的檔名。so,這個東西常常用來判斷,你是在import還是直接在命令列裡執行這個檔案。python
.py
所以,上一行,我們生成了一個Flask例項並且把這個例項賦給了app這個變數。
1 2 |
mongoClient = pymongo.MongoClient('localhost', 27017) db = mongoClient['dbmeizi'] |
這兩句很簡單,就是用pymongo這個第三方庫,開啟我們的mongodb資料庫,並且拿到我們的dbmeizi這個database。
1 2 |
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()這個方法。
1 2 3 4 5 6 7 8 9 |
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
perfect!
資料已經返回給瀏覽器了。
這時候我們編寫一個簡單的iOS客戶端,驗證一下。
我們建立一個swift的iOS程式,用cocoapods安裝下列庫。
1 2 3 4 5 6 7 8 9 10 |
platform :ios, '8.0' use_frameworks! target 'HotGirls' do pod 'Alamofire' pod 'Kingfisher' end target 'HotGirlsTests' do end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
注意,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. } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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.
現在,到了我展現真正實力的時候了。