js提取指定网站内容(如何去组织所有文章的功能了吗?|之旅)
优采云 发布时间: 2021-11-20 12:17js提取指定网站内容(如何去组织所有文章的功能了吗?|之旅)
1. 前言
这个文章藏在心里很久了,久久不敢开始写。主要是不知道怎么组织这样的技术文章文章。
其实我个人觉得技术文章是最难写的,细节往往很难把握。一些技术细节没有解释到位,对于害怕阅读的人来说很难。相反,一些简单的东西怕解释的太多,增加了不必要的章节。
教人钓鱼不如教人钓鱼
所以,在这个文章中,我会尽量少发代码,多谈谈思考过程。
2. 编码的原因
最近在重建我的个人网站,想把我在简写的所有文章数据导入到那个网站。
这时候可能有朋友不高兴了:“如果你想得到你写的所有文章,为什么还要写代码?简书是不是已经提供了下载所有文章的功能?当前用户?”
兄弟们说得对。首先我要声明,简书确实是一个很棒的写作平台。可以满足大部分用户的需求,但是对于我这个癌症晚期的“懒惰”程序员来说,还是有点不适应。太多了。我有以下考虑
坚持
不要重复自己
带着这个原理,让我们开始这次的爬虫之旅。
3. 功能介绍
简述爬虫的作用
向服务器发送请求,获取所有作者的文章。从文章中提取你需要的数据(文章内容、标题、发布日期...),整理好数据,存放到对应的数据库中。
本文将重点介绍页面请求、分析和数据组织。至于如何在数据库中存储数据,我就不多说了。有兴趣的可以直接看源码。
4. 技术栈
最后选择使用node.js来写这个爬虫。毕竟重新学习python或ruby等服务器端语言要花很多时间。当然这还不够。
工欲善其事,必先利其器
为了写更少的代码,我还需要一个更成熟的爬虫框架。这里使用了节点爬虫。个人觉得这是一个很酷的爬虫框架,前端人员在使用的时候会觉得比较亲切——我们可以用我们最亲切的jQuery语法来解析响应返回的页面。
5. 数据模型
在我们要爬取东西之前,首先要确认我们要爬取什么。为了简化文章,我将需要爬取的内容设置为以下三个字段(源码可以多于这三个字段)。
我只需要找到一种方法,从响应返回的页面中提取上述三种类型的数据。至于把数据存放在什么数据库中,如何存放,就看个人喜好了。
6. 制定爬取策略(1) 基本信息爬取
首先进入通讯作者短书首页,类似这个页面。我们会看到一堆文章列表,看看能不能提取出我们需要的信息?
嗯,问题挺多的,除了文章这个标题,其他的内容好像很难抓取。不管怎样,都得去文章详情页查看
看来详情页还是可以满足我的需求的。因此,我决定使用以下策略来提取基本信息
(2)面对滚动加载,如何获取所有文章?
当我进入特定作者的简书首页时,我发现简书实际上并没有加载作者的所有文章,他只是加载了文章列表的一部分。如果需要获取更多的 文章 列表,则需要向下滚动并从服务器请求更多页面。
一开始我是站在前端的角度考虑这个问题,真的很痛苦。我最初的想法是使用一些库,例如 phantomjs 来模拟浏览器的行为。我打算模拟浏览器的滚动行为,并在数据加载后继续滚动,直到不再从服务器请求数据。我真的这样做了,后来才发现这是一场噩梦,这意味着我不得不做以下事情:
先不说有没有库支持上面的功能,上面的策略对于挑剔的人来说都是不靠谱的,估计只有我一个人傻傻的跑着试了。经过多次尝试,发现模拟滚动行为相当困难,使用phantomjs时开发环境经常卡死。我的直觉告诉我,可能有更好的方法来做到这一点。当你对一个问题感到困惑时,你可能会尝试超越原来的思维方式,换个角度思考问题。问题往往要简单得多。
后来从后端的角度考虑了这个
问:这种加载更多行为的本质是什么?
答:向服务器发送请求以获取更多页面数据。
那么我只需要知道浏览器向服务器发送的请求的URL以及页面滚动时的相应参数,就可以使用爬虫来迭代发送这个请求,进而达到获取<的完整列表的目的@文章 ?
OK,马上去Network,就可以看到我们发送的网络请求了。清除网络下的历史记录后,我滚动页面,发现以下内容。
服务器响应后,返回渲染的列表数据。果然,如我所料。然后我会看完整的网址
原来它是使用分页参数page向服务器请求分页数据的。我通过代码封装了这个过程
var i = 1;
var queue = []
while( i < 10) {
var uriObject = {
uri: 'http://www.jianshu.com/u/a8522ac98584?order_by=shared_at&&page=' + i,
queue.push(uriObject);
i ++;
}
这只是代码片段的一部分,将 10 个 URL 存储到队列数组中。最后只能得到10页的数据,但是有问题。如果真实数据少于10页,如何处理?
我试图请求第 100 页上的数据,看看会发生什么。发现简书服务器返回302状态码,然后浏览器跳转到个人动态页面。
这个状态码很有用,我可以判断我们请求的页码参数page的值是否超过了这个状态码指定的页数。
我可以预设更多的请求。如果请求返回此状态码,则不会对请求的数据进行任何处理(因为已超出页数)。否则,分析返回的数据以提取我们需要的关键数据。
当然,这种方式相当粗鲁,会发送很多不必要的请求。有时间我会优化这部分代码。
7. 页面分析
发送请求后,我们可以在服务器响应后得到我们需要的页面。接下来要做的就是解析页面结果,提取我们需要的内容。如上所述,node-crawler 的爬虫框架内置了 jQuery,这让我们的页面解析工作变得更加轻松。
(1)获取文章详情页的url
我们来看看文章列表中每个条目的html结构(简书的程序员也有评论)。
我们只需要提取ul.note-list中的每个li a.title,然后提取它们的href属性对应的值,也就是我们需要获取的url。这种操作对于jQuery来说简直是小菜一碟。以下是我的代码片段。我把所有的网址都提出来之后,就会存放在articlesLink数组中,以备后用。
let articlesLink = [];
$('ul.note-list').find('li').each((i, item) => {
var $article = $(item);
let link = $article.find('.title').attr('href');
articlesLink.push(link);
})
(2)从详情页提取数据
查看详情页的结构
从页面结构中,我们可以很容易的提取出发布日期这两个字段的标题和内容
let title = $article.find('.title').text();
let date = $article.find('.publish-time').text().replace('*', '');
一些更新的文章在发布日期结束时会有一个*,所以我需要处理它们以避免干扰。但是,文章主体的提取存在一些问题。
我希望得到的最后一件事是降价格式的字符串。此时,我可以通过 to-markdown 包将 html 转换为 markdown。但是现在的问题是这个包好像无法解析div标签。我在想把文章的body里面的div标签全部删掉,然后通过to-markdown把处理后的字符串转换成对应的markdown格式字符串,然后就可以得到我们期望的数据了。
既然jQuery是神器,实现起来也不会很麻烦。但是我也想删除收录类名image-caption的label——这是简书的默认设置,有时候有点碍眼,可以考虑删除。
下面是我的代码片段:
var toMarkdown = require('to-markdown');
// 删除图片的标题
let $content = $article.find('.show-content');
$content.find('.image-caption').remove();
$content.find('div').each(function(i, item) {
var children = $(this).html();
$(this).replaceWith(children);
})
// 获取markdown格式的文章
let articleBody = toMarkdown($content.html());
最后,只需将它们放入对象中:
let article;
article = {
title: title,
date: new Date(date),
articleBody: articleBody
}
至于如何将上述数据存入数据库,方法有很多种。考虑到文章的长度,这里就不赘述了。个人尊重MongoDB。它是目前使用最多的非关系型数据库,非常灵活。对于构建渐进式应用程序,我认为这是一个更好的选择。在频繁修改表结构的情况下,至少不需要维护大量的迁移文件。
最终的存储结果如下,正好是68篇文章
结尾
不知不觉这个文章已经占用了好几个小时了,就算这个文章我把不必要的代码细节去掉了,重点说说自己的思考过程和遇到的问题。,不过还是写了很多字。视觉概括能力有待提高~~~~
注:本文只是一些经验和思考过程的总结,github源码仅供参考,并非即插即用包。可以根据自己的情况写一个符合自己需求的爬虫。我相信你可以比我做得更好。
快乐编码和写作!!