1
restaurant-search/build/scrape.go

176 lines
3.7 KiB
Go
Raw Normal View History

2015-08-11 11:30:42 +00:00
/*
* Copyright (c) 2015 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package main
2015-08-21 09:21:52 +00:00
import (
2015-09-18 07:55:39 +00:00
"log"
2015-08-21 09:21:52 +00:00
"net/url"
2015-08-23 08:52:21 +00:00
"sync"
2015-08-23 06:16:01 +00:00
2015-08-23 08:52:21 +00:00
"github.com/PuerkitoBio/goquery"
2015-09-18 08:26:37 +00:00
"github.com/fatih/color"
2015-08-21 09:21:52 +00:00
)
2015-08-16 10:12:16 +00:00
2015-09-17 06:54:32 +00:00
type feature struct {
value float64
weight float64
2015-08-24 08:03:00 +00:00
}
2015-09-17 06:54:32 +00:00
type review struct {
2015-08-23 08:52:21 +00:00
name string
address string
url string
2015-08-21 09:21:52 +00:00
2015-09-17 06:54:32 +00:00
features map[string]feature
2015-08-21 09:21:52 +00:00
2015-08-23 08:52:21 +00:00
latitude float64
longitude float64
2015-08-21 09:21:52 +00:00
2015-09-18 07:33:47 +00:00
scr scraper
2015-09-18 04:33:33 +00:00
err error
2015-08-17 05:23:03 +00:00
}
2015-08-23 08:52:21 +00:00
type scraper interface {
index(doc *goquery.Document) (string, []string)
2015-09-17 06:54:32 +00:00
review(doc *goquery.Document) (string, string, map[string]feature, error)
decode(address string) (float64, float64, error)
load(url string) (*goquery.Document, error)
2015-08-23 08:52:21 +00:00
}
2015-08-16 10:12:16 +00:00
2015-08-23 08:52:21 +00:00
func makeAbsUrl(ref, base string) (string, error) {
b, err := url.Parse(base)
2015-08-16 10:12:16 +00:00
if err != nil {
2015-08-23 08:52:21 +00:00
return "", err
2015-08-16 10:12:16 +00:00
}
2015-08-23 08:52:21 +00:00
r, err := url.Parse(ref)
2015-08-22 08:54:46 +00:00
if err != nil {
2015-08-23 08:52:21 +00:00
return "", err
2015-08-22 09:14:42 +00:00
}
2015-08-23 08:52:21 +00:00
return b.ResolveReference(r).String(), nil
2015-08-22 09:14:42 +00:00
}
2015-09-17 07:37:08 +00:00
func decodeReviews(in chan review, out chan review, scr scraper) {
2015-09-18 08:14:48 +00:00
for rev := range in {
2015-09-18 04:33:33 +00:00
if rev.err == nil {
rev.latitude, rev.longitude, rev.err = scr.decode(rev.address)
2015-08-23 08:52:21 +00:00
}
2015-09-18 04:33:33 +00:00
out <- rev
2015-08-22 08:54:46 +00:00
}
2015-09-18 04:33:33 +00:00
close(out)
2015-08-22 09:14:42 +00:00
}
2015-09-17 07:37:08 +00:00
func scrapeReview(url string, out chan review, scr scraper, group *sync.WaitGroup) {
2015-08-23 08:52:21 +00:00
defer group.Done()
2015-08-23 06:43:07 +00:00
2015-09-18 04:33:33 +00:00
var (
doc *goquery.Document
2015-09-18 07:33:47 +00:00
rev = review{url: url, scr: scr}
2015-09-18 04:33:33 +00:00
)
2015-08-23 06:16:01 +00:00
2015-09-18 04:33:33 +00:00
if doc, rev.err = scr.load(rev.url); rev.err == nil {
rev.name, rev.address, rev.features, rev.err = scr.review(doc)
2015-08-23 06:16:01 +00:00
}
2015-09-18 04:33:33 +00:00
out <- rev
2015-08-23 08:52:21 +00:00
}
2015-08-23 06:16:01 +00:00
2015-09-18 04:33:33 +00:00
func scrapeIndex(indexUrl string, out chan review, scr scraper) error {
var group sync.WaitGroup
2015-08-23 06:16:01 +00:00
2015-09-18 04:33:33 +00:00
defer func() {
group.Wait()
close(out)
}()
2015-08-23 06:16:01 +00:00
2015-09-18 04:33:33 +00:00
for {
doc, err := scr.load(indexUrl)
2015-08-23 08:52:21 +00:00
if err != nil {
2015-09-18 04:33:33 +00:00
return err
2015-08-23 06:16:01 +00:00
}
2015-09-18 04:33:33 +00:00
nextIndexUrl, reviewUrls := scr.index(doc)
if err != nil {
return err
}
for _, reviewUrl := range reviewUrls {
absUrl, err := makeAbsUrl(reviewUrl, indexUrl)
if err != nil {
return err
}
group.Add(1)
go scrapeReview(absUrl, out, scr, &group)
}
2015-08-23 06:16:01 +00:00
2015-08-23 08:52:21 +00:00
if err != nil {
2015-09-18 04:33:33 +00:00
return err
2015-08-23 08:52:21 +00:00
}
2015-08-23 06:16:01 +00:00
2015-09-18 04:33:33 +00:00
if nextIndexUrl == "" {
break
}
indexUrl, err = makeAbsUrl(nextIndexUrl, indexUrl)
if err != nil {
return err
}
2015-08-23 06:16:01 +00:00
}
2015-09-18 04:33:33 +00:00
return nil
2015-08-23 06:16:01 +00:00
}
2015-09-18 04:46:56 +00:00
func scrape(url string, scr scraper) ([]review, error) {
2015-09-17 06:54:32 +00:00
out := make(chan review, 128)
in := make(chan review, 128)
2015-08-22 09:14:42 +00:00
2015-09-18 07:55:39 +00:00
var (
reviews []review
wg sync.WaitGroup
)
wg.Add(1)
2015-09-18 08:14:48 +00:00
defer wg.Wait()
2015-09-18 07:55:39 +00:00
go func() {
defer wg.Done()
2015-09-18 08:14:48 +00:00
for rev := range out {
2015-09-18 07:55:39 +00:00
if rev.err == nil {
2015-09-18 08:26:37 +00:00
log.Print(color.GreenString(rev.name))
2015-09-18 07:55:39 +00:00
reviews = append(reviews, rev)
2015-09-18 08:14:48 +00:00
} else {
2015-09-18 08:26:37 +00:00
log.Printf("%s (%s)", color.YellowString(rev.name), color.RedString(rev.err.Error()))
2015-09-18 07:55:39 +00:00
}
}
}()
2015-09-17 07:37:08 +00:00
go decodeReviews(in, out, scr)
2015-09-18 04:46:56 +00:00
err := scrapeIndex(url, in, scr)
2015-08-23 06:16:01 +00:00
2015-09-18 04:46:56 +00:00
return reviews, err
2015-08-11 11:30:42 +00:00
}