GitChat 课程页面分析
课程列表页
GitChat 课程页面链接为:https://gitbook.cn/gitchat/columns,第一次加载完成加载了30条数据,之后通过滚动条滚动到页面底部懒加载的方式,调用相关方法,获取之后的数据。
1559571985328-4c14f0a9-c4ab-40fd-94f4-fc665736d1f1.png查看浏览器控制台得知,每次请求数据调用的接口为:https://gitbook.cn/gitchat/columns?page=n&searchKey=&tag=,其中 n
为当前页面的页数, searchKey
为查询的关键词、 tag
为查询的标签,因为这里要爬取所有的课程数据,所以这里不需要 searchKey
和 tag
参数。
当请求 https://gitbook.cn/gitchat/columns?page=1 时,也就是获取第一页数据,和页面初次加载(访问 https://gitbook.cn/gitchat/columns)获取到的数据是一样的,所以通过 https://gitbook.cn/gitchat/columns?page=n 不断请求接口,就可以获取到所有的课程列表数据(课程链接)
GitChat 源码中调用该接口的具体方法为:
function loadMoreItems() {
currentPage += 1;
$.ajax({
url: "/gitchat/columns?page=" + currentPage + '&searchKey=&tag=',
type: "GET",
contentType: "application/json; charset=utf-8",
success: function(data, status) {
if (status == 'success' && data.code == 0) {
$('#colum-items #list_view').append(data.data);
console.log(data)
if (data.isEnd) {
if ('特价' == '') {
currentPage--;
$.ajax({
url: "/gitchat/columns?page=" + currentPage + '&searchKey=',
type: "GET",
contentType: "application/json; charset=utf-8",
success: function(data, status) {
if (status == 'success' && data.code == 0) {
$('#colum-items #list_view').append(data.data);
if (data.isEnd) {
scene.destroy(true);
$("#loader").removeClass("active");
$("#loader").hide();
$("#noMoreToLoad").show();
} else {
scene.update();
$("#loader").removeClass("active");
}
}
}
})
} else {
scene.destroy(true);
$("#loader").removeClass("active");
$("#loader").hide();
$("#noMoreToLoad").show();
}
} else {
scene.update();
$("#loader").removeClass("active");
}
$('.banner').lazyload({
threshold: 100,
effect: "fadeIn"
})
}
}
});
}
当 response.isEnd
为 true
时,表示结束了,这是最后一页数据了,这时就不再需要调用接口去请求了。
课程详情页
从课程列表页点击任意一条课程链接,会进入课程详情页,如:https://gitbook.cn/gitchat/column/5c0e149eedba1b683458fd5f,当前我的是超级会员,进入课程详情页后,页面右上角显示两个按钮:会员免费学和免费试读,点击会员免费学后触发方法来订阅该课程:
function submitVIPColomnOrder(o, r) {
var i = $("#payColmunBtn")
, a = i.html();
i.addClass("link-not-active"),
i.html("预订中...");
var e = encodeURI(window.location.href);
$.ajax({
url: "/m/mazi/vip/order/column",
type: "POST",
data: JSON.stringify({
columnId: o,
requestUrl: e
}),
dataType: "json",
contentType: "application/json; charset=utf-8",
success: function(e, t) {
if ("success" == t && 0 == e.code && "success" == e.message)
window.location.href = "/m/mazi/comp/column?columnId=" + o + "&refresh=true&newBuyer=true";
else if (-1 == e.code && "reader exist" == e.message)
window.location.href = "/m/mazi/comp/column?columnId=" + o + "&refresh=true&newBuyer=true";
else if (-1 == e.code && "no quota" == e.message)
if (r) {
var n = $("#payMethodModal");
$("#showVIPBuyTextTip").text("精英会员:尊敬的精英会员,您本月的达人课兑换名额已用完,请下个月再来或付费购买。"),
n.modal("show"),
i.removeClass("link-not-active"),
i.html(a)
} else {
n = $("#showVIPBuyModal");
$("#showVIPBuyText").text("精英会员:尊敬的精英会员,您本月的达人课兑换名额已用完,请下个月再来或付费购买。"),
n.modal("show"),
i.removeClass("link-not-active"),
i.html(a)
}
else
-1 == e.code && e.redirectUrl && "" != e.redirectUrl ? (i.removeClass("link-not-active"),
i.html(a),
window.location.href = e.redirectUrl + "?err=" + e.message) : (alert("请检查会员是否到期,如果有问题,联系客服。"),
window.location.href = location.href)
}
})
}
submitVIPColomnOrder('59e467846958416d85ae933e',true);
只有订阅后才有权限阅读,订阅后点击右侧该课程的主题列表,可以查看该课程下的每一篇主题文章,如:https://gitbook.cn/gitchat/column/5ce4ff9a308dd66813d92799/topic/5ce50135308dd66813d927a6,有了最终具体一篇文章的最终链接了,就可以获取到文章内容了。
爬取过程
工具下载和使用
本次爬取数据使用的 Node 模块有:request,cheerio,fs
其中 request 用来根据 url 发起 GET 或 POST 请求,这次爬取的数据属于需要登录才能查看到的内容,所以请求时需要携带 Cookie 来模拟登录状态,使用 User-Agent 模拟浏览器操作,它们都可以放在 request 方法的 options.headers
中
cheerio 用来操作获取到的数据,使用方法类似 jQuery
fs 是 Node 自带模块,可以将数据写入保存到本地
安装 request、cheerio:
npm install --save request cheerio
此时安装后的版本分别是:
"dependencies": {
"cheerio": "^1.0.0-rc.2",
"request": "^2.88.0"
}
引入资源
var request = require("request");
var cheerio = require("cheerio");
var fs = require("fs");
GitChat 网站查看控制台,获取到 Cookie 和 User-Agent 数据保存下来:
var cookieStr = "cookieStr";
var userAgentStr = "userAgentStr";
var headers = {
"Cookie": cookieStr,
"User-Agent": userAgentStr
}
获取所有课程的 url
不断请求接口,获取课程列表的数据,当 response.isEnd
为 true
时,停止继续请求,然后一次性订购所有课程
function getAllCourseUrl(){
var requestUrl = "https://gitbook.cn/gitchat/columns?page=" + currentPage;
var options = {
url: requestUrl,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var resObj=JSON.parse(body);
console.log("resObj", resObj);
var columnList = resObj.columnList;
columnList.forEach(function(item){
allCourseIds.push(item._id);
allTopicN+=item.columnTopics.length;
});
if (resObj.code === 0 && resObj.isEnd !== true){
currentPage++;
getAllCourseUrl();
}
else{
allCourseIds.forEach(function(item){
submitVIPColomnOrder(item, true);
});
}
} else {
console.log("getAllCourseUrl 失败", "\n", currentPage);
}
});
}
getAllCourseUrl();
订阅所有课程
重复调用 submitVIPColomnOrder 方法,传入课程 id,订阅所有课程
function submitVIPColomnOrder(courseId, bol) {
request.post(
{
headers: headers,
url: 'https://gitbook.cn/m/mazi/vip/order/column',
form: { columnId: courseId, requestUrl: bol },
},
function (err, httpResponse, body) {
var resObj = {};
if (err === null && body) {
resObj = JSON.parse(body);
if (resObj.code === 0 && resObj.message === "success") {
successBugCourseN++;
if (successBugCourseN === allCourseIds.length) {
allCourseIds.forEach(function (item) {
getTopicAll(courseBaseUrl + item);
});
}
}
}
else {
}
}
);
}
获取某一课程中的全部文章 id
function getTopicAll(courseUrl){
var options = {
url: courseUrl,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var $ = cheerio.load(body);
var courseTitle = $(".catalog_items_head .lesson_intro").text();
var topic = $(".catalog_view .catalog_items_item");
topic.each(function(index){
var courseNum=index+1;
var topicTitle = $(this).children(".catalog_item_txt").attr("title");
if ($(this).attr("onclick")){
var topicId = $(this).attr("onclick").substring(14, 38);
getTopicOne(courseUrl, courseTitle, courseNum, topicTitle, topicId);
}
});
} else {
}
});
};
根据文章 id 获取文章内容并保存
function saveTopicOne(courseTitle, courseNum, topicTitle, pageCont){
var dir = "./courses/";
var fileName = courseTitle + "-" + courseNum + "-" + topicTitle + ".html";
fileName = fileName.replace(/\s+/g, "");
fileName = fileName.replace(/[<>:"/|?*]+/g, "");
var path = dir + fileName;
fs.writeFile(path, pageCont, function (error) {
if (error) {
console.log("error", error);
} else {
successSavedN++;
}
});
}
function getTopicOne(courseUrl, courseTitle, courseNum, topicTitle, topicId){
var options = {
// https://gitbook.cn/gitchat/column/5c3168154fcd483b02710425/topic/5c317c264fcd483b02710541
url: courseUrl + "/topic/" + topicId,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var $ = cheerio.load(body);
var column_topic_view = $(".column_topic_view").html();
var pageCont =''+
'<!DOCTYPE html>'+
'<html lang="en">'+
'<head>'+
'<meta charset="UTF-8">'+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">'+
'<meta http-equiv="X-UA-Compatible" content="ie=edge">'+
'<title>Document</title>'+
'<link rel="stylesheet" href="../courseDetail.css">'+
'</head>'+
'<body>'+
'<div className="column_topic_view">'+
column_topic_view+
'</div>'+
'</body>'+
'</html>'+
'';
saveTopicOne(courseTitle, courseNum, topicTitle, pageCont)
} else {
}
});
}
我事先创建了 courses 目录用来保存文章 html 文件,并把需要的样式保存到 courseDetail.css 中,放到文章的上级目录中
代码中是要获取课程的所有文章内容,因为有些课程并没有完全更新完,课程中后面的文章状态是写作中,这样的文章获取到的内容就是空的,只有一个文章标题
执行结果
1559587440829-f49c8bf1-aaf9-4052-8f66-72933e5ccfab.gif全部代码
var request = require("request");
var cheerio = require("cheerio");
var fs = require("fs");
var cookieStr = "cookieStr";
var userAgentStr = "userAgentStr";
var headers = {
"Cookie": cookieStr,
"User-Agent": userAgentStr,
}
var courseBaseUrl ="https://gitbook.cn/gitchat/column/";
// 当前页数
var currentPage = 1;
// 所有课程 id,如:[5a0aaf9e61c244061266375f]
// https://gitbook.cn/gitchat/column/5a0aaf9e61c244061266375f
var allCourseIds = [];
// 所有主题个数
var allTopicN=0;
// 成功购买的课程数
var successBugCourseN = 0;
// 成功保存文件个数
var successSavedN=0;
// 保存该课程的一个主题
function saveTopicOne(courseTitle, courseNum, topicTitle, pageCont){
var dir = "./courses/";
var fileName = courseTitle + "-" + courseNum + "-" + topicTitle + ".html";
fileName = fileName.replace(/\s+/g, "");
fileName = fileName.replace(/[<>:"/|?*]+/g, "");
var path = dir + fileName;
fs.writeFile(path, pageCont, function (error) {
if (error) {
console.log("保存失败", "\n", path);
console.log("error", error);
} else {
successSavedN++;
console.log("保存成功", successSavedN, "/", allTopicN, path);
}
});
}
// 获取课程一个主题的内容
function getTopicOne(courseUrl, courseTitle, courseNum, topicTitle, topicId){
var options = {
url: courseUrl + "/topic/" + topicId,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var $ = cheerio.load(body);
var column_topic_view = $(".column_topic_view").html();
var pageCont =''+
'<!DOCTYPE html>'+
'<html lang="en">'+
'<head>'+
'<meta charset="UTF-8">'+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">'+
'<meta http-equiv="X-UA-Compatible" content="ie=edge">'+
'<title>Document</title>'+
'<link rel="stylesheet" href="../courseDetail.css">'+
'</head>'+
'<body>'+
'<div className="column_topic_view">'+
column_topic_view+
'</div>'+
'</body>'+
'</html>'+
'';
saveTopicOne(courseTitle, courseNum, topicTitle, pageCont)
} else {
console.log("获取主题失败", "\n", courseTitle, topicTitle);
}
});
}
// 根据课程url,获取课程所有主题的内容
function getTopicAll(courseUrl){
var options = {
url: courseUrl,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var $ = cheerio.load(body);
// 课程名字
var courseTitle = $(".catalog_items_head .lesson_intro").text();
// 所有主题
var topic = $(".catalog_view .catalog_items_item");
// 主题id
topic.each(function(index){
var courseNum=index+1;
// 主题名字
var topicTitle = $(this).children(".catalog_item_txt").attr("title");
if ($(this).attr("onclick")){
var topicId = $(this).attr("onclick").substring(14, 38);
getTopicOne(courseUrl, courseTitle, courseNum, topicTitle, topicId);
}
});
} else {
console.log("get topic all 失败", "\n", courseUrl);
}
});
};
// getTopicAll("https://gitbook.cn/gitchat/column/5ce4ff9a308dd66813d92799");
// 购买某一门课程
function submitVIPColomnOrder(courseId, bol) {
request.post(
{
headers: headers,
url: 'https://gitbook.cn/m/mazi/vip/order/column',
form: { columnId: courseId, requestUrl: bol },
},
function (err, httpResponse, body) {
var resObj = {};
if (err === null && body) {
resObj = JSON.parse(body);
if (resObj.code === 0 && resObj.message === "success") {
successBugCourseN++;
console.clear();
console.log("成功购买的课程 success BugCourse N", successBugCourseN);
if (successBugCourseN === allCourseIds.length) {
console.log("已成功购买全部课程");
allCourseIds.forEach(function (item) {
getTopicAll(courseBaseUrl + item);
});
}
}
}
else {
console.log("submit VIP ColomnOrder 失败", "\n", err);
}
}
);
}
// 获取所有课程的 url,如:https://gitbook.cn/gitchat/column/5a0aaf9e61c244061266375f
function getAllCourseUrl(){
var requestUrl = "https://gitbook.cn/gitchat/columns?page=" + currentPage;
var options = {
url: requestUrl,
headers: headers,
}
request(options, function (error, response, body) {
if (error === null && response.statusCode == 200) {
var resObj=JSON.parse(body);
var columnList = resObj.columnList;
columnList.forEach(function(item){
allCourseIds.push(item._id);
allTopicN+=item.columnTopics.length;
});
console.clear();
console.log("获取到的所有课程url总数", allCourseIds.length);
if (resObj.code === 0 && resObj.isEnd !== true){
currentPage++;
getAllCourseUrl();
}
else{
console.clear();
console.log("获取到的所有课程url总数", allCourseIds.length);
console.log("所有主题数", allTopicN);
allCourseIds.forEach(function(item){
submitVIPColomnOrder(item, true);
});
}
} else {
console.log("getAllCourseUrl 失败", "\n", currentPage);
}
});
}
getAllCourseUrl();
网友评论