小斑Swift相关iOS学习

Swift Crawler

2016-12-06  本文已影响416人  晚雪浓情

写在前面

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/

上一篇下一篇

猜你喜欢

热点阅读