亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Golang多線程爬蟲高效抓取大量數(shù)據(jù)的利器

 更新時間:2023年05月09日 10:28:37   作者:Luyoungs  
Golang多線程爬蟲是一種高效抓取大量數(shù)據(jù)的利器。Golang語言天生支持并發(fā)和多線程,可以輕松實現(xiàn)多線程爬蟲的開發(fā)。通過使用Golang的協(xié)程和通道,可以實現(xiàn)爬蟲的高效并發(fā)抓取、數(shù)據(jù)處理和存儲

前言

Golang 是一種并發(fā)友好的語言,使用 goroutines 和 channels 可以輕松地實現(xiàn)多線程爬蟲。具體地說,實現(xiàn)的是多協(xié)程。協(xié)程是一種比線程更輕量化的最小邏輯可運行單位,它不受操作系統(tǒng)調度,由用戶調度。因此對于協(xié)程并發(fā)的控制,有較高的要求。

goroutine(Go 協(xié)程)

Go 協(xié)程(Goroutine)是與其他函數(shù)同時運行的函數(shù)??梢哉J為 Go 協(xié)程是輕量級的線程,由 Go 運行時來管理。在函數(shù)調用前加上 go 關鍵字,這次調用就會在一個新的 goroutine 中并發(fā)執(zhí)行。當被調用的函數(shù)返回時,這個 goroutine 也自動結束。

比如:

func son() {
	for {
		fmt.Printf("son says:hello, world!\n")
		time.Sleep(time.Second)
	}
}
func father() {
	go son()
	for {
		fmt.Printf("father says:你好,世界!\n")
		time.Sleep(time.Second)
	}
}
func main() {
	father()
}

運行結果:

father says:你好,世界!

son says:hello, world!

son says:hello, world!

father says:你好,世界!

father says:你好,世界!

son says:hello, world!

在這個例子中,main() 函數(shù)里面執(zhí)行 father(),而 father()中又開啟了一個協(xié)程 son(),之后兩個死循環(huán)分別執(zhí)行。

當然,如果主協(xié)程運行結束時子協(xié)程還沒結束,那么就會被 kill 掉。需要注意的是,如果這個函數(shù)有返回值,那么這個返回值會被丟棄。

func main() {
	go loop()
	fmt.Println("hello,world!")
}
func loop() {
	for i := 0; i < 10000; i++ {
		fmt.Println(i)
	}
}

運行結果:

hello,world!

0

1

可以看到,子協(xié)程剛打印了 0、1 之后 main()函數(shù)就結束了,這顯然不符合我們的預期。我們有多種方法來解決這個問題。比如在主協(xié)程里面 sleep(),或者使用 channel、waitGroup 等。

Go 協(xié)程(Goroutine)之間通過信道(channel)進行通信,簡單的說就是多個協(xié)程之間通信的管道。信道可以防止多個協(xié)程訪問共享內存時發(fā)生資源爭搶的問題。Go 中的 channel 是 goroutine 之間的通信機制。這就是為什么我們之前說過 Go 實現(xiàn)并發(fā)的方式是:“不是通過共享內存通信,而是通過通信共享內存。”

比如:

var (
	myMap = make(map[int]int, 10)
)
// 計算n!并放入到map里
func operation(n int) {
	res := 1
	for i := 1; i <= n; i++ {
		res *= i
	}
	myMap[n] = res
}
func Test3() {
	//我們開啟多個協(xié)程去完成這個任務
	for i := 1; i <= 200; i++ {
		go operation(i)
	}
	time.Sleep(time.Second * 10)
	fmt.Println(myMap)
}
func main() {
	Test3()
}

運行結果:

fatal error: concurrent map writes

goroutine 42 [running]:

這里產(chǎn)生了一個 fatal error,因為我們創(chuàng)建的 myMap 不支持同時訪問,這有點像 Java 里面的非線程安全概念。因此我們需要一種“線程安全”的數(shù)據(jù)結構,就是 Go 中的channel。

