go爬蟲系列


一、go語言爬取豆瓣電影top250

package main

import (
	"net/http"
	"io/ioutil"
	"os"
	"fmt"
	"strconv"
	"regexp"
	"time"
)

//定義新的數據類型
type Spider struct {
	url    string
	header map[string]string
}


//定義 Spider get的方法
func (keyword Spider) get_html_header() string {
	client := &http.Client{}
	req, err := http.NewRequest("GET", keyword.url, nil)
	if err != nil {
	}
	for key, value := range keyword.header {
		req.Header.Add(key, value)
	}
	resp, err := client.Do(req)
	if err != nil {
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
	}
	return string(body)

}
func parse()  {
	header := map[string]string{
		"Host": "movie.douban.com",
		"Connection": "keep-alive",
		"Cache-Control": "max-age=0",
		"Upgrade-Insecure-Requests": "1",
		"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
		"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
		"Referer": "https://movie.douban.com/top250",
	}

	//創建excel文件
	f, err := os.Create("spider.txt")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	//寫入標題
	f.WriteString("電影名稱"+"\t"+"評分"+"\t"+"評價人數"+"\t"+"\r\n")

	//循環每頁解析並把結果寫入excel
	for i:=0;i<10;i++{
		fmt.Println("正在抓取第"+strconv.Itoa(i)+"頁......")
		url:="https://movie.douban.com/top250?start="+strconv.Itoa(i*25)+"&filter="
		spider := &Spider{url, header}
		html := spider.get_html_header()

		//評價人數
		pattern2:=`<span>(.*?)評價</span>`
		rp2 := regexp.MustCompile(pattern2)
		find_txt2 := rp2.FindAllStringSubmatch(html,-1)

		//評分
		pattern3:=`property="v:average">(.*?)</span>`
		rp3 := regexp.MustCompile(pattern3)
		find_txt3 := rp3.FindAllStringSubmatch(html,-1)

		//電影名稱
		pattern4:=`alt="(.*?)" src="`
		rp4 := regexp.MustCompile(pattern4)
		find_txt4 := rp4.FindAllStringSubmatch(html,-1)

		// 寫入UTF-8 BOM
		f.WriteString("\xEF\xBB\xBF")
		//  打印全部數據和寫入excel文件
		for i:=0;i<len(find_txt2);i++{
			fmt.Printf("%s %s %s\n",find_txt4[i][1],find_txt3[i][1],find_txt2[i][1], )
			f.WriteString(find_txt4[i][1]+"\t"+find_txt3[i][1]+"\t"+find_txt2[i][1]+"\t"+"\r\n")
		}
	}
}

func main() {
	t1 := time.Now() // get current time
	parse()
	elapsed := time.Since(t1)
	fmt.Println("爬蟲結束,總共耗時: ", elapsed)
}

二、goquery

上面第一部分的例子,使用了正則表達式來匹配,使用goquery會更方便。goquery是一個使用go語言寫成的HTML解析庫,可以讓你像jQuery那樣的方式來操作DOM文檔。
獲取代碼:

gopm get -g github.com/PuerkitoBio/goquery

直接使用go get方式安裝失敗,這里使用了gopm來安裝,關於gopm可以參考國內的go get問題的解決 --gopm
在代碼中引用時:

import “github.com/PuerkitoBio/goquery”

以下參考golang goquery selector(選擇器) 示例大全
如果大家以前做過前端開發,對jquery不會陌生,goquery類似jquery,它是jquery的go版本實現。使用它,可以很方便的對HTML進行處理。

  1. 基於HTML Element元素的選擇器
    這個比較簡單,就是基於a,p等這些HTML的基本元素進行選擇,這種直接使用Element名稱作為選擇器即可。比如dom.Find("div")。
func findDiv() {
    html := `<body>
                <div>DIV1</div>
                <div>DIV2</div>
                <span>SPAN</span>
            </body>
            `

    dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
    if err!=nil{
        log.Fatalln(err)
    }

    dom.Find("div").Each(func(i int, selection *goquery.Selection) {
        fmt.Println("i",i,"select text",selection.Text())
    })
}
  1. ID選擇器
    這個是使用頻次最多的,類似於上面的例子,有兩個div元素,其實我們只需要其中的一個,那么我們只需要給這個標記一個唯一的id即可,這樣我們就可以使用id選擇器,精確定位了。
