1. 程式人生 > 其它 >將百度萬年曆存入自己的資料庫

將百度萬年曆存入自己的資料庫

前言

最近有需要研究陰曆和陽曆互相轉換的問題。因此找到兩個庫carbonsolarlunar
但是感覺計算出來的總是不太放心,而且也會佔用計算資源。我的想法是通過介面獲取現成的陰曆和陽曆資料,存到本地資料庫,這樣查詢的時候一步到位。

方案

我通過百度搜索萬年曆,抓取網頁請求得到百度的一個介面正好可以獲取萬年曆的資訊,還是挺全面的。
因此我寫程式碼實現了將百度萬年曆的資料獲取,然後存入資料庫的程式碼。

下面是calendar.go的程式碼,主要使用gorm建立表,以及寫入拉取的資料。

package calendar

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"
	"time"

	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
	"gorm.io/driver/mysql"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type (
	PerpetualCalendar struct {
		//Status       string `json:"status"`
		//T            string `json:"t"`
		//SetCacheTime string `json:"set_cache_time"`
		Data []PerpetualCalendarData `json:"data"`
	}
	PerpetualCalendarData struct {
		//ExtendedLocation string `json:"ExtendedLocation"`
		//OriginQuery      string `json:"OriginQuery"`
		//SiteID           int    `json:"SiteId"`
		//StdStg           int    `json:"StdStg"`
		//StdStl           int    `json:"StdStl"`
		//SelectTime       int    `json:"_select_time"`
		//UpdateTime       string `json:"_update_time"`
		//Version          int    `json:"_version"`
		//Appinfo          string `json:"appinfo"`
		//CambrianAppid    string `json:"cambrian_appid"`
		//DispType         int    `json:"disp_type"`
		//Fetchkey         string `json:"fetchkey"`
		//Key              string `json:"key"`
		//Loc              string `json:"loc"`
		//Resourceid       string `json:"resourceid"`
		//RoleID           int    `json:"role_id"`
		//Showlamp         string `json:"showlamp"`
		//Tplt             string `json:"tplt"`
		//URL              string `json:"url"`
		Almanac []PerpetualCalendarAlmanac `json:"almanac"`
	}
	PerpetualCalendarAlmanac struct {
		Id int `gorm:"primarykey"` // 自增主鍵

		Animal         string    `json:"animal" gorm:"column:animal;not null;size:4"`                     // 生肖
		Suit           string    `json:"suit" gorm:"column:suit;not null"`                                // 宜
		Avoid          string    `json:"avoid" gorm:"column:avoid;not null"`                              // 忌
		CnDay          string    `json:"cnDay" gorm:"column:cnDay;not null;size:4"`                       // 星期
		Day            int       `json:"day,string" gorm:"column:day;not null;uniqueIndex:Solar"`         // 陽曆日
		Month          int       `json:"month,string" gorm:"column:month;not null;uniqueIndex:Solar"`     // 陽曆月
		Year           int       `json:"year,string" gorm:"column:year;not null;uniqueIndex:Solar"`       // 陽曆年
		GzDate         string    `json:"gzDate" gorm:"column:gzDate;not null;size:8"`                     // 干支日
		GzMonth        string    `json:"gzMonth" gorm:"column:gzMonth;not null;size:8"`                   // 干支月
		GzYear         string    `json:"gzYear" gorm:"column:gzYear;not null;size:8"`                     // 干支年
		IsBigMonth     string    `json:"isBigMonth" gorm:"-"`                                             // json取資料,忽略gorm
		IsBigMonthBool bool      `gorm:"column:isBigMonth;not null;default:0"`                            // 是否為陰曆大月
		LDate          string    `json:"lDate" gorm:"column:lDate;not null;size:4"`                       // 陰曆日,漢字
		LMonth         string    `json:"lMonth" gorm:"column:lMonth;not null;size:4"`                     // 陰曆月,漢字,帶'閏'字表示閏月
		LunarDate      int       `json:"lunarDate,string" gorm:"column:lunarDate;not null;index:Lunar"`   // 陰曆日,數字
		LunarMonth     int       `json:"lunarMonth,string" gorm:"column:lunarMonth;not null;index:Lunar"` // 陰曆月,數字
		LunarYear      int       `json:"lunarYear,string" gorm:"column:lunarYear;not null;index:Lunar"`   // 陰曆年,數字
		ODate          time.Time `json:"oDate" gorm:"column:oDate;not null"`                              // ODate.Local(),陽曆當天0點
		Term           string    `json:"term,omitempty" gorm:"column:term;not null"`                      // 如'除夕','萬聖節','三伏'等
		Desc           string    `json:"desc,omitempty" gorm:"column:desc;not null"`                      // 如'臘八節','下元節'等
		Type           string    `json:"type,omitempty" gorm:"column:type;not null;size:2"`               // 各種和節日有關的型別
		Value          string    `json:"value,omitempty" gorm:"column:value;not null"`                    // 如'國際殘疾人日'等
		Status         int       `json:"status,string,omitempty" gorm:"column:status;not null;default:0"` // 0 工作日,1 休假,2 上班,3 週末
	}
)

func (PerpetualCalendarAlmanac) TableName() string {
	return "perpetualCalendarAlmanac"
}

