如果你打算用 RN 寫某網(wǎng)站的第三方 App,但該網(wǎng)站不提供可以返回 JSON 的接口坊夫,這種情況下就需要自己進行頁面抓取及解析搬葬。
首先,我們需要明確一件事绍移,RN 既不是 browser 也不是 node悄窃,這意味著有些 js 庫是不能直接拿來用的。
HTTP 請求
RN 提供了 Fetch API 和 XMLHttpRequest API蹂窖,基于這兩個庫的二次封裝庫也是可以用的轧抗,比如 frisbee 和 axios,所以在 RN 下進行 HTTP 請求不是什么問題瞬测。
HTML 解析
當前横媚,最好用的 js html parser 應屬 cheerio,是否可以在 RN 使用呢月趟?讓我們試試灯蝴。
首先,安裝 cherrio(注意孝宗,一定是要 v0.22.0穷躁,后面解釋):
$ npm i cheerio@0.22.0
使用:
import cheerio from 'cheerio'
const $ = cheerio.load('<h2 class="title">Hello world</h2>')
很不幸,出現(xiàn)了錯誤:
error: bundling failed: "Unable to resolve module `events`
這是因為 cheerio 的依賴 htmlparser2 依賴一些 node 內(nèi)置的庫因妇。不過這是可以被解決的问潭,理論上,只要這些依賴庫不依賴更底層的接口沙峻,那么就可以通過 npm 安裝上這些依賴:
$ npm i events stream buffer
再次刷新睦授,我們發(fā)現(xiàn) cheerio 已經(jīng)可以正常使用了!
其實這個問題有在 cheerio 的 issues 上討論過:https://github.com/cheeriojs/cheerio/issues/1058摔寨。有人為了解決這個問題弄了另外一個庫 cheerio-without-node-native去枷,然而這種做法不僅沒有必要而且非常糟糕,因為這個分裂出去的版本的質(zhì)量是難以保證的。作者的觀點是:
You can install the missing packages from npm (events, stream and utils afaict) and they will be automatically picked up.
I would not recommend the usage of a fork as it will make it difficult to track down issues and will delay, if not prevent, patches for bugs.
至于為什么只能用 cheerio@0.22.0删顶,是因為之后的版本竖螃,cheerio 引入了 parse5,而 parse5 依賴 stream.Writable逗余,npm 安裝的 stream 并不提供特咆。
測試
由于網(wǎng)頁隨時可能發(fā)生變化,測試就顯得尤為重要录粱。這里我以一段獲取簡書用戶數(shù)據(jù)的代碼為例腻格,做一個簡單的黑箱測試。
// api.js
// 這里啥繁,我實現(xiàn)了一個 getUserData 函數(shù)菜职,以 UserID 為參數(shù),
// 獲取個人主頁數(shù)據(jù)旗闽,并解析出用戶頭像鏈接酬核、用戶昵稱、發(fā)表的文章
async function getUserData(user) {
const response = await fetch('http://www.reibang.com/u/' + user)
const $ = cheerio.load(await response.text())
return {
avatar: 'http:' + $('.avatar img').attr('src'),
name: $('.title .name').text(),
articles: $('.note-list li').map(function () {
return {
title: $('.title', this).text(),
}
}).get()
}
}
export {getUserData}
為了能在 node 環(huán)境下使用 fetch适室,需要安裝 node-fetch嫡意。RN 已經(jīng)默認安裝了 jest,我們就用它來測試吧:
// __test__/api.js
// 測試 getUserData 是否能正常運行捣辆,并返回預期的結果
// 這里為了更真實的模擬實際情況蔬螟,而用 node-fetch 模擬了 RN 里的 fetch
// 也可以 mock fetch 然后返回預設的測試數(shù)據(jù)
import {getUserData} from '../api'
global.fetch = require('node-fetch')
test('getUserData', async () => {
const data = await getUserData('3747663284a0')
expect(data.name).toBe('7c00')
expect(data.avatar).toMatch(/http:\/\/upload\.jianshu\.io\/users\/upload_avatars.*\.jpg/)
data.articles.forEach(article => expect(article.title).not.toBeNull())
console.log(data)
})
運行測試:
$ npm test
另一種獲取網(wǎng)頁數(shù)據(jù)的黑科技
除了傳統(tǒng)的 HTML 請求解析,在某些情況下我們還可以用類似 PhantomJS 的方案罪帖,優(yōu)點是可以很好地避開一些限制促煮,降低解析難度。RN 里當然用不了 PhantomJS整袁,但我們有 WebView菠齿,可以通過 injectedJavaScript 注入 js,用 postMessage 回傳數(shù)據(jù)坐昙,比如這段用于獲取頁面中視頻鏈接的代碼:
<WebView
injectedJavaScript={`
const video = document.querySelector('video');
if (video) {
postMessage(video.src);
}
`}
onMessage={event => this._loaded(event.nativeEvent.data)}
source={{
uri: this.state.webViewUrl,
headers: {
referer: 'https://newplayer.jfrft.com',
}
}}
/>
PS. 慎用該方法绳匀,首先是 WebView 消耗資源太大,其次是難以測試炸客,缺乏穩(wěn)定性疾棵。