channel(通道)

channel 分為無緩沖和有緩沖的。無緩沖是同步的,例如make(chan int),就是一個送信人去你家門口送信,你不在家他不走,你一定要接下信,他才會走,無緩沖保證信能到你手上。 有緩沖是異步的,例如make(chan int, 1),就是一個送信人去你家仍到你家的信箱,轉身就走,除非你的信箱滿了,他必須等信箱空下來,有緩沖的保證信能進你家的郵箱。

換句話說,有緩存的channel使用環(huán)形數(shù)組實現(xiàn),當緩存未滿時,向channel發(fā)送消息不會阻塞,當緩存滿時,發(fā)送操作會阻塞,直到其他goroutine從channel中讀取消息;同理,當channel中消息不為空時,讀取消息不會阻塞,當channel為空時,讀取操作會阻塞,直至其他goroutine向channel發(fā)送消息。

// 非緩存channel
ch := make(chan int)
// 緩存channel
bch := make(chan int, 2)

channel和map類似,make創(chuàng)建了底層數(shù)據(jù)結構的引用,當賦值或參數(shù)傳遞時,只是拷貝了一個channel的引用,其指向同一channel對象,與其引用類型一樣,channel的空值也為nil。使用==可以對類型相同的channel進行比較,只有指向相同對象或同為nil時,結果為true。

channel 的初始化

channel在使用前,需要初始化,否則永遠阻塞。

ch := make(chan int)
ch <- x
y <- ch

channel的關閉

golang提供了內置的close函數(shù),對channel進行關閉操作。

// 初始化channel
ch := make(chan int)
// 關閉channel ch
close(ch)

關于channel的關閉,需要注意以下事項:

  • 關閉未初始化的channle(nil)會panic
  • 重復關閉同一channel會panic
  • 向以關閉channel發(fā)送消息會panic
  • 從已關閉channel讀取數(shù)據(jù),不會panic,若存在數(shù)據(jù),則可以讀出未被讀取的消息,若已被讀出,則獲取的數(shù)據(jù)為零值,可以通過ok-idiom的方式,判斷channel是否關閉
  • channel的關閉操作,會產(chǎn)生廣播消息,所有向channel讀取消息的goroutine都會接受到消息

waitGroup 的使用

正常情況下,新激活的goroutine的結束過程是不可控制的,唯一可以保證終止goroutine的行為是main goroutine的終止。

也就是說,我們并不知道哪個goroutine什么時候結束。

但很多情況下,我們正需要知道goroutine是否完成。這需要借助sync包的WaitGroup來實現(xiàn)。

WatiGroup是sync包中的一個struct類型,用來收集需要等待執(zhí)行完成的goroutine。下面是它的定義:

type WaitGroup struct {
        // Has unexported fields.
}
    A WaitGroup waits for a collection of goroutines to finish. The main
    goroutine calls Add to set the number of goroutines to wait for. Then each
    of the goroutines runs and calls Done when finished. At the same time, Wait
    can be used to block until all goroutines have finished.
A WaitGroup must not be copied after first use.
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()

waitGroup有三個方法:

  • Add():每次激活想要被等待完成的goroutine之前,先調用Add(),用來設置或添加要等待完成的goroutine數(shù)量。例如Add(2)或者兩次調用Add(1)都會設置等待計數(shù)器的值為2,表示要等待2個goroutine完成
  • Done():每次需要等待的goroutine在真正完成之前,應該調用該方法來人為表示goroutine完成了,該方法會對等待計數(shù)器減1。
  • Wait():在等待計數(shù)器減為0之前,Wait()會一直阻塞當前的goroutine也就是說,Add()用來增加要等待的goroutine的數(shù)量,Done()用來表示goroutine已經(jīng)完成了,減少一次計數(shù)器,Wait()用來等待所有需要等待的goroutine完成。

比如:

