Форум программистов, компьютерный форум, киберфорум
Наши страницы
Go
Войти
Регистрация
Восстановить пароль
 
Firik67
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
1

Кодревью rss парсера

04.02.2018, 13:23. Просмотров 293. Ответов 7

Решил тут попробовать силы в Go, набросал немного кода. Хотелось бы немного кодревью. Куда можно скинуть? Посмотреть можно тут. Что интересует больше всего: правильно ли используются каналы и горутины?
Ну и сама задача примерно следующая:
Написать парсер, который будет подключатся к rss-лентам, собирать их в один источник, сортировать все записи по дате и сохранять в формате rss
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
04.02.2018, 13:23
Ответы с готовыми решениями:

Лучший язык программирования для написания WEB парсера
Здравствуйте друзья. Интересует вопрос, на каком ЯП лучше всего писать...

Как исправить ошибку в коде RSS-парсера
Доброго всем времени суток . Дали задание написать парсер для rss . Но всё да...

Количество новостей rss ленты и подключения несколько rss лент
Здравствуйте, возник такой вопрос, у меня есть rss-лента, одна она работает...

Результат парсера
Я спарсил текст между <teg> и </teg> <teg> TEXT TEXT TEXT </teg> ...

Написание парсера
В общем не так давно учу VB, и тут поставил себе задачу написать "разделитель"...

7
MonaxGT
Эксперт по компьютерным сетям
275 / 275 / 25
Регистрация: 02.08.2012
Сообщений: 1,219
05.02.2018, 18:05 2
Firik67, Код можно приложить с помощью стандартного функционала форума.
0
Firik67
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
05.02.2018, 21:26  [ТС] 3
MonaxGT, всегда думал, что на гитхабе смотреть удобнее

main.go
Кликните здесь для просмотра всего текста

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main
 
import "os"
 
import (
    "rss_parser/rss"
    "rss_parser/file"
)
 
func main() {
    var rssItemsChannel = make(chan []rss.Item)
    var readyToGoChannel = make(chan bool)
    var Rss rss.Rss
 
    urls := file.GetUrlsFromFile("rss_sources.txt")
    urlsCount := len(*urls)
    for _, url := range *urls {
        go Rss.ProcessUrl(url, rssItemsChannel)
    }
 
    go Rss.CombineItems(rssItemsChannel, readyToGoChannel, urlsCount)
 
    for {
        if <-readyToGoChannel {
            Rss.SortItems()
            rssBytes := Rss.XmlBytes()
            file.SaveToXmlFile(*rssBytes)
 
            os.Exit(0)
        }
    }
}


helper.go
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package file
 
import (
    "os"
    "log"
    "bufio"
    "path/filepath"
)
 
func SaveToXmlFile(rssBytes []byte) {
    currentDir := getCurrentDirAbsolute()
    xmlFile, err := os.Create(currentDir + "output.xml")
    if err != nil {
        log.Fatal(err)
    }
    f := bufio.NewWriter(xmlFile)
    f.Write(rssBytes)
    xmlFile.Close()
}
 
func GetUrlsFromFile(filename string) *[]string {
    urls := make([]string, 0)
    currentDir := getCurrentDirAbsolute()
    file, err := os.Open(currentDir + filename)
    if err != nil {
        log.Fatal(err)
    }
 
    f := bufio.NewReader(file)
    for {
        readLine, err := f.ReadString('\n')
        if err != nil && readLine == "" {
            break
        }
        urls = append(urls, readLine)
    }
    file.Close()
 
    return &urls
}
 
func getCurrentDirAbsolute() string {
    dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
    if err != nil {
        log.Fatal(err)
    }
    return dir + "/"
}


client.go
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package http
 
import (
    "net/http"
    "strings"
    "log"
)
 