func main() {
    html := `<body>

                <div id="div1">DIV1</div>
                <div>DIV2</div>
                <span>SPAN</span>

            </body>
            `

    dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
    if err!=nil{
        log.Fatalln(err)
    }

    dom.Find("#div1").Each(func(i int, selection *goquery.Selection) {
        fmt.Println(selection.Text())
    })
}
  1. Element ID選擇器
    id選擇器以#開頭,緊跟着元素id的值,使用語法為dom.Find(#id),后面的例子我會簡寫為Find(#id),大家知道這是代表goquery選擇器的即可。

如果有相同的ID,但是它們又分別屬於不同的HTML元素怎么辦?有好辦法,和Element結合起來。比如我們篩選元素為div,並且id是div1的元素,就可以使用Find(div#div1)這樣的篩選器進行篩選。

所以這類篩選器的語法為Find(element#id),這是常用的組合方法,比如后面講的過濾器也可以采用這種方式組合使用。
4. Class選擇器
class也是HTML中常用的屬性,我們可以通過class選擇器來快速的篩選需要的HTML元素,它的用法和ID選擇器類似,為Find(".class")。

func main() {
    html := `<body>

                <div id="div1">DIV1</div>
                <div class="name">DIV2</div>
                <span>SPAN</span>

            </body>
            `

    dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
    if err!=nil{
        log.Fatalln(err)
    }

    dom.Find(".name").Each(func(i int, selection *goquery.Selection) {
        fmt.Println(selection.Text())
    })
}

以上示例中,就篩選出來class為name的這個div元素。
5. Element Class 選擇器
class選擇器和id選擇器一樣,也可以結合着HTML元素使用,他們的語法也類似Find(element.class),這樣就可以篩選特定element、並且指定class的元素。

三、使用goquery爬取豆瓣電影top250

package main

import (
    "net/http"
    "fmt"
    "github.com/PuerkitoBio/goquery"
    "strconv"
)

func GetMovie(url string) {
    fmt.Println(url)
    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    //bodyString, err := ioutil.ReadAll(resp.Body)
    //fmt.Println(string(bodyString))
    if resp.StatusCode != 200 {
        fmt.Println("err")
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        panic(err)
    }

    //

    doc.Find("#content h1").Each(func(i int, s *goquery.Selection) {
        // name
        fmt.Println("name:" + s.ChildrenFiltered(`[property="v:itemreviewed"]`).Text())
        // year
        fmt.Println("year:" + s.ChildrenFiltered(`.year`).Text())
    })

    // #info > span:nth-child(1) > span.attrs
    director := ""
    doc.Find("#info span:nth-child(1) span.attrs").Each(func(i int, s *goquery.Selection) {
        // 導演
        director += s.Text()
        //fmt.Println(s.Text())
    })
    fmt.Println("導演:" + director)
    //fmt.Println("\n")

    pl := ""
    doc.Find("#info span:nth-child(3) span.attrs").Each(func(i int, s *goquery.Selection) {
        pl += s.Text()
    })
    fmt.Println("編劇:" + pl)

    charactor := ""
    doc.Find("#info span.actor span.attrs").Each(func(i int, s *goquery.Selection) {
        charactor += s.Text()
    })
    fmt.Println("主演:" + charactor)

    typeStr := ""
    doc.Find("#info > span:nth-child(8)").Each(func(i int, s *goquery.Selection) {
        typeStr += s.Text()
    })
    fmt.Println("類型:" + typeStr)
}

func GetToplist(url string) []string {
    var urls []string
    resp, err := http.Get(url)
    if err != nil {
        panic(err)
    }
    //bodyString, err := ioutil.ReadAll(resp.Body)
    //fmt.Println(string(bodyString))
    if resp.StatusCode != 200 {
        fmt.Println("err")
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        panic(err)
    }

    doc.Find("#content div div.article ol li div div.info div.hd a").
    Each(func(i int, s *goquery.Selection) {
        // year
        fmt.Printf("%v", s)
        herf, _ := s.Attr("href")
        urls = append(urls, herf)
    })
    return urls
}

func main() {
    url := "https://movie.douban.com/top250?start="
    var urls []string
    var newUrl string
    fmt.Println("%v", urls)
    for i := 0; i < 10; i++ {
        start := i * 25
        newUrl = url + strconv.Itoa(start)
        urls = GetToplist(newUrl)

        for _, url := range urls {
            GetMovie(url)
        }
    }
}

注意這個Find:doc.Find("#content div div.article ol li div div.info div.hd a"),參考返回的html內容,這里是個多層查找,#是查找id,帶點的是找相應的class。當然改成doc.Find("ol.grid_view li div.hd a")也是可以的。最終都是要找列表里的li,然后挑出href。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM