func parseItem(item *Item, wg *sync.WaitGroup, items chan *Item) { throttleWait() doc, err := goquery.NewDocument(item.url) if err != nil { Error("%s", err.Error()) wg.Done() return } item.location = doc.Find(".avito-address-text").First().Text() item.location = strings.TrimSpace(item.location) doc.Find(".action-show-number").Each(func(i int, s *goquery.Selection) { phone_url, exists := s.Attr("href") if exists { Debug("Found phone url: %s", phone_url) phone_url := strings.Join([]string{BASE_URL, phone_url, "?async"}, "") item.phone, err = getPhone(phone_url, item.url) if err != nil { Error("%s", err.Error()) } else { items <- item } } }) wg.Done() }
func main() { var query string var location string var category string var path_to_csvfile string var verbose bool var max_items int64 var pause int64 var SelaAvitoCmd = &cobra.Command{ Use: "selavito", Short: "Утилита для парсинга объявлений (вместе с телефонными номерами) с сайта avito.ru", Example: "selavito -l moskva -q macbook --csv output.csv\niselavito -l sankt-peterburg -с rabota -q golang --csv output.csv", Run: func(cmd *cobra.Command, args []string) { InitLoggers(verbose) if query == "" || path_to_csvfile == "" { cmd.Help() return } throttleSet(pause) var page_url string counter := max_items items := make(chan *Item) save_wg := new(sync.WaitGroup) parse_wg := new(sync.WaitGroup) save_wg.Add(1) go saveToCSV(path_to_csvfile, items, save_wg) if category == "" { page_url = fmt.Sprintf("%s/%s?q=%s", BASE_URL, location, query) } else { page_url = fmt.Sprintf("%s/%s/%s?q=%s", BASE_URL, location, category, query) } items_done := 0 // max_items == 0 - без ограничения for page_url != "" && (counter > 0 || max_items == 0) { Info("Парсинг страницы: %s", page_url) throttleWait() doc, err := goquery.NewDocument(page_url) if err != nil { Error(err.Error()) break } next_page_url, exists := doc.Find(".page-next").Find("a").First().Attr("href") if exists { next_page_url = fmt.Sprintf("%s%s", BASE_URL, next_page_url) Info("Следующая страница: %s", next_page_url) } items_category := doc.Find(".nav-helper-header").First().Text() if items_category == "" { Error("Неверный формат страницы! Скорее всего ваш IP забанили!") break } items_count := doc.Find(".nav-helper-text").First().Text() items_category = strings.TrimSpace(items_category) items_count = strings.TrimSpace(items_count) if items_done == 0 { fmt.Println("Категория:", items_category) fmt.Println("Найдено объявлений:", items_count) } else { fmt.Printf("Процесс выполнения: %d/%s\n", items_done, items_count) } doc.Find(".b-item").Each(func(i int, s *goquery.Selection) { if counter > 0 { item_url, exists := s.Find(".item-link").Attr("href") if exists { var item Item item.header = s.Find(".header-text").First().Text() item.location = s.Find(".info-location").First().Text() item.url = fmt.Sprintf("%s%s", BASE_URL, item_url) parse_wg.Add(1) go parseItem(&item, parse_wg, items) counter-- items_done++ Debug("%+v\n", item) } else { Error(".item-link not found") } } }) page_url = next_page_url } // Дожидаемся завершения работы всех парсеров... parse_wg.Wait() // ...и только после закрываем канал close(items) // Ждём пока данные окончательно сохранятся save_wg.Wait() }, } SelaAvitoCmd.Flags().StringVarP(&query, "query", "q", "", "Строка для поиска") SelaAvitoCmd.Flags().StringVarP(&location, "location", "l", "rossiya", "Фильтр по региону (примеры: moskva, moskovskaya_oblast, sankt-peterburg)") SelaAvitoCmd.Flags().StringVarP(&category, "category", "c", "", "Фильтр по категории (примеры: nedvizhimost, transport, rabota, rezume, vakansii)") SelaAvitoCmd.Flags().StringVar(&path_to_csvfile, "csv", "", "Путь к csv файлу для сохранения данных") SelaAvitoCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Более подробный вывод в консоль") SelaAvitoCmd.Flags().Int64VarP(&max_items, "max", "m", 1, "Максимальное количество элементов для поиска (0 - без ограничения)") SelaAvitoCmd.Flags().Int64VarP(&pause, "pause", "p", 0, "Пауза между запросами (в микросекундах)") SelaAvitoCmd.Execute() }