// GetPerpetualCalendar 返回[前一個月,本月,後一個月]的資料
func GetPerpetualCalendar(year, mouth int) ([]PerpetualCalendarAlmanac, error) {
	u := url.Values{}
	u.Add("tn", "wisetpl")
	u.Add("format", "json")
	u.Add("resource_id", "39043") // 這個用瀏覽器請求後得到的
	u.Add("query", fmt.Sprintf("%d年%d月", year, mouth))
	u.Add("t", strconv.FormatInt(time.Now().UnixMilli(), 10))

	urls := "https://sp1.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?" + u.Encode()
	resp, err := http.Get(urls) // 百度這個介面可能現在請求速度,所以可能報錯
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var ret PerpetualCalendar // 需要將gbk轉為utf8
	err = json.NewDecoder(transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())).Decode(&ret)
	if err != nil {
		return nil, err
	}
	if len(ret.Data) != 1 { // 該陣列目前只會有一個
		return nil, errors.New("get Data error")
	}

	for i, v := range ret.Data[0].Almanac {
		// 賦值大月
		ret.Data[0].Almanac[i].IsBigMonthBool = v.IsBigMonth == "1"

		if v.Status == 0 && (v.CnDay == "六" || v.CnDay == "日") {
			ret.Data[0].Almanac[i].Status = 3 // 不是特殊型別,且為週末則賦值
		}
	}
	return ret.Data[0].Almanac, nil
}

func SaveCalendar(dsnSrc string) error {
	ts := time.Now()
	defer func() {
		fmt.Println(time.Since(ts))
	}()

	iDsn := strings.Index(dsnSrc, ":")
	if iDsn < 0 {
		return errors.New("dsn error")
	}

	var gormOpen gorm.Dialector
	switch strings.ToLower(dsnSrc[:iDsn]) {
	case "mysql":
		gormOpen = mysql.Open(dsnSrc[iDsn+1:])
	case "sqlite":
		gormOpen = sqlite.Open(dsnSrc[iDsn+1:])
	default:
		return errors.New("just support mysql or sqlite")
	}

	db, err := gorm.Open(gormOpen, &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		return err
	}

	res := db.Exec("DROP table if exists " + PerpetualCalendarAlmanac{}.TableName())
	if res.Error != nil {
		return res.Error
	}
	// 每次重建資料表
	err = db.AutoMigrate(PerpetualCalendarAlmanac{})
	if err != nil {
		return err
	}

	// 起止時間按照百度萬年曆得到
	start := time.Date(1900, time.February, 1, 0, 0, 0, 0, time.Local)
	end := time.Date(2050, time.December, 1, 0, 0, 0, 0, time.Local)
	wg := sync.WaitGroup{}
	// 由於每次查詢包含前一個月,當月,下個月,因此每次都增加3個月進行查詢
	for ; start.Before(end); start = start.AddDate(0, 3, 0) {
		wg.Add(1)
		y, m, _ := start.Date()
		go func(y, m int) {
			defer wg.Done()
			for { // 使用協程併發請求,提高速度,出現錯誤時重試
				data, err := GetPerpetualCalendar(y, m)
				if err != nil {
					fmt.Println("GetPerpetualCalendar", y, m, err)
					continue // 報錯重試,直到成功
				}
				res := db.Create(&data)
				if res.Error != nil {
					fmt.Println("Create", y, m, res.Error)
					continue // 報錯重試,直到成功
				}
				break
			}
		}(y, int(m))
	}
	wg.Wait()
	return nil
}

下面是main.go,根據傳入的引數,選擇是儲存在mysql還是sqlite中。

package main

import (
	calendar "interesting/perpetual_calendar"
)

func main() {
	dsn := "mysql:user:pass@tcp(127.0.0.1:3306)/janbar?charset=utf8mb4&parseTime=True&loc=Local"
	//dsn := "sqlite:test.db"
	err := calendar.SaveCalendar(dsn)
	if err != nil {
		panic(err)
	}
}

結果展示

如下圖所示,是存入的資料,已經為陽曆年月日建立唯一聯合索引,陰曆年月日因為存在閏月會重複,因此只建立了聯合索引。

查詢語句可以按照下面的來做。大部分屬性已經按照我的理解寫到註釋裡面了,可以結合百度的萬年曆看看,展示在哪個位置吧。

查詢陽曆 2021-09-16 號的資料,結果有對應的陰曆情況
SELECT *FROM perpetualCalendarAlmanac WHERE year=2021 AND month=9 AND day=16

查詢陰曆 2021-09-03 號的資料,結果有對應陽曆的情況
SELECT *FROM perpetualCalendarAlmanac WHERE lunarYear=2021 AND lunarMonth=9 AND lunarDate=3

總結

這個介面呼叫,和存資料沒啥難度。主要是讓我加深對gorm等庫的使用。
當然最主要是我想實現按照陰曆定時的cron規則,結合cron庫來搞。

作者:janbar 出處:https://www.cnblogs.com/janbar 本文版權歸作者和部落格園所有,歡迎轉載,轉載請標明出處。喜歡我的文章請 [關注我] 吧。 如果您覺得本篇博文對您有所收穫,可點選 [推薦] [收藏] ,或到右側 [打賞] 裡請我喝杯咖啡,非常感謝。