写在前面
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里加了一个数据展示。⬇️大概就是这个丑样子😂
(内心os) 还有好多东西可以认真琢磨,不仅仅是这个爬虫的部分,服务器的,移动端的,都有很多东西要互相考虑。能收集数据了,能存储数据了,能数据展示了,全栈Swifter的路才迈出了第一步。也希望这只爬虫能变成蝴蝶而不是蛾子(Absolutely Not!)
万分感谢您一路看我碎碎念到现在。🙇
网友评论