一步一步教你的機(jī)器人尋找資源鏈接

1. 前言

從之前的文章 從零到一:用Golang編寫機(jī)器人 ,我們已經(jīng)可以編寫一個屬于自己的小機(jī)器人了。

而本文將講解自己的機(jī)器人Samaritan找電影技能的實(shí)現(xiàn),算是拋磚引玉吧。

本文技術(shù)僅供交流學(xué)習(xí),請尊重影視版權(quán)。

2. 明確需求與前期準(zhǔn)備

當(dāng)我們想下載電影時:

  1. 輸入電影名稱
  2. 找到相關(guān)頁面
  3. 找到下載資源超鏈接
  4. 復(fù)制鏈接地址用于最終的下載

而交給機(jī)器人做的話:

  1. 識別用戶的輸入
  2. 找到資源鏈接并格式化
  3. 輸出格式化之后的結(jié)果

其中第1步和第3步是不是似曾相識?其實(shí)這正是之前文章實(shí)現(xiàn)的一個對話過程,只不過我們不再是讓機(jī)器人“自由發(fā)揮”,而是告訴機(jī)器人該回復(fù)什么內(nèi)容。

所以我們還需要做的,僅是教會機(jī)器人怎么從網(wǎng)絡(luò)中搜索信息,以及哪些是我們所需要的信息。最好的辦法便是“身教”,讓機(jī)器人學(xué)習(xí)并模仿我們完成整個過程的所有動作。

3. 獲取并解析資源

此處以電影“星球大戰(zhàn)7”為例,資源站點(diǎn)選擇龍部落,目標(biāo)是找到可用下載鏈接。

以下操作,實(shí)為我們用瀏覽器找到最終鏈接的操作記錄

3.1 搜索“星球大戰(zhàn)7”

搜索頁面顯示截圖

而對于機(jī)器人,便是請求http://www.lbldy.com/search/星球大戰(zhàn)7,獲取頁面返回:

    movie:= "星球大戰(zhàn)7"
    resp, _ := http.Get("http://www.lbldy.com/search/" + movie)
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)

這里暫時忽略錯誤處理,此時 body的值便是我們剛才在瀏覽器內(nèi)看到的頁面的源碼了,通過瀏覽器審查元素同樣可以看到:

3.2 找到第一個結(jié)果鏈接

右鍵復(fù)制鏈接地址可知為:http://www.lbldy.com/movie/64115.html
唯一的變量便是64115這個數(shù)字,這正是網(wǎng)頁源碼中出現(xiàn)的數(shù)字

    <div> class="postlist" id="post-64115"

大膽猜測,只需要提取出id="post-64115"中的數(shù)字即可,此時比較簡單的做法便是利用正則:

    re, _ := regexp.Compile("<div class=\"postlist\" id=\"post-(.*?)\">")
    firstId := re.FindSubmatch(body) //find first match case

3.3 進(jìn)入資源下載頁

此時瀏覽器部分顯示內(nèi)容為:

下載頁截圖

審查元素:

可以看到下載地址已經(jīng)看到了,接下來要做的就是讓機(jī)器人從中提取所有相關(guān)鏈接了。
上一步我們已經(jīng)找到電影id,讓機(jī)器人同樣訪問此頁面:

    resp, _ = http.Get("http://www.lbldy.com/movie/" + id + ".html")
    defer resp.Body.Close()
    doc, err := goquery.NewDocumentFromReader(io.Reader(resp.Body))
    if err != nil {
        return
    }

雖然依舊可以用正則來搜索下載鏈接,但此時可用goquery庫來處理較為復(fù)雜的html頁面。

    doc.Find("p").Each(func(i int, selection *goquery.Selection) {
        name := selection.Find("a").Text()
        link, _ := selection.Find("a").Attr("href")
        if strings.HasPrefix(link, "ed2k") || strings.HasPrefix(link, "magnet") || strings.HasPrefix(link, "thunder") {
            m := Media{
                Name: name,
                Link: link,
            }
            ms = append(ms, m)
        }
    })

goquery通過對html標(biāo)簽的解析,為我們找到了所有的下載結(jié)果列表。

3.4 復(fù)制下載鏈接

