知乎一条龙第二弹,API 部署开放、H5线上展示与源码共享-程序员宅基地

     作者:周萝卜

     来源:萝卜大杂烩

前面写了一个知乎爬虫、API 和小程序一条龙第一弹,反响还不错,于是在这些天的空闲时间里,我又优化下代码,并且把服务部署到了云服务器上,开放了 API 供需要的小伙伴使用。

也有很多人要源代码看看,想自己动手实践下,今天就把代码放出来,写的不好,仅供参考,也欢迎一起讨论维护!

功能增强之token

因为准备开放 API 接口出来,所以考虑了下,还是做一些简单的验证,毕竟安全措施做的好,你好我也好!

首先我们先来看下整体的请求流程

客户端先通过 getToken 接口来获取一个具有时间期限的 token 信息,然后再携带该 token 信息访问对应的数据接口

token 实现

我这里使用第三方库 itsdangerous 来做 token 签名

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

itsdangerous 提供了多种生成签名令牌的方式,我这里选择的 TimedJSONWebSignatureSerializer 可以生成一种具有过期时间的 JSON Web 签名,这样我们也就可以控制我们所签发的 token 是具有时效性的。

生成签名并加密成 token

access_token_gen = Serializer(secret_key=secret_key, salt=salt, expires_in=access_token_expires_in)
timtstamp = time.time()
access_token = access_token_gen.dumps({
        "userid": userid,
        "iat": timtstamp
    })

然后在需要解析 token 时,只要调用 loads 即可

s = Serializer(secret_key=secret_key, salt=salt)
data = s.loads(token)

访问限制装饰器

装饰器是 Python 语言的一大利器,我们当然要好好利用起来了。

在最开始的设计中,我们的路由都是可以直接访问的,没有任何限制

@api.route('/api/zhihu/hot/', methods=['GET', 'POST'])
def zhihu_api_data():
    pass

现在我们想达到一种效果,就是不改变当前视图函数的写法,还要增加访问限制,只有携带了正确 token 的请求才能够正确访问对应的路由

@api.route('/api/zhihu/hot/', methods=['GET', 'POST'])
@token.tokenRequired
def zhihu_api_data():
    pass

毫无疑问,这个功能交给装饰器真是再好不过了

