1、前言
最近接了点项目,需求是是先通过网络爬虫将网页上的文章给爬取下来,然后通过分析文章的关键词,最终可以在网页上显示总体的关键词的词云图,通过点击关键词就可以实现找到对应的文章列表,且能跳转到对应文章的Web页面。大概项目的思路我将在以下分享。注:所有数据均脱敏处理。
2、技术栈
前端:HTML、CSS、Javasript、BootStrap、Echarts-wordclould
后端:Python、Django
数据库:Mysql
3、项目代码
3.1、爬取网站
使用了requests进行请求网站,lxml进行对html分析。
首先使用代理头,以免识别是python无法爬取结果。
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36'
}
首先对网站源码进行分析,得到我们需要的网站都是放在如下的<a>标签中,那么接下里的任务就是将文章网址爬取下来。
将url与headers带入,先requests进行get请求得到html,再经过etree解析html就可以得到需要的文章网站,当然,这时的网址是相对路径,需要带上之前的url。
page_taxt = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_taxt)
page_list = tree.xpath('//*[@id="article"]/a/@href')
既然我们得到了所有文章链接,那么就可以获取每一片文章,在这里还调用了词频函数,当然这在后面就会呈现。
for page in page_list:
# 获取每篇笔记
page_url = url + page
response = requests.get(url=page_url, headers=headers)
response.encoding = 'utf-8'
page_data = response.text
tree = etree.HTML(page_data)
# 获取笔记原文数据
data_al = tree.xpath('//*[@id="article"]//text()')
for h in data_al:
data_all = data_all + h
name = tree.xpath('//h1/@id')
try:
# 获取笔记原文地址
# 部分文章找不到kw,因而使用try跳过
origin_page_url = tree.xpath('//*[@id="article"]/ol/li[4]/a/@href')[0]
kw_url = origin_page_url + '/keywords#keywords'
# 获取文章关键字
response_kw = requests.get(url=kw_url, headers=headers).text
ex = '"kwd":\[(.*?)\]'
kw = re.findall(ex, response_kw, re.S)
# 转换关键字存储方式,转化为列表
ex = '"(.*?)"'
kw_li = re.findall(ex, str(kw), re.S)
# 存入整体kw列表
for li in kw_li:
kw_list.append(li)
# 处理单篇文章的kw
data = {}
data = rank(kw_li)
name_1 = str(name[0])
name_1 = name_1.replace("阅读笔记:", '')
data_list = []
for fluent_name, value in data.items():
data = {'papername':name_1, 'url':page_url, 'name':fluent_name, 'value':value}
data_list.append(data)
# 通过csv存储
df = pd.DataFrame(data_list,
# index=[0],
columns=['papername','url','name', 'value'])
df.to_csv("test.csv", mode="a", index=False, header=False, encoding='gb18030')
# 通过mysql存储
# Paper.objects.create(papername = name[0], url=page_url, name=name, value=value) # 此处只有定时更新数据的时候,才能够取消注释,否则会一直在数据库内追加数据,造成数据混乱
# print(df)
print('成功获取: ', name[0], ' 笔记原文献keywords')
except:
# pass
print('无法获取: ', name[0], ' 笔记原文献keywords')
# time.sleep(2)
# rank为字频统计函数
data1 = rank(kw_list)
3.2、字频统计
将爬取的文章关键词进行统计,就可以得到字频。这里运用普通的循环便利即可实现。
def rank(data_list):
# 词频统计函数,获取原列表,统计列表元素重复,返回web所需键值对
data_result = {}
data_last = []
for k in data_list:
if k not in data_result:
data_result[k] = 1
else:
data_result[k] += 1
return data_result
3.3、前端
在这里,我们已经将前端界面设计好了,需要的数据留有接口,让后端处理好传递到前端,然后前端点击词云得到的关键词,可以通过echarts的点击传递href到后端,当然用ajax,将前端数据同步到后端也是可以的。在这里传输词云数据,我是使用的ajax通信,比较方便,jquery绑定也可以。在此之前,我们要下载echarts、echarts-wordcloud、jquery,可以在官网或者github上下载,找到dist文件夹下的如下js文件,导入依赖即可,不过因为我最近没有梯子,所以下载别人分享的,貌似不能用。。。前端一直报错,最后才发现是echarts.js的问题。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src='https://cdn.bootcss.com/echarts/3.7.0/echarts.simple.js'></script>
<script src='../static/js/echarts-wordcloud.min.js'></script>
<script src='../static/js/echarts-wordcloud.js'></script>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
{# <script src='../static/js/echarts.js'></script>#}
</head>
<body>
<div id="main" style="width: 1500px;height: 700px; "></div>
<script>
var chart = echarts.init(document.getElementById('main'));
var option = {
tooltip: {},
series: [ {
type: 'wordCloud',
gridSize: 2,
sizeRange: [12, 50],
rotationRange: [-90, 90],
shape: 'pentagon',
width: 600,
height: 400,
drawOutOfBound: true,
textStyle: {
normal: {
color: function () {
return 'rgb(' + [
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') + ')';
}
},
emphasis: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data:[]
} ]
};
chart.showLoading({text: '正在加载数据'});
$.ajax({
url:'/word/data',
type: 'get',
datatype: 'JSON',
success(res){
//将后台返回的数据,更新到option中
if (res.status){
chart.hideLoading();
console.log("res.data"+res.data)
{#option.series.data = res.data#}
chart.setOption({
series:{
data:res.data
}
}
)
}
}
})
chart.setOption(option);
window.onresize = chart.resize;
var ecConfig = echarts.config;
chart.on('click', eConsole);
function eConsole(param) {
if(typeof param.seriesIndex != 'undefined') {
if(typeof param.name != 'none') {
switch(param.name) { //简单的switch,大家应该能够明白怎么设置
case param.name:
window.location.href = "/tiaozhuan/?name="+param.name;
{#$.ajax({#}
{# url:'/tiaozhuan',#}
{# type: 'get',#}
{# datatype: 'JSON',#}
{# data:{#}
{# name:param.name#}
{# },success:function (res) {#}
{# console.log(res);#}
{# }#}
{#})#}
{#window.open('/tiaozhuan/')#}
// window.open("https://jbk.39.net/bw/zhongliuke/", "_blank");//在新页面打开
break;
default:
break;
}
}
}
}
</script>
</body>
</html>
主界面已经设计好了,接下来就是跳转之后的界面,在这个界面里,我们需要有文章的链接,实现查找文章。在这里要导入bootstrap依赖,就可以设计页面了。
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.min.css' %}">
<style>
.navbar{
border-radius: 0;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"> </a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#"> </a></li>
</ul>
</div>
</div>
</nav>
<div>
<div class="container">
<div style="margin-bottom: 10px">
<a class="btn btn-success" href="#">
<span class="glyphicon glyphicon-triangle-bottom" aria-hidden="true"></span>
查询关键词结果</a>
</div>
<div class="bs-example" data-example-id="table-within-panel">
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<span class="glyphicon glyphicon-list" aria-hidden="true"></span>
相关文章
</div>
<div class="panel-body">
</div>
<!-- Table -->
<table class="table table-bordered">
<thead>
<tr>
<th>文章</th>
</tr>
</thead>
<tbody>
{% for obj in data %}
<tr>
<td>
{# {{ obj.paper }}#}
<a href="{{ obj.url }}" >{{ obj.paper }}</a>
</td>
{# <th scope="row" >{{ obj }}</th>#}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</body>
</html>
3.4、后端
首先在终端环境中创建一个项目与app,在此之前需要配置django-admin的全局变量。不然不能创建项目。
sudo vim ~/.bashrc
之后source一下让配置立即生效就可以了
sudo source ~/.bashrc
因为我的django是安装在anaconda环境下的,所以将我自己ananconda创建的虚拟环境下的bin文件夹直接加入环境变量中就可以了。
现在就可以在终端中创建django项目了。
cd /home/chenyu/PythonFile
django-admin startproject Word
接下来创建一个app
manage.py startapp app01
这样我们的app就部署成功了,但是我们还需要将app激活,在seetings.py增加配置即可。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
]
django是基于MVC(Model、View、Control)的Web开发框架,那么我们先来配置路由。
from django.urls import path
from app01 import views
urlpatterns = [
path('index/', views.index),
path('word/data/', views.word_data),
path('tiaozhuan/', views.tiaozhuan),
path('test/', views.test),
]
路由对应的链接,可以跳转到我们的视图函数,接下来就要设计视图函数了。
首先设计一个主页,在这里可以实现词云功能。这里返回的html就是我们在前端中设计的第一个文件,返回一个render,将请求与html一起返回即可在浏览器中看到页面了。
def index(request):
return render(request, 'index.html')
在这里,我们设计了一个关于词云数据的,这些数据取自数据库,将我们之前存储到的关键词频率全部读取后以Json格式的response传入到前端。这与我们在前端第一个文件中留的ajax接口是一致的,data_list将传到ajax实现同步通信。
def word_data(request):
data_list = Fluent.Object.all()
li = []
for obj in data_list:
li.append({
'name': obj.name,
'value': obj.value
})
data_dict = {"status": True,
'data':li
}
return JsonResponse(data_dict)
当点击词云图的关键词之后,如果是get请求进来之后,则在数据库查到有多少篇文章对应这个关键词,然后将数据再返还给前端,且跳转到一个新的页面。
def tiaozhuan(request):
if request.method =='GET':
url = 'https://www.xxx'
keywords = request.GET.get('name')
info = Paper.Object.filter(name=keywords)
data = []
for obj in info:
data.append({'papername':obj.papername, 'url':obj.url})
return render(request, 'tiaozhuan.html', {'data':data})
3.5、数据库
首先在终端中打开mysql,输入mysql -u root -p后会提示输入密码,输入密码后进入mysql控制台。
mysql -u root -p
进入mysql中想要创建新的数据库的话则需要create,之后再建立表格,如下操作可以根据个人要求来选择使用。
CREATE DATABASE cy; # 这里数据库名称根据自己需求设定
use cy; # 使用该数据库
show tables; # 展示该数据库下的数据表
create table paper (
`id` varchar(8) NOT NULL AUTO_INCREMENT,
`papername` varchar(64) DEFAULT NULL,
`name` varchar(64) DEFAULT NULL,
`url` varchar(64) DEFAULT NULL,
`value` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
INSERT INTO (xxx) VALUES (xxx);# 如果想要在mysql直接插入数据,则可以使用该命令
select * from paper; # 可以查看全部数据
在django的setting配置中,将自己的数据库与项目绑定,则可以直接通过django来控制数据的生成与销毁。xxx代表自己设置的数据库信息,需要根据自己的数据库来填写。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'xxx', # 数据库名字
'USER': 'xxx', # 默认为root
'PASSWORD': "xxx",
'HOST': '127.0.0.1', # 那台机器安装了MySQL
'PORT': 3306, # 端口一般为3306,如果更改了端口可以在终端中查找
}
}
接下来就是我们的Model,通过如下命令就可以生成数据表,如果通过该操作可以省略mysql中前两行后的指令,但是也可以相互配合着使用。
from django.db import models
class Paper(models.Model):
papername = models.CharField(verbose_name="papername", max_length=64, default=True, null=True)
url = models.CharField(verbose_name="url", max_length=64, default=True, null=True)
name = models.CharField(verbose_name="name", max_length=64, default=True, null=True)
value = models.IntegerField(verbose_name="value", default=True, null=True)
class Fluent(models.Model):
name = models.CharField(verbose_name="name", max_length=64, default=True, null=True)
value = models.IntegerField(verbose_name="value", default=True, null=True)
之后要进行数据迁移,将Model数据导入到项目中。
manage.py makemigrations
manage.py migrate
4、项目界面
当我们访问主页面的时候,就会得到词云图,字体越大代表出现的频率越高。我们在前端中设置了echarts的windows属性,那么各个关键词就不是普通文本了,可以实现点击传输数据,将点击的文本传输到后端,与数据库的中的数据进行查找比对。
通过点击关键词,就可以通过Web框架传入参数跳转到下一个页面。这个页面就是所有的文章列表,所有的信息均是超链接的形式,点击即可跳转到相应的官网的文章。
本图片已抹除重要信息,仅保留文章部分内容,至此已实现全部项目。
如果还有时间的话,可以再用Vue搭建框架,用Docker容器搭建环境进行上线。过一段时间可以再发布一个Vue从头搭建的项目。