Форум программистов, компьютерный форум, киберфорум
Go (Golang)
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.86/7: Рейтинг темы: голосов - 7, средняя оценка - 4.86
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25

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

04.02.2018, 13:23. Показов 1654. Ответов 7

Студворк — интернет-сервис помощи студентам
Решил тут попробовать силы в Go, набросал немного кода. Хотелось бы немного кодревью. Куда можно скинуть? Посмотреть можно тут. Что интересует больше всего: правильно ли используются каналы и горутины?
Ну и сама задача примерно следующая:
Написать парсер, который будет подключатся к rss-лентам, собирать их в один источник, сортировать все записи по дате и сохранять в формате rss
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
04.02.2018, 13:23
Ответы с готовыми решениями:

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

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

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

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

main.go
Go
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
Go
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
Go
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
Go
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
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
06.02.2018, 10:50
Цитата Сообщение от Firik67 Посмотреть сообщение
всегда думал, что на гитхабе смотреть удобнее
+1.

По коду:

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

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

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

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

Go
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

Go
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
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
06.02.2018, 22:47  [ТС]
korvin_,
1) Я что-то не понял: сначала ты берёшь айтемы из rss.Channel.Items и отправляешь их в канал rssItemsChannel (процедура ProcessUrl), а потом берёшь их из канала в процедуре CombineItems и добавляешь в этот же массив (rss.Channel.Items = append(rss.Channel.Items, item))?
Да, действительно, допустил логическую ошибку. Срез каждый раз передавал сам себя + новые элементы, что в 7 раз по объему увеличивало выходной файл и время работы скрипта. Корни этой ошибки вижу в том, что изначально скрипт писался на простых функциях, без привязки к структуре, а потом просто не до конца отрефакторился.

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

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

И не нужно использовать указатели на слайсы.
Потому что при передаче слайса, как аргумента, аргумент уже является указателем?
0
Эксперт функциональных языков программированияЭксперт Java
 Аватар для korvin_
4575 / 2774 / 491
Регистрация: 28.04.2012
Сообщений: 8,779
07.02.2018, 08:45
Цитата Сообщение от Firik67 Посмотреть сообщение
Я правильно понимаю, что пока не завершатся все дочерние горутины, основная не завершится?
Неправильно. Только какое это имеет отношение к моему замечанию?

Цитата Сообщение от Firik67 Посмотреть сообщение
Потому что при передаче слайса, как аргумента, аргумент уже является указателем?
Go Slices: usage and internals
0
0 / 0 / 1
Регистрация: 22.10.2014
Сообщений: 25
07.02.2018, 14:08  [ТС]
Цитата Сообщение от korvin_ Посмотреть сообщение
Только какое это имеет отношение к моему замечанию?
хочу понять, как оно работает
0
1443 / 1326 / 131
Регистрация: 20.03.2009
Сообщений: 4,689
Записей в блоге: 11
09.02.2018, 12:16
Цитата Сообщение от Firik67 Посмотреть сообщение
Странно, возможно свою роль сыграло разное время ответа от серверов
Рекомендую проверять код с Data Race Detector
Ну и как вводная в язык есть https://www.coursera.org/learn... services-1
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
09.02.2018, 12:16
Помогаю со студенческими работами здесь

Настройка парсера C#
Помогите с настройкой парсера C# namespace Parser.Core.Habra { class HabraParser : IParser&lt;string&gt; { public...

Написание парсера
В общем не так давно учу VB, и тут поставил себе задачу написать &quot;разделитель&quot; объясню кто не понял к примеру есть текстовик с ...

Результат парсера
Я спарсил текст между &lt;teg&gt; и &lt;/teg&gt; &lt;teg&gt; TEXT TEXT TEXT &lt;/teg&gt; Спарсил в цикле,то есть все результаты.В итоге я получил...

Примеры парсера
Здравствуйте! Можете кинуть литературу парсеров на UWP?

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


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

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Реализация движения на Box2D v3 - трение и коллизии с повёрнутыми стенами
8Observer8 20.02.2026
Содержание блога Box2D позволяет легко создать главного героя, который не проходит сквозь стены и перемещается с заданным трением о препятствия, которые можно располагать под углом, как верхнее. . .
Конвертировать закладки radiotray-ng в m3u-плейлист
damix 19.02.2026
Это можно сделать скриптом для PowerShell. Использование . \СonvertRadiotrayToM3U. ps1 <path_to_bookmarks. json> Рядом с файлом bookmarks. json появится файл bookmarks. m3u с результатом. # Check if. . .
Семь CDC на одном интерфейсе: 5 U[S]ARTов, 1 CAN и 1 SSI
Eddy_Em 18.02.2026
Постепенно допиливаю свою "многоинтерфейсную плату". Выглядит вот так: https:/ / www. cyberforum. ru/ blog_attachment. php?attachmentid=11617&stc=1&d=1771445347 Основана на STM32F303RBT6. На борту пять. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru