├── .gitignore ├── README.md ├── main.go └── structs.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | .DS_Store 3 | dist/ 4 | .goreleaser.yml 5 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## My First App In Go 2 | 3 | # Amazon Keyword Suggestion Tool 4 | From 1 keyword you can get up to hundred or even thousands **Unique and Relevant Keywords** with a **Number of Active Products** per each keyword and ready to be used on Amazon. 5 | 6 | KeyWords are collected from Amazon Web API. 7 | Result will be saved to a csv file. 8 | 9 | #### Latest Release 10 | [Download Latest Release](https://github.com/drawrowfly/amazon-keyword-suggestion-golang/releases/) 11 | 12 | ## Example 13 | ```go 14 | akst -keyword "iphone" -limit 300 15 | ``` 16 | ## Result in CLI 17 | ![Demo](https://i.imgur.com/O2Dgehi.png) 18 | 19 | ## CSV Example 20 | ![Demo](https://i.imgur.com/OwCLSev.png) 21 | 22 | 23 | # Commands 24 | ``` 25 | -keyword string 26 | keyword to use (default "iphone") 27 | -limit int 28 | number of keywords to collect (default 100) 29 | -concurency int 30 | the number of goroutines that are allowed to run concurrently (default 2) 31 | ``` 32 | 33 | *** 34 | Buy Me A Coffee 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "compress/gzip" 5 | "encoding/csv" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "math/rand" 13 | "net/http" 14 | "os" 15 | "regexp" 16 | "strconv" 17 | 18 | "github.com/fatih/color" 19 | "github.com/gosuri/uiprogress" 20 | ) 21 | 22 | var ( 23 | g = color.New(color.FgHiGreen) 24 | y = color.New(color.FgHiYellow) 25 | r = color.New(color.FgHiRed) 26 | ) 27 | 28 | func main() { 29 | // Init progress bar 30 | uiprogress.Start() 31 | 32 | // Flags 33 | concurency := flag.Int("concurency", 5, "the number of goroutines that are allowed to run concurrently") 34 | limit := flag.Int("limit", 100, "number of keywords to collect") 35 | keywordToUse := flag.String("keyword", "", "keyword to use") 36 | flag.Parse() 37 | 38 | if *keywordToUse == "" { 39 | r.Println("KeyWord is missing. To view help enter: akrt -help") 40 | os.Exit(1) 41 | } 42 | g.Printf("Collect %d relevant keywords to the keyword '%s' \n", *limit, *keywordToUse) 43 | 44 | // Keyword Collector progress bar 45 | keywordBar := uiprogress.AddBar(*limit).AppendCompleted().PrependElapsed() 46 | keywordBar.PrependFunc(func(b *uiprogress.Bar) string { 47 | return fmt.Sprintf("Keywords (%d/%d)", b.Current(), *limit) 48 | }) 49 | 50 | // Limiting concurent requests to collect keywords 51 | concurrentGoroutines := make(chan struct{}, *concurency) 52 | 53 | keyword := Keyword{*keywordToUse, 0} 54 | keyWordList := make(map[string]Keyword) 55 | keyChannel := make(chan Keyword) 56 | 57 | go requestKeyWords(keyChannel, keyword) 58 | 59 | toLongKeys := 0 60 | for item := range keyChannel { 61 | if len(keyWordList) >= *limit { 62 | break 63 | } 64 | if toLongKeys > 10 { 65 | break 66 | } 67 | if item.Keyword == "" { 68 | toLongKeys++ 69 | } else { 70 | if _, ok := keyWordList[item.Keyword]; !ok { 71 | keywordBar.Incr() 72 | keyWordList[item.Keyword] = item 73 | go func(item Keyword) { 74 | concurrentGoroutines <- struct{}{} 75 | requestKeyWords(keyChannel, item) 76 | <-concurrentGoroutines 77 | }(item) 78 | } 79 | } 80 | } 81 | // Limiting concurent requests to collect number of products per keyword 82 | concurrentGoroutinesProductCount := make(chan struct{}, 5) 83 | totalResultCount := make(chan Keyword) 84 | 85 | // Product Count Collector progress bar 86 | productCountBar := uiprogress.AddBar(len(keyWordList)).AppendCompleted().PrependElapsed() 87 | productCountBar.PrependFunc(func(b *uiprogress.Bar) string { 88 | return fmt.Sprintf("Product Count (%d/%d)", b.Current(), len(keyWordList)) 89 | }) 90 | 91 | for key := range keyWordList { 92 | go func(item Keyword) { 93 | concurrentGoroutinesProductCount <- struct{}{} 94 | keywordMetadata(totalResultCount, item) 95 | <-concurrentGoroutinesProductCount 96 | }(keyWordList[key]) 97 | } 98 | products := 0 99 | productCountBar.Incr() 100 | for item := range totalResultCount { 101 | productCountBar.Incr() 102 | products++ 103 | keyWordList[item.Keyword] = item 104 | if products >= len(keyWordList) { 105 | close(totalResultCount) 106 | } 107 | } 108 | 109 | // Saving result to the CSV file 110 | records := [][]string{ 111 | {"#", "key_words", "total_products"}, 112 | } 113 | csvFile, err := os.Create(*keywordToUse + ".csv") 114 | if err != nil { 115 | log.Fatalf("Failed creating file: %s", err) 116 | } 117 | csvwriter := csv.NewWriter(csvFile) 118 | count := 1 119 | for key := range keyWordList { 120 | totalProducts := strconv.FormatInt(keyWordList[key].TotalResultCount, 10) 121 | records = append(records, []string{strconv.Itoa(count), keyWordList[key].Keyword, totalProducts}) 122 | count++ 123 | } 124 | csvwriter.WriteAll(records) 125 | 126 | y.Printf("Collected %d keywords: '%s.csv' \n", len(keyWordList), *keywordToUse) 127 | 128 | } 129 | 130 | func requestKeyWords(keyChannel chan Keyword, keyword Keyword) { 131 | client := http.Client{} 132 | 133 | req, _ := http.NewRequest("GET", "https://completion.amazon.com/api/2017/suggestions", nil) 134 | 135 | req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_"+strconv.Itoa(rand.Intn(15-9)+9)+"_1) AppleWebKit/531.36 (KHTML, like Gecko) Chrome/"+strconv.Itoa(rand.Intn(79-70)+70)+".0.3945.130 Safari/531.36") 136 | 137 | q := req.URL.Query() 138 | q.Add("mid", "ATVPDKIKX0DER") 139 | q.Add("alias", "aps") 140 | q.Add("fresh", "0") 141 | q.Add("ks", "88") 142 | q.Add("prefix", keyword.Keyword) 143 | q.Add("event", "onKeyPress") 144 | q.Add("limit", "11") 145 | req.URL.RawQuery = q.Encode() 146 | 147 | resp, err := client.Do(req) 148 | 149 | if err != nil { 150 | log.Fatalln(err) 151 | } 152 | 153 | var result KeywordSuggestions 154 | 155 | json.NewDecoder(resp.Body).Decode(&result) 156 | 157 | if len(result.Suggestions) == 0 { 158 | log.Fatalln("No keywords found") 159 | } 160 | 161 | if len(result.Suggestions) == 1 { 162 | keyChannel <- Keyword{"", 0} 163 | } else { 164 | for _, item := range result.Suggestions { 165 | keyChannel <- Keyword{item.Value, 0} 166 | } 167 | } 168 | } 169 | 170 | func keywordMetadata(totalResultCount chan Keyword, keyword Keyword) { 171 | client := http.Client{} 172 | 173 | req, _ := http.NewRequest("GET", "https://www.amazon.com/s", nil) 174 | 175 | req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_"+strconv.Itoa(rand.Intn(15-9)+9)+"_1) AppleWebKit/531.36 (KHTML, like Gecko) Chrome/"+strconv.Itoa(rand.Intn(79-70)+70)+".0.3945.130 Safari/531.36") 176 | req.Header.Set("Origin", "https://www.amazon.com") 177 | req.Header.Set("Referer", "https://www.amazon.com/") 178 | req.Header.Set("Accept-Encoding", "gzip") 179 | req.Header.Set("Accept-Language", "en-US,en;q=0.9,ru;q=0.8") 180 | req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") 181 | 182 | q := req.URL.Query() 183 | q.Add("i", "aps") 184 | q.Add("k", keyword.Keyword) 185 | q.Add("ref", "nb_sb_noss") 186 | q.Add("url", "search-alias=aps") 187 | req.URL.RawQuery = q.Encode() 188 | 189 | r, err := client.Do(req) 190 | 191 | if err != nil { 192 | log.Fatalln(err) 193 | } 194 | var reader io.ReadCloser 195 | reader, _ = gzip.NewReader(r.Body) 196 | 197 | dataInBytes, _ := ioutil.ReadAll(reader) 198 | pageContent := string(dataInBytes) 199 | 200 | reTotalCount := regexp.MustCompile(`(\w*"totalResultCount":\w*)(.[0-9])`) 201 | res := reTotalCount.FindAllString(string(pageContent), -1) 202 | 203 | var total int64 = 0 204 | if len(res) > 0 { 205 | reCount := regexp.MustCompile(`[-]?\d[\d,]*[\.]?[\d{2}]*`) 206 | submatchall := reCount.FindAllString(res[0], -1) 207 | total, _ = strconv.ParseInt(submatchall[0], 0, 64) 208 | } 209 | 210 | totalResultCount <- Keyword{keyword.Keyword, total} 211 | } 212 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Suggestion struct { 4 | SuggType string `json:"suggType"` 5 | Value string `json:"value"` 6 | RefTag string `json:"refTag"` 7 | StrategyId string `json:"strategyId"` 8 | Ghost bool `json:"ghost"` 9 | Help bool `json:"help"` 10 | Fallback bool `json:"fallback"` 11 | SpellCorrected bool `json:"spellCorrected"` 12 | BlackListed bool `json:"blackListed"` 13 | XcatOnly bool `json:"xcatOnly"` 14 | } 15 | type KeywordSuggestions struct { 16 | Alias string `json:"alias"` 17 | Prefix string `json:"prefix"` 18 | Suffix string `json:"suffix"` 19 | Suggestions []Suggestion `json:"suggestions"` 20 | SuggestionTitleId string `json:"suggestionTitleId"` 21 | ResponseId string `json:"responseId"` 22 | Shuffled bool `json:"shuffled"` 23 | } 24 | 25 | type Keyword struct { 26 | Keyword string 27 | TotalResultCount int64 28 | } 29 | --------------------------------------------------------------------------------