太好玩了,爬蟲、部署API、加小程序,一條龍玩轉知乎熱榜!

一直想作一個從爬蟲到數據處理,到API部署,再到小程序展現的一條龍項目,最近抽了些時間,實現了一個關於知乎熱榜的,今天就來分享一下!javascript

因爲代碼尚未徹底整理好,今天只給出一個大體的思路和部分代碼,最終的詳細代碼能夠關注後續的文章!css

數據爬取

首先咱們看下須要爬取的知乎熱榜html

https://www.zhihu.com/billboard前端

這個熱榜能夠返回50條熱榜數據,而這些數據都是經過頁面的一個 JavaScript 返回的vue

因而咱們就能夠經過解析這段 JS 代碼來獲取對應數據java

url = 'https://www.zhihu.com/billboard'
headers = {"User-Agent""""Cookie"""}


def get_hot_zhihu():
    res 
= requests.get(url, headers=headers)
    content = BeautifulSoup(res.text, "html.parser")
    hot_data = content.find('script', id='js-initialData').string
    hot_json = json.loads(hot_data)
    hot_list = hot_json['initialState']['topstory']['hotList']
    return hot_list
複製代碼

而後咱們再點擊一個熱榜,查看下具體的熱榜頁面,咱們一直向下下拉頁面,並打開瀏覽器的調試板,就能夠看到以下的一個請求python

該接口返回了一個包含熱榜回答信息的 json 文件,能夠經過解析該文件來獲取對應的回答nginx

def get_answer_zhihu(id):
    url = 'https://www.zhihu.com/api/v4/questions/%s/answers?include=' % id
    headers = {"User-Agent""""Cookie"""}
    res = requests.get(url + Config.ZHIHU_QUERY, headers=headers)
    data_json = res.json()
    answer_info = []
    for i in data_json['data']:
        if 'paid_info' in i:
            continue
        answer_info.append({'author': i['author']['name'], 'voteup_count': i['voteup_count'],
                            'comment_count': i['comment_count'], 'content': i['content'],
                            'reward_info': i['reward_info']['reward_member_count']})
    return answer_info
複製代碼

數據存儲

獲取到數據以後,咱們須要存儲到數據庫中,以便於後續使用。由於後面準備使用 Flask 來搭建 API 服務,因此這裏存儲數據的過程也基於 Flask 來作,用插件 flask_sqlalchemy。web

定義數據結構

咱們定義三張表,分別存儲知乎熱榜的詳細列表信息,熱榜的熱度信息和熱榜對應的回答信息sql

class ZhihuDetails(db.Model):
    __tablename__ = 'ZhihuDetails'
    id = db.Column(db.Integer, primary_key=True)
    hot_id = db.Column(db.String(32), unique=True, index=True)
    hot_name = db.Column(db.Text)
    hot_link = db.Column(db.String(64))
    hot_cardid = db.Column(db.String(32))


class ZhihuMetrics(db.Model):
    __tablename__ = 'ZhihuMetrics'
    id = db.Column(db.Integer, primary_key=True)
    hot_metrics = db.Column(db.String(64))
    hot_cardid = db.Column(db.String(32), index=True)
    update_time = db.Column(db.DateTime)


class ZhihuContent(db.Model):
    __tablename__ = 'ZhihuContent'
    id = db.Column(db.Integer, primary_key=True)
    answer_id = db.Column(db.Integer, index=True)
    author = db.Column(db.String(32), index=True)
    voteup_count = db.Column(db.Integer)
    comment_count = db.Column(db.Integer)
    reward_info = db.Column(db.Integer)
    content = db.Column(db.Text)
複製代碼

定時任務

因爲咱們須要定時查詢熱榜列表和熱榜的熱度值,因此這裏須要定時運行相關的任務,使用插件 flask_apscheduler 來作定時任務

咱們的定時任務,涉及到了網絡請求和數據入庫的操做,把這部分定時任務代碼單獨拉出來,在 Flask 項目的根目錄下建立一個文件 apschedulerjob.py,因爲在運行該文件時,是沒有 Flask app 變量的,因此咱們須要手動調用 app_context() 方法來建立 app 上下文

def opera_db():
    with scheduler.app.app_context():
