This commit is contained in:
Rustem Kamalov
2023-06-23 04:08:00 +03:00
parent dbcb116e48
commit 8b87e7b4ab
29 changed files with 2480 additions and 8 deletions
+120
View File
@@ -0,0 +1,120 @@
package google
import (
"errors"
"regexp"
"strconv"
"strings"
"time"
"github.com/go-rod/rod"
"github.com/karust/googler/core"
"github.com/sirupsen/logrus"
)
type Google struct {
core.Browser
findNumRgxp *regexp.Regexp
checkTimeout time.Duration
}
func New(browser core.Browser) *Google {
gogl := Google{Browser: browser}
gogl.checkTimeout = time.Second * 2
gogl.findNumRgxp = regexp.MustCompile("\\d")
return &gogl
}
func (gogl *Google) Name() string {
return "google"
}
func (gogl *Google) FindTotalResults(page *rod.Page) (int, error) {
resultsStats, err := page.Timeout(gogl.checkTimeout).Search("div#result-stats")
if err != nil {
return 0, errors.New("Result stats not found: " + err.Error())
}
stats, err := resultsStats.First.Text()
if err != nil {
return 0, errors.New("Cannot extract result stats text: " + err.Error())
}
// Escape moment with `seconds` and extract digits
allNums := gogl.findNumRgxp.FindAllString(stats[:len(stats)-15], -1)
stats = strings.Join(allNums, "")
total, err := strconv.Atoi(stats)
if err != nil {
return 0, err
}
return total, nil
}
func (gogl *Google) Search(query core.Query) ([]core.SearchResult, error) {
logrus.Tracef("Start Google search, query: %+v", query)
searchResults := []core.SearchResult{}
// Build URL from query struct to open in browser
url, err := BuildURL(query)
if err != nil {
return nil, err
}
page := gogl.Navigate(url)
totalResults, err := gogl.FindTotalResults(page)
if err != nil {
return nil, err
}
if totalResults == 0 {
return searchResults, nil
}
results, err := page.Search("div>div.g")
if err != nil {
return nil, err
}
resultElements, err := results.All()
if err != nil {
return nil, err
}
for i, r := range resultElements {
// Get URL
link, err := r.Element("a")
if err != nil {
continue
}
linkText, err := link.Property("href")
// Get title
titleTag, err := link.Element("h3")
if err != nil {
logrus.Error(err)
continue
}
title, err := titleTag.Text()
if err != nil {
title = "No title"
logrus.Error(err)
}
// Get description
descTag, err := r.Element(`div[data-sncf~="1"]`)
desc := "No description found"
if err == nil {
desc = descTag.MustText()
}
gR := core.SearchResult{Rank: i, URL: linkText.String(), Title: title, Description: desc}
searchResults = append(searchResults, gR)
}
err = page.Close()
if err != nil {
logrus.Error(err)
}
return searchResults, nil
}
+93
View File
@@ -0,0 +1,93 @@
package google
import (
"net/http"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/corpix/uarand"
"github.com/karust/googler/core"
"github.com/sirupsen/logrus"
)
func googleRequest(searchURL string) (*http.Response, error) {
baseClient := &http.Client{}
req, err := http.NewRequest("GET", searchURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", uarand.GetRandom())
res, err := baseClient.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
func googleResultParser(response *http.Response) ([]core.SearchResult, error) {
doc, err := goquery.NewDocumentFromResponse(response)
if err != nil {
return nil, err
}
results := []core.SearchResult{}
rank := 1
// Get individual results
sel := doc.Find("div.g")
for i := range sel.Nodes {
item := sel.Eq(i)
// Find URL
linkTag := item.Find("a")
link, _ := linkTag.Attr("href")
link = strings.Trim(link, " ")
// Find title
titleTag := item.Find("h3")
title := titleTag.Text()
// Find description
descTag := item.Find(`div[data-sncf~="1"]`)
desc := descTag.Text()
if link != "" && link != "#" {
result := core.SearchResult{
Rank: rank,
URL: link,
Title: title,
Description: desc,
}
results = append(results, result)
rank++
}
}
logrus.Tracef("Google search document size: %d", len(doc.Text()))
return results, err
}
func Search(query core.Query) ([]core.SearchResult, error) {
googleURL, err := BuildURL(query)
if err != nil {
return nil, err
}
logrus.Debugf("Google URL built: %s", googleURL)
res, err := googleRequest(googleURL)
if err != nil {
return nil, err
}
logrus.Debugf("Google Raw response: code=%d", res.StatusCode)
results, err := googleResultParser(res)
if err != nil {
return nil, err
}
logrus.Debugf("Google Raw results : %v", results)
return results, nil
}
+29
View File
@@ -0,0 +1,29 @@
package google
import (
"testing"
"time"
"github.com/karust/googler/core"
)
var browser *core.Browser
func init() {
opts := core.BrowserOpts{IsHeadless: true, IsLeakless: true, Timeout: time.Second * 2, WaitRequests: false}
browser, _ = core.NewBrowser(opts)
}
func TestSearchGoogle(t *testing.T) {
gogl := New(*browser)
query := core.Query{Text: "HEY", Limit: 10}
results, err := gogl.Search(query)
if err != nil {
t.Fatalf("Cannot [SearchGoogle]: %s", err)
}
if len(results) == 0 {
t.Fatalf("[SearchGoogle] returned empty result")
}
}
+271
View File
@@ -0,0 +1,271 @@
package google
import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"github.com/karust/googler/core"
"github.com/sirupsen/logrus"
)
var GoogleDomains = map[string]string{
"": "com",
"en": "com",
"ac": "ac",
"ad": "ad",
"ae": "ae",
"af": "com.af",
"ag": "com.ag",
"ai": "com.ai",
"al": "al",
"am": "am",
"ao": "co.ao",
"ar": "com.ar",
"as": "as",
"at": "at",
"au": "com.au",
"az": "az",
"ba": "ba",
"bd": "com.bd",
"be": "be",
"bf": "bf",
"bg": "bg",
"bh": "com.bh",
"bi": "bi",
"bj": "bj",
"bn": "com.bn",
"bo": "com.bo",
"br": "com.br",
"bs": "bs",
"bt": "bt",
"bw": "co.bw",
"by": "by",
"bz": "com.bz",
"ca": "ca",
"kh": "com.kh",
"cc": "cc",
"cd": "cd",
"cf": "cf",
"cat": "cat",
"cg": "cg",
"ch": "ch",
"ci": "ci",
"ck": "co.ck",
"cl": "cl",
"cm": "cm",
"cn": "cn",
"co": "com.co",
"cr": "co.cr",
"cu": "com.cu",
"cv": "cv",
"cy": "com.cy",
"cz": "cz",
"de": "de",
"dj": "dj",
"dk": "dk",
"dm": "dm",
"do": "com.do",
"dz": "dz",
"ec": "com.ec",
"ee": "ee",
"eg": "com.eg",
"es": "es",
"et": "com.et",
"fi": "fi",
"fj": "com.fj",
"fm": "fm",
"fr": "fr",
"ga": "ga",
"gb": "co.uk",
"ge": "ge",
"gf": "gf",
"gg": "gg",
"gh": "com.gh",
"gi": "com.gi",
"gl": "gl",
"gm": "gm",
"gp": "gp",
"gr": "gr",
"gt": "com.gt",
"gy": "gy",
"hk": "com.hk",
"hn": "hn",
"hr": "hr",
"ht": "ht",
"hu": "hu",
"id": "co.id",
"iq": "iq",
"ie": "ie",
"il": "co.il",
"im": "im",
"in": "co.in",
"io": "io",
"is": "is",
"it": "it",
"je": "je",
"jm": "com.jm",
"jo": "jo",
"jp": "co.jp",
"ke": "co.ke",
"ki": "ki",
"kg": "kg",
"kr": "co.kr",
"kw": "com.kw",
"kz": "kz",
"la": "la",
"lb": "com.lb",
"lc": "com.lc",
"li": "li",
"lk": "lk",
"ls": "co.ls",
"lt": "lt",
"lu": "lu",
"lv": "lv",
"ly": "com.ly",
"ma": "co.ma",
"md": "md",
"me": "me",
"mg": "mg",
"mk": "mk",
"ml": "ml",
"mm": "com.mm",
"mn": "mn",
"ms": "ms",
"mt": "com.mt",
"mu": "mu",
"mv": "mv",
"mw": "mw",
"mx": "com.mx",
"my": "com.my",
"mz": "co.mz",
"na": "com.na",
"ne": "ne",
"nf": "com.nf",
"ng": "com.ng",
"ni": "com.ni",
"nl": "nl",
"no": "no",
"np": "com.np",
"nr": "nr",
"nu": "nu",
"nz": "co.nz",
"om": "com.om",
"pa": "com.pa",
"pe": "com.pe",
"ph": "com.ph",
"pk": "com.pk",
"pl": "pl",
"pg": "com.pg",
"pn": "pn",
"pr": "com.pr",
"ps": "ps",
"pt": "pt",
"py": "com.py",
"qa": "com.qa",
"ro": "ro",
"rs": "rs",
"ru": "ru",
"rw": "rw",
"sa": "com.sa",
"sb": "com.sb",
"sc": "sc",
"se": "se",
"sg": "com.sg",
"sh": "sh",
"si": "si",
"sk": "sk",
"sl": "com.sl",
"sn": "sn",
"sm": "sm",
"so": "so",
"st": "st",
"sv": "com.sv",
"td": "td",
"tg": "tg",
"th": "co.th",
"tj": "com.tj",
"tk": "tk",
"tl": "tl",
"tm": "tm",
"to": "to",
"tn": "tn",
"tr": "com.tr",
"tt": "tt",
"tw": "com.tw",
"tz": "co.tz",
"ua": "com.ua",
"ug": "co.ug",
"uk": "co.uk",
"uy": "com.uy",
"uz": "co.uz",
"vc": "com.vc",
"ve": "co.ve",
"vg": "vg",
"vi": "co.vi",
"vn": "com.vn",
"vu": "vu",
"ws": "ws",
"za": "co.za",
"zm": "co.zm",
"zw": "co.zw",
}
// Build Google query URL from Query struct
func BuildURL(q core.Query) (string, error) {
googleBase := GoogleDomains[strings.ToLower(q.LangCode)]
base, err := url.Parse(fmt.Sprintf("https://www.google.%s", googleBase))
if err != nil {
return "", err
}
base.Path += "search"
params := url.Values{}
// Set request text
if q.Text != "" || q.Site != "" || q.Filetype != "" {
text := q.Text
if q.Site != "" {
text += " site:" + q.Site
}
if q.Filetype != "" {
text += " filetype:" + q.Filetype
}
logrus.Tracef("Query text: %s", text)
params.Add("q", text)
}
if len(params.Get("q")) == 0 {
return "", errors.New("Empty query built")
}
// Set search date range
if q.DateInterval != "" {
intervals := strings.Split(q.DateInterval, "..")
if len(intervals) != 2 {
return "", errors.New("Incorrect data interval provided")
}
dataParam := fmt.Sprintf("cdr:1,cd_min:%s,cd_max:%s", intervals[0], intervals[1])
params.Add("tbs", dataParam)
}
// Limit number of results
if q.Limit != 0 {
params.Add("num", strconv.Itoa(q.Limit))
}
if q.LangCode != "" {
params.Add("hl", q.LangCode)
params.Add("lr", "lang_"+strings.ToLower(q.LangCode))
}
params.Add("pws", "0") // Do not personalize earch results
params.Add("nfpr", "1") // Do not auto correct search queries
base.RawQuery = params.Encode()
return base.String(), nil
}