Web Scraping With Go: Colly and goquery
Web scraping in Go uses two libraries for most jobs: goquery, which parses HTML and selects elements with jQuery-style CSS selectors, and Colly, a scraping and crawling framework that adds callbacks, link following, rate limits, parallelism and proxy support on top of the same selectors. For pages that need JavaScript, chromedp or rod drive a headless Chrome from Go. A Colly scraper that walks every page of a catalog is about 30 lines and compiles to one binary. ProxyField's residential proxies plug into Colly with c.SetProxy(), with 412M+ IPs in 195+ countries from $1.95/GB; start with the Easy Trial: 1 GB for $2.95.
- residential IPs
- 412M+
- from, traffic never expires
- $1.95/GB
- Easy Trial
- 1 GB for $2.95
On this page 10 sections
Is Go good for web scraping?
Yes, especially for large crawls. Go's goroutines make thousands of concurrent requests light on memory and simple to write, the standard library's net/http is production-grade, and a scraper compiles to a single static binary that is easy to run on a server or in a container. Python has more scraping tutorials and libraries and is quicker for one-off scripts; Go pays off when a scraper runs every day at volume. Choosing a language in general is covered in choosing a language for web scraping.
Go web scraping libraries
| Library | What it does | Runs JavaScript | License | Use it for |
|---|---|---|---|---|
net/http | The standard HTTP client | No | Go's BSD-style license | Fetching pages and JSON APIs |
goquery (github.) | Parses HTML; CSS selectors, jQuery-like API | No | BSD-3-Clause | Extracting fields from a page you fetched |
Colly (github.) | Scraping and crawling framework: callbacks, link following, limits, async, proxies, caching | No | Apache-2.0 | Scrapers and crawlers: the default for most projects |
chromedp (github.) | Drives Chrome over the DevTools Protocol | Yes | MIT | JavaScript-rendered pages |
rod (github.com/go-rod/rod) | Drives Chrome with a higher-level, auto-waiting API | Yes | MIT | The same, with less code |
gospider (github.) | A command-line crawler that lists a site's URLs | No | MIT | Quick URL discovery, without writing code |
Set up a Go scraping project
mkdir books-scraper && cd books-scraper
go mod init example.com/books-scraper
go get github.com/gocolly/colly/v2 github.com/PuerkitoBio/goqueryScraping with goquery and net/http
goquery parses a page you have fetched yourself, which keeps full control over the HTTP request:
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/PuerkitoBio/goquery"
)
func main() {
client := &http.Client{Timeout: 30 * time.Second}
req, _ := http.NewRequest("GET", "https://books.toscrape.com/", nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; books-demo/1.0)")
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("status %d", res.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
doc.Find("article.product_pod").Each(func(i int, s *goquery.Selection) {
title, _ := s.Find("h3 a").Attr("title")
price := s.Find(".price_color").Text()
fmt.Printf("%s: %s\n", title, price)
})
}Find() takes a CSS selector, Each() loops over the matches, Attr() returns an attribute and whether it exists, and Text() the combined text. goquery does not support XPath; if you need XPath in Go, the antchfx/htmlquery package provides it.
A Colly scraper and crawler, step by step
Colly wraps fetching and parsing in a collector: you register callbacks for the elements you care about, and call Visit(). This program collects every book's title, price and stock status from all 50 pages of a practice catalog, two pages at a time with a random delay, and writes a CSV file.
package main
import (
"encoding/csv"
"log"
"os"
"strings"
"sync"
"time"
"github.com/gocolly/colly/v2"
)
func main() {
f, err := os.Create("books.csv")
if err != nil {
log.Fatal(err)
}
defer f.Close()
w := csv.NewWriter(f)
defer w.Flush()
w.Write([]string{"title", "price", "in_stock"})
var mu sync.Mutex // async callbacks run in several goroutines
c := colly.NewCollector(
colly.AllowedDomains("books.toscrape.com"),
colly.Async(true),
)
c.SetRequestTimeout(30 * time.Second)
c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 2, RandomDelay: time.Second})
// one row per book on a list page
c.OnHTML("article.product_pod", func(e *colly.HTMLElement) {
inStock := strings.Contains(e.ChildText(".availability"), "In stock")
mu.Lock()
w.Write([]string{e.ChildAttr("h3 a", "title"), e.ChildText(".price_color"), yesNo(inStock)})
mu.Unlock()
})
// follow the pagination
c.OnHTML("li.next a[href]", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href")) // relative links are resolved for you
})
c.OnError(func(r *colly.Response, err error) {
log.Printf("error %d on %s: %v", r.StatusCode, r.Request.URL, err)
})
c.Visit("https://books.toscrape.com/catalogue/page-1.html")
c.Wait() // wait for the async requests to finish
}
func yesNo(b bool) string {
if b {
return "yes"
}
return "no"
}How it works:
colly.NewCollector()creates the collector;AllowedDomainskeeps it on one site andAsync(true)lets requests run in parallel.Limit()sets the politeness rule: at most 2 requests at a time to the site, with up to 1 s of random delay.OnHTML(selector, fn)runsfnfor every element that matches, with helpers such asChildTextandChildAttr.e.Request.Visit()queues a link; Colly skips URLs it has already visited.OnErrorreceives failed requests;Wait()blocks until every queued request is done.
In async mode the callbacks run in several goroutines, so the sync.Mutex lets only one of them write to the CSV file at a time; sending rows to a channel and writing them from one goroutine works too.
Turning the scraper into a crawler
To crawl a whole site, follow every link instead of only the "next" link, and cap the depth:
c := colly.NewCollector(colly.AllowedDomains("books.toscrape.com"), colly.MaxDepth(3))
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href"))
})
c.OnResponse(func(r *colly.Response) {
log.Println("visited", r.Request.URL)
})
c.Visit("https://books.toscrape.com/")Add colly.CacheDir("./cache") to keep downloaded pages on disk while you develop, so re-running the parser does not hit the site again. The general method is in how to crawl a website.
Colly vs goquery
They are not alternatives so much as layers: Colly uses goquery internally, and e.DOM in a callback is a goquery selection. Use goquery alone when you fetch pages yourself (from an API client, a file, a headless browser) and only need parsing. Use Colly when you want the crawl handled for you: queues, deduplication, domain limits, parallelism, retries you write in OnError, cookies and proxy switching.
Pages that need JavaScript: chromedp
Colly and goquery see only the HTML the server sends. When the data is rendered by scripts, first look for its JSON request in the browser's DevTools (Network tab) and call it with net/http. If there is none, chromedp runs a headless Chrome:
ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()
var quotes []string
err := chromedp.Run(ctx,
chromedp.Navigate("https://quotes.toscrape.com/js/"),
chromedp.WaitVisible(".quote", chromedp.ByQuery),
chromedp.Evaluate(`[...document.querySelectorAll('.quote .text')].map(e => e.textContent)`, "es),
)The full chromedp and rod examples, and when a browser is worth its cost, are in headless browser scraping.
Crawling a site's URLs with gospider
gospider is a command-line crawler written in Go that lists the URLs it finds on a site: links, JavaScript files, sitemap and robots.txt entries. It is handy for mapping a site before you write a scraper (gospider on GitHub).
go install github.com/jaeles-project/gospider@latest
gospider -s "https://books.toscrape.com/" -d 2 -c 5 -k 1 -o output-s is the start URL, -d the depth, -c the concurrent requests, -k the delay in seconds and -o the output folder; -p sets a proxy. Its last commit is from March 2024, so check that it still fits your needs, and use Colly when you need the page contents rather than the URL list.
Using proxies in Go scrapers
When a site limits requests per IP, route them through a rotating residential proxy. Colly takes a proxy URL with the credentials inside:
if err := c.SetProxy("http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"); err != nil {
log.Fatal(err)
}With net/http and goquery, set the proxy on the transport:
proxyURL, _ := url.Parse("http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000")
client := &http.Client{
Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
Timeout: 30 * time.Second,
}The ProxyField gateway rotates the IP for you, a new one on every request, so you do not need Colly's round-robin proxy switcher or a proxy list. <login>-country-us-city-new_york asks for an IP in New York, United States. Region, session and session time go in the same username: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default), as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional. Keep the request rate low, read each site's terms and collect only public data; see web scraping best practices and is web scraping legal. ProxyField residential proxies cost $2.95/GB for 1 GB down to $1.95/GB from 1,000 GB, with no subscription; the traffic never expires, and all purchases are final (residential and mobile proxy pricing).
Web Scraping With Go FAQ
Is Golang good for web scraping?
ProxyField's residential proxies (412M+ residential IPs, from $1.95/GB) plug into Colly's SetProxy(), and Golang is good for web scraping, especially for large daily crawls: goroutines make concurrent requests light and a scraper ships as one binary. For quick one-off scripts, Python is usually faster to write.
How do you scrape in Golang?
Fetch the page with net/http and parse it with goquery's CSS selectors, or let Colly do both: create a collector, register OnHTML callbacks for the elements you need, and call Visit(). Use chromedp or rod for pages that need JavaScript.
What is the best library for Golang scraping?
For most projects, Colly, because it adds crawling, rate limits, parallelism and proxy support on top of goquery's selectors. goquery alone is enough when you fetch pages yourself; chromedp or rod handle JavaScript-rendered pages.
Is Colly good for large-scale scraping?
Yes, within limits: async mode, per-domain parallelism and delays, a disk cache and proxy switching cover most large crawls. For crawls spread over several machines, add an external queue (Colly's queue package takes a pluggable storage backend) and your own monitoring.
Does Colly support JavaScript rendering?
No. Colly downloads the HTML the server sends and does not run scripts. Call the page's JSON endpoint instead, or render the page with chromedp or rod and parse the result with goquery.
What is goquery?
A Go library (BSD-3-Clause) that parses HTML and lets you select elements with CSS selectors in a jQuery-like style: doc.Find(".price").Text(). It does only parsing, so you pair it with net/http or another fetcher.
Goquery vs Colly: which should I use?
Use goquery when you only need to parse pages you fetch yourself, and Colly when you want the crawling handled: visiting links, deduplication, limits, parallelism and proxies. Colly uses goquery inside, so the selectors are the same.
How do I use gospider for web crawling?
Install it with go install github.com/jaeles-project/gospider@latest and run gospider -s <url> -d 2 -c 5 -o output, where -d is the depth and -c the concurrent requests; it writes the URLs it finds. Add -k for a delay and -p for a proxy.
What is chromedp?
A Go library (MIT) that drives Chrome or Chromium over the Chrome DevTools Protocol, without external dependencies such as Selenium. You run a list of actions (navigate, wait, click, evaluate) in a context and read the results.
How do I use a proxy with Colly?
Call c.SetProxy("http://user:pass@host:port") on the collector, or c.SetProxyFunc() with Colly's round-robin switcher to cycle a list of proxies. With a rotating gateway, one proxy URL is enough.
Show 1 more questionShow fewer questions
How do I build a web crawler in Golang?
Create a Colly collector with AllowedDomains and MaxDepth, add an OnHTML("a[href]") callback that calls e.Request.Visit() on each link, set a Limit rule for politeness, and start with Visit(). Colly skips URLs it has already seen.
Related pages
Easy Trial: 1 GB for $2.95.
Try for the price of 1 GB, no subscription, traffic never expires.