...
複製代碼

固然,這裏的 scheduler 變量是在 create_app 中初始化過的

from flask_apscheduler import APScheduler

scheduler = APScheduler()


def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    db.init_app(app)
    scheduler.init_app(app)
...
複製代碼

接着,咱們就能夠根據前面的兩個爬蟲函數,來分別入庫數據了

入庫熱榜熱度數據

update_metrics = ZhihuMetrics(hot_metrics=i['target']['metricsArea']['text'],
                                              hot_cardid=i['cardId'],
                                              update_time=datetime.datetime.now())
複製代碼

入庫熱榜列表數據

new_details = ZhihuDetails(hot_id=i['id'], hot_name=i['target']['titleArea']['text'],
                                           hot_link=i['target']['link']['url'], hot_cardid=i['cardId'])
複製代碼

入庫熱榜回答數據

new_content = ZhihuContent(answer_id=answer_id, author=answer['author'], voteup_count=answer['voteup_count'],
                                                   comment_count=answer['comment_count'], reward_info=answer['reward_info'],
                                                   content=answer['content'])
複製代碼

最後咱們就能夠在 Flask 的入口程序中啓動定時任務了

import os
from app import create_app, scheduler


app = create_app(os.getenv('FLASK_CONFIG'or 'default')


if __name__ == '__main__':
    scheduler.start()
    app.run(debug=True)
複製代碼

編寫 API

熱榜列表 API

咱們首先先來作熱榜列表的接口,在數據庫表 ZhihuMetrics 中拿到當天的熱榜最新熱度信息,而後再根據熱榜熱度信息來獲取對應的列表信息,能夠總結到以下的一個函數中

def zhihudata():
    current_time = '%s-%s-%s 00:00:00' % (datetime.now().year, datetime.now().month, datetime.now().day,)
    zhihumetrics_data = ZhihuMetrics.query.filter(ZhihuMetrics.update_time > current_time).group_by(ZhihuMetrics.hot_cardid).order_by(ZhihuMetrics.update_time).all()
    metrics_list = db_opera.db_to_list(zhihumetrics_data)
    details_list = []
    for d in metrics_list:
        zhihudetails_data = ZhihuDetails.query.filter_by(hot_cardid=d[1]).first()
        details_list.append([zhihudetails_data.hot_name, zhihudetails_data.hot_link, d[0], d[1], d[2]])

    return details_list
複製代碼

接着定義一個視圖函數返回 json 數據

@api.route('/api/zhihu/hot/')
def zhihu_api_data():
    zhihu_data = zhihudata()
    data_list = []
    for data in zhihu_data:
        data_dict = {'title'data[0], 'link'data[1], 'metrics'data[2], 'hot_id'data[3], 'update_time'data[4]}
        data_list.append(data_dict)

    return jsonify({'code'0'content': data_list}), 200
複製代碼

熱榜詳情 API

下面再來作熱榜詳情接口,該接口能夠返回熱榜熱度走勢信息,爲前端畫圖提供數據。

def zhihudetail(hot_id):
    zhihumetrics_details = ZhihuMetrics.query.filter_by(hot_cardid=hot_id).order_by(ZhihuMetrics.update_time).all()
    Column = {'categories': [], 'series': [{'name''熱度走勢''data': []}]}

    for i in zhihumetrics_details:
        Column['categories'].append(datetime.strftime(i.update_time, "%Y-%m-%d %H:%M"))
        Column['series'][0]['data'].append(int(i.hot_metrics.split()[0]))

    return Column



@api.route('/api/zhihu/detail/<id>/')
def zhihu_api_detail(id):
    zhihu_detail = zhihudetail(id)
    return jsonify({'code'0'data': zhihu_detail}), 200
複製代碼

接入小程序

對於小程序端,咱們這裏使用了 uni-app 框架,這是一個能夠一份代碼多端運行的框架,仍是比較不錯的。

建立項目

首先經過 IDE HBuilder 建立一個 uni-app 模板

改造項目

咱們簡單改造下該模板,首先修改下 index.nvue 文件,把 tabList 修改以下

data() {
            return {
                tabList: [{
                    id: "tab01",
                    name: '知乎熱榜',
                    newsid: 0
                }, {
                    id: "tab02",
                    name: '微博熱榜',
                    newsid: 23
                }, 
複製代碼


咱們暫時只保留兩個 tab 頁籤,沒錯後面還要再作微博的熱榜!
接下來打開 news-page.nvue 文件,修改網絡請求地址

uni.request({

                    url'http://127.0.0.1:5000/api/zhihu/hot/',
                    data: '',
複製代碼

把 URL 地址指向咱們本身的 API 服務地址
而後再添加咱們本身的新聞參數

hot_idnews.hot_id,
metricsnews.metrics,
news_urlnews.link
複製代碼

再修改函數 goDetail 以下

goDetail(detail) {
                if (this.navigateFlag) {
                    return;
                }
                this.navigateFlag = true;
                uni.navigateTo({
                    url'/pages/detail/detail-new?query=' + encodeURIComponent(JSON.stringify(detail))
                });
                setTimeout(() => {
                    this.navigateFlag = false;
                }, 200)
            },
複製代碼

點擊每條熱榜時,就會跳轉到 url 對應的 /pages/detail/detail-new 頁面

引入 uCharts

下面編寫 detail-new.nvue 文件,這裏主要用到了 uni-app 的插件 uCharts。
這是一個高性能的跨端圖表插件,很是好用。
template 部分

<template>
    <view class="qiun-columns">
        <view class="qiun-bg-white qiun-title-bar qiun-common-mt" >
            <view class="qiun-title-dot-light">柱狀熱力分佈</view>
        </view>
        <view class="qiun-charts" >
            <canvas canvas-id="canvasColumn" id="canvasColumn" class="charts" @touchstart="touchColumn"></canvas>
        </view>

        <view class="qiun-bg-white qiun-title-bar qiun-common-mt" >
            <view class="qiun-title-dot-light">線性走勢</view>
        </view>
        <view class="qiun-charts" >
            <canvas canvas-id="canvasLine" id="canvasLine" class="charts" @touchstart="touchColumn"></canvas>
        </view>
    </view>
</template>
複製代碼

建立兩個 view,分別用於展現柱狀圖和折線圖

再編寫 script 部分

getServerData(){
                console.log("hahahah"this.details.hot_id);
                uni.request({
                    url: 'http://127.0.0.1:5000/api/zhihu/detail/' + this.details.hot_id,
                    data:{
                    },
                    success: function(res) {
                        _self.serverData=res.data.data;
                        let Column={categories:[],series:[]};
                        Column.categories=res.data.data.categories;
                        Column.series=res.data.data.series;
                        _self.showColumn("canvasColumn",Column);
                        _self.showLine("canvasLine",Column);
                    },
                    fail: () => {
                        _self.tips="網絡錯誤,小程序端請檢查合法域名";
                    },
                });
            }
複製代碼

再根據 uCharts 的官方文檔編寫對應的展現圖表函數

showColumn(canvasId,chartData){
                canvaColumn=new uCharts({
                    $this:_self,
                    canvasId: canvasId,
                    type'column',
                    legend:{show:true},
                    fontSize:11,
                    background:'#FFFFFF',
                    pixelRatio:_self.pixelRatio,
                    animation: true,
                    categories: chartData.categories,
                    series: chartData.series,
                    enableScroll: true,
                    xAxis: {
                        disableGrid:true,
                        scrollShow:true,
                        itemCount:4,
                    },
                    yAxis: {
                        //disabled:true
                    },
                    dataLabel: true,
                    width: _self.cWidth*_self.pixelRatio,
                    height: _self.cHeight*_self.pixelRatio,
                    extra: {
                        column: {
                            type:'group',
                            width: _self.cWidth*_self.pixelRatio*0.45/chartData.categories.length
                        }
                      }
                });

            }
複製代碼

這樣,基本上咱們就能夠到小程序的模擬器來查看效果啦
熱榜列表頁面

熱榜詳情頁面

基本的效果是有了,不過還有不少須要優化的地方,下一次,我會分享出優化後的代碼以及如何把 API 服務部署到雲端,同時仍是提供出供你們練習的 API,不要錯過哦!

相關文章
相關標籤/搜索