这篇文章,分享下网易云开源的一个api,通过伪造请求来获取网易云的歌曲,评论和电台等信息。
首先,大致描述项目里用到的一些知识点
涉及到的知识点
- apicache 缓存中间件,可以用于redis,项目中的作用在于,避免频繁的请求网易云后台
- 文件操作 项目中通过获取router文件加下的文件名来动态加载路由,一个文件名就是一个接口
- crypto 加密模块,因为网易云网页api基本都是加密处理了 参考网易云音乐新版webApi分析
apicache 缓存中间件
api缓存中间件这个没什么可说的,为什么用到 缓存,觉得文档解释法就很好
Because route-caching of simple data/responses should ALSO be simple.
因为简单数据的和路由本来就是缓存提供。
apicache 是可配置的 可以单独为某一个接口请求缓存,也可以配置全局接口缓存,还可以对特定的响应缓存,如这个项目就是特定的响应缓存
const onlyStatus200 = (req, res) => res.statusCode === 200;
app.use(cache("2 minutes", onlyStatus200));
可以通过函数的形式来配置,可以说很强大了,针对接口响应的200俩缓存,还可以设置缓存时间,可以说很强大,要做详细的了解可以参考npm文档
文件操作来配置路由(接口)
这个项目中的router文件加下的一个文件就是一个接口,很清晰。具体看代码操作。
// 简化 路由 导出方式, 由这里统一对 router 目录中导出的路由做包装, 路由实际对应的文件只专注做它该做的事情, 不用重复写样板代码
const { createWebAPIRequest, request } = require("./util/util");
const Wrap = fn => (req, res) => fn(req, res, createWebAPIRequest, request);
// 同步读取 router 目录中的js文件, 根据命名规则, 自动注册路由
fs.readdirSync("./router/").reverse().forEach(file => {
if (/\.js$/i.test(file) === false) {
return;
}
let route;
if (typeof UnusualRouteFileMap[file] !== "undefined") {
route = UnusualRouteFileMap[file];
} else {
route =
"/" +
file
.replace(/\.js$/i, "")
.replace(/_/g, "/")
.replace(/[A-Z]/g, a => {
return "/" + a.toLowerCase();
});
}
app.use(route, Wrap(require("./router/" + file)));
});
这里稍微解释下
app.use(route, Wrap(require("./router/" + file)));
这行代码,这里的形式看上去很复杂,Wrap是个什么鬼,你跑去一看Wrap这个函数定义
const Wrap = fn => (req, res) => fn(req, res, createWebAPIRequest, request);
这个看起来很头晕啊,那我们就从最普通的路由说起
app.use("路径",function(req,res){
//处理请求代码
})
这样一看是不是有点对上头了,Wrap就是一个function(req,res){}的形式,只不过函数体是由require导出来的,这里点到为止,你在回头看wrap是不是有点柑橘了呢。
接口设计分析一波
以artist_list.js为例
//分类歌单
// 歌手分类
module.exports = (req, res, createWebAPIRequest, request) => {
const cookie = req.get("Cookie") ? req.get("Cookie") : "";
const data = {
categoryCode: req.query.cat || "1001",
offset: req.query.offset || 0,
total: req.query.total ? "true" : "false",
limit: req.query.limit || 30,
initial: (req.query.initial || "").toUpperCase().charCodeAt() || ""
};
createWebAPIRequest(
"music.163.com",
"/weapi/artist/list",
"POST",
data,
cookie,
music_req => {
res.send(music_req);
},
err => res.status(502).send("fetch error")
);
};
- 获取cookie
- 请求和默认的参数合并
- 携带请求地址,方式(post),真实接口,调用工具的createWebAPIRequest()
通用的工具方法
const Encrypt = require("./crypto.js");
const request = require("request");
const querystring = require("querystring");
request.debug = true;
function randomUserAgent() {
const userAgentList = [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1",
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1",
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 6 Build/LYZ28E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Mobile Safari/537.36",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Mobile/14F89;GameHelper",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4",
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:46.0) Gecko/20100101 Firefox/46.0",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:46.0) Gecko/20100101 Firefox/46.0",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)",
"Mozilla/5.0 (Windows NT 6.3; Win64, x64; Trident/7.0; rv:11.0) like Gecko",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/13.10586",
"Mozilla/5.0 (iPad; CPU OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1"
];
const num = Math.floor(Math.random() * userAgentList.length);
return userAgentList[num];
}
function createWebAPIRequest(
host,
path,
method,
data,
cookie,
callback,
errorcallback
) {
// console.log(cookie);
if (cookie.match(/_csrf=[^(;|$)]+/g))
data.csrf_token = cookie.match(/_csrf=[^(;|$)]+/g)[0].slice(6);
else data.csrf_token = "";
const proxy = cookie.split("__proxy__")[1];
cookie = cookie.split("__proxy__")[0];
const cryptoreq = Encrypt(data);
const options = {
url: `http://${host}${path}`,
method: method,
headers: {
Accept: "*/*",
"Accept-Language": "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4",
Connection: "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
Referer: "http://music.163.com",
Host: "music.163.com",
Cookie: cookie,
"User-Agent": randomUserAgent()
},
body: querystring.stringify({
params: cryptoreq.params,
encSecKey: cryptoreq.encSecKey
}),
proxy: proxy
};
console.log(
`[request] ${options.method} ${options.url} proxy:${options.proxy}`
);
request(options, function(error, res, body) {
if (error) {
console.error(error);
errorcallback(error);
} else {
//解决 网易云 cookie 添加 .music.163.com 域设置。
//如: Domain=.music.163.com
let cookie = res.headers["set-cookie"];
if (Array.isArray(cookie)) {
cookie = cookie
.map(x => x.replace(/.music.163.com/g, ""))
.sort((a, b) => a.length - b.length);
}
callback(body, cookie);
}
});
}
function createRequest(path, method, data) {
return new Promise((resolve, reject) => {
const options = {
url: `http://music.163.com${path}`,
method: method,
headers: {
Referer: "http://music.163.com",
Cookie: "appver=1.5.2",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": randomUserAgent()
}
};
if (method.toLowerCase() === "post") {
options.body = data;
}
request(options, function(error, res, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
});
});
}
module.exports = {
request,
createWebAPIRequest,
createRequest
};
这个工具js直接看看核心方法
function createWebAPIRequest(
host,
path,
method,
data,
cookie,
callback,
errorcallback
) {
// console.log(cookie);
if (cookie.match(/_csrf=[^(;|$)]+/g))
data.csrf_token = cookie.match(/_csrf=[^(;|$)]+/g)[0].slice(6);
else data.csrf_token = "";
const proxy = cookie.split("__proxy__")[1];
cookie = cookie.split("__proxy__")[0];
const cryptoreq = Encrypt(data);
const options = {
url: `http://${host}${path}`,
method: method,
headers: {
Accept: "*/*",
"Accept-Language": "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4",
Connection: "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
Referer: "http://music.163.com",
Host: "music.163.com",
Cookie: cookie,
"User-Agent": randomUserAgent()
},
body: querystring.stringify({
params: cryptoreq.params,
encSecKey: cryptoreq.encSecKey
}),
proxy: proxy
};
console.log(
`[request] ${options.method} ${options.url} proxy:${options.proxy}`
);
request(options, function(error, res, body) {
if (error) {
console.error(error);
errorcallback(error);
} else {
//解决 网易云 cookie 添加 .music.163.com 域设置。
//如: Domain=.music.163.com
let cookie = res.headers["set-cookie"];
if (Array.isArray(cookie)) {
cookie = cookie
.map(x => x.replace(/.music.163.com/g, ""))
.sort((a, b) => a.length - b.length);
}
callback(body, cookie);
}
});
}
大概就是
扫描二维码关注公众号,回复:
9638645 查看本文章
- 仿照请求头参数
- 加密请求参数
- 发送http请求