想要用RN写个小说APP第二天【抓取内容💻】

579 阅读4分钟

我正在参加「掘金·启航计划」

在某天某时,我立下了这个奇怪的flag,关于我和他的爱恨情仇,请看我的第一篇文章
想要用RN写个小说APP第一天
当前已经实现的功能有:

  • 爬取页面内容
  • 状态管理以及数据持久化
  • 小说内容主题自定义(字体/颜色/...)
  • 爬取后如何进行文本分页
  • 分页后如何在用户阅读时,无感加载上/下一章
    (相关的bug经历我最近都会写出来🧐🧐🧐)

    项目地址,star看我后续更新🌠🌠🌠

今日内容

我在上一篇大致实现完三个Tab栏的布局以及在非相干页面将在下方的Tab进行隐藏。也许是你的话,还会继续将页面的导航流程继续完善下去进行。也就是我们经常将所有页面的子路由定义好页面文件。最后再去实现详细功能。而我就不一样了🕺🕺。我想要立刻实现关于阅读一本小说的相关所有的流程。

抓取内容

首先我们需要的是将小说的内容从网页中抓取下来,也就是爬虫,将页面请求下来我们一般需要使用网络请求Api,类似ajaxfetch等。而RN的官方文档中明确标明了以及内置了fetch。所以这当然也就是第一选择。

SHOW CODE

fetch(url)
    .then(function (response) {
      console.log(response.text());
      return response.text();
    })

控制台输出: 作为一个纯真的切图仔,看到这熟悉的HTML文本不得不说是有多么亲切了㊗️㊗️㊗️

image.png

解析内容

虽然说吧,上面的HTML文本再让我亲切。当时他不够方便啊🤹
有那么一瞬间,我竟然想像Vue的模板解析那样,通过匹配尖括号将其进行解析。但是吧,emmm我很快就将这个不实际的想法抛走了。老老实实的找到了那个它。

cheerio
note:为服务器特别定制的,快速、灵活、实施的jQuery核心实现.

简单的说就是他可以将我们上面获取到的HTML文本进行解析,并返回一个类似于我们在jquery中使用的$对象,然后你就可以尽情的享受jquery方法带来的快乐。(快乐何止是加倍那么简单🤸🤸)

获取内容标题

喜闻乐见的Dom选择器环节,通过浏览器访问,确定我们要获取的目标选择器(后续替换成自定义书源选择器匹配),上面是标题,下面是内容:

image.png
相关代码如下:

const $ = cheerio.load(
        html.replace(/<\s*br[^>]*>/gi, '\n').replace(/\n{2,}/g, '\n'),
      );
      const textContent = $('.box_box')
        .text()
        .trim()
        .split('\n')
        .map(_ => '\u3000\u3000' + _.trim())
        .join('\n');
title = $('.box_con h1').text();
content = toDBC(textContent);

内容处理

cheerio.load传入我们简单处理后的HTML文本,为什么要处理?获取到的内容有时候会有两个<br>或者多个换行标签,显示出来之后会很丑(对我来说哈哈哈哈)。我们首先通过正则将其全都替换成一个\n
再通过\n符号对文本分段,再在每一段的开头插入两个全角空格\u3000,全角的空格在显示中与一个汉字同等宽度。视觉上更加舒服(还是对我来说哈哈哈🙌🙌):

image.png

后续在还进行了一个`toDBC`函数的转换:这个函数的作用是将文本当中的所有非全角符号转换成全角显示。比如有英文字母、中文标点符号。代码如下(非原创):
const reg =
  /[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]/;
function toDBC(str: string): string {
  var result = '';
  for (var i = 0; i < str.length; i++) {
    const code = str.charCodeAt(i);
    if (code >= 33 && code <= 126) {
      result += String.fromCharCode(str.charCodeAt(i) + 65248);
    } else if (code === 32) {
      result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
    } else {
      result += str.charAt(i);
    }
    if (reg.test(str[i])) {
      result += ' ';
    }
  }
  return result.replace(/【/g, '[').replace(/】/g, ']').replace(/!/g, '!');
}

封装与缓存

最后将获取的这一段逻辑封装成hooks,将对应文本的的url作为键,设置缓存,在下一次获取时,判断当前缓存是否命中,若存在,直接获取缓存:

const useChapterForUrl = (url: string) => {
  const [content, setContent] = useState<ChapterContent>({
    content: '',
    title: '',
  });
  const isGet = useRef(false);
  useLayoutEffect(() => {
    if (cache.has(url)) {
      const data: ChapterContent = cache.get(url) || {};
      setContent(data ?? '');
      isGet.current = true;
    } else {
      getNovelContent(url).then(res => {
        setContent(res);
        cache.set(url, res);
        isGet.current = true;
      });
    }
  }, [url]);
  return {...content, get: isGet.current};
};

结语

在开始写的时候还是很顺利的, 这次滴内容就写到这了,后续计划更新的内容有: (下面计划的功能都是已经大致完成了💨)

项目地址,star看我后续更新🌠🌠🌠