var wg sync.WaitGroup // 創(chuàng)建同步等待組對象
func main() {
	//設置等待組中,要執(zhí)行的goroutine的數(shù)量
	wg.Add(2)
	go fun1()
	go fun2()
	fmt.Println("main進入阻塞狀態(tài),等待wg中的子goroutine結束")
	wg.Wait() //表示main goroutine進入等待,意味著阻塞
	fmt.Println("main解除阻塞")
}
func fun1() {
	for i := 1; i <= 10; i++ {
		fmt.Println("fun1.i:", i)
	}
	wg.Done() //給wg等待中的執(zhí)行的goroutine數(shù)量減1.同Add(-1)
}
func fun2() {
	defer wg.Done()
	for j := 1; j <= 10; j++ {
		fmt.Println("\tfun2.j,", j)
	}
}

運行結果:

main進入阻塞狀態(tài),等待wg中的子goroutine結束 fun1.i: 1

fun2.j, 1

fun2.j, 2

fun2.j, 3

fun2.j, 4

fun2.j, 5

fun1.i: 2

fun1.i: 3

fun1.i: 4

main解除阻塞

可以看到起到了很好的控制效果。

如果用第一個例子來說明,效果更好:

func main() {
	var wg sync.WaitGroup // 創(chuàng)建同步等待組對象
	wg.Add(1)
	go loop(&wg)
	wg.Wait()
	fmt.Println("hello,world!")
}
func loop(wg *sync.WaitGroup) {
	defer wg.Done()
	for i := 0; i < 100; i++ {
		fmt.Println(i)
	}
}

運行結果:

0

1

99

hello,world!

爬蟲

爬蟲的功能是爬取豆瓣top250的電影的數(shù)據(jù),并將爬到的數(shù)據(jù)永久花存儲。

思路是首先爬取所有的鏈接,這個鏈接的提取通過10 個并行的goroutine處理,然后存儲到 channel 中。然后立即創(chuàng)建 250個 goroutine,每一個協(xié)程分別爬取一個鏈接。 再將爬到的數(shù)據(jù)存儲到本地。

爬蟲配置

type SpiderConfig struct {
	InitialURL   string // 初始 URL
	MaxDepth     int    // 最大深度
	MaxGoroutine int    // 最大并發(fā)數(shù)
}

爬蟲數(shù)據(jù)

type SpiderData struct {
	URL       string // 鏈接
	FilmName  string // 電影名
	Director  string // 導員
	Actors    Actor  // 演員列表
	Year      string // 年份
	Score     string // 評分
	Introduce string // 簡介
}
type Actor struct {
	actor1 string
	actor2 string
	actor3 string
	actor4 string
	actor5 string
	actor6 string
}

開啟并行

func spider(config SpiderConfig, chLinks chan string, wg *sync.WaitGroup) {
	for i := 0; i < 10; i++ {
		fmt.Println("正在爬取第", i, "個信息")
		go Spider(strconv.Itoa(i*25), chLinks, wg)
	}
}

爬取某個鏈接

func Spider(page string, chLinks chan string, wg *sync.WaitGroup) {
	// client
	client := http.Client{}
	URL := "https://movie.douban.com/top250?start=" + page + "&filter="
	req, err := http.NewRequest("GET", URL, nil)
	if err != nil {
		fmt.Println("req err", err)
	}
	// UA偽造,明顯比 python復雜得多
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
	req.Header.Set("Sec-Fetch-Site", "same-origin")
	req.Header.Set("Sec-Fetch-Mode", "navigate")
	req.Header.Set("Sec-Fetch-User", "?1")
	req.Header.Set("Sec-Fetch-Dest", "document")
	req.Header.Set("Referer", "https://movie.douban.com/chart")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("resp err", err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
			fmt.Println("close err", err)
		}
	}(resp.Body)
	// 網(wǎng)頁解析
	docDetail, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		fmt.Println("解析失?。?, err)
	}
	// 選擇器
	// #content > div > div.article > ol > li:nth-child(1) > div > div.info > div.hd > a
	title := docDetail.Find("#content > div > div.article > ol > li").
		Each(func(i int, s *goquery.Selection) { // 繼續(xù)找
			link := s.Find("div > div.pic > a")
			linkTemp, OK := link.Attr("href")
			if OK {
				chLinks <- linkTemp
			}
		})
	title.Text()
	wg.Done()
}

