1.goquery 爬蟲庫
goquery的選擇器功能很強大,很好用。
go get github.com/PuerkitoBio/goquery
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
"github.com/PuerkitoBio/goquery"
)
func fetch(url string) *goquery.Document {
fmt.Println("Fetch Url", url)
client := &http.Client{}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
resp, err := client.Do(req)
if err != nil {
log.Fatal("Http get err:", err)
}
if resp.StatusCode != 200 {
log.Fatal("Http status code:", resp.StatusCode)
}
defer resp.Body.Close()
//將html body轉換成Document
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
log.Fatal(err)
}
return doc
}
func parseUrls(url string, ch chan bool) {
doc := fetch(url)
doc.Find("ol.grid_view li").Find(".hd").Each(func(index int, ele *goquery.Selection) {
movieUrl, _ := ele.Find("a").Attr("href")
fmt.Println(ele.Find(".title").Eq(0).Text(),movieUrl)
})
time.Sleep(2 * time.Second)
ch <- true
}
func main() {
start := time.Now()
ch := make(chan bool)
for i := 0; i < 10; i++ {
//異步開啓線程
go parseUrls("https://movie.douban.com/top250?start="+strconv.Itoa(25*i), ch)
}
for i := 0; i < 10; i++ {
data :=<-ch
fmt.Println("data:",data)
}
elapsed := time.Since(start)
fmt.Printf("Took %s", elapsed)
}
2. 併發通道
chan 可以理解爲隊列,遵循先進先出的規則。
3. defer 函數
defer 函數大家肯定都用過,它在聲明時不會立刻去執行,而是在函數 return 後去執行的。 它的主要應用場景有異常處理、記錄日誌、清理數據、釋放資源 等等。
package main
import (
"fmt"
)
func main() {
//defer 函數大家肯定都用過,它在聲明時不會立刻去執行,而是在函數 return 後去執行的。
//defer 函數定義的順序 與 實際執的行順序是相反的,也就是最先聲明的最後才執行。
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
fmt.Println("main")
}
4.常用併發操作
package goroutinue
import (
"context"
"errors"
"fmt"
"sync"
"time"
)
// 超時檢測機制
func GetWithTimeout(d time.Duration) (string, error) {
ret := make(chan string)
go func() {
//do something
time.Sleep(d)
ret <- "Hello"
}()
select {
case result := <-ret:
return result, nil
case <-time.After(time.Millisecond * 100):
return "", errors.New("time out")
}
}
// 當我們啓動多個任務時,如果有一個任務完成之後即可返回
// 例如:我們通過關鍵字去百度和谷歌搜索
// 當其中任意一個先拿到結果之後,我們就直接返回那一個
func GetOne() string {
ret := make(chan string)
for i := 0; i < 10; i++ {
go func(i int) {
ret <- fmt.Sprintf("%d : I get the result", i)
}(i)
}
//當ret中一有結果,就直接返回了
return <-ret
}
// 當所有任務執行完成之後,纔將結果返回
func GetAll() string {
//開啓10個線程
ret := make(chan string, 10)
for i := 0; i < 10; i++ {
go func(i int) {
ret <- fmt.Sprintf("%d : I get the result \n", i)
}(i)
}
// 當所有結果拿到之後,組裝結果,返回
result := ""
for i := 0; i < 10; i++ {
result += <-ret
}
return result
}
// 使用watiGroup實現獲取所有結果之後再返回
func GetAllWithGroup() string {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
fmt.Println(i,": I get the result ")
wg.Done()
}(i)
}
wg.Wait()
result := ""
return result
}
// 取消任務
func CancelTask() {
//取消任務標識通道
cancel := make(chan struct{})
for i := 0; i < 5; i++ {
go func(i int, cancel chan struct{}) {
for {
if isCancel(cancel) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, ":Canceled")
}(i, cancel)
}
//當關閉通道,那麼所有的goroutine都可以監聽到
//如果僅僅是往這個通道發信息,那麼只會有一個goroutine被取消
close(cancel)
time.Sleep(time.Millisecond * 20)
}
// 判斷通道是否取消
func isCancel(cancel chan struct{}) bool {
select {
case <-cancel:
return true
default:
return false
}
}
// 使用上下文取消全部任務(一般這些任務分上下級的)
// 一個goroutine中又啓動了其他的goroutine
func CancelAllTask() {
ctx, cancelFunc := context.WithCancel(context.Background())
for i := 0; i < 5; i++ {
go func(i int, ctx context.Context) {
for {
if isCancelFunc(ctx) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, ":Canceled")
}(i, ctx)
}
//取消全部任務
cancelFunc()
time.Sleep(time.Millisecond * 20)
}
func isCancelFunc(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}