友情提示:此篇文章大約需要閱讀 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