機(jī)器人將找到的結(jié)果通過channel返回給用戶:

    if len(ms) == 0 {
        results <- fmt.Sprintf("No results for *%s* from LBL", movie)
        return
    } else {
        ret := "Results from LBL:\n\n"
        for i, m := range ms {
            ret += fmt.Sprintf("*%s*\n```%s```\n\n", m.Name, m.Link)
            //when results are too large, we split it.
            if i%4 == 0 && i < len(ms)-1 && i > 0 {
                results <- ret
                ret = fmt.Sprintf("*LBL Part %d*\n\n", i/4+1)
            }
        }
        results <- ret
    }

此時我們可以從機(jī)器人處獲得回復(fù):

LBL部分結(jié)果截圖

4. 從更多資源站點(diǎn)獲取

通常我們會通過多個的資源站點(diǎn)搜索同一資源,Samaritan在搜索電影時,除了龍部落,還會從字幕組獲取。

字幕組的資源搜索流程和龍部落差不多,只不過涉及到登錄,所以在獲取資源前需讓機(jī)器人先登錄,并攜帶cookie訪問:

//zmz.tv needs to login before downloading
var zmzClient http.Client

func loginZMZ() {
    gCookieJar, _ := cookiejar.New(nil)
    zmzURL := "http://www.zimuzu.tv/User/Login/ajaxLogin"
    zmzClient = http.Client{
        Jar: gCookieJar,
    }
    zmzClient.PostForm(zmzURL, url.Values{"account": {"username"}, "password": {"password"}, "remember": {"0"}})
}

通過cookiejar登錄,zmzClient在后續(xù)訪問時便可攜帶用戶cookie,得以訪問需登錄的頁面。
同樣的電影,從字幕組獲取的資源:

ZMZ部分結(jié)果截圖

5. 更快地返回結(jié)果

當(dāng)我們有A, B, C..若干個資源站點(diǎn)時,寫出的代碼很可能是這樣

func DownloadMovie(){
      retA := getResourceFromA()
      retB := getResourceFromB()
      retC := getResourceFromC()
      ...
      return retA + retB + retC
}

而理想情況下,我們希望并發(fā)地進(jìn)行資源獲取,只要一有結(jié)果,立馬返回給用戶。
利用Golang的CSP并發(fā)模型,用goroutine不難寫出并發(fā)的版本:

func DownloadMovie(results chan<- string){
        var wg sync.WaitGroup
        wg.Add(3)
        go func() {
            defer wg.Done()
            results <- getResourceFromA()
        }()
        go func() {
            defer wg.Done()
            results <- getResourceFromB()
        }()
        go func() {
            defer wg.Done()
            results <- getResourceFromC()
        }()
        wg.Wait()
        close(results)
}

而調(diào)用者只需不斷從channel獲取:

func(){
        results:= make(chan string)
        go DownloadMovie(results)

        for {
            msg, ok := <-results //retrive result from channel
            if !ok {
                return
            }
            reply(msg)
        }
}

這樣,用戶就可以第一時間收到回復(fù)了。這便是goroutinechannel配合的精妙之處了。

6. 總結(jié)

通過上一篇文章,我們搭建了一個可以對話的小機(jī)器人,而本文講解了機(jī)器人常見的一個技能:爬取資源(爬蟲)。
經(jīng)過已有的知識儲備,然后通過分析,明確了我們的目標(biāo)。無非是接受用戶輸入->找到資源->輸出給用戶。
然后以找電影資源為例,讓機(jī)器人一步步地模擬用戶操作,最終找到了資源鏈接。
可我們并未滿足于此,提出了兩個優(yōu)化點(diǎn),功能性需求上,我們從更多的站點(diǎn)獲取到了資源; 非功能需求上,我們通過Golang的并發(fā)特性使得結(jié)果返回更快。

源碼參考
Have Fun!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,319評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評論 19 139
  • 社交紅利閱讀筆記 書名:社交紅利(修訂升級版) 作者:徐志斌 出版社:中信出版社 正文前筆記: 推薦序1摘要 社交...
    鳧水閱讀 9,423評論 4 26
  • 作者簡介 原創(chuàng)微信公眾號郭霖 WeChat ID: guolin_blog 本篇是猴菇先生的第二篇投稿,高度還原了...
    木木00閱讀 1,821評論 0 11
  • 感賞兒子完成一天的學(xué)習(xí),兒子辛苦了。這一周兒子表現(xiàn)不錯,獨(dú)立性越來越強(qiáng)了,遇事多思考,控制自己的情緒,對達(dá)不...
    金色陽光魏艷春閱讀 272評論 0 2

友情鏈接更多精彩內(nèi)容