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

发布于: 2017-01-11 18:57
阅读: 1631
评论: 0
喜欢: 16

本文提供了一种可通过URL链接爬取“简书”文章模型的方法。

引言

前几天突然想把简书的阅读和点赞数据也累加到「我的博客」的访问量中,于是就简单地写了一段代码来根据文章URL爬取页面上的文章模型。

代码比较简陋,只保证在简书现在的版本上可用,如果将来简书改了页面了有可能会失效。

模型

简书文章页面的源代码中有这么一段JSON记录了文章的相关信息: 17002_1

所以只要把这一段字符串爬出来,构造自己的模型即可。这里使用了「SwiftyJSON」来处理JSON的字符串,如果对它的实现感兴趣,可以看我这篇文章:「SwiftyJSON 源码解析」。根据JSON的结构,模型分为以下三个部分:

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
    }
}
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"
    }
}
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包含了JSArticleNoteJSArticleNoteShow两个结构,模型中详细描述了文章ID、作者ID,选集ID,阅读量,点赞量等等数据,大家可以自己选用。

爬取

得益于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

不过这段代码在Linux中并不能很好的工作。在Linux中单线程使用会阻塞PerfectHTTPServer导致其他请求进不来,多线程使用时会直接报错崩溃:

falat error: transfer completed, but there's no current request.file Foundation/NSURLSession/NSURLSessionTask.swift, line 794

因此如果要在Linux中使用,需要将网络部分改用URLSession,具体代码为:

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以后一直很怕操作字符串。虽然这对于保证字符串和切片操作的安全来说很有效,但语法上有些晦涩,还是需要多加练习。

对于这段代码,想玩儿的同学拿去玩儿吧。😌


Thanks for reading.

All the best wishes for you! 💕