基于Nodejs的爬虫
简介
基于 Node.JS 爬取 博客园 1W+博文,对博文内容做关键词提取,生成词云。
演示
安装
克隆代码
git clone git@github.com:ZhihaoJian/bokeyuan_spider.git
如果觉得安装速度慢,可将源切换到淘宝,cmd
或者 powershell
下执行
yarn config set registry 'https://registry.npm.taobao.org'
进入bokeyuan_spider
文件夹安装依赖
yarn install
目录结构
整个项目重要目录是public
和server
,public
目录放置词云的前端代码,server
目录放置后端代码。在项目中,server
目录还放置了爬虫、数据库等相关代码。另外,根目录下的 word.txt
是 jieba
分词结果。
基本工作原理
我们知道互联网是通过每一份HTML通过某种方式互相关联在一起,从而形成一个巨大的 网
。我们只要在其中一份页面就可以沿着 网
去到不同的页面。而页面和页面之间是通过 超链接
方式联系在一起,所以我们只要找到这个 超链接
就可以到达下一个页面。而爬虫就是这样的工作方式,找到 超链接
,沿着超链接一直前进并记录下所到之处,就可以抵达互联网的任何一个角落。
核心功能
- 抓取博文链接
在 spider.js
中我们将使用 Google Chrome 的 puppeteer
,作为演示
打开server
目录下的spider
文件里的spider.js
。spider.js
的主要功能是使用 puppeteer 对博客园的 班级列表博文 链接进行爬取。
以下是spider.js
的核心代码
/**
* spider.js
*/
toPage(page, URL).then(async (url) => {
console.log('PAGE LOG'.blue + ' Page has been loaded');
//分页数量
totalPages = await page.$eval('.last', el => Number.parseInt(el.textContent));
console.log(`PAGE LOG`.blue + ` site:${URL} has ${totalPages} pages`);
//抓取post文超链接
for (let i = 1; i <= totalPages; i++) {
url = getNextUrl(i);
await toPage(page, url, 1500);
let links = await parseElementHandle(page, url);
let result = await getPostUrls(links);
postUrls.push(result);
}
//保存到数据库
saveToDB(postUrls);
console.log('PAGE LOG : All tasks have been finished.'.green);
writeToFileSys();
await broswer.close();
});
toPage
方法是根据指定的URL跳转的相应页面,方法接收两个参数,page
是经过 puppeteer
实例化的对象,URL
是我们指定爬虫的入口。待页面加载成功以后,响应回调函数,获取当前页面的最大分页数量,for
循环每隔 1500ms
跳转到下一页并抓取页面中所有博文链接。最后保存到数据库中。
- 抓取博文内容
打开 content.js
,在这里我们不用前面演示的 puppeteer
模块而使用 cheerio
和 request
模块。
yarn add cheerio request
cheerio
可以简单看作是服务器端的jQuery,而request
是一个高度封装好了的 nodejs
http模块
以下是 content.js
的核心代码示例
/* content.js
* 根据post文链接抓取post文内容
*/
getIPs().then(async ipTable => {
for (let i = 0; i < postLen; i++) {
let postUrl = docs[i];
proxyIndex < ipTable.length ? proxyIndex : proxyIndex = 0;
rq(postUrl, ipTable[proxyIndex++], (body) => parseBody(body, postUrl))
.catch(async e => {
console.log('LOG'.red + ': Request ' + postUrl + ' failed. Retrying...');
ipTable.splice(proxyIndex, 1);
await delay(3000);
getIPs().then(ips => ipTable = ipTable.concat(ips));
await rq(postUrl, ipTable[++proxyIndex], (body) => parseBody(body, postUrl));
})
}
})
函数 getIps
用于获取三方代理IP,然后使用 request
模块对指定的博文链接发起http请求。函数 parseBody
使用 cheerio
模块解析博文内容,然后保存到数据库中。在 catch
块中我们将处理请求失败的情况,这里我们更换新的代理IP,针对请求失败的博文链接重新发起请求。
- 分词
关于分词,我们选择 node-jieba,它是python jieba库的一个nodejs版本
安装 node-jieba
,详细 API
yarn add node-jieba
核心代码如下
/* jieba.js
* 分词,以txt形式保存到文件系统
*/
(() => {
const jiebaResult = [];
POST.find({}, async (err, docs) => {
if (err) {
throw new Error(err)
}
docs.forEach((v) => {
jiebaResult.push(jieba(v.post));
});
await Promise.all(jiebaResult).then(() => {
writeToFileSys();
})
console.log('end');
})
})()
我们从数据库中取出所有的博文,循环依次对博文做一个关键词提取。因为文本量巨大,所以这里的重点是 异步分词
。待所有 异步分词
结束以后,将分词结果写入文件系统。
下面给出异步分词的实现
/**
* jieba异步分词
*/
function jieba(post) {
return new Promise(resolve => {
analyzer.tags(post, {
top: 20,
withWeight: false,
textRank: false,
allowPOS: ['ns', 'n', 'vn', 'v']
}, (err, results) => {
if (err) {
console.log(err);
}
if (results) {
results.forEach(word => {
if (wordMap.has(word)) {
let count = wordMap.get(word);
wordMap.set(word, ++count);
} else {
wordMap.set(word, 0);
}
})
}
resolve(wordMap);
})
})
}
jieba
函数返回一个 Promise
,Promise
是 es6 新增的一种异步解决方案,比传统的解决方案,例如回调函数和事件更强大和合理。因为要对词频做统计,使用 Map
对象保存分词结果,这从查找性能或是可读性上解释都更加合理。
踩坑之路
- 使用
cheerio
解析HTML,中文乱码
在使用 cheerio.html()
方法时候,发现多数博文内容都变成了 x56ED
等 Unicode编码。经查阅,可以关闭这个转换实体编码的功能
const $ = cheerio.load(html)
改成
const $ = cheerio.load(html,{decodeEntities:false})
- 代理问题
单IP爬取1W数据量,明显要被封号的。最佳的解决方式是买一堆的代理IP,配合 request
库,轮询使用代理IP进行请求,进行爬取。亲测使用得当的情况下,1W+博文可以在5min内爬取完毕。
示例代码如下
/**
*
* @param {string} REQUEST_URL 待爬取的URL
* @param {string} proxy 代理IP
* @param {fn} success 成功回调函数
* @param {fn} fail 失败回调函数
*/
function rq(REQUEST_URL, proxy, callback) {
return rp({ 'url': url.parse(REQUEST_URL), 'proxy': `http://${proxy}` })
.then(res => callback(res))
}
词频前200
基于Nodejs的爬虫的更多相关文章
- 基于nodejs模拟浏览器post请求爬取json数据
今天想爬取某网站的后台传来的数据,中间遇到了很多阻碍,花了2个小时才请求到数据,所以我在此总结了一些经验. 首先,放上我所爬取的请求地址http://api.chuchujie.com/api/?v= ...
- 浏览器自动刷新——基于Nodejs的Gulp LiveReload与VisualStudio完美结合。
本文版权桂博客园和作者吴双共同所有,转载和爬虫请注明原文地址 http://www.cnblogs.com/tdws/p/6016055.html 写在前面 大家好我是博客园的蜗牛,博客园的蜗牛就是我 ...
- 一个基于NodeJS开发的APP管理CMS系统
花了大概3周独立开发了一个基于NodeJS的CMS系统,用于公司APP的内容管理( **公司APP?广告放在最后 ^_^ ** ,管理员请理解~~~ )晚上看了部电影还不想睡,闲着也是闲着就作下小小总 ...
- nodejs豆瓣爬虫
从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎.chrome浏 ...
- 转-基于NodeJS的14款Web框架
基于NodeJS的14款Web框架 2014-10-16 23:28 作者: NodeJSNet 来源: 本站 浏览: 1,399 次阅读 我要评论暂无评论 字号: 大 中 小 摘要: 在几年的时间里 ...
- [Intel Edison开发板] 04、Edison开发基于nodejs和redis的服务器搭建
一.前言 intel-iot-examples-datastore 是Intel提供用于所有Edison开发板联网存储DEMO所需要的服务器工程.该工程是基于nodejs和redis写成的一个简单的工 ...
- 基于NodeJS的全栈式开发
前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们重新思考了“前后端”的定义,引入前端同学都熟悉的 NodeJS,试 ...
- 基于Nodejs生态圈的TypeScript+React开发入门教程
基于Nodejs生态圈的TypeScript+React开发入门教程 概述 本教程旨在为基于Nodejs npm生态圈的前端程序开发提供入门讲解. Nodejs是什么 Nodejs是一个高性能Ja ...
- (转)也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离)
原文链接:http://ued.taobao.org/blog/2014/04/full-stack-development-with-nodejs/ 随着不同终端(pad/mobile/pc)的兴起 ...
随机推荐
- codeforces704D Captain America【上下界最大流】
分别给行和列hash建两排点,对(x,y)坐标连x行y列的点 设红色价格低,那么就要尽量多选红色 设一个点出度为s,要求最小的最大差值为d,又,假设有流量表示选红没流量表示选蓝,那么要求就变成了这个点 ...
- js中的原型以及原型链
在js中原型是每个构造函数的属性: 这个算 js 核心概念的一部分 var f1 = new Foo(); 对象 f1 的构造函数就是 Foo , f1的原型 __proto__ 就指向构造函数 Fo ...
- Mac 下sublime的插件
最近更换了本本,入了港行的Mac pro,来替代原来的Thinkpad,在windows上工作做完之后,搭建了一下开发环境,eclipse是必须的,但是luna没有html editor,然后就在 e ...
- 01分数规划初探?!By cellur925
都要\(NOIp\)了为啥我还在看这种玄学玩意..... \(01\)分数规划:这是一个问题模型\(qwq\),一般是在求\[\frac{\sum_{i=1}^{n} a_i*x_i}{\sum_{i ...
- pyinstaller打包多个py文件仍报错ModuleNotFoundError: No module named 'xxx'
[问题现象] 使用pyinstaller A.py -p b.py -p c.py打包多个文件 或者使用main.spec在Analysis配置好各个文件打包 打包成功后,运行main.exe仍然报错 ...
- git push error: ! [rejected] failed to push some refs to . . .
报错情况: 报错原因:远程库与本地库不一致造成的,需要把远程库同步到本地库! 解决办法: git pull --rebase origin master 这条指令是将远程库中的更新合并到本地库,--r ...
- php追加数组的问题
PHP数组合并两种方法及区别 如果是关联数组,如下: 代码如下: $a = array( 'where' => 'uid=1', 'order' => 'uid', ); $b = arr ...
- Java与Javac版本不一致问题解决方案
问题:在自己电脑上运行java -version和javac -version,发现java版本不一致,然后查看了环境变量,JAVA_HOME 是 C:\Program Files(x86)\Java ...
- [软件工程基础]Alpha 展示博客
团队成员 个人简介 个人简介参照团队介绍博客. 个人博客 王嘉睿爵:http://www.cnblogs.com/whynotRW/ 游心:http://www.cnblogs.com/jefhq/ ...
- Codeforces Round #564 (Div. 2) C. Nauuo and Cards
链接:https://codeforces.com/contest/1173/problem/C 题意: Nauuo is a girl who loves playing cards. One da ...