def tokenRequired(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        pass
    return decorated_function

下面的工作就是编写 decorated_function 函数的内容了,只需要加上我们需要的判断即可

if request.method == 'POST':
    post_data = json.loads(request.data)
    if 'token' in post_data and 'secret' in post_data and post_data['secret'] == '周萝卜真帅':
        token = post_data['token']
        check_result = check_token(token)
        if check_result is True:
            return f(*args, **kwargs)
        else:
            return jsonify(check_result), 401
    return jsonify({'code': 422, 'message': '按套路出牌啊'}), 422

当请求方法是 POST 时,如果 token 字段不在请求体内或者请求体的 secret 字段没有按照套路出牌的话,都会返回错误响应的(这里请牢记暗号啊,夸我就对了!)

接下来我们再看看 check_token 函数,这就是具体的校验 token 的方法了

def check_token(token):
    token_list = []
    if rd.keys("token*"):
        for t in rd.keys("token*"):
            token_list.append(rd.get(t))
    if token in token_list:
        return {'code': 401, 'message': 'token is blocked'}, 401
    validator = validateToken(token)
    if validator['code'] != 200:
        if validator['message'] == 'toekn expired':
            return validator
        else:
            return validator
    elif validator['code'] == 200:
        return True

留用了 block  token 的功能,以便后面使用。而 validateToken 函数就是调用 loads 方法解析加密后的 token。

功能增强之频率限制

所谓的频率限制,就是在指定的时间之内,访问请求的次数不能超过多少次。我这里设置的是一分钟之内,访问次数不能超过20次

REQUEST_NUM = 20

为了实现这个功能,我们需要用到 Flask 程序的全局请求钩子 before_app_request。该钩子的作用就是在任何请求发生之前,都会先调用该函数。这样我们就可以添加自己的判断逻辑,增加访问频率限制

@main.before_app_request
def before_request():
    remote_add = request.remote_addr
    rd_add = rd.get('access_num_%s' % remote_add)
    if rd_add:
        if int(rd_add) <= Config.REQUEST_NUM:
            rd.incr('access_num_%s' % remote_add)
        else:
            return jsonify({'code': 422, 'message': '访问太频繁啦!'}), 422
    else:
        rd.set('access_num_%s' % remote_add, 1, ex=60)

每个 IP 的访问频率都存储在 redis 中,且该 redis key 的过期时间为60秒。当然这种限制属于防君子不防小人的做法,为什么这么说呢,因为如果你想突破这种入门级的限制,实在是太 easy 啦,而且使用手机4G网络的请求,IP 地址还会不停变化,太楠啦!

功能增强之高频词汇

在上一次的文章中,我们在前端(小程序端)只展示了知乎热点随着时间的走势情况,今天再加上每个热点的回答中的高频词汇,通过 jieba 来分词,还是很容易实现的。

将获取到的回答内容分词并统计词频

def cut_word(word):
    word = re.sub('[a-zA-Z0-9]', '', word)
    empty_str = ' '
    with open(stopwords_path, encoding='utf-8') as f:
        stop_words = f.read()
    stop_words = stop_words + empty_str
    counts = {}
    txt = jieba.lcut(word)
    for w in txt:
        if w not in stop_words:
            counts[w] = counts.get(w, 0) + 1
    sort_counts = sorted(counts.items(), key=lambda item: item[1], reverse=True)

    return sort_counts[:20]

在这里我们去掉了英文和数字,并且返回了词频前20的数据

然后我们修改视图函数 zhihu_api_detail

@api.route('/api/zhihu/detail/<id>/', methods=['GET', 'POST'])
@token.tokenRequired
def zhihu_api_detail(id):
    zhihu_detail = zhihudetail(id)
    redis_word = rd.get('wordcloud_%s' %id)
    redis_content = rd.get('content_%s' % id)
    if redis_word:
        count_list = json.loads(redis_word)
        content_list = json.loads(redis_content)
    else:
        count_list = []
        count_word, content_list = zhihucontent(id)  # 获取回答的词频数据和回答内容
        for count in count_word:
            count_list.append({'name': count[0], 'textSize': count[1]})
        rd.set('wordcloud_%s' %id, json.dumps(count_list), ex=604800)
        rd.set('content_%s' %id, json.dumps(content_list), ex=604800)

    if count_list[0]['textSize'] < 10:
        for i in count_list:
            i['textSize'] = i['textSize']*10
    elif count_list[0]['textSize'] > 200:
        for i in count_list:
            i['textSize'] = i['textSize']/10

    return jsonify({'code': 0, 'data': zhihu_detail, 'count_word': count_list, 'content': content_list}), 200

因为每次使用 jieba 分词时还是比较耗费时间的,所以这里把处理好的数据保存到 redis 中,下次再请求时直接拿数据即可。

现在我们的详情页面展示如下

部署 API

最后我们把已经完成的代码部署到云服务器上,使用的还是那套 Nginx + Gunicorn + Flask + MySQL

配置详情

Nginx 配置

server {
    gzip on;
    listen       443;
    server_name  www.luobodazahui.top;
    ssl on;
    root        /home/mini/mini/      ;
    ssl_certificate  cert/luobodazahui.top.crt;
    ssl_certificate_key cert/luobodazahui.top.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    location / {
        proxy_pass       http://127.0.0.1:5002;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        index  index.html index.htm;
    }

    proxy_set_header X-Real-IP $remote_addr;

}
server {
    listen 80;
    server_name luobodazahui.top;
    rewrite ^(.*)$ https://$host$1 permanent;
    }

因为 API 后面想给小程序使用,所以应用了 域名 + HTTPS

Gunicorn 配置

#from gevent import monkey
#monkey.patch_all()

import multiprocessing

#debug = True
loglevel = 'debug'
bind = '127.0.0.1:5002'
#bind = '0.0.0.0:5000'
#pidfile = 'pid/gunicorn.pid' 
accesslog = '/home/mini/mini/log/ser_access.log'
errorlog = '/home/mini/mini/log/ser_error.log'

workers = 1
#workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync' 
#reload = True

同样是比较简单的配置,打印了访问和错误日志,还启用了适量的 workers。

启动脚本 run.sh

/root/miniconda3/bin/gunicorn -D -c /home/mini/mini/gunicorn manage:app

停止脚本 stop.sh

kill -9 $(ps -ef | grep '/home/mini/mini/gunicorn' | grep -v grep | awk '{print $2}') 2>&1 >/dev/null;echo 0

API 信息

我们来看下当前提供的 API 信息

API地址 请求参数 支持方法‍‍‍
https://www.luobodazahui.top/api/auth/token/ table1 POST/GET
https://www.luobodazahui.top/api/zhihu/hot/ table2 POST/GET
https://www.luobodazahui.top/api/zhihu/detail// table3 POST/GET
table1
{
    "username": "admin",
    "pwd": "admin"
}

请求示例

table2
{
    "token":"eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3NzI0NDE4MywiZXhwIjoxNTc3MjQ1OTgzfQ.eyJ1c2VyaWQiOjEsImlhdCI6MTU3NzI0NDE4My4zMjcwNjY0fQ.FptYNm0KnA8b4G_zcRJn9POrOgkiZxpvfBbzQqxoTTt7q96WeMo7Y6xCLL_oS4ksBP8jMztqopDRRqScXPKowg",
    "secret":"周萝卜真帅"}

请求示例

table3
{
    "token":"eyJhbGciOiJIUzUxMiIsImlhdCI6MTU3NzI0NDE4MywiZXhwIjoxNTc3MjQ1OTgzfQ.eyJ1c2VyaWQiOjEsImlhdCI6MTU3NzI0NDE4My4zMjcwNjY0fQ.FptYNm0KnA8b4G_zcRJn9POrOgkiZxpvfBbzQqxoTTt7q96WeMo7Y6xCLL_oS4ksBP8jMztqopDRRqScXPKowg",
    "secret":"周萝卜真帅"}

请求示例

未来优化

  • 完善日志:当前只在定时任务当中加了日志,其余功能都未打印日志,后续把日志优化进来,方便问题定位

  • 接口完善:当前接口返回数据庞杂,后续将接口拆分,增加更多参数,比如按照时间请求等

  • 其他数据:后续增加微博、金融,票房等相关数据接口和展示

最后给出代码地址:https://github.com/zhouwei713/Mini_Flask
欢迎大家来讨论,如果有需要(遇到服务挂死,API 不可用,需求增强等情况)可以加我微信,备注“入群”,一起解决问题

◆ ◆ ◆  ◆ ◆

长按二维码关注我们
数据森麟公众号的交流群已经建立,许多小伙伴已经加入其中,感谢大家的支持。大家可以在群里交流关于数据分析&数据挖掘的相关内容,还没有加入的小伙伴可以扫描下方管理员二维码,进群前一定要关注公众号奥,关注后让管理员帮忙拉进群,期待大家的加入。

管理员二维码:


猜你喜欢● 笑死人不偿命的知乎沙雕问题排行榜● 我用Python纪念了那些被烂片收割的智商税!● 互联网大佬学历&背景大揭秘,看看是你的老乡还是校友● 上万条数据撕开微博热搜的真相!● 你相信逛B站也能学编程吗? 
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_38753213/article/details/103750518

智能推荐

dell服务器开启64位支持,dell服务器虚拟化开启(戴尔bios设置虚拟化)-程序员宅基地

文章浏览阅读2.9k次。开机按f2进入bios设置界面,将光标移至“advanced”再使用上下方向键将光标移至。 以上就是设置戴尔笔记本硬盘模式为ahci教程,有遇到戴尔笔记本不懂的如何修改硬。您好!戴尔电脑,一般进入bios的方法为开机按f2,而开机按f12是选择启动引导项。开机,按del 进入bios 进入 第二项 advanced 开头的选项。 找到 cpu virtual 类似选。 如果没有类似选项,说明..._dell bios 64位系统设置

HTTP 文件下载时中文文件名乱码问题处理_使用http输出流下载文件中文名乱码-程序员宅基地

文章浏览阅读6.4k次。之前有做文件下载处理,但由于文件名一直是英文的,所以并未发现有该问题,直到最近项目中有中文名出现. 以前的代码设置:header['Content-Disposition'] = 'attachment; filename=\"'+result['out_filename']+'\"'; 现在的代码设置:result['out_filename'] = encodeURI_使用http输出流下载文件中文名乱码

毕业进入HW,从测试工程师到项目经理,现如今在鹅厂年收入百万,我的给大家的一些建议..._测试转项目经理-程序员宅基地

文章浏览阅读1.9k次,点赞5次,收藏23次。毕业进入HW,研发测试岗,从测试工程师,到测试经理,再到项目经理。HW工作十年后离职,与人合伙创业三年,这中间我主要负责项目和市场,现如今在鹅厂,主要从事管理相关工作..._测试转项目经理

python查询整年节假日,指定日期的星期_python判断当年所有节假日-程序员宅基地

文章浏览阅读943次。python获取日期,python获取全年节假日_python判断当年所有节假日

速锐得在自动驾驶环卫车上装配TBOX完成车端适配-程序员宅基地

文章浏览阅读212次。自动驾驶一直是人类的梦想。在整个车联网发展的过程中,自动驾驶应用也是业界关注的热点。自动驾驶是车联网服务的第三阶段,通过高级或者完全的自动驾驶解放驾驶人的双手和大脑,将驾驶者的注意力解放出来,车联网业务形态将快速迭代和极大丰富,汽车空间真正开放给业务开发者,形成汽车和交通环境下的信息服务新生态。​在汽车智能化和网联化深度融合的基础上,自动驾驶应用通过车载TBOX和其他传感设备感知车辆及所处的环境,面对非视距的路况,依靠V2X协同信息交换和交通环境数据弥补。通过人工智能算法识别车辆环境和交通...

什么是微信小程序基础库_微信小程序开发基础库-程序员宅基地

文章浏览阅读3.7k次。由于公司需要开发小程序项目,就查看了小程序的官方文档 很多地方看到 有关基础库的东西在开发工具中的配置项中也看到了于是就在想 这个基础库 就是微信客户端的版本? 官方文档有一个 小程序的运行环境,基础库就是对这个环境的支持吗?那小程序是个啥呢 既不是原生App也不是传统浏览器的WEB应用,看起来像是在微信中虚拟出的一个APP。基础库是什么?基础库是小程序运行的必要环境,我们的开发主要就是面向基础库开发的。基础库封装了微信和手机的能力并提供给小程序使用,我们使用基础库提供的组件和API开发起来非常的_微信小程序开发基础库

随便推点

样本不平衡--SMOTE算法-学习笔记_小样本训练 smote-程序员宅基地

文章浏览阅读3.9k次。1 SMOTE算法的简单理解一个数集中的数据是分布在特征空间中的,假设数据是2维的,那么数据的就是一个平面上的点。对于类别不平衡数据来说,假设负样本数据是少量的,那么这个数据只占据了空间的一小部分。SMOTE 算法就是对这些小样本数据占据的空间中进行插值。 而不影响到正样本的空间。2 如何插值SMOTE算法采取了一种策略,选择两个距离接近的点进行插值。_小样本训练 smote

java 读取sqlserver image_PHP读取SQL Server Image类型字段问题记录-程序员宅基地

文章浏览阅读431次。PHP读取SQL Server Image类型字段问题记录写在前面​ 前几天朋友叫帮忙做一个小工具,读取ERP 的SQL Server数据库里面的款式表,用于网页展示,网站采用的是前后端分离架构。写在中间​ SQL Server数据库存储图片的字段是image类型,我直接用PHP读取出来的时候,会显示乱码。数据库字段内容如下,由于数据太长,只做部分截图: PHP代码如下:$serverName =...

java 项目部署不加载jar包_web项目部署后动态编译无法找到依赖的jar包-程序员宅基地

文章浏览阅读874次。很纳闷的一个问题,通过配置文件生成的java源码在本地动态编译没有问题,但是部署服务器后编译不通过,找不到依赖的jar包。通过网上查资料,找到一个兄弟提供的方法,问题解决了;下面贴出代码以供参考:package com.songxingzhu.utils.compile;import org.apache.commons.io.FileUtils;import com.songxingzhu.ut..._为什么服务器构建没有加入指定的jar包

mysql教程表怎么写数据库_MySQL(一) 数据表数据库的基本操作-程序员宅基地

文章浏览阅读3.2k次。这类文章,记录我看《MySQL5.6从零开始学》这本书的过程,将自己觉得重要的东西记录一下,并有可能帮助到你们,在写的博文前几篇度会非常基础,只要动手敲,跟着我写的例子全部实现一遍,基本上就搞定了,前期很难理解的东西基本没有,所以写博文的内容,就是以练题的形式来呈现的。需要用的资料以链接的形式给需要的同学。图形化工具 Navicat(前期不推荐用,直接手动敲):Navicat 密码:c7fs开..._数据库表怎么写

SSM框架详解_ssm框架内容-程序员宅基地

文章浏览阅读1.6w次,点赞29次,收藏194次。SSM框架详解,实例解释_ssm框架内容

java8 stream().map().collect()用法_rating1.stream().map(a -> a.score).collect(collect-程序员宅基地

文章浏览阅读184次。java8 stream().map().collect()用法有一个集合:List<User> users = getList(); //从数据库查询的用户集合现在想获取User的身份证号码;在后续的逻辑处理中要用;常用的方法我们大家都知道,用for循环,List<String> idcards=new ArrayList<String>();//定义一个集合来装身份证号码for(int i=0;i<users.size();i++){_rating1.stream().map(a -> a.score).collect(collectors.tolist());

推荐文章

热门文章

相关标签