React Native网页数据抓取与解析实现
优采云 发布时间: 2022-05-11 08:51React Native网页数据抓取与解析实现
RN 提供了Fetch API和 XMLHttpRequest API,基于这两个库的二次封装库也是可以用的,比如 frisbee和axios,所以在 RN 下进行 HTTP 请求不是什么问题。
HTML 解析
当前,最好用的 js html parser 应属cheerio,是否可以在 RN 使用呢?让我们试试。
首先,安装 cherrio(注意,一定是要 v0.22.0,后面解释):
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">$ npm i cheerio@0.22.0</p>
使用:
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">import cheerio from 'cheerio'const $ = cheerio.load('Hello world')</p>
很不幸,出现了错误:
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">error: bundling failed: "Unable to resolve module `events`</p>
这是因为 cheerio 的依赖 htmlparser2 依赖一些 node 内置的库。不过这是可以被解决的,理论上,只要这些依赖库不依赖更底层的接口,那么就可以通过 npm 安装上这些依赖:
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">$ npm i events stream buffer</p>
再次刷新,我们发现 cheerio 已经可以正常使用了!
其实这个问题有在 cheerio 的 issues 上讨论过:。有人为了解决这个问题弄了另外一个库cheerio-without-node-native,然而这种做法不仅没有必要而且非常糟糕,因为这个分裂出去的版本的质量是难以保证的。作者的观点是:
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 并不提供。
测试
由于网页随时可能发生变化,测试就显得尤为重要。这里我以一段获取简书用户数据的代码为例,做一个简单的黑箱测试。
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">// api.js// 这里,我实现了一个 getUserData 函数,以 UserID 为参数,// 获取个人主页数据,并解析出用户头像链接、用户昵称、发表的文章async function getUserData(user) { const response = await fetch('http://www.jianshu.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}</p>
为了能在 node 环境下使用 fetch,需要安装 node-fetch。RN 已经默认安装了 jest,我们就用它来测试吧:
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">// __test__/api.js// 测试 getUserData 是否能正常运行,并返回预期的结果// 这里为了更真实的模拟实际情况,而用 node-fetch 模拟了 RN 里的 fetch// 也可以 mock fetch 然后返回预设的测试数据import {getUserData} from '../api'global.fetch = require('node-fetch')1test('getUserData', async () => { const data = await getUserData('3747663284a0')<br />2 expect(data.name).toBe('7c00')<br />3 expect(data.avatar).toMatch(/http:\/\/upload\.jianshu\.io\/users\/upload_avatars.*\.jpg/)<br />4 data.articles.forEach(article => expect(article.title).not.toBeNull()) console.log(data)<br />5})<br /></p>
运行测试:
<p style="margin-left: 8px;margin-right: 8px;letter-spacing: 0.5px;line-height: 1.75em;margin-bottom: 10px;">$ npm test</p>
npm test
另一种获取网页数据的黑科技
除了传统的 HTML 请求解析,在某些情况下我们还可以用类似 PhantomJS 的方案,优点是可以很好地避开一些限制,降低解析难度。RN 里当然用不了 PhantomJS,但我们有 WebView,可以通过 injectedJavaScript 注入 js,用 postMessage 回传数据,比如这段用于获取页面中视频链接的代码:
1 this._loaded(event.nativeEvent.data)}<br />2 source={{ uri: this.state.webViewUrl, headers: { referer: 'https://newplayer.jfrft.com',<br />3 }<br />4 }}<br />5/><br />
PS. 慎用该方法,首先是 WebView 消耗资源太大,其次是难以测试,缺乏稳定性。
本文摘自异步社区,作者:xiangzhihong作品:《React Native网页数据抓取与解析实现》,未经授权,禁止转载。