func CreateRequest(url string) *http.Request {
    req, err := http.NewRequest(http.MethodGet, strings.Trim(url, "\n"), nil)
    if err != nil {
        log.Fatal(err)
    }
 
    req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml,application/rss+xml;q=0.9,*/*;q=0.8;q=0.7")
    req.Header.Add("Cache-Control", "no-cache")
    req.Header.Add("Connection", "keep-alive")
    req.Header.Add("Pragma", "no-cache")
    req.Header.Add("Upgrade-Insecure-Requests", "1")
    req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0")
 
    return req
}
 
func SendRequest(req *http.Request) (*http.Response) {
    client := &http.Client{}
    response, err := client.Do(req)
    if err != nil {
        errorMessage := "Error while get data from rss " + req.URL.String() + "\n"
        log.Println(errorMessage, err)
    }
 
    return response
}


rss.go
Кликните здесь для просмотра всего текста
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package rss
 
import (
    "encoding/xml"
    "net/http"
    "io/ioutil"
    "log"
    "strings"
    "golang.org/x/net/html/charset"
    "sort"
    "time"
)
 
import rssHttp "rss_parser/http"
 
type Rss struct {
    XMLName xml.Name `xml:"rss"`
    Channel Channel  `xml:"channel"`
    Version xml.Attr `xml:"version,attr"`
}
 
type Channel struct {
    XMLName  xml.Name `xml:"channel"`
    Title    string   `xml:"title"`
    Link     string   `xml:"link"`
    Language string   `xml:"language"`
    Items    []Item   `xml:"item"`
}
 
type Item struct {
    XMLName     xml.Name     `xml:"item"`
    Title       string       `xml:"title"`
    Link        string       `xml:"link"`
    Description xml.CharData `xml:"description"`
    PubDate     string       `xml:"pubDate"`
}
 
func (rss *Rss) DecodeXmlHttpResponse(response *http.Response) {
    url := response.Request.URL.String()
    xmlData, err := ioutil.ReadAll(response.Body)
    if err != nil {
        log.Fatal(url, err)
    }
    reader := strings.NewReader(string(xmlData))
    decoder := xml.NewDecoder(reader)
    decoder.CharsetReader = charset.NewReaderLabel
    err = decoder.Decode(&rss)
    if err != nil {
        log.Println(url, err)
    }
}
 
func (rss *Rss) ProcessUrl(url string, rssItemsChannel chan<- []Item) {
    request := rssHttp.CreateRequest(url)
    response := rssHttp.SendRequest(request)
    rss.DecodeXmlHttpResponse(response)
    response.Body.Close()
    rssItemsChannel <- rss.Channel.Items
}
 
func (rss *Rss) CombineItems(rssItemsChannel <-chan []Item, readyToGoChannel chan<- bool, urlsCount int) {
    var i = 0
    for {
        items := <-rssItemsChannel
        for _, item := range items {
            rss.Channel.Items = append(rss.Channel.Items, item)
        }
        i++
 
        if i == urlsCount {
            readyToGoChannel <- true
            break
        }
    }
}
 
func (rss *Rss) SortItems() {
    sort.Slice(rss.Channel.Items, func(i, j int) bool {
        time1, err := time.Parse(time.RFC1123Z, rss.Channel.Items[i].PubDate)
        if err != nil {
            log.Println("Can't parse time1: ", err)
        }
        time2, err := time.Parse(time.RFC1123Z, rss.Channel.Items[j].PubDate)
        if err != nil {
            log.Println("Can't parse time2: ", err)
        }
 
        return time1.Before(time2)
    })
}
 
func (rss *Rss) XmlBytes() *[]byte {
    rss.setDefaultAttributes()
    rssBytes, err := xml.Marshal(rss)
    if err != nil {
        log.Fatal(err)
    }
    return &rssBytes
}
 
func (rss *Rss) setDefaultAttributes() {
    rss.Version = xml.Attr{
        Name:  xml.Name{Local: "version"},
        Value: "2.0",
    }
 
    rss.Channel.Title = "Local Rss"
    rss.Channel.Link = "http://localhost"
    rss.Channel.Language = "ru-RU"
}
0
korvin_
2160 / 1649 / 318
Регистрация: 28.04.2012
Сообщений: 5,906
06.02.2018, 10:50 4
Цитата Сообщение от Firik67 Посмотреть сообщение
всегда думал, что на гитхабе смотреть удобнее
+1.

По коду:

1) Я что-то не понял: сначала ты берёшь айтемы из rss.Channel.Items и отправляешь их в канал rssItemsChannel (процедура ProcessUrl), а потом берёшь их из канала в процедуре CombineItems и добавляешь в этот же массив (rss.Channel.Items = append(rss.Channel.Items, item))? И вообще, у тебя горутины ProcessUrl используют конкурентно один и тот же экземпляр структуры Rss, что крайне не хорошо, странно, что ты не столкнулся с неконсистентностью данных.

2) В CombineItems:
C
1
2
    for {
        items := <-rssItemsChannel
можно заменить на
C
1
    for items := range rssItemsChannel {
Кроме того, саму CombineItems нет нужды запускать как горутину, это здесь ничего не даёт, только добавляет лишние действия с каналом readyToGoChannel (который обычно называют просто done).

Добавлено через 38 минут
И не нужно использовать указатели на слайсы.

Навскидку как-то так:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package rss
 
import (
    "encoding/xml"
    "net/http"
    "io/ioutil"
    "strings"
    "golang.org/x/net/html/charset"
    "sort"
    "time"
)
 
import rssHttp "rss_parser/http"
 
type Rss struct {
    XMLName xml.Name `xml:"rss"`
    Channel Channel  `xml:"channel"`
    Version xml.Attr `xml:"version,attr"`
}
 
type Channel struct {
    XMLName  xml.Name `xml:"channel"`
    Title    string   `xml:"title"`
    Link     string   `xml:"link"`
    Language string   `xml:"language"`
    Items    []Item   `xml:"item"`
}
 
type Item struct {
    XMLName     xml.Name     `xml:"item"`
    Title       string       `xml:"title"`
    Link        string       `xml:"link"`
    Description xml.CharData `xml:"description"`
    PubDate     string       `xml:"pubDate"`
}
 
func NewFromItems(items []Item) *Rss {
    rss := Rss{}
    rss.setDefaultAttributes()
    rss.Channel.Items = items
    return &rss
}
 
func decodeXmlHttpResponse(response *http.Response) (rss *Rss, err error) {
    url := response.Request.URL.String()
    xmlData, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return nil, err
    }
    reader  := strings.NewReader(string(xmlData))
    decoder := xml.NewDecoder(reader)
    decoder.CharsetReader = charset.NewReaderLabel
    rss := &Rss{}
    err = decoder.Decode(rss)
    if err != nil {
        return nil, err
    }
    return rss, nil
}
 
func ProcessUrl(url string, rssItemsChannel chan<- []Item, errChan chan<- error) {
    request  := rssHttp.CreateRequest(url)
    response := rssHttp.SendRequest(request)
    rss, err := decodeXmlHttpResponse(response)
    response.Body.Close()
    if err != nil {
        errChan <- err
        rssItemsChannel <- nil
    } else {
        rssItemsChannel <- rss.Channel.Items
    }
}
 
func CombineItems(rssItemsChannel <-chan []Item, urlsCount int) []Item {
    result := make([]Item, 0, urlsCount*4) // 4 is just some random number
    for urlsCount > 0 {
        items := <-rssItemsChannel
        if items != nil {
            for _, item := range items {
                result = append(rss.Channel.Items, item)
            }
        }
        urlsCount--
    }
    return result
}
 
func SortItems(items []Item) {
    sort.Slice(items, func(i, j int) bool {
        time1, err := time.Parse(time.RFC1123Z, rss.Channel.Items[i].PubDate)
        if err != nil {
            log.Println("Can't parse time1: ", err)
        }
        time2, err := time.Parse(time.RFC1123Z, rss.Channel.Items[j].PubDate)
        if err != nil {
            log.Println("Can't parse time2: ", err)
        }
 
        return time1.Before(time2)
    })
}
 
func (rss *Rss) XmlBytes() (bytes []byte, err error) {
    rss.setDefaultAttributes()
    rssBytes, err := xml.Marshal(rss)
    if err != nil {
        return nil, err
    }
    return rssBytes, nil
}
 
func (rss *Rss) setDefaultAttributes() {
    rss.Version = xml.Attr{
        Name:  xml.Name{Local: "version"},
        Value: "2.0",
    }
 
    rss.Channel.Title = "Local Rss"
    rss.Channel.Link = "http://localhost"
    rss.Channel.Language = "ru-RU"
}
--- https://play.golang.org/p/jW3b_G6zN_C

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main
 
import "os"
 
import (
    "log"
    "rss_parser/rss"
    "rss_parser/file"
)
 
func main() {
    urls := file.GetUrlsFromFile("rss_sources.txt")
    urlsCount := len(urls)
    rssItemsChannel := make(chan []rss.Item)
    errors := make(chan error)
 
    go func() {
        for err := range errors {
            log.Println(err)
        }
    }()
 
    for _, url := range urls {
        go rss.ProcessUrl(url, rssItemsChannel, errors)
    }
 
    combined := rss.CombineItems(rssItemsChannel, urlsCount)
    rss.SortItems(combined)
    v := rss.NewFromItems(combined)
    bytes, err := v.XmlBytes()
    if err != nil {
        log.Fatalln(err)
    }
    file.SaveToXmlFile(bytes)
}
--- https://play.golang.org/p/s1ojsZt93Dh
1
Firik67
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
06.02.2018, 22:47  [ТС] 5
korvin_,
1) Я что-то не понял: сначала ты берёшь айтемы из rss.Channel.Items и отправляешь их в канал rssItemsChannel (процедура ProcessUrl), а потом берёшь их из канала в процедуре CombineItems и добавляешь в этот же массив (rss.Channel.Items = append(rss.Channel.Items, item))?
Да, действительно, допустил логическую ошибку. Срез каждый раз передавал сам себя + новые элементы, что в 7 раз по объему увеличивало выходной файл и время работы скрипта. Корни этой ошибки вижу в том, что изначально скрипт писался на простых функциях, без привязки к структуре, а потом просто не до конца отрефакторился.

И вообще, у тебя горутины ProcessUrl используют конкурентно один и тот же экземпляр структуры Rss, что крайне не хорошо, странно, что ты не столкнулся с неконсистентностью данных.
Странно, возможно свою роль сыграло разное время ответа от серверов

Кроме того, саму CombineItems нет нужды запускать как горутину, это здесь ничего не даёт, только добавляет лишние действия с каналом readyToGoChannel (который обычно называют просто done).
Я правильно понимаю, что пока не завершатся все дочерние горутины, основная не завершится?

И не нужно использовать указатели на слайсы.
Потому что при передаче слайса, как аргумента, аргумент уже является указателем?
0
korvin_
2160 / 1649 / 318
Регистрация: 28.04.2012
Сообщений: 5,906
07.02.2018, 08:45 6
Цитата Сообщение от Firik67 Посмотреть сообщение
Я правильно понимаю, что пока не завершатся все дочерние горутины, основная не завершится?
Неправильно. Только какое это имеет отношение к моему замечанию?

Цитата Сообщение от Firik67 Посмотреть сообщение
Потому что при передаче слайса, как аргумента, аргумент уже является указателем?
Go Slices: usage and internals
0
Firik67
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
07.02.2018, 14:08  [ТС] 7
Цитата Сообщение от korvin_ Посмотреть сообщение
Только какое это имеет отношение к моему замечанию?
хочу понять, как оно работает
0
Dmitriy_M
1427 / 1308 / 130
Регистрация: 20.03.2009
Сообщений: 4,675
Записей в блоге: 11
09.02.2018, 12:16 8
Цитата Сообщение от Firik67 Посмотреть сообщение
Странно, возможно свою роль сыграло разное время ответа от серверов
Рекомендую проверять код с Data Race Detector
Ну и как вводная в язык есть https://www.coursera.org/learn/golang-webservices-1
1
09.02.2018, 12:16
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
09.02.2018, 12:16

Настройка парсера C#
Помогите с настройкой парсера C# namespace Parser.Core.Habra { class...

Цикл парсера
Всем привет, у меня проблема, я не могу понять как правильно сделать цикл для...

Идея парсера
На днях начал разбираться с темой парсеров. Хочу создать парсер спортивных...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru