友情提示:此篇文章大約需要閱讀 27分鐘32秒,不足之處請多指教,感謝你的閱讀。? 訂閱本站
事情的起因是這樣的,前幾日在看 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資料
啟動服務,可以得到對應的 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 資源訪問了:demo.debuginn.cn/doubanmovies
效果
解決了 HTTPS 訪問 HTTP 資源的問題,就解決了所有問題,實現了效果。
我的觀影
具體效果如下:
我的觀影 - Debug客棧
動態同步豆瓣資料 | Github倉庫 | Go \ Gin \ VUE \ MDUI …
開源
針對於此小專案,我已經開源至 Github 中,若是你感興趣或者有什麼建議,可以聯絡我,我們一起改進,同時希望你可以給我一個 Star,萬分感謝!
GitHub
debuginn/DouBanMyMovies
如何將豆瓣觀影記錄實時同步至部落格中 www.debuginn.cn/6016.html
本作品採用《CC 協議》,轉載必須註明作者和本文連結