Golang实现网页抓取与浏览器自动化速度优化

Golang实现网页抓取与浏览器自动化速度优化 我想使用Go进行网页抓取和Web应用自动化。

如果我希望我的Go程序在某个网站上执行职位搜索,结果是分布在40个页面上的3000条结果,那么让程序在职位搜索页面输入搜索词和地点,点击每个职位发布并抓取该职位的页面,对页面上的所有职位都执行此操作,当到达页面底部时,点击下一页并重复相同的过程,直到到达结果中的最后一页(在本例中是第40页),这对于程序来说工作量会太大吗?

一个人手动打开40个页面上的3000个职位发布中的每一个都需要一段时间,我不确定网页抓取和Web自动化程序做这件事能有多快。我认为网页抓取器会很快并且不会占用太多内存,但对于Web驱动程序,我不确定它执行上述操作是否会太慢?


更多关于Golang实现网页抓取与浏览器自动化速度优化的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好,你试过看看 https://github.com/gocolly/colly 吗?听起来这正是你要找的。

更多关于Golang实现网页抓取与浏览器自动化速度优化的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


对于网页抓取和浏览器自动化,Go语言可以通过不同的策略来优化速度。以下是具体的技术方案和示例代码:

1. 并发抓取优化

使用colly框架进行高效并发抓取,配合适当的延迟控制:

package main

import (
    "fmt"
    "sync"
    "time"
    "github.com/gocolly/colly"
)

func main() {
    c := colly.NewCollector(
        colly.Async(true),
        colly.UserAgent("Mozilla/5.0"),
    )
    
    c.Limit(&colly.LimitRule{
        DomainGlob:  "*",
        Parallelism: 10,
        Delay:       500 * time.Millisecond,
    })
    
    var wg sync.WaitGroup
    jobCount := 0
    mutex := &sync.Mutex{}
    
    c.OnHTML(".job-listing", func(e *colly.HTMLElement) {
        wg.Add(1)
        go func() {
            defer wg.Done()
            
            jobURL := e.Attr("href")
            c.Visit(jobURL)
        }()
    })
    
    c.OnHTML(".job-details", func(e *colly.HTMLElement) {
        mutex.Lock()
        jobCount++
        fmt.Printf("抓取第%d个职位\n", jobCount)
        mutex.Unlock()
    })
    
    for page := 1; page <= 40; page++ {
        url := fmt.Sprintf("https://example.com/jobs?page=%d", page)
        c.Visit(url)
    }
    
    c.Wait()
    wg.Wait()
}

2. 无头浏览器优化

使用chromedp进行浏览器自动化,通过并行处理多个标签页:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
    "github.com/chromedp/chromedp"
)

func scrapeJob(url string, wg *sync.WaitGroup) {
    defer wg.Done()
    
    ctx, cancel := chromedp.NewContext(context.Background())
    defer cancel()
    
    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
    defer cancel()
    
    var jobTitle, jobDescription string
    err := chromedp.Run(ctx,
        chromedp.Navigate(url),
        chromedp.WaitVisible(`.job-content`),
        chromedp.Text(`h1.job-title`, &jobTitle),
        chromedp.Text(`.job-description`, &jobDescription),
    )
    
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        return
    }
    
    fmt.Printf("职位: %s\n描述: %s\n", jobTitle, jobDescription)
}

func main() {
    var wg sync.WaitGroup
    maxConcurrent := 5
    semaphore := make(chan struct{}, maxConcurrent)
    
    jobURLs := []string{
        "https://example.com/job/1",
        "https://example.com/job/2",
        // 添加更多URL
    }
    
    for _, url := range jobURLs {
        wg.Add(1)
        semaphore <- struct{}{}
        
        go func(u string) {
            defer func() { <-semaphore }()
            scrapeJob(u, &wg)
        }(url)
    }
    
    wg.Wait()
}

3. 混合方案:API探测+直接抓取

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
    "time"
)

type Job struct {
    ID    string `json:"id"`
    Title string `json:"title"`
    URL   string `json:"url"`
}

func fetchJobListings(page int) ([]Job, error) {
    url := fmt.Sprintf("https://api.example.com/jobs?page=%d", page)
    
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    
    var jobs []Job
    json.Unmarshal(body, &jobs)
    return jobs, nil
}

func main() {
    var wg sync.WaitGroup
    results := make(chan Job, 100)
    
    for page := 1; page <= 40; page++ {
        wg.Add(1)
        go func(p int) {
            defer wg.Done()
            
            jobs, err := fetchJobListings(p)
            if err != nil {
                fmt.Printf("页面%d错误: %v\n", p, err)
                return
            }
            
            for _, job := range jobs {
                results <- job
            }
        }(page)
    }
    
    go func() {
        wg.Wait()
        close(results)
    }()
    
    for job := range results {
        fmt.Printf("获取职位: %s - %s\n", job.ID, job.Title)
    }
}

4. 性能优化配置

// 自定义HTTP客户端配置
func createOptimizedClient() *http.Client {
    return &http.Client{
        Transport: &http.Transport{
            MaxIdleConns:        100,
            MaxIdleConnsPerHost: 10,
            IdleConnTimeout:     90 * time.Second,
            TLSHandshakeTimeout: 10 * time.Second,
        },
        Timeout: 30 * time.Second,
    }
}

// 内存池优化
var bufferPool = sync.Pool{
    New: func() interface{} {
        return make([]byte, 32*1024)
    },
}

执行时间估算:

  • 纯抓取方案:使用10个并发,每页延迟500ms,约需:40页 × 0.5秒 = 20秒基础时间 + 3000个详情页抓取 ≈ 5-10分钟
  • 浏览器自动化:使用5个并行Chrome实例,每个页面加载3秒,约需:3000个页面 ÷ 5并发 × 3秒 = 1800秒 ≈ 30分钟

建议方案:

  1. 首先尝试直接HTTP请求抓取(方案1)
  2. 如果遇到JavaScript渲染内容,使用无头浏览器(方案2)
  3. 对于3000个职位,建议采用混合方案,优先使用API接口,必要时使用浏览器自动化

这些方案在16GB内存的机器上处理3000个职位发布完全可行,内存占用通常在500MB-2GB之间,具体取决于并发级别和页面复杂度。

回到顶部