mirror of
https://github.com/karust/openserp.git
synced 2026-09-09 04:56:23 +08:00
Init
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user