爬取某個鏈接的電影數(shù)據(jù)

func crawl(url string, wg *sync.WaitGroup) {
	client := http.Client{}
	URL := url
	req, err := http.NewRequest("GET", URL, nil)
	if err != nil {
		fmt.Println("req err", err)
	}
	// UA偽造,明顯比 python復雜得多
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Cache-Control", "max-age=0")
	req.Header.Set("sec-ch-ua-mobile", "?0")
	req.Header.Set("Upgrade-Insecure-Requests", "1")
	req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
	req.Header.Set("Sec-Fetch-Site", "same-origin")
	req.Header.Set("Sec-Fetch-Mode", "navigate")
	req.Header.Set("Sec-Fetch-User", "?1")
	req.Header.Set("Sec-Fetch-Dest", "document")
	req.Header.Set("Referer", "https://movie.douban.com/chart")
	req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("resp err", err)
	}
	defer func(Body io.ReadCloser) {
		err := Body.Close()
		if err != nil {
		}
	}(resp.Body)
	// 網(wǎng)頁解析
	docDatail, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		fmt.Println("解析失??!", err)
	}
	var data SpiderData
	// 選擇器
	// #content > h1 > span:nth-child(1)	movie_name
	movie_name := docDatail.Find("#content > h1 > span:nth-child(1)").Text()
	// #info > span:nth-child(1) > span.attrs > a	director
	director := docDatail.Find("#info > span:nth-child(1) > span.attrs > a").Text()
	// #info > span.actor > span.attrs > span:nth-child(1) > a
	actor01, OK1 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(1) > a").Attr("hel")
	if OK1 {
	}
	actor02 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(2) > a").Text()
	actor03 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(3) > a").Text()
	actor04 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(4) > a").Text()
	actor05 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(5) > a").Text()
	actor06 := docDatail.Find("#info > span.actor > span.attrs > span:nth-child(6) > a").Text()
	// #content > h1 > span.year
	year := docDatail.Find("#content > h1 > span.year").Text()
	// #interest_sectl > div.rating_wrap.clearbox > div.rating_self.clearfix > strong
	score := docDatail.Find("#interest_sectl > div.rating_wrap.clearbox > div.rating_self.clearfix > strong").Text()
	//#link-report-intra > span.all.hidden
	introduce := docDatail.Find("#link-report-intra > span.all.hidden").Text()
	data.URL = URL
	data.FilmName = movie_name
	data.Director = director
	data.Actors.actor1 = actor01
	data.Actors.actor2 = actor02
	data.Actors.actor3 = actor03
	data.Actors.actor4 = actor04
	data.Actors.actor5 = actor05
	data.Actors.actor6 = actor06
	data.Year = year
	data.Score = score
	data.Introduce = introduce
	result := data2string(data)
	filename := strconv.Itoa(rand.Int()) + ".txt"
	f, err := os.Create(filename)
	if err != nil {
		fmt.Println(err)
	}
	_, err = f.Write([]byte(result))
	if err != nil {
		return
	}
	err = f.Close()
	if err != nil {
		return
	}
	defer wg.Done()
}
func data2string(data SpiderData) string {
	result := data.FilmName + data.Score + data.Director + data.Year + data.Introduce
	return result
}

main 函數(shù)開啟爬蟲

