how to download DDG image with golang
While DuckDuckGo offers Instant Answer APIs, it does not provide URLs to download images, certainly because of copyright isusses.
One way is to fetch a vqd
token from an initial basic request, then use it to find the image URLs, and finally download
the image.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"net/http"
"regexp"
"strings"
)
func main() {
arg := os.Args[1]
img := make(chan *ImgFetchResult)
f := &DDGImgFetcher{}
go f.Fetch(img, arg)
result := <-img
if result.Error != nil {
log.Fatalln(result.Error)
}
createFile(result.Img, "/tmp/"+arg+"."+result.ContentType)
}
func createFile(content []byte, filePath string) {
file, err := os.Create(filePath)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
_, err = io.Copy(file, bytes.NewReader(content))
if err != nil {
log.Fatalln(err)
}
}
// DDGImgFetcher fetches the image from duckduckgo
type DDGImgFetcher struct {
}
// ImgFetchResult is the representation of the result to fetch the image
// It wraps an error in case something went wrong
type ImgFetchResult struct {
Img []byte
ContentType string
Error error
}
const ddgURL = "https://duckduckgo.com"
// Fetch the image from duckduckgo
// Code inspired from https://github.com/deepanprabhu/duckduckgo-images-api/blob/master/duckduckgo_images_api/api.py
func (f *DDGImgFetcher) Fetch(result chan *ImgFetchResult, title string) {
token, err := fetchToken(title)
if err != nil {
result <- &ImgFetchResult{nil, "", err}
return
}
imgURL, err := fetchImgURL(title, token)
if err != nil {
result <- &ImgFetchResult{nil, "", err}
return
}
img, contentType, err := downloadImg(imgURL)
if err != nil {
result <- &ImgFetchResult{nil, "", err}
return
}
result <- &ImgFetchResult{img, contentType, nil}
}
// fetchToken needed to perform the image search
func fetchToken(title string) (string, error) {
req, err := http.NewRequest("GET", ddgURL, nil)
if err != nil {
return "", fmt.Errorf("Could not build the request for URL %s. Error was %s", ddgURL, err)
}
q := req.URL.Query()
q.Add("q", title)
req.URL.RawQuery = q.Encode()
resp, err := http.Get(req.URL.String())
if err != nil {
return "", fmt.Errorf("Could not fetch the result of %s", req.URL.String())
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("Duckduckgo returns %v status code", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("Could not read the response body from duckduckgo. Error was %s", err)
}
content := string(body)
r := regexp.MustCompile("vqd=([\\d-]+)")
token := strings.ReplaceAll(r.FindString(content), "vqd=", "")
return token, nil
}
func fetchImgURL(title, token string) (string, error) {
url := ddgURL + "/i.js"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", fmt.Errorf("Could not build the request for URL %s. Error was %s", url, err)
}
q := req.URL.Query()
q.Add("l", "wt-wt")
q.Add("o", "json")
q.Add("q", title)
q.Add("vqd", token)
q.Add("f", ",,,")
q.Add("p", "2")
req.URL.RawQuery = q.Encode()
resp, err := http.Get(req.URL.String())
if err != nil {
return "", fmt.Errorf("Could not fetch the result of %s", req.URL.String())
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("Duckduckgo returns %v status code", resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("Could not read the response body from duckduckgo. Error was %s", err)
}
var data map[string]interface{}
err = json.Unmarshal(body, &data)
if err != nil {
return "", fmt.Errorf("Could not parse the response body from wikipedia. Error was %s", err.Error())
}
results := data["results"].([]interface{})
firstResult := results[0].(map[string]interface{})
imgURL := firstResult["image"].(string)
return imgURL, nil
}
func downloadImg(imgURL string) ([]byte, string, error) {
resp, err := http.Get(imgURL)
if err != nil {
return nil, "", fmt.Errorf("Could not download the image from %s", imgURL)
}
if resp.StatusCode != 200 {
return nil, "", fmt.Errorf("Could not download the image from %s. Status comde was %v", imgURL, resp.StatusCode)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, "", fmt.Errorf("Could not read the response body from %s. Error was %s", imgURL, err.Error())
}
contentType := strings.ToLower(strings.ReplaceAll(resp.Header.Get("Content-Type"), "image/", ""))
return body, contentType, nil
}
# Copy above content to main.go file
vim main.go
# Run with the following
go run main.go
References
- Code in python: https://github.com/deepanprabhu/duckduckgo-images-api