Add FastMode to fetch results much faster (#102)

Utilizing scrapemate's stealth mode to try to fetch directly the results
without JS rendering. This version is much faster.

However the version is still in BETA.
Results are not extracted in full and there is the possibility of the
client being blocked
This commit is contained in:
Georgios Komninos
2024-12-15 13:06:01 +02:00
committed by GitHub
parent 1015b4c978
commit 00bb13a087
18 changed files with 585 additions and 61 deletions
+23 -7
View File
@@ -42,8 +42,6 @@
---
## Try it
A command line and web based google maps scraper build using
[scrapemate](https://github.com/gosom/scrapemate) web crawling framework.
@@ -53,11 +51,8 @@ customize it to your needs
![Example GIF](img/example.gif)
### Web UI:
```
mkdir -p gmapsdata && docker run -v $PWD/gmapsdata:/gmapsdata -p 8080:8080 gosom/google-maps-scraper -data-folder /gmapsdata
```
@@ -98,6 +93,7 @@ Your support helps ensure continued improvement and maintenance.
- Optionally extracts emails from the website of the business
- SOCKS5/HTTP/HTTPS proxy support
- Serverless execution via AWS Lambda functions (experimental & no documentation yet)
- Fast Mode (BETA)
## Notes on email extraction
@@ -113,6 +109,22 @@ For the moment it only checks only one page of the website (the one that is regi
Keep in mind that enabling email extraction results to larger processing time, since more
pages are scraped.
## Fast Mode
Fast mode returns you at most 21 search results per query ordered by distance from the **latitude** and **longitude** provided.
All the results are within the specificied **radius**
It does not contain all the data points but basic ones.
However it provides the ability to extract data really fast.
When you use the fast mode ensure that you have provided:
- zoom
- radius (in meters)
- latitude
- longitude
**Fast mode is Beta, you may experience blocking**
## Extracted Data Points
@@ -195,7 +207,6 @@ The results are written when they arrive in the `results` file you specified
### Command line options
try `./google-maps-scraper -h` to see the command line options available:
```
-aws-access-key string
AWS access key
@@ -225,6 +236,8 @@ try `./google-maps-scraper -h` to see the command line options available:
extract emails from websites
-exit-on-inactivity duration
exit after inactivity duration (e.g., '5m')
-fast-mode
fast mode (reduced data collection)
-function-name string
AWS Lambda function name
-geo string
@@ -239,6 +252,8 @@ try `./google-maps-scraper -h` to see the command line options available:
produce seed jobs only (requires dsn)
-proxies string
comma separated list of proxies to use in the format protocol://user:pass@host:port example: socks5://localhost:9050 or http://user:pass@localhost:9050
-radius float
search radius in meters. Default is 10000 meters (default 10000)
-results string
path to the results file [default: stdout] (default "stdout")
-s3-bucket string
@@ -248,7 +263,7 @@ try `./google-maps-scraper -h` to see the command line options available:
-writer string
use custom writer plugin (format: 'dir:pluginName')
-zoom int
set zoom level (0-21) for search
set zoom level (0-21) for search (default 15)
```
## Using a custom writer
@@ -445,3 +460,4 @@ banner is generated using OpenAI's DALE
If you register via the links on my page I may get a commission. This is another way to support my work
+71
View File
@@ -3,7 +3,10 @@ package gmaps
import (
"encoding/json"
"fmt"
"iter"
"math"
"runtime/debug"
"slices"
"strconv"
"strings"
)
@@ -91,6 +94,33 @@ type Entry struct {
Emails []string `json:"emails"`
}
func (e *Entry) haversineDistance(lat, lon float64) float64 {
const R = 6371e3 // earth radius in meters
clat := lat * math.Pi / 180
clon := lon * math.Pi / 180
elat := e.Latitude * math.Pi / 180
elon := e.Longtitude * math.Pi / 180
dlat := elat - clat
dlon := elon - clon
a := math.Sin(dlat/2)*math.Sin(dlat/2) +
math.Cos(clat)*math.Cos(elat)*
math.Sin(dlon/2)*math.Sin(dlon/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return R * c
}
func (e *Entry) isWithinRadius(lat, lon, radius float64) bool {
distance := e.haversineDistance(lat, lon)
return distance <= radius
}
func (e *Entry) IsWebsiteValidForEmail() bool {
if e.WebSite == "" {
return false
@@ -555,3 +585,44 @@ func decodeURL(url string) (string, error) {
return unquoted, nil
}
type EntryWithDistance struct {
Entry *Entry
Distance float64
}
func filterAndSortEntriesWithinRadius(entries []*Entry, lat, lon, radius float64) []*Entry {
withinRadiusIterator := func(yield func(EntryWithDistance) bool) {
for _, entry := range entries {
distance := entry.haversineDistance(lat, lon)
if distance <= radius {
if !yield(EntryWithDistance{Entry: entry, Distance: distance}) {
return
}
}
}
}
entriesWithDistance := slices.Collect(iter.Seq[EntryWithDistance](withinRadiusIterator))
slices.SortFunc(entriesWithDistance, func(a, b EntryWithDistance) int {
switch {
case a.Distance < b.Distance:
return -1
case a.Distance > b.Distance:
return 1
default:
return 0
}
})
resultIterator := func(yield func(*Entry) bool) {
for _, e := range entriesWithDistance {
if !yield(e.Entry) {
return
}
}
}
return slices.Collect(iter.Seq[*Entry](resultIterator))
}
+16
View File
@@ -1,6 +1,7 @@
package gmaps_test
import (
"fmt"
"os"
"testing"
@@ -200,3 +201,18 @@ func Test_EntryFromJSONRaw2(t *testing.T) {
require.NoError(t, err)
require.Greater(t, len(entry.About), 0)
}
func Test_EntryFromJsonC(t *testing.T) {
raw, err := os.ReadFile("../testdata/output.json")
require.NoError(t, err)
require.NotEmpty(t, raw)
entries, err := gmaps.ParseSearchResults(raw)
require.NoError(t, err)
for _, entry := range entries {
fmt.Printf("%+v\n", entry)
}
}
+90
View File
@@ -0,0 +1,90 @@
package gmaps
import (
"encoding/json"
"fmt"
"strings"
olc "github.com/google/open-location-code/go"
)
func ParseSearchResults(raw []byte) ([]*Entry, error) {
var data []any
if err := json.Unmarshal(raw, &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
if len(data) == 0 {
return nil, fmt.Errorf("empty JSON data")
}
container, ok := data[0].([]any)
if !ok || len(container) == 0 {
return nil, fmt.Errorf("invalid business list structure")
}
items := getNthElementAndCast[[]any](container, 1)
if len(items) < 2 {
return nil, fmt.Errorf("empty business list")
}
entries := make([]*Entry, 0, len(items)-1)
for i := 1; i < len(items); i++ {
arr, ok := items[i].([]any)
if !ok {
continue
}
business := getNthElementAndCast[[]any](arr, 14)
var entry Entry
entry.ID = getNthElementAndCast[string](business, 0)
entry.Title = getNthElementAndCast[string](business, 11)
entry.Categories = toStringSlice(getNthElementAndCast[[]any](business, 13))
entry.WebSite = getNthElementAndCast[string](business, 7, 0)
entry.ReviewRating = getNthElementAndCast[float64](business, 4, 7)
entry.ReviewCount = int(getNthElementAndCast[float64](business, 4, 8))
fullAddress := getNthElementAndCast[[]any](business, 2)
entry.Address = func() string {
sb := strings.Builder{}
for i, part := range fullAddress {
if i > 0 {
sb.WriteString(", ")
}
sb.WriteString(fmt.Sprintf("%v", part))
}
return sb.String()
}()
entry.Latitude = getNthElementAndCast[float64](business, 9, 2)
entry.Longtitude = getNthElementAndCast[float64](business, 9, 3)
entry.Phone = strings.ReplaceAll(getNthElementAndCast[string](business, 178, 0, 0), " ", "")
entry.OpenHours = getHours(business)
entry.Status = getNthElementAndCast[string](business, 34, 4, 4)
entry.Timezone = getNthElementAndCast[string](business, 30)
entry.DataID = getNthElementAndCast[string](business, 10)
entry.PlusCode = olc.Encode(entry.Latitude, entry.Longtitude, 10)
entries = append(entries, &entry)
}
return entries, nil
}
func toStringSlice(arr []any) []string {
ans := make([]string, 0, len(arr))
for _, v := range arr {
ans = append(ans, fmt.Sprintf("%v", v))
}
return ans
}
+140
View File
@@ -0,0 +1,140 @@
package gmaps
import (
"bytes"
"context"
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/gosom/google-maps-scraper/exiter"
"github.com/gosom/scrapemate"
)
type SearchJobOptions func(*SearchJob)
type MapLocation struct {
Lat float64
Lon float64
ZoomLvl float64
Radius float64
}
type MapSearchParams struct {
Location MapLocation
Query string
ViewportW int
ViewportH int
Hl string
}
type SearchJob struct {
scrapemate.Job
params *MapSearchParams
ExitMonitor exiter.Exiter
}
func NewSearchJob(params *MapSearchParams, opts ...SearchJobOptions) *SearchJob {
const (
defaultPrio = scrapemate.PriorityMedium
defaultMaxRetries = 3
baseURL = "https://maps.google.com/search"
)
job := SearchJob{
Job: scrapemate.Job{
ID: uuid.New().String(),
Method: http.MethodGet,
URL: baseURL,
URLParams: buildGoogleMapsParams(params),
MaxRetries: defaultMaxRetries,
Priority: defaultPrio,
},
}
job.params = params
for _, opt := range opts {
opt(&job)
}
return &job
}
func WithSearchJobExitMonitor(exitMonitor exiter.Exiter) SearchJobOptions {
return func(j *SearchJob) {
j.ExitMonitor = exitMonitor
}
}
func (j *SearchJob) Process(_ context.Context, resp *scrapemate.Response) (any, []scrapemate.IJob, error) {
defer func() {
resp.Document = nil
resp.Body = nil
resp.Meta = nil
}()
body := removeFirstLine(resp.Body)
if len(body) == 0 {
return nil, nil, fmt.Errorf("empty response body")
}
entries, err := ParseSearchResults(body)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse search results: %w", err)
}
entries = filterAndSortEntriesWithinRadius(entries,
j.params.Location.Lat,
j.params.Location.Lon,
j.params.Location.Radius,
)
if j.ExitMonitor != nil {
j.ExitMonitor.IncrSeedCompleted(1)
j.ExitMonitor.IncrPlacesFound(len(entries))
j.ExitMonitor.IncrPlacesCompleted(len(entries))
}
return entries, nil, nil
}
func removeFirstLine(data []byte) []byte {
if len(data) == 0 {
return data
}
index := bytes.IndexByte(data, '\n')
if index == -1 {
return []byte{}
}
return data[index+1:]
}
func buildGoogleMapsParams(params *MapSearchParams) map[string]string {
params.ViewportH = 800
params.ViewportW = 600
ans := map[string]string{
"tbm": "map",
"authuser": "0",
"hl": params.Hl,
"q": params.Query,
}
pb := fmt.Sprintf("!4m12!1m3!1d3826.902183192154!2d%.4f!3d%.4f!2m3!1f0!2f0!3f0!3m2!1i%d!2i%d!4f%.1f!7i20!8i0"+
"!10b1!12m22!1m3!18b1!30b1!34e1!2m3!5m1!6e2!20e3!4b0!10b1!12b1!13b1!16b1!17m1!3e1!20m3!5e2!6b1!14b1!46m1!1b0"+
"!96b1!19m4!2m3!1i360!2i120!4i8",
params.Location.Lon,
params.Location.Lat,
params.ViewportW,
params.ViewportH,
params.Location.ZoomLvl,
)
ans["pb"] = pb
return ans
}
+16 -10
View File
@@ -11,8 +11,9 @@ require (
github.com/aws/aws-sdk-go-v2/service/lambda v1.64.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.66.2
github.com/golangci/golangci-lint v1.61.0
github.com/google/open-location-code/go v0.0.0-20241213145606-bf601ad90a45
github.com/google/uuid v1.6.0
github.com/gosom/scrapemate v0.8.2
github.com/gosom/scrapemate v0.8.3
github.com/jackc/pgx/v5 v5.7.1
github.com/mattn/go-runewidth v0.0.16
github.com/mcnijman/go-emailaddress v1.1.1
@@ -21,8 +22,8 @@ require (
github.com/shirou/gopsutil/v4 v4.24.9
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
golang.org/x/sync v0.8.0
golang.org/x/term v0.25.0
golang.org/x/sync v0.10.0
golang.org/x/term v0.27.0
modernc.org/sqlite v1.33.1
)
@@ -39,11 +40,16 @@ require (
github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Noooste/azuretls-client v1.5.11 // indirect
github.com/Noooste/fhttp v1.0.12 // indirect
github.com/Noooste/utls v1.2.12 // indirect
github.com/Noooste/websocket v1.0.3 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
@@ -75,6 +81,7 @@ require (
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/ckaznocha/intrange v0.2.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -83,7 +90,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/purego v0.8.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.5 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
@@ -141,6 +148,7 @@ require (
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.10 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
@@ -233,12 +241,12 @@ require (
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/net v0.32.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
@@ -254,5 +262,3 @@ require (
mvdan.cc/gofumpt v0.7.0 // indirect
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect
)
///replace github.com/gosom/scrapemate v0.8.2 => ../scrapemate
+34 -16
View File
@@ -57,6 +57,14 @@ github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Noooste/azuretls-client v1.5.11 h1:adoAsHjKbxcxDxineOMMS2z2qwauHXvNhqOC3+N2pYU=
github.com/Noooste/azuretls-client v1.5.11/go.mod h1:EmuuM4FlELR5XlTqJQfe02dIP6Fi92bhcEPtnjJg0SU=
github.com/Noooste/fhttp v1.0.12 h1:2N15bIATKaC6q+LVyRGyxPyuqEPvwAS3Uk1peC3YVHU=
github.com/Noooste/fhttp v1.0.12/go.mod h1:CMVxKOhNheqJN5HYE4Rlvz2SRdV8Uv7YWmi6OwmB/Bk=
github.com/Noooste/utls v1.2.12 h1:Zcm/7OB6W4Ro1q2OV1BrFb3qBI7uqYeC21wHYX+Ez9I=
github.com/Noooste/utls v1.2.12/go.mod h1:CJaLzDHOhjuKESY3/wTSEzs3N2QgdXTrNQE3sW2632M=
github.com/Noooste/websocket v1.0.3 h1:drW7tvZ3YqzqI9wApnaH1Q0syFMXO7gbLlsBWjZvMNA=
github.com/Noooste/websocket v1.0.3/go.mod h1:Qhw0Rtuju/fPPbcb3R5XGq7poa51qPDL462jTltl9nQ=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ=
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
@@ -78,6 +86,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
@@ -162,6 +172,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs=
github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -186,8 +198,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA=
@@ -332,6 +344,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/open-location-code/go v0.0.0-20241213145606-bf601ad90a45 h1:+2LSGdn52FkvjGv/BPigVlhtKXQQO1MKUxG14AiKUAw=
github.com/google/open-location-code/go v0.0.0-20241213145606-bf601ad90a45/go.mod h1:eJfRN6aj+kR/rnua/rw9jAgYhqoMHldQkdTi+sePRKk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@@ -350,8 +364,8 @@ github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
github.com/gosom/kit v0.0.0-20230309082109-543b32ac686a h1:5tcB33GTXm0pFUiEFpmE91tMsHQj+I+W7zubT8J/ugI=
github.com/gosom/kit v0.0.0-20230309082109-543b32ac686a/go.mod h1:ngnWSsuBEpCA5Y43kZRa3x8RBYZZ4LDtvZHO4N5dHZ0=
github.com/gosom/scrapemate v0.8.2 h1:QdzMtYwjSIRqC6aFvZAXppKlzmGGJ6FetIE420+hqec=
github.com/gosom/scrapemate v0.8.2/go.mod h1:0EuH67Lz16HlyxQfoSOY46zpLNq/75/qlarYstMPHiQ=
github.com/gosom/scrapemate v0.8.3 h1:okEpdLKkdaivKlZt0GzCVouwRnWGMei5OU4okAuHVYA=
github.com/gosom/scrapemate v0.8.3/go.mod h1:k6nFr9vq78/JHPAq6MgLKo2lh3eMaYnRNmv/CrxNY0s=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
@@ -418,6 +432,8 @@ github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg=
github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -677,6 +693,8 @@ github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZy
github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
@@ -726,8 +744,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -816,8 +834,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -837,8 +855,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -900,8 +918,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -911,8 +929,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -925,8 +943,8 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+36 -1
View File
@@ -25,8 +25,18 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUu
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/Noooste/azuretls-client v1.5.11 h1:adoAsHjKbxcxDxineOMMS2z2qwauHXvNhqOC3+N2pYU=
github.com/Noooste/azuretls-client v1.5.11/go.mod h1:EmuuM4FlELR5XlTqJQfe02dIP6Fi92bhcEPtnjJg0SU=
github.com/Noooste/fhttp v1.0.12 h1:2N15bIATKaC6q+LVyRGyxPyuqEPvwAS3Uk1peC3YVHU=
github.com/Noooste/fhttp v1.0.12/go.mod h1:CMVxKOhNheqJN5HYE4Rlvz2SRdV8Uv7YWmi6OwmB/Bk=
github.com/Noooste/utls v1.2.12 h1:Zcm/7OB6W4Ro1q2OV1BrFb3qBI7uqYeC21wHYX+Ez9I=
github.com/Noooste/utls v1.2.12/go.mod h1:CJaLzDHOhjuKESY3/wTSEzs3N2QgdXTrNQE3sW2632M=
github.com/Noooste/websocket v1.0.3 h1:drW7tvZ3YqzqI9wApnaH1Q0syFMXO7gbLlsBWjZvMNA=
github.com/Noooste/websocket v1.0.3/go.mod h1:Qhw0Rtuju/fPPbcb3R5XGq7poa51qPDL462jTltl9nQ=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
@@ -34,6 +44,8 @@ github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
github.com/confluentinc/confluent-kafka-go v1.9.2 h1:gV/GxhMBUb03tFWkN+7kdhg+zf+QUM+wVkI9zwh770Q=
github.com/confluentinc/confluent-kafka-go v1.9.2/go.mod h1:ptXNqsuDfYbAE/LBW6pnwWZElUoWxHoV8E43DCrliyo=
@@ -45,6 +57,8 @@ github.com/cristalhq/acmd v0.12.0 h1:RdlKnxjN+txbQosg8p/TRNZ+J1Rdne43MVQZ1zDhGWk
github.com/cristalhq/acmd v0.12.0/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ=
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
@@ -89,6 +103,8 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gosom/scrapemate v0.8.1 h1:xyKeZTQ+mrnjCA6W7KnsXLuyUBEcbiChhpJtC8haI+8=
github.com/gosom/scrapemate v0.8.1/go.mod h1:0EuH67Lz16HlyxQfoSOY46zpLNq/75/qlarYstMPHiQ=
github.com/gosom/scrapemate v0.8.3 h1:okEpdLKkdaivKlZt0GzCVouwRnWGMei5OU4okAuHVYA=
github.com/gosom/scrapemate v0.8.3/go.mod h1:k6nFr9vq78/JHPAq6MgLKo2lh3eMaYnRNmv/CrxNY0s=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY=
@@ -112,7 +128,6 @@ github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
github.com/ismurov/swaggerui v0.2.0 h1:rx/BTbufsCUMq0G2a0Cmd045nkrRmHBa7T249wqnVBM=
github.com/ismurov/swaggerui v0.2.0/go.mod h1:EaaariTC2xXLMsKU9v3MdYT62/akXBvRFxmuY9zyqF0=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -122,6 +137,8 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -205,16 +222,34 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.196.0 h1:k/RafYqebaIJBO3+SMnfEGtFVlvp5vSgqTUF54UN/zg=
+12 -6
View File
@@ -65,13 +65,17 @@ func New(cfg *runner.Config) (runner.Runner, error) {
)
}
if cfg.Debug {
opts = append(opts, scrapemateapp.WithJS(
scrapemateapp.Headfull(),
scrapemateapp.DisableImages(),
))
if !cfg.FastMode {
if cfg.Debug {
opts = append(opts, scrapemateapp.WithJS(
scrapemateapp.Headfull(),
scrapemateapp.DisableImages(),
))
} else {
opts = append(opts, scrapemateapp.WithJS(scrapemateapp.DisableImages()))
}
} else {
opts = append(opts, scrapemateapp.WithJS(scrapemateapp.DisableImages()))
opts = append(opts, scrapemateapp.WithStealth())
}
matecfg, err := scrapemateapp.NewConfig(
@@ -130,12 +134,14 @@ func (d *dbrunner) produceSeedJobs(ctx context.Context) error {
}
jobs, err := runner.CreateSeedJobs(
d.cfg.FastMode,
d.cfg.LangCode,
input,
d.cfg.MaxDepth,
d.cfg.Email,
d.cfg.GeoCoordinates,
d.cfg.Zoom,
d.cfg.Radius,
nil,
nil,
)
+13 -7
View File
@@ -76,12 +76,14 @@ func (r *fileRunner) Run(ctx context.Context) (err error) {
exitMonitor := exiter.New()
seedJobs, err = runner.CreateSeedJobs(
r.cfg.FastMode,
r.cfg.LangCode,
r.input,
r.cfg.MaxDepth,
r.cfg.Email,
r.cfg.GeoCoordinates,
r.cfg.Zoom,
r.cfg.Radius,
dedup,
exitMonitor,
)
@@ -194,14 +196,18 @@ func (r *fileRunner) setApp() error {
)
}
if r.cfg.Debug {
opts = append(opts, scrapemateapp.WithJS(
scrapemateapp.Headfull(),
scrapemateapp.DisableImages(),
),
)
if !r.cfg.FastMode {
if r.cfg.Debug {
opts = append(opts, scrapemateapp.WithJS(
scrapemateapp.Headfull(),
scrapemateapp.DisableImages(),
),
)
} else {
opts = append(opts, scrapemateapp.WithJS(scrapemateapp.DisableImages()))
}
} else {
opts = append(opts, scrapemateapp.WithJS(scrapemateapp.DisableImages()))
opts = append(opts, scrapemateapp.WithStealth())
}
matecfg, err := scrapemateapp.NewConfig(
+76 -9
View File
@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"plugin"
"strconv"
"strings"
"github.com/gosom/google-maps-scraper/deduper"
@@ -16,15 +17,56 @@ import (
)
func CreateSeedJobs(
fastmode bool,
langCode string,
r io.Reader,
maxDepth int,
email bool,
geoCoordinates string,
zoom int,
radius float64,
dedup deduper.Deduper,
exitMonitor exiter.Exiter,
) (jobs []scrapemate.IJob, err error) {
var lat, lon float64
if fastmode {
if geoCoordinates == "" {
return nil, fmt.Errorf("geo coordinates are required in fast mode")
}
parts := strings.Split(geoCoordinates, ",")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid geo coordinates: %s", geoCoordinates)
}
lat, err = strconv.ParseFloat(parts[0], 64)
if err != nil {
return nil, fmt.Errorf("invalid latitude: %w", err)
}
lon, err = strconv.ParseFloat(parts[1], 64)
if err != nil {
return nil, fmt.Errorf("invalid longitude: %w", err)
}
if lat < -90 || lat > 90 {
return nil, fmt.Errorf("invalid latitude: %f", lat)
}
if lon < -180 || lon > 180 {
return nil, fmt.Errorf("invalid longitude: %f", lon)
}
if zoom < 1 || zoom > 21 {
return nil, fmt.Errorf("invalid zoom level: %d", zoom)
}
if radius < 0 {
return nil, fmt.Errorf("invalid radius: %f", radius)
}
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
@@ -40,18 +82,43 @@ func CreateSeedJobs(
id = strings.TrimSpace(after)
}
opts := []gmaps.GmapJobOptions{}
var job scrapemate.IJob
if dedup != nil {
opts = append(opts, gmaps.WithDeduper(dedup))
if !fastmode {
opts := []gmaps.GmapJobOptions{}
if dedup != nil {
opts = append(opts, gmaps.WithDeduper(dedup))
}
if exitMonitor != nil {
opts = append(opts, gmaps.WithExitMonitor(exitMonitor))
}
job = gmaps.NewGmapJob(id, langCode, query, maxDepth, email, geoCoordinates, zoom, opts...)
} else {
jparams := gmaps.MapSearchParams{
Location: gmaps.MapLocation{
Lat: lat,
Lon: lon,
ZoomLvl: float64(zoom),
Radius: radius,
},
Query: query,
ViewportW: 1920,
ViewportH: 450,
Hl: langCode,
}
opts := []gmaps.SearchJobOptions{}
if exitMonitor != nil {
opts = append(opts, gmaps.WithSearchJobExitMonitor(exitMonitor))
}
job = gmaps.NewSearchJob(&jparams, opts...)
}
if exitMonitor != nil {
opts = append(opts, gmaps.WithExitMonitor(exitMonitor))
}
job := gmaps.NewGmapJob(id, langCode, query, maxDepth, email, geoCoordinates, zoom, opts...)
jobs = append(jobs, job)
}
+2
View File
@@ -79,12 +79,14 @@ func (l *lambdaAwsRunner) handler(ctx context.Context, input lInput) error {
exitMonitor := exiter.New()
seedJobs, err = runner.CreateSeedJobs(
false, // TODO supoort fast mode
input.Language,
in,
input.Depth,
false,
"",
0,
10000, // TODO support radius
nil,
exitMonitor,
)
+5 -1
View File
@@ -74,6 +74,8 @@ type Config struct {
AwsLambdaInvoker bool
FunctionName string
AwsLambdaChunkSize int
FastMode bool
Radius float64
}
func ParseConfig() *Config {
@@ -103,7 +105,7 @@ func ParseConfig() *Config {
flag.BoolVar(&cfg.Email, "email", false, "extract emails from websites")
flag.StringVar(&cfg.CustomWriter, "writer", "", "use custom writer plugin (format: 'dir:pluginName')")
flag.StringVar(&cfg.GeoCoordinates, "geo", "", "set geo coordinates for search (e.g., '37.7749,-122.4194')")
flag.IntVar(&cfg.Zoom, "zoom", 0, "set zoom level (0-21) for search")
flag.IntVar(&cfg.Zoom, "zoom", 15, "set zoom level (0-21) for search")
flag.BoolVar(&cfg.WebRunner, "web", false, "run web server instead of crawling")
flag.StringVar(&cfg.DataFolder, "data-folder", "webdata", "data folder for web runner")
flag.StringVar(&proxies, "proxies", "", "comma separated list of proxies to use in the format protocol://user:pass@host:port example: socks5://localhost:9050 or http://user:pass@localhost:9050")
@@ -115,6 +117,8 @@ func ParseConfig() *Config {
flag.StringVar(&cfg.AwsRegion, "aws-region", "", "AWS region")
flag.StringVar(&cfg.S3Bucket, "s3-bucket", "", "S3 bucket name")
flag.IntVar(&cfg.AwsLambdaChunkSize, "aws-lambda-chunk-size", 100, "AWS Lambda chunk size")
flag.BoolVar(&cfg.FastMode, "fast-mode", false, "fast mode (reduced data collection)")
flag.Float64Var(&cfg.Radius, "radius", 10000, "search radius in meters. Default is 10000 meters")
flag.Parse()
+18 -1
View File
@@ -178,12 +178,20 @@ func (w *webrunner) scrapeJob(ctx context.Context, job *web.Job) error {
exitMonitor := exiter.New()
seedJobs, err := runner.CreateSeedJobs(
job.Data.FastMode,
job.Data.Lang,
strings.NewReader(strings.Join(job.Data.Keywords, "\n")),
job.Data.Depth,
job.Data.Email,
coords,
job.Data.Zoom,
func() float64 {
if job.Data.Radius <= 0 {
return 10000 // 10 km
}
return float64(job.Data.Radius)
}(),
dedup,
exitMonitor,
)
@@ -243,10 +251,19 @@ func (w *webrunner) scrapeJob(ctx context.Context, job *web.Job) error {
func (w *webrunner) setupMate(_ context.Context, writer io.Writer, job *web.Job) (*scrapemateapp.ScrapemateApp, error) {
opts := []func(*scrapemateapp.Config) error{
scrapemateapp.WithConcurrency(w.cfg.Concurrency),
scrapemateapp.WithJS(scrapemateapp.DisableImages()),
scrapemateapp.WithExitOnInactivity(time.Minute * 3),
}
if !job.Data.FastMode {
opts = append(opts,
scrapemateapp.WithJS(scrapemateapp.DisableImages()),
)
} else {
opts = append(opts,
scrapemateapp.WithStealth(),
)
}
hasProxy := false
if len(w.cfg.Proxies) > 0 {
+1
View File
File diff suppressed because one or more lines are too long
+6
View File
@@ -66,6 +66,8 @@ type JobData struct {
Zoom int `json:"zoom"`
Lat string `json:"lat"`
Lon string `json:"lon"`
FastMode bool `json:"fast_mode"`
Radius int `json:"radius"`
Depth int `json:"depth"`
Email bool `json:"email"`
MaxTime time.Duration `json:"max_time"`
@@ -93,5 +95,9 @@ func (d *JobData) Validate() error {
return errors.New("missing max time")
}
if d.FastMode && (d.Lat == "" || d.Lon == "") {
return errors.New("missing geo coordinates")
}
return nil
}
+10 -2
View File
@@ -71,11 +71,11 @@
</div>
<div class="form-group">
<label for="latitude">Latitude:</label>
<input type="number" step="0.000001" id="latitude" name="latitude" value="{{.Lat}}">
<input type="number" step="0.000000000000001" id="latitude" name="latitude" value="{{.Lat}}">
</div>
<div class="form-group">
<label for="longitude">Longitude:</label>
<input type="number" step="0.000001" id="longitude" name="longitude" value="{{.Lon}}">
<input type="number" step="0.000000000000001" id="longitude" name="longitude" value="{{.Lon}}">
</div>
</fieldset>
</details>
@@ -83,6 +83,14 @@
<details class="expandable-section">
<summary>Advanced Options</summary>
<fieldset>
<div class="form-group">
<label for="fastmode">Fast Mode (BETA):</label>
<input type="checkbox" id="fastmode" name="fastmode" {{if .FastMode}}checked{{end}}>
</div>
<div class="form-group">
<label for="radius">Radius (BETA):</label>
<input type="number" id="radius" name="radius" value="{{.Radius}}">
</div>
<div class="form-group">
<label for="depth">Depth:</label>
<input type="number" step="1" id="depth" name="depth" value="{{.Depth}}">
+16 -1
View File
@@ -102,6 +102,8 @@ type formData struct {
Keywords []string
Language string
Zoom int
FastMode bool
Radius int
Lat string
Lon string
Depth int
@@ -138,7 +140,9 @@ func (s *Server) index(w http.ResponseWriter, r *http.Request) {
MaxTime: "10m",
Keywords: []string{},
Language: "en",
Zoom: 0,
Zoom: 15,
FastMode: false,
Radius: 10000,
Lat: "0",
Lon: "0",
Depth: 10,
@@ -213,6 +217,17 @@ func (s *Server) scrape(w http.ResponseWriter, r *http.Request) {
return
}
if r.Form.Get("fastmode") == "on" {
newJob.Data.FastMode = true
}
newJob.Data.Radius, err = strconv.Atoi(r.Form.Get("radius"))
if err != nil {
http.Error(w, "invalid radius", http.StatusUnprocessableEntity)
return
}
newJob.Data.Lat = r.Form.Get("latitude")
newJob.Data.Lon = r.Form.Get("longitude")