Swift Crawler

作者: 晚雪浓情 | 来源:发表于2016-12-06 14:36 被阅读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/

    相关文章

      网友评论

      本文标题:Swift Crawler

      本文链接:https://www.haomeiwen.com/subject/cexrmttx.html