Swift Crawler

写在前面

Perfect[1]这家提供Swift服务端技术的公司,推出了Perfct Assistant(PA)[2]这款助手工具来更"Swift"地创建,开发,部署Swift服务器项目。👏👏
关于Perfect以及PA的任何疑问请登录Slack的中文频道[3]@rockford大神~

服务端

我们要在Swift服务器中加入一个路由,由于服务器并没有部署上线,所以通过http://127.0.0.1/data访问就行了。

    //添加路由
    routes.add(method: .get, uri: "/data", handler: dataHandler)
    private func dataHandler(request:HTTPRequest,_ response:HTTPResponse)
    {
        //在请求中创建并开始爬一次
        var crawler = myCrawler(url:"https://movie.douban.com/")
        crawler.start()
        
        //如果有爬到数据,就添加到Response中返回
        response.appendBody(string: crawler.results.characters.count > 0 ? crawler.results : "")
        response.completed()
    }
这只是一只小小虫。

所以调用方法也很简单:]

    //一个url属性来接收传入的主url
    private var url:String
    //一个results属性来输出结果
    internal var results = ""
    //myCrawler这个结构体的初始化和开始方法
    init(url:String)
    {
        self.url = url
    }
    
    internal mutating func start()
    {
        do
        {
            try handleData(data: setUp(urlString: url))
        }
        catch
        {
            debugPrint(error)
        }
    }

那么它究竟做了些什么?这里可能要提到一下网络爬虫的原理,根据这个试手了一个简单又偷懒的爬虫程序。那么我们来尝试爬一下豆瓣电影的本周口碑榜。⬇️(代码配合注释食用效果更佳)⬇️

    private func setUp(urlString:String) throws ->[String]
    {
        //目的就是抓到口碑榜上的那些url
        var URLArray = [String]()
        
        if let url = URL(string:urlString)
        {
            debugPrint("开始获取url")
            //通过创建Scanner
            let scanner = Scanner(string: try String(contentsOf:url))
            
            while !scanner.isAtEnd
            {
                //以及首尾字段的定位,抓出url
                URLArray.append(scanWith(head:"{from:'mv_rk'})\" href=\"",foot:"\">",scanner:scanner))
            }
            
            if URLArray.count == 0
            {
                throw crawlerError(msg:"数据初始化失败")
            }
            
            debugPrint("获取url结束")
        }
        else
        {
            throw crawlerError(msg:"查询URL初始化失败")
        }
        return URLArray.filter{$0.characters.count > 0}
    }

核心的函数就是
private func scanWith(head:String,foot:String,scanner:Scanner)->String
代码如下,其实就是对传入的Scanner参数的内容来获取夹在head&foot之间的字符串。�因为获取出来的字符串还包含head的部分所以我们要去掉它。

    private func scanWith(head:String,foot:String,scanner:Scanner)->String
    {
        var str:NSString?
        
        scanner.scanUpTo(head, into: nil)
        scanner.scanUpTo(foot, into: &str)
        
        return str == nil ? "" : str!.replacingOccurrences(of: head, with: "")
    }

拿到了所有的url之后,就要去对应的页面看一下需要的数据。比如我想拿到电影模型(名称,导演,评分等)以及电影简介,只需要更改对应的head&foot来获取对应信息就好了。

    //因为要改变
    private mutating func handleData(data:[String]) throws
    {
        debugPrint("开始获取信息")
        
        var index = 0
        
        //映射成url数组
        for case let url in data.map({ URL(string:$0) })
        {
            guard let _ = url else { throw crawlerError(msg:"数据\(index)初始化失败") }
            
            DispatchQueue.global().sync
            {
                do
                {
                    let scanner = Scanner(string: try String(contentsOf:url!))
                    
                    //创建一个head & foot 元组,方便处理
                    var (head,foot) = ("data-name=",".jpg")
                    
                    //电影模型
                    var tempStr = (head + self.scanWith(head:head,foot:foot,scanner:scanner) + foot).components(separatedBy: "data-").map{
                        "\"\($0)".replacingOccurrences(of: "=", with: "\":").trim(string:" ")
                    }
                    
                    tempStr.removeFirst()
                    
                    var content = ""
                    
                    _ = tempStr.map{ content += "\($0),\n" }
                    
                    content = content.replace(of: ",", with: "\"")
                    
                    //电影简介
                    var intro = ""
                    
                    (head,foot) = try String(contentsOf:url!).contains(string: "<span class=\"all hidden\">") ? ("<span class=\"all hidden\">","</span>") : ("<span property=\"v:summary\" class=\"\">","</span>")
                    
                    _ = self.scanWith(head:head,foot:foot,scanner:scanner).components(separatedBy: "<br />").map{
                        intro += $0.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
                    }
                    //手动拼成JSON
                    results += "\"\(index)\":{\"content\":{\(content)},\"intro\":\"\(intro)\"},"
                }
                catch
                {
                    debugPrint(error)
                }
            }
            index += 1
        }
        
        debugPrint("获取信息结束")
        
        results = results.replace(of: ",", with: "")
        results = results.characters.count > 0 ? "{\(results)}" : ""
    }

最后附上工具代码

//自定义一个错误处理
struct crawlerError:Error
{
    var message:String
    
    init(msg:String)
    {
        message = msg
    }
}

extension String
{
    //去掉字符串(空格之类的)
    func trim(string:String) -> String
    {
        return self == "" ? "" : self.trimmingCharacters(in: CharacterSet(charactersIn: string))
    }
    //替换从末尾出现的第一个指定字符串
    func replace(of pre:String,with next:String)->String
    {
        return replacingOccurrences(of: pre, with: next, options: String.CompareOptions.backwards, range: index(endIndex, offsetBy: -2)..<endIndex)
    }
}

所以。。该结束了?

移动端

Well,被扔上服务器的爬虫已经可以工作了。但觉得还不够,光是网页上能看到总觉得整体上还少了点什么。于是昨天又花了一点时间�在测试登陆注册功能的那个demo App里加了一个数据展示。⬇️大概就是这个样子😂

Paste_Image.png

(内心os) 还有好多东西可以认真琢磨,不仅仅是这个爬虫的部分,服务器的,移动端的,都有很多东西要互相考虑。能收集数据了,能存储数据了,能数据展示了,全栈Swifter的路才迈出了第一步。也希望这只爬虫能变成蝴蝶而不是蛾子(Absolutely Not!)

万分感谢您一路看我碎碎念到现在。🙇


  1. https://www.perfect.org/

  2. https://www.perfect.org/en/assistant/

  3. https://perfectswift.slack.com/messages/china-developers/details/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容