使用 Swift 3.0 爬取“简书”的文章模型

发布于:2017-01-11 18:57,阅读数:2128,点赞数:17


> 本文提供了一种可通过`URL`链接爬取“简书”文章模型的方法。 # 引言 前几天突然想把简书的阅读和点赞数据也累加到[「我的博客」](http://enumsblog.com)的访问量中,于是就简单地写了一段代码来根据文章`URL`爬取页面上的文章模型。 代码比较简陋,只保证在简书现在的版本上可用,如果将来简书改了页面了有可能会失效。 # 模型 简书文章页面的源代码中有这么一段`JSON`记录了文章的相关信息: ![17002_1](//cdn.blog.yuusann.com/img/posts/17002_1.png) 所以只要把这一段字符串爬出来,构造自己的模型即可。这里使用了[「SwiftyJSON」](https://github.com/SwiftyJSON/SwiftyJSON)来处理`JSON`的字符串,如果对它的实现感兴趣,可以看我这篇文章:[「SwiftyJSON 源码解析」](http://www.enumsblog.com/post?pid=16007)。根据`JSON`的结构,模型分为以下三个部分: ```swift class JSArticleNote { var slug: String var likes_count: Int var id: Int var views_count: Int var commentable: Bool var user_id: Int var public_wordage: Int var comments_count: Int var notebook_id: Int init(json: JSON) { slug = json["slug"].string ?? "null" likes_count = json["likes_count"].int ?? 0 id = json["id"].int ?? 0 views_count = json["views_count"].int ?? 0 commentable = json["commentable"].bool ?? false user_id = json["user_id"].int ?? 0 public_wordage = json["public_wordage"].int ?? 0 comments_count = json["comments_count"].int ?? 0 notebook_id = json["notebook_id"].int ?? 0 } } ``` ```swift class JSArticleNoteShow { var is_author: Bool var is_following_author: Bool var is_liked_note: Bool var uuid: String init(json: JSON) { is_author = json["is_author"].bool ?? false is_following_author = json["is_following_author"].bool ?? false is_liked_note = json["is_liked_note"].bool ?? false uuid = json["uuid"].string ?? "null" } } ``` ```swift class JSArticle { var note: JSArticleNote var os: String var read_font: String var note_show: JSArticleNoteShow var user_signed_in: Bool var read_mode: String var locale: String init(json: JSON) { note = JSArticleNote.init(json: json["note"]) os = json["os"].string ?? "null" read_font = json["read_font"].string ?? "null" note_show = JSArticleNoteShow.init(json: json["note_show"]) user_signed_in = json["user_signed_in"].bool ?? false read_mode = json["read_mode"].string ?? "null" locale = json["locale"].string ?? "null" } } ``` `JSArticle`包含了`JSArticleNote`和`JSArticleNoteShow`两个结构,模型中详细描述了文章ID、作者ID,选集ID,阅读量,点赞量等等数据,大家可以自己选用。 # 爬取 得益于`Swift`处理字符串的丰富的库函数,代码非常简单。 ```swift func getJianshuArticle(url: String) -> JSArticle? { guard let url = URL.init(string: url) else { return nil } guard let data = try? Data.init(contentsOf: url) else { return nil } guard let html = String.init(data: data, encoding: .utf8) else { return nil } guard let jsonBegin = html.range(of: "<script type=\"application/json\" data-name=\"page-data\">")?.upperBound else { return nil } guard let jsonEnd = html.range(of: "}}</script>")?.lowerBound else { return nil } let jsonRange = jsonBegin..<html.index(jsonEnd, offsetBy: 2) let subString = html.substring(with: jsonRange) let json = JSON.parse(subString) return JSArticle.init(json: json) } ``` 以上是同步爬取模型的代码。测试结果: ![17002_2](//cdn.blog.yuusann.com/img/posts/17002_2.png) 不过这段代码在`Linux`中并不能很好的工作。在`Linux`中单线程使用会阻塞`Perfect`的`HTTPServer`导致其他请求进不来,多线程使用时会直接报错崩溃: ``` falat error: transfer completed, but there's no current request.file Foundation/NSURLSession/NSURLSessionTask.swift, line 794 ``` 因此如果要在`Linux`中使用,需要将网络部分改用`URLSession`,具体代码为: ```swift fileprivate func asyncSetJianshuArticle(url: URL, handle: @escaping ((JSArticle?) -> Void)) { URLSession.init(configuration: URLSessionConfiguration.default).dataTask(with: url, completionHandler: { data, _, _ in handle(self.buildJianshuArticle(data: data)) }).resume() } fileprivate func buildJianshuArticle(data: Data?) -> JSArticle? { guard let data = data else { return nil } guard let html = String.init(data: data, encoding: .utf8) else { return nil } guard let jsonBegin = html.range(of: "<script type=\"application/json\" data-name=\"page-data\">")?.upperBound else { return nil } guard let jsonEnd = html.range(of: "}}</script>")?.lowerBound else { return nil } let jsonRange = jsonBegin..<html.index(jsonEnd, offsetBy: 2) let subString = html.substring(with: jsonRange) let json = JSON.parse(subString) return JSArticle.init(json: json) } ``` 以上代码能在`Linux`中高效工作并爬取模型。 # 结语   其实`Swift`引入`String.Index`以后一直很怕操作字符串。虽然这对于保证字符串和切片操作的安全来说很有效,但语法上有些晦涩,还是需要多加练习。 对于这段代码,想玩儿的同学拿去玩儿吧。😌


评论:0条


返回列表

返回主页