Go语言实战爬虫项目
因為要做一個爬蟲系統, 用的是python,可是最后發現效率很低,恰好遇到一個go大神,他建議go試試,效果還不錯,所以惡補了一下資料!
Go語言爬蟲框架之Colly和Goquery
Python爬蟲框架比較多有requests、urllib, pyquery,scrapy等,解析庫有BeautifulSoup、pyquery、Scrapy和lxml等等,基于Go的爬蟲框架是比較強健的,尤其Colly和Goquery是比較強大的工具,其靈活性和 表達性都比較優秀。
網絡爬蟲
網絡爬蟲是什么?從本質上講,網絡爬蟲的工作原理通過檢查web頁面的HTML內容和執行某種類型的行動基于內容。通常,抓取暴露的鏈接,爬蟲按照隊列的去爬取。我們也可以從當前頁面保存數據提取。例如,如果我們的維基百科頁面上開始,我們可能保存頁面的文本和標題。
爬蟲的簡單算法
initialize?Queue enqueue?SeedURLwhile?Queue?is?not?empty:URL?=?Pop?element?from?QueuePage?=?Visit(URL)Links?=?ExtractLinks(Page)Enqueue?Links?on?Queue 12345678Visit和ExtractLinks函數是改變的地方,兩個函數的應用都是特定的。我們的爬蟲會盡力解釋整個WEB的圖,就像google一樣,或者像Wikipedia一樣簡單一些。
隨著你使用的用例的增加許多事情會變得復雜起來,許多許多的頁面會被抓取,你可能需要一個更尖端的爬蟲同時運行,對于更為復雜的頁面,你需要一個更強大的HTML解釋器。
Colly
Colly是一個基于Go語言的靈活的爬蟲框架,開箱即用,你會獲得一些速率限制,并行爬行等支持。
Colly基本組件之一是Collector,Collector保持跟蹤那些需要被爬取的頁面,并且保持回調當頁面被爬取的時候。
一、開始
創造一個Collector是容易的,但是我們有許多可選項我們可以使用。
| 1 2 3 4 5 6 7 8 9 | c := colly.NewCollector( ????// Restrict crawling to specific domains ????colly.AllowedDomains("godoc.org"), ????// Allow visiting the same page multiple times ????colly.AllowURLRevisit(), ????// Allow crawling to be done in parallel / async ????colly.Async(true), ) 12345678 |
你可以只有colly.NewCollector(),然后自己添加那些可選項。
我們也可以使用一些特別的限制讓我們的爬蟲表現的像一個行為良好的網絡公民,Colly添加速率限制是簡單的。
| 1 2 3 4 5 6 7 8 9 | c.Limit(&colly.LimitRule{ ????// Filter domains affected by this rule ????DomainGlob:??"godoc.org/*", ????// Set a delay between requests to these domains ????Delay: 1 * time.Second ????// Add an additional random delay ????RandomDelay: 1 * time.Second, }) 12345678 |
某些網頁可能對高流量的訪問比較挑剔,他會將你斷線。通常設置一個延遲維持幾秒中就可讓你里淘氣榜單遠一點。
從這里開始,我們能開始我們的collector通過一個URL種子。
| 1 | c.Visit("https://godoc.org") |
二、OnHTML
我們有一個好的collector他可以從任意網站開始工作,現在我們希望我們的collector做一些什么的話他需要檢查頁面以便提取鏈接和其他的數據。
colly.Collector.OnHTML方法允許注冊一個回調為當收集器達到頁面相匹配的一部分特定的HTML標簽說明符。首先,我們可以得到一個回調時當爬蟲看到[標記包含一個href鏈接。]()
| 1 2 3 4 5 6 7 | c.OnHTML("a[href]",?func(e *colly.HTMLElement) { ????// Extract the link from the anchor HTML element??? ????link := e.Attr("href") ????// Tell the collector to visit the link ????c.Visit(e.Request.AbsoluteURL(link)) }) 123456 |
就像和上面看到的一樣,在這個回調中你得到一個colly.HTMLElement它包含了匹配到的HTML的數據。
現在,我們有一個實際的網絡爬蟲的開始:我們發現頁面上的鏈接訪問,并告訴我們的collector在后續請求訪問這些鏈接。
OnHTML是一個功能強大的工具。它可以搜索CSS選擇器(即div.my_fancy_class或# someElementId),你可以連接多個OnHTML回調你的收集器處理不同類型的頁面。
Colly的HTMLElement結構非常有用。除了使用Attr函數獲得那些屬性之外,還可以提取文本。例如,我們可能想要打印頁面的標題:
| 1 2 3 4 | c.OnHTML("title",?func(e *colly.HTMLElement) { ????fmt.Println(e.Text) }) 123 |
三、OnRequest / OnResponse
有些時候你不需要一個特定的HTML元素從一個頁面,而是想知道當你的爬蟲檢索或剛剛檢索頁面。為此,Colly暴露OnRequest OnResponse回調。
所有這些回調將被調用當訪問到每個頁面的時候。至于如何在符合OnHTML的使用要求?;卣{被調用的時候有一些順序:1。OnRequest 2。OnResponse 3。OnHTML 4。OnScraped(在這邊文章中沒有提及到,但可能對你有用)
尤其使用的是OnRequest中止回調的能力。這可能是有用的,當你想讓你的collector停止。
| 1 2 3 4 | c.OnHTML("title",?func(e *colly.HTMLElement) { ????fmt.Println(e.Text) }) 123 |
在OnResponse,可以訪問整個HTML文檔,這可能是有用的在某些情況下:
| 1 2 3 4 | c.OnResponse(func(r *colly.Response) { ????fmt.Println(r.Body) }) 123 |
四、HTMLElement
除了colly.HTMLElement的Attr()方法和text,我們還可以使用它來遍歷子元素。ChildText(),ChildAttr()特別是ForEach()方法非常有用。
例如,我們可以使用ChildText()獲得所有段落的文本部分:
| 1 2 3 4 | c.OnHTML("#myCoolSection",?func(e *colly.HTMLElement) { ????fmt.Println(e.ChildText("p")) }) 123 |
我們可以使用ForEach()循環遍歷一個孩子匹配一個特定的元素選擇器:
| 1 2 3 4 5 6 7 8 | c.OnHTML("#myCoolSection",?func(e *colly.HTMLElement) { ????e.ForEach("p",?func(_ int, elem *colly.HTMLElement) { ????????if?strings.Contains(elem.Text,?"golang") { ????????????fmt.Println(elem.Text) ????????}??? ????}) }) 1234567 |
五、Bringing in Goquery
Colly的內置HTMLElement對大多數抓取任務都很有用,但是如果我們想對DOM進行特別高級的遍歷,我們就必須去別處尋找。 例如,(目前)沒有辦法將DOM遍歷到父元素或通過兄弟元素橫向遍歷。
輸入Goquery,“就像那個j-thing,只在Go中”。 它基本上是jQuery。 在Go。 (這很棒)對于你想從HTML文檔中刪除的任何內容,可以使用Goquery完成。
雖然Goquery是以jQuery為模型的,但我發現它在很多方面與BeautifulSoup API非常相似。 所以,如果你來自Python抓取世界,那么你可能會對Goquery感到滿意。
Goquery允許我們進行比Colly的HTMLElement提供的更復雜的HTML選擇和DOM遍歷。 例如,我們可能想要找到我們的錨元素的兄弟元素,以獲得我們已經抓取的鏈接的一些上下文:
| 1 2 3 4 5 | dom, _ := qoquery.NewDocument(htmlData) dom.Find("a").Siblings().Each(func(i int, s *goquery.Selection) { ????fmt.Printf("%d, Sibling text: %s\n", i, s.Text()) }) 1234 |
此外,我們可以輕松找到所選元素的父級。 如果我們從Colly給出一個錨標記,并且我們想要找到頁面
| 1 2 | anchor.ParentsUntil("~").Find("title").Text() 1 |
ParentsUntil遍歷DOM,直到找到與傳遞的選擇器匹配的東西。 我們可以使用?遍歷DOM的頂部,然后允許我們輕松獲取標題標記。
這實際上只是抓住了Goquery可以做的事情。 到目前為止,我們已經看到了DOM遍歷的示例,但Goquery也對DOM操作提供了強大的支持 - 編輯文本,添加/刪除類或屬性,插入/刪除HTML元素等。
將它帶回網絡抓取,我們如何將Goquery與Colly一起使用? 它很簡單:每個Colly HTMLElement都包含一個Goquery選項,您可以通過DOM屬性訪問它。
| 1 2 3 4 5 6 7 8 | c.OnHTML("div",?func(e *colly.HTMLElement) { ????// Goquery selection of the HTMLElement is in e.DOM ????goquerySelection := e.DOM ? ????// Example Goquery usage ????fmt.Println(qoquerySelection.Find(" span").Children().Text()) }) 1234567 |
值得注意的是,大多數抓取任務都可以以不需要使用Goquery的方式構建! 只需為html添加一個OnHTML回調,就可以通過這種方式訪問整個頁面。 但是,我仍然發現Goquery是我的DOM遍歷工具帶的一個很好的補充。
實戰項目
1. metalsucks專輯評論排名信息
-
代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | // go get github.com/PuerkitoBio/goquery // git clone? https://github.com/golang/net ? package?main ? import?( ??"fmt" ??"log" ??"net/http" ? ??"github.com/PuerkitoBio/goquery" ) ? func?main() { ??// 請求html頁面 ??res, err := http.Get("http://metalsucks.net") ??if?err != nil { ??????// 錯誤處理 ??????log.Fatal(err) ??} ??defer?res.Body.Close() ??if?res.StatusCode != 200 { ??????log.Fatalf("status code error: %d %s", res.StatusCode, res.Status) ??} ??// 加載 HTML document對象 ??doc, err := goquery.NewDocumentFromReader(res.Body) ??if?err != nil { ??????log.Fatal(err) ??} ??// Find the review items ??doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) { ??????// For each item found, get the band and title ??????band := s.Find("a").Text() ??????title := s.Find("i").Text() ??????fmt.Printf("Review %d: %s - %s\n", i, band, title) ??}) } |
-
輸出
Review?0:?Darkthrone?-?Old?Star Review?1:?Baroness?-?Gold?&?Grey Review?2:?Death?Angel?-?Humanicide Review?3:?Devin?Townsend?-?Empath Review?4:?Whitechapel?-?The?Valley
2. emojipedia表情抓取(colly + goquery)
-
代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | package?main ? import?( ??"fmt" ??"strings" ??"time" ? ??"github.com/PuerkitoBio/goquery" ??"github.com/gocolly/colly" ) ? func?main() { ??c := colly.NewCollector( ??????colly.AllowedDomains("emojipedia.org"), ??) ? ??// Callback for when a scraped page contains an article element ??c.OnHTML("article",?func(e *colly.HTMLElement) { ??????isEmojiPage := false ??// Extract meta tags from the document ??metaTags := e.DOM.ParentsUntil("~").Find("meta") ??metaTags.Each(func(_ int, s *goquery.Selection) { ??????// Search for og:type meta tags ??????property, _ := s.Attr("property") ??????if?strings.EqualFold(property,?"og:type") { ??????????content, _ := s.Attr("content") ? ??????????// Emoji pages have "article" as their og:type ??????????isEmojiPage = strings.EqualFold(content,?"article") ??????} ??}) ? ??if?isEmojiPage { ??????// Find the emoji page title ??????fmt.Println("Emoji: ", e.DOM.Find("h1").Text()) ??????// Grab all the text from the emoji's description ??????fmt.Println( ??????????"Description: ", ??????????e.DOM.Find(".description").Find("p").Text()) ??} ??}) ? ??// Callback for links on scraped pages ??c.OnHTML("a[href]",?func(e *colly.HTMLElement) { ??????// Extract the linked URL from the anchor tag ??????link := e.Attr("href") ??????// Have our crawler visit the linked URL ??????c.Visit(e.Request.AbsoluteURL(link)) ??}) ? ??c.Limit(&colly.LimitRule{ ??????DomainGlob:??"*", ??????RandomDelay: 1 * time.Second, ??}) ? ??c.OnRequest(func(r *colly.Request) { ??????fmt.Println("Visiting", r.URL.String()) ??}) ? ??c.Visit("https://emojipedia.org") } |
-
運行結果
3.?;ňW圖片爬取
- 代碼
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | // 知識點 // 1. http 的用法,返回數據的格式、編碼 // 2. 正則表達式 // 3. 文件讀寫 package?main ? import?( ????"bytes" ????"fmt" ????"io/ioutil" ????"net/http" ????"os" ????"path/filepath" ????"regexp" ????"strings" ????"sync" ????"time" ? ????"github.com/axgle/mahonia" ) ? var?workResultLock sync.WaitGroup ? func?check(e error) { ????if?e != nil { ????????panic(e) ????} } ? func?ConvertToString(src string, srcCode string, tagCode string) string { ????srcCoder := mahonia.NewDecoder(srcCode) ????srcResult := srcCoder.ConvertString(src) ????tagCoder := mahonia.NewDecoder(tagCode) ????_, cdata, _ := tagCoder.Translate([]byte(srcResult), true) ????result := string(cdata) ????return?result } ? func?download_img(request_url string, name string, dir_path string) { ????image, err := http.Get(request_url) ????check(err) ????image_byte, err := ioutil.ReadAll(image.Body) ????defer?image.Body.Close() ????file_path := filepath.Join(dir_path, name+".jpg") ????err = ioutil.WriteFile(file_path, image_byte, 0644) ????check(err) ????fmt.Println(request_url +?"\t下載成功") } ? func?spider(i int, dir_path string) { ????defer?workResultLock.Done() ????url := fmt.Sprintf("http://www.xiaohuar.com/list-1-%d.html", i) ????response, err2 := http.Get(url) ????check(err2) ????content, err3 := ioutil.ReadAll(response.Body) ????check(err3) ????defer?response.Body.Close() ????html := string(content) ????html = ConvertToString(html,?"gbk",?"utf-8") ????// fmt.Println(html) ????match := regexp.MustCompile(`<img width="210".*alt="(.*?)".*src="(.*?)"?/>`) ????matched_str := match.FindAllString(html, -1) ????for?_, match_str :=?range?matched_str { ????????var?img_url string ????????name := match.FindStringSubmatch(match_str)[1] ????????src := match.FindStringSubmatch(match_str)[2] ????????if?strings.HasPrefix(src,?"http") != true { ????????????var?buffer bytes.Buffer ????????????buffer.WriteString("http://www.xiaohuar.com") ????????????buffer.WriteString(src) ????????????img_url = buffer.String() ????????}?else?{ ????????????img_url = src ????????} ????????download_img(img_url, name, dir_path) ????} } ? func?main() { ????start := time.Now() ????dir := filepath.Dir(os.Args[0]) ????dir_path := filepath.Join(dir,?"images") ????err1 := os.MkdirAll(dir_path, os.ModePerm) ????check(err1) ????for?i := 0; i < 4; i++ { ????????workResultLock.Add(1) ????????go?spider(i, dir_path) ????} ????workResultLock.Wait() ????fmt.Println(time.Now().Sub(start)) } |
?
- 運行結果
- 下載的圖片
作者:張亞飛?
出處:https://www.cnblogs.com/zhangyafei?
gitee:https://gitee.com/zhangyafeii?
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
標簽:?Go之路
總結
以上是生活随笔為你收集整理的Go语言实战爬虫项目的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dede搜索php在哪,dede搜索页面
- 下一篇: 带电插拔损坏设备原理_Win10拔U盘不