前两天忽然想爬取微信热文,分析一下关键字,看最近热文都在关注什么话题。
本来想完全用html+js实现的,但是爬取文章数据绕不过跨域拦截问题,我也暂时没找到好的处理方案,所以还是放在nodejs中做爬数据的事了。
爬取的网站是微小宝,我没做登录,最多爬取50篇文章。
分词模块换了3个:segment、novel-segment、nodejieba
segment 文章字数稍微多点,分词过程中就会出现内存溢出的情况,还需要使用--max-old-space-size配置更大的内存。
novel-segment 解决了内存溢出的问题但是分词模块感觉有误,虽然中文难以分词,但是你这分的也太随性了吧。
nodejieba 解决了以上两个问题,虽然依然存在分词不准确的情况,但是相比前两个已经很好了。
安装nodejieba 出现了一些小插曲,根据这篇文章解决了 https://blog.csdn.net/fengxiaoxiao_1/article/details/77073918
效果图:
image.png
可以看出目前最热的话题还是疫情相关信息,当然跟抓取的时间也有很大关系。上午我抓取的时候,最热的话题还是芯片,因为最近美国疫情的问题都没空解决又忙于制裁华为了。
代码如下:
html部分:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="box">
<div class="form">
<div>
<label for="">抓取文章数:</label>
<input id="page-size" type="number" placeholder="请输入抓取的文章数,默认为1"><button id="submit">提交</button>
</div>
</div>
<div class="echarts-box">
<div id="wait" style="display:none;">请求数据中,请耐心等待......</div>
<div id="main" style="width: 1400px;height:500px;display:none;"></div>
</div>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.7.0/echarts.min.js"></script>
<script>
$(document).ready(function () {
// 初始化图表
function initEcharts(xAxisData, seriesData) {
var myChart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: '微信公众号热文关键词'
},
tooltip: {},
xAxis: {
name: '关键词',
data: xAxisData,
axisLabel: {
interval: 0
},
},
yAxis: {},
series: [{
type: 'bar',
data: seriesData,
itemStyle: {
normal: {
label: {
show: true,
}
}
}
}]
};
myChart.setOption(option);
}
// 发起请求获取数据
function ajaxSubmit() {
const waitEl = document.querySelector("#wait");
const echartsEl = document.querySelector("#main");
const pageSize = $('#page-size').val()
waitEl.style.display = "block";
echartsEl.style.display = "none";
$.ajax({
type: 'get',
url: 'http://127.0.0.1:8000/',
data: {
pageSize: pageSize > 50 ? 50 : (pageSize || 1)
},
success(res) {
if (res.code === 0) {
let resultArr = res.data;
resultArr.sort(function (a, b) {
return b.count - a.count
})
resultArr = resultArr.slice(0, 25)
const wordArr = resultArr.map(item => item.word);
const countArr = resultArr.map(item => item.count);
initEcharts(wordArr, countArr);
waitEl.style.display = "none";
echartsEl.style.display = "block";
}
}
})
}
// 点击按钮事件
$('#submit').click(ajaxSubmit)
})
</script>
</body>
</html>
nodejs部分如下:
const express = require("express");
const axios = require("axios");
var nodejieba = require("nodejieba"); // 分词模块
const cheerio = require("cheerio"); // 解析dom模块
const app = express();
const config = {
port: 8000,
url: (pageSize) => `https://data.wxb.com/rank/article?baidu_cat=%E6%80%BB%E6%A6%9C&baidu_tag=&page=1&pageSize=${pageSize}&type=2&order=`
}
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
// 请求文章链接
function reqArticleUrl(pageSize) {
console.log('开始爬取文章列表...----------------------start-------------------------');
return new Promise((resolve, reject) => {
axios.get(config.url(pageSize))
.then(res => {
if (res.data.errcode === 0) {
resolve(res)
}
})
.catch(err => {
reject(err)
})
})
}
// 获取处理后文章字符串
async function reqArticleData(urlArr) {
console.log('开始爬取文章内容...');
return new Promise((resolve, reject) => {
let pAllArr = [];
urlArr.forEach((el, i) => {
pAllArr.push(reqUrl(el.url))
})
Promise.all(pAllArr).then(res => {
resolve(res.join(""))
})
})
}
// 请求具体文章内容
function reqUrl(url) {
return new Promise((resolve, reject) => {
axios.get(url).then(res => {
let $ = cheerio.load(res.data);
let text = htmlDecode($('#js_content').text().replace(/[\r\n]/g, "").replace(/\s+/g, ""));
resolve(text)
})
})
}
// Unicode编码转为汉字
function htmlDecode(str) {
str = unescape(str.replace(/\\u/g, "%u"));
str = str.replace(/&#(x)?(\w+);/g, function ($, $1, $2) {
return String.fromCharCode(parseInt($2, $1 ? 16 : 10));
});
return str;
}
// 将文章进行分词处理
async function packageData(str) {
return new Promise((resolve, reject) => {
console.log('开始分词...');
var arr = nodejieba.tag(str)
console.log('分词结束...');
var myarr = [];
console.log('筛选特定符号中...');
arr.forEach(e => {
if (e.tag === 'n' && e.word.length > 1) {
// 只保留两字以上的名词
myarr.push(e.word)
}
});
console.log('计算单次个数中...');
var myJson = {};
myarr.forEach(data => {
if (!myJson[data]) {
myJson[data] = 1;
} else {
myJson[data]++;
}
});
console.log('去掉1次以下词语');
var arr2 = [];
for (var word in myJson) {
if (myJson[word] <= 1) {
continue;
}
arr2.push({ word, count: myJson[word] });
}
arr2.sort((json1, json2) => json2.c - json1.c);
resolve(arr2)
})
}
app.get('/', async function (req, res) {
let pageSize = req.query.pageSize || 1;
const result = await reqArticleUrl(pageSize);
const result2 = await reqArticleData(result.data.data);
console.log('未分词前文章长度', result2.length);
const data = await packageData(result2)
console.log('处理完毕,返回数据----------------------end-------------------------');
res.send({ code: 0, data })
});
app.listen(config.port, () => {
console.log(`${config.port}端口服务已启动`);
});
如有错误还请指出










网友评论