使用 React-Native 抓取网站
多年 Web 开发经验让我对 React Native 有了全新的认识,感觉就像开启了新篇章。它不仅能让你更好地使用原生功能,还能减少应用的限制。例如,你可以使用fetch()toy 来获取任何你想要的网站。这使得客户端网站爬虫成为可能。
为什么
也许你需要从某个服务获取数据,但他们没有开放 API,或者 API 无法提供你所需的所有数据,又或者 API 本身就不好用。通常情况下,你需要搭建一个服务器来抓取目标网站并将其转换为可用的 API,但如果你能直接在客户端访问所有网站的全部数据,就能节省大量时间。
以亚马逊网站为例。你想显示页面上的所有产品,并提供一种加载下一页的方法,但你想把它放在我们自己的数据结构中,这样你就可以围绕它构建自己的用户界面。
如何
- 从服务器获取 HTML
- 从HTML中提取所需数据
- 重新整理数据以供我们使用
1. 从服务器获取 HTML
那只是最简单的部分。
async function loadGraphicCards(page = 1) {
const searchUrl = `https://www.amazon.de/s/?page=${page}&keywords=graphic+card`;
const response = await fetch(searchUrl); // fetch page
const htmlString = await response.text(); // get response text
...
}
使用搜索模式获取 URL 会返回一个包含一些项目的 HTML 页面。
2. 从HTML中提取所需数据
这有点棘手。数据在HTML代码里,但它是一个字符串。
最简单的办法是使用正则表达式来解析字符串并获取数据,但 HTML 没有正则语法,所以这种方法行不通。
更好的方法是使用 HTML 解析器和 CSS 选择器。
Cheerio就是这个解决方案。它自带 HTML 解析器,并重新实现了 jQuery 的核心功能,因此你可以在 Node.js 上使用它。
问题是,React-Native 缺少大部分 Node.js 包,所以无法正常工作。
我花了很长时间才找到一个可以在 React-Native 上运行的 Cheerio 的重新实现版本,这个包的命名有点奇怪,哈哈。
但有了这项技术,数据提取也变得轻而易举了。
async function loadGraphicCards(page = 1) {
const searchUrl = `https://www.amazon.de/s/?page=${page}&keywords=graphic+card`;
const response = await fetch(searchUrl); // fetch page
const htmlString = await response.text(); // get response text
const $ = cheerio.load(htmlString); // parse HTML string
const liList = $("#s-results-list-atf > li"); // select result <li>s
...
}
3. 重塑数据以供后续使用
从 HTML 中提取数据后,我们就可以开始根据我们的使用场景对其进行重塑。提取和重塑之间的界限在这里有些模糊,<li>我们选择的元素包含大量的标记,从中提取正确的数据也属于提取过程,但通常这两个步骤是相辅相成的。
async function loadGraphicCards(page = 1) {
const searchUrl = `https://www.amazon.de/s/?page=${page}&keywords=graphic+card`;
const response = await fetch(searchUrl); // fetch page
const htmlString = await response.text(); // get response text
const $ = cheerio.load(htmlString); // parse HTML string
return $("#s-results-list-atf > li") // select result <li>s
.map((_, li) => ({ // map to an list of objects
asin: $(li).data("asin"),
title: $("h2", li).text(),
price: $("span.a-color-price", li).text(),
rating: $("span.a-icon-alt", li).text(),
imageUrl: $("img.s-access-image").attr("src")
}));
}
这虽然不是一个全面的例子,但我想你应该明白我的意思了。现在我们可以利用应用程序中新增的对象列表,为亚马逊的搜索结果创建我们自己的用户界面。
class App extends ReactComponent {
state = {
page: 0,
items: [],
};
componentDidMount = () => this.loadNextPage();
loadNextPage = () =>
this.setState(async state => {
const page = state.page + 1;
const items = await loadGraphicCards(page);
return {items, page};
});
render = () => (
<ScrollView>
{this.state.items.map(item => <Item {...item} key={item.asin}/>)}
</ScrollView>
);
}
const Item = props => (
<TouchableOpacity onPress={() => alert("ASIN:" + props.asin)}>
<Text>{props.title}</Text>
<Image source={{uri: props.imageUrl}}/>
<Text>{props.price}</Text>
<Text>{props.rating}</Text>
</TouchableOpacity>
);
结论
和大多数问题一样,只要拥有合适的工具,解决方案就会变得简单。通常,问题更多在于如何找到这些工具 :D
这种客户端爬虫方法无需 API 即可快速构建原型。亚马逊提供的静态 HTML 还算不错,所以这种方法在他们的网站上效果很好。
文章来源:https://dev.to/kayis/crawling-websites-in-react-native-38b