前言,此文章仅作教学用途,如果有人拿去干别的事情,我概不负责,如果该文章侵害到了掘金社区的利益,请膜法小编立刻联系我删除.
这是我在掘金的第一篇文章,遂想写个爬虫教程吧,目标就是掘金,嘿嘿
本文用到的三个工具为
- cheerio:jQuery语法,帮助你在非浏览器环境下解析网页用的
- qs 序列化成url的查询字符串,(不知道说没说对…)例:{a:1,b2} => a=1&b=2
- request 一个封装好的好用的请求库,我自己把它promise化了一下
全部代码见github
开始我是尝试直接请求掘金首页,然后用cheerio解析,然后拿到网页继续干活的。。可是事情并没有这么简单,通过这个方法爬取的网页跟我们正常浏览的首页不一样(有可能是我哪姿势不对) 没办法,只能从接口出发了
首先打开网页版掘金, 然后打开chrome的network,查看相关请求
咦!recommend?推荐?好了,进去一看,果然是首页热门文章,但是。。。 请求参数suid是什么?查看请求调用堆栈,,再看源码,emmmm 源码已经被混淆压缩了
这可怎么办?我没有登陆 查看完所有请求响应都没看到跟suid有关的,这可咋整?
直接进入请求网址,再更改suid,发现随便更改都可以得到相应 但是。。。这并没有什么用啊!就10条信息还需要爬吗?
没办法了,只能老套路了。先登陆再说
为了防止页面跳转后登陆请求消失,需要先勾选Preserve log,使页面跳转后前面的请求不会消失
差点忘打码了,qwq
模拟登陆
我是使用邮箱注册的,可能使用其它账号注册的接口会不一样
let data = await request.create({
url: 'https://juejin.im/auth/type/email',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({email: '155com', password: 'cfdsd.'}) //账号密码
});
直接一个请求搞定,得到如下相应,问题的关键就在于cookie
好了,接下来该找接口了,点击最新,发现network多了下面这个请求,其响应数据就是最新板块的文章
相关参数有来源,设备id,用户id,token等,其中最重要的就是token,id什么的随便改两个字符好像也没问题,但是token错了它会报illegal token,请求方法错了,就算参数对了也会报missing src。
当你看到token的时候,你会发现,哪都找不到这个数据
但是你仔细看第5张图的时候,你会发现这auth后面这串字符很像base64编码
获取token
打开相关网站,尝试解码
答案呼之欲出啊,最重要的三个参数全在这了,那么问题来了,node如何解析base64编码呢?
一行代码解决,buffer对象本身提供了base64的解码功能,最后调用toString方法,转成字符串,最后parse得到对象
const cookie = data.headers['set-cookie'];
const encodeToken = cookie[0]
.split(';')[0]
.split('=')[1];
const decodeToken = JSON.parse(new Buffer(encodeToken, 'base64').toString())
有了token,你就可以随心所欲的爬了,爬图片?主题?标题?文章内容?完全o98k
爬图片
async function getPictureUrl(request, url) {
let data = await request.get({ url });
console.log(data.body.match(/(https:\/\/user-gold-cdn).+?\/ignore-error\/1/g)); //匹配出图片url,这里掘金使用了cdn来存储图片
}
爬最新文章
async function getTopics(request, typeKey) {
const { token, clientId, userId } = require('./user.json');
const querystring = qs.stringify({
src: 'web',
uid: userId,
device_id: clientId,
token: token,
limit: 20,
category: 'all',
recomment: 1
});
const types = {
timeline: 'get_entry_by_timeline', //最新
comment: 'get_entry_by_comment', //评论
rank: 'get_entry_by_rank' //热门
};
const data = await request.get({
url: `https://timeline-merger-ms.juejin.im/v1/${types[typeKey]}?${querystring}`,
headers: {
host: 'timeline-merger-ms.juejin.im',
referer: 'https://juejin.im/timeline?sort=comment'
}
});
const body = data.body;
if (body.s !== 1) { //出错时清空信息
fs.writeFileSync('./user.json', JSON.stringify({}));
throw { type: 'token', message: body.m };
} else {
return body.d.entrylist;
}
}
简单的处理错误
try {
(async function() {
const request = new Riven();
request.setDefaultOptions({
headers: {
Cookie:
'gr_user_id=44868117-2a80-49e8-ba2b-2acd2a77a887; ab={}; _ga=GA1.2.1234597644.150' +
'6904166; MEIQIA_EXTRA_TRACK_ID=0uMtBISQ3EoiMICJMjpaZedfTBz; _gid=GA1.2.100579701' +
'2.1523672771; Hm_lvt_93bbd335a208870aa1f296bcd6842e5e=1521573516,1521573752,1522' +
'270605,1523672771; gr_session_id_89669d96c88aefbc=d54a635e-cece-4f16-aca4-808ae9' +
'2ee559; gr_cs1_d54a635e-cece-4f16-aca4-808ae92ee559=objectId%3A5a974f6ef265da4e8' +
'53d8d52; auth=; auth.sig=25Jg_aucc6SpX1VH8RlCoh6azLU; Hm_lpvt_93bbd335a208870aa1' +
'f296bcd6842e5e=1523675329; QINGCLOUDELB=165e4274d6090771b096025ed82d52a1ab7e48fb' +
'3972913efd95d72fe838c4fb|WtFwy|WtFwr'
}
}); //设不设置cookie都OK的
if (!isLogin()) {
login(request);
}
const topics = await getTopics(request, 'commnet');
for (let i = 0; i < topics.length; ++i) {
//await getDetailData(request, topics[i].objectId);
getPictureUrl(request, topics[i].originalUrl);
await sleep(2000); //伪线程挂起
}
})();
process.on('unhandledRejection', error => {
if (error.type === 'token') {
login();
}
console.log(error.message);
});
} catch (error) {
console.log(error.message);
}
当然,我没有使用数据库来保存数据,这只是教大家爬取原理,到这里,一个超级简单的爬虫就完成了
到最后好像也没用到cheerio了 ◔ ‸◔? 不过会在下一篇文章中使用
以上代码或言论如有错误,还望大家指出
学习了~
和《Node.js 包教不包会》里面的很类似哈
@fairyly 之前听说《Node.js 包教不包会》是个很棒的教程,但是一直没有看,哈哈,这是我自己摸索出来的
不错,很好的爬虫经历,支持
来自酷炫的 CNodeMD
你好,我是掘金社区负责人,请跟我走一趟:)
@yikejiucai 真的假的,不会封号吧,我现在还在准备下一部分教程呢…
dddddd