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

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


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

# 引言

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

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

# 模型

简书文章页面的源代码中有这么一段`JSON`记录了文章的相关信息: ![17002_1](..///cdn.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.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条


返回列表

返回归档

返回主页