func main() {
	// 先爬取初始URL,將爬來的 links放在 chan中
	// 定義一個 chLinks,這里面將會放置 250 個links
	chLinks := make(chan string, 1000) // 有緩沖的 chan
	config := SpiderConfig{
		InitialURL:   "https://movie.douban.com/top250",
		MaxDepth:     1,
		MaxGoroutine: 10,
	}
	wg := sync.WaitGroup{}
	wg.Add(10)
	spider(config, chLinks, &wg) // 主線程,并發(fā)爬取所有的 href
	wg.Wait()
	//for i := 0; i < 250; i++ {
	//	fmt.Println(i, <-chLinks)
	//}
	// 爬完了所有的鏈接,250 個鏈接放在 chLinks
	//建立250 個協(xié)程來爬每個鏈接
	wg.Add(250)
	for i := 0; i < 250; i++ {
		go crawl(<-chLinks, &wg)
	}
	wg.Wait()
}

爬取結果:

總結

本文實現(xiàn)了一個普通的多線(協(xié))程爬蟲,用來爬去某些數(shù)據(jù)。缺點是并沒有用到并發(fā)深度的功能,因為爬取的數(shù)據(jù)結構不一樣,因此本嘗試并不是一個很好的練手項目。

還可以改進的是可以在爬到連接之后,立即對該鏈接進行開啟協(xié)程爬取,本文是爬完之后才開始的。

到此這篇關于Golang多線程爬蟲高效抓取大量數(shù)據(jù)的利器的文章就介紹到這了,更多相關Golang多線程爬蟲內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • GoLang中panic與recover函數(shù)以及defer語句超詳細講解

    GoLang中panic與recover函數(shù)以及defer語句超詳細講解

    這篇文章主要介紹了GoLang的panic、recover函數(shù),以及defer語句,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2023-01-01
  • golang值類型轉換成[]uint8類型的操作

    golang值類型轉換成[]uint8類型的操作

    這篇文章主要介紹了golang值類型轉換成[]uint8類型的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Go語言流程控制語句

    Go語言流程控制語句

    這篇文章介紹了Go語言流程控制語句的用法,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • Golang工作池的使用實例講解

    Golang工作池的使用實例講解

    我們使用Go語言開發(fā)項目,常常會使用到goroutine;goroutine太多會造成系統(tǒng)占用過高或其他系統(tǒng)異常,我們可以將goroutine控制指定數(shù)量,且減少goroutine的創(chuàng)建,這就運用到Go工作池,下面就介紹和使用一下
    2023-02-02
  • 詳解Go中gin框架如何實現(xiàn)帶顏色日志

    詳解Go中gin框架如何實現(xiàn)帶顏色日志

    當我們在終端上(比如Goland)運行gin框架搭建的服務時,會發(fā)現(xiàn)輸出的日志是可以帶顏色的,那這是如何實現(xiàn)的呢?本文就來和大家簡單講講
    2023-04-04
  • 使用Go語言生成二維碼并在命令行中輸出

    使用Go語言生成二維碼并在命令行中輸出

    二維碼(QR code)是一種矩陣條碼的標準,廣泛應用于商業(yè)、移動支付和數(shù)據(jù)存儲等領域,在開發(fā)過程中,我們可能需要在命令行中顯示二維碼,這可以幫助我們快速生成和分享二維碼信息,本文將介紹如何使用Go語言生成二維碼并在命令行中輸出,需要的朋友可以參考下
    2023-11-11
  • go local history本地歷史恢復代碼神器

    go local history本地歷史恢復代碼神器

    這篇文章主要為大家介紹了go local history本地歷史恢復代碼神器的使用功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • Golang中panic的異常處理

    Golang中panic的異常處理

    本文主要介紹了Golang中panic的異常處理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • 對Golang中的runtime.Caller使用說明

    對Golang中的runtime.Caller使用說明

    這篇文章主要介紹了對Golang中的runtime.Caller使用說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 通過與Java功能上的對比來學習Go語言

    通過與Java功能上的對比來學習Go語言

    這篇文章主要介紹了通過與Java功能上的對比來學習Go語言的相關資料,需要的朋友可以參考下
    2023-02-02

最新評論