如何將豆瓣觀影記錄實時同步至博客中

友情提示:此篇文章大約需要閱讀 27分鐘32秒,不足之處請多指教,感謝你的閱讀。👉 訂閱本站

此文首發於 Debug客棧 www.debuginn.cn

事情的起因是這樣的,前幾日在看 idealclover 大佬的博客,不經意間看到了他的豆瓣觀影記錄,他博客中關於豆瓣觀影記錄是實時同步的,很好奇是如何實現的,經過查看,他是爬取的豆瓣觀影界面來實現的,其實關於豆瓣觀影記錄,網上也有很多的教程,恰巧自己所學的 Go語言也可以做簡單的爬蟲實現其效果,於是開始上手造輪子了,PS:瞭解到非法爬取網站信息是違法的,之前豆瓣 API 接口,關閉訪問,在豆瓣上找了好久,終於在我的主頁中找到了對於觀影記錄的官方提供RSS訂閱,打開訂閱,看到有自己所需要的字段,比較好獲取,於是就開始了此項目。

分析

首先,需要獲取豆瓣提供的 XML 文件,在我的主頁右下角就可以看到 RSS 訂閱鏈接:

豆瓣訂閱地址

找到了訂閱地址,點擊查看 XML 結構,可以看到豆瓣提供的結構還是挺理想的:

<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
    <title>Meng小羽 的收藏</title>
    <link>https://www.douban.com/people/debuginn/</link>
    <description>
        <![CDATA[ Meng小羽 的收藏:想看、在看和看過的書和電影,想聽、在聽和聽過的音樂 ]]>
    </description>
    <language>zh-cn</language>
    <copyright>© 2013, douban.com.</copyright>
    <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate>
    <item>
        <title>看過黑衣人:全球追緝</title>
        <link>http://movie.douban.com/subject/19971676/</link>
        <description>
        <![CDATA[ <table><tr> <td width="80px"><a href="https://movie.douban.com/subject/19971676/" title="Men in Black International"> <img src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.webp" alt="Men in Black International"></a></td> <td> <p>推薦: 力薦</p> </td></tr></table> ]]>
        </description>
        <dc:creator>Meng小羽</dc:creator>
        <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate>
        <guid isPermaLink="false">https://www.douban.com/people/debuginn/interests/2402808825</guid>
    </item>
    ......
<channel>

其實,我們提取的主要就是 item 標籤下對應的電影信息內容:

<item>
    <title>看過黑衣人:全球追緝</title>
    <link>http://movie.douban.com/subject/19971676/</link>
    <description>
    <![CDATA[ <table><tr> <td width="80px"><a href="https://movie.douban.com/subject/19971676/" title="Men in Black International"> <img src="https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.webp" alt="Men in Black International"></a></td> <td> <p>推薦: 力薦</p> </td></tr></table> ]]>
    </description>
    <dc:creator>Meng小羽</dc:creator>
    <pubDate>Sat, 30 May 2020 09:14:08 GMT</pubDate>
    <guid isPermaLink="false">https://www.douban.com/people/debuginn/interests/2402808825</guid>
</item>

設計

根據豆瓣官方提供的 XML 標籤數據,可以利用 Go 語言中 encoding/xml 包來進行對數據的映射,可以設計成如下結構體:

// 豆瓣 xml 描述結構體
type Attributes struct {
	XMLName xml.Name `xml:"rss"`
	Version string   `xml:"version,attr"`
	Channel Channel  `xml:"channel"`
}

// XML 主題結構拆分
type Channel struct {
	Title       string      `xml:"title"`
	Link        string      `xml:"link"`
	Description string      `xml:"description"`
	Language    string      `xml:"language"`
	Copyright   string      `xml:"copyright"`
	Pubdate     string      `xml:"pubDate"`
	MovieItem   []MovieItem `xml:"item"`
}

// 豆瓣 電影列表結構體
type MovieItem struct {
	Title       string `xml:"title"`
	Link        string `xml:"link"`
	Description string `xml:"description"`
	Pubdate     string `xml:"pubDate"`
}

可以和 XML 文件對應字段進行匹配,可以從上面的結構體中我們可以看到,最終我們想獲取到的數據就是結構體 MovieItem 的數據。

由於是從網上鍊接獲取數據的,在這裏首先我們需要將網上豆瓣提供的 XML 文件轉換成 []byte 類型的數據:

// 獲取 xml 文件數據
func getXMLData(url string) (data []byte, err error) {
	// 讀取 xml 文件
	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	// 自定義Header
	req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close() // 關閉文件
	// 讀取所有文件內容保存至 []byte
	data, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	return
}

上面這個函數實現的就是將 XML 文件保存至 Go 語言的數據結構的操作,現在可以將 XML 文件成功讀取出來,接下來就是要進行 XML 字段與上面作出的結構體之間的映射,其實映射至結構體的過程是比較簡單的,首先聲明 Attributes{} 類型的結構體,之後通過 xml.Unmarshal 來實現映射拷貝,就可以得到對應的結構體類型的數據,由於我們想要的數據是結構體數據中的一部分,即 MovieItem,在得到結構體數據後就可以將想要的這一部分的數據選擇抽取出來:

v := Attributes{}
unMarshalErr := xml.Unmarshal(data, &v)
if unMarshalErr != nil {
	fmt.Printf("xml unmarshal failed, err:%v\n", err)
}

movieItem := v.Channel.MovieItem

Map 轉換

在這裏我們可以得到結構體中嵌套的結構體,在結構體中有一些字段我們是不想要的,需要進行處理,對於 description 這個字段中,官方提供的是一段 HTML 描述串,其中電影的描述文件是我們所需要的,對於 HTML 字符串的拆分,我們可以藉助strings.Split 函數來實現截取,使用 \" 符號截取,雖然可以獲取到我們想要的數據了,但是由於這個是嵌套的結構體,我們需要做一個匹配的 map 來進行存儲處理好的數據,可以看代碼中我的設計:

MoviesMap := make(map[int]interface{})
for i := 0; i < len(movieItem); i++ {
	movie := make(map[string]string)
	description := strings.Split(movieItem[i].Description, "\"")
	movie["Title"] = string([]rune(movieItem[i].Title)[2:])
	movie["Link"] = movieItem[i].Link
	movie["Img"] = description[7]
	movie["Pubdate"] = movieItem[i].Pubdate

	MoviesMap[i] = movie
}

外層 map 是採用 map[int]interface{} 類型,在 interface{} 中存儲這內層 map map[string]string 類型。

針對於 Img 地址的獲取,是現根據特定符號拆分,之後獲取制定位置的數據獲取的。

0 map[Img:https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2558701068.jpg Link:http://movie.douban.com/subject/19971676/ Pubdate:Sat, 30 May 2020 09:14:08 GMT Title:黑衣人:全球追緝]
1 map[Img:https://img1.doubanio.com/view/photo/s_ratio_poster/public/p2263408369.jpg Link:http://movie.douban.com/subject/1294371/ Pubdate:Thu, 28 May 2020 10:06:23 GMT Title:摩登時代]
......

最後就是將這個 map 做一下序列化處理,這樣就可以返回給前臺數據了。

data, _ = json.Marshal(MoviesMap)

服務

處理好數據,做了對應的處理,怎麼將數據作爲服務端提供給前臺,在這裏需要使用 Web 服務,Go 中可以使用原生 Web,不過我在這裏使用的是之前學過的 Gin 框架,來提供服務的:

r := gin.Default()
r.GET("/doubanmovies", func(context *gin.Context) {
	context.JSON(http.StatusOK, MoviesMap)
})
_ = r.Run(":8080")

 

api json數據

api json數據

啓動服務,可以得到對應的 json 數據,你若以爲現在就可以實現了,那麼你錯了,遠遠沒有那麼簡單……

前臺

由於我知曉我的博客採用的前臺 UI 技術是 MDUI, 我利用自身的卡片 UI 迅速設計了一個模塊,因爲後期需要放在我的博客頁面上,前端讀取數據採用的是 VUE 和 axios 技術:

<div class="mdui-container-fluid" id="app">
    <div class="mdui-row">
        <div v-for="item in info">
            <div class="mdui-col-xs-6 mdui-col-sm-4 mdui-col-md-3 mdui-col-lg-3 mdui-m-b-1 mdui-m-t-1">
                <a :href="item.Link" target="_blank">
                    <div class="mdui-card mdui-hoverable">
                        <div class="mdui-card-media">
                            <img :src="item.Img" style="height: 260px;" />
                            <div class="mdui-card-media-covered">
                                <div class="mdui-card-primary">
                                    <div class="mdui-card-primary-subtitle">{{ item.Title }}</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </a>
            </div>
        </div>
    </div>
</div>
<script src="./static/js/mdui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js"></script>
<script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                info: null
            }
        },
        mounted() {
            axios
                .get('http://127.0.0.1:8080/doubanmovies')
                .then(response => (this.info = response.data))
                .catch(function (error) { // 請求失敗處理
                    console.log(error);
                });
        }
    })
</script>

設計好了以後,訪問頁面,卻加載不出來,emmmmmm

CORS

CORS

看到了是 CORS 同源策略的原因,接下來就是要解決同源問題了,方法比較簡單,就是將 Go 服務端加上 CORS 同源策略就可以了,方法如下:

r := gin.Default()
r.Use(Cors())
r.GET("/doubanmovies", func(context *gin.Context) {
	context.JSON(http.StatusOK, MoviesMap)
})

_ = r.Run(":8080")

在路由訪問中添加 Cors() 函數:

// 跨域
func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method               //請求方法
		origin := c.Request.Header.Get("Origin") //請求頭部
		var headerKeys []string                  // 聲明請求頭keys
		for k, _ := range c.Request.Header {
			headerKeys = append(headerKeys, k)
		}
		headerStr := strings.Join(headerKeys, ", ")
		if headerStr != "" {
			headerStr = fmt.Sprintf("access-control-allow-origin, access-control-allow-headers, %s", headerStr)
		} else {
			headerStr = "access-control-allow-origin, access-control-allow-headers"
		}
		if origin != "" {
			c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
			c.Header("Access-Control-Allow-Origin", "*")                                       // 這是允許訪問所有域
			c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") //服務器支持的所有跨域請求的方法,爲了避免瀏覽次請求的多次'預檢'請求
			//  header的類型
			c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
			//              允許跨域設置                                                                                                      可以返回其他子段
			c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar") // 跨域關鍵設置 讓瀏覽器可以解析
			c.Header("Access-Control-Max-Age", "172800")                                                                                                                                                           // 緩存請求信息 單位爲秒
			c.Header("Access-Control-Allow-Credentials", "false")                                                                                                                                                  //  跨域請求是否需要帶cookie信息 默認設置爲true
			c.Set("content-type", "application/json")                                                                                                                                                              // 設置返回格式是json
		}

		//放行所有OPTIONS方法
		if method == "OPTIONS" {
			c.JSON(http.StatusOK, "Options Request!")
		}
		// 處理請求
		c.Next() //  處理請求
	}
}

這樣就可以看到結果了,如下圖:

演示

看到結果後,心中竊喜,感覺成功了,接下來就需要將 Go 服務部署到我的服務器中去了,部署步驟比較簡單,就不過多解釋了,最後訪問服務器 IP 及對應單口可以呈現結果,最後將前臺代碼粘貼到新建的頁面中,生成預覽,emmmm,啥都沒有,瀏覽器居然報 HTTPS 請求 HTTP 資源是不安全的,吐了一口血,解決吧,唉,經過查詢資料,得出如下兩個解決方案:

  • Gin 框架服務本身使用 SSL 證書,實現 HTTPS 訪問,不過需要配置域名;
  • 使用 Nginx 服務做一下代理,將一個特定鏈接代理到本身服務中去。

作爲學生黨的我,沒有太多的資金去申請過多的 SSL 證書(省着點用),於是我就在我的 demo.debuginn.cn 子域名下做了一個代理。

代理

Nginx 代理實現也是比較簡單的,就是將前端訪問某個接口代理至服務器中某個端口的服務中,表面上看是 Nginx 在做數據處理,實際上是 Nginx 只做了一個代理轉發,由於我demo.debuginn.cn 子域名本身就是 https 的,所以設置好了代理之後,就可以使用固定的代理鏈接訪問了,配置如下:

server{
    .....
    location /doubanmovies {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host:80;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

這樣就可以實現 https 資源訪問了:https://demo.debuginn.cn/doubanmovies

效果

解決了 HTTPS 訪問 HTTP 資源的問題,就解決了所有問題,實現了效果。

我的觀影

具體效果如下:https://www.debuginn.cn/doubanmovies

開源

針對於此小項目,我已經開源至 Github 中,若是你感興趣或者有什麼建議,可以聯繫我,我們一起改進,同時希望你可以給我一個 Star,萬分感謝!

 GitHub debuginn/DouBanMyMovies

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章