Почему в Go зависает http.Get()?

Есть задача заключающаяся в скачивании большого количества страниц. Сперва использовал библиотеку goquery, что бы было проще получать текст нужных мне блоков, но заметил что в некоторых ситуациях программа просто останавливалась, без ошибок, без остановки программы, она просто зависала. Думал что проблема в этой библиотеке, отказался от нее и стал получать код страницы стандартным способом Go - http.Get(url) но проблема так и не исчезла. Удалось выяснить только то, что программа останавливается на вызове http.Get(). В чем может быть проблема?
func getPage(url string)(HTML string, err error){
	res, err := http.Get(url) 
	if err != nil { 
		return "", err
	} 
	defer res.Body.Close() 
	body, err := ioutil.ReadAll(res.Body) 
	if err != nil { 
		return "", err
	} 
	lenp := len(body) 
	return string(body[:lenp]), nil
}
  • Вопрос задан
  • 3445 просмотров
Решения вопроса 1
Я бы повесил timeout (уменьшил бы его до приемлемого).
Если ответ не пришёл в течение N секунд - возвращать ошибку.
Timeout создается при помощи добавления кастомного http.Transport в конструктор http.Client (15-20 строки).

Код программы c timeout'ом.
package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"time"
)

func getPage(url string, timeout time.Duration) (HTML string, e error) {
	client := &http.Client{
		Transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				return net.DialTimeout(network, addr, timeout)
			},
			ResponseHeaderTimeout: timeout,
		},
	}

	req, e := http.NewRequest("GET", url, nil)
	if e != nil {
		return "", errors.New(fmt.Sprintf(`http.NewRequest failed: %s`, e.Error()))
	}

	resp, e := client.Do(req)
	if e != nil {
		return "", errors.New(fmt.Sprintf("client.Do failed: %s)", e.Error()))
	}
	defer resp.Body.Close()

	bodyAsBytes, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return "", errors.New(fmt.Sprintf("ioutil.ReadAll failed: %s)", e.Error()))
	}
	bodyAsBuffer := bytes.NewBuffer(bodyAsBytes)

	return bodyAsBuffer.String(), nil
}

func main() {
	HTML, e := getPage("http://google.com/", time.Duration(1*time.Second))
	if e != nil {
		fmt.Printf("[ERROR] %s\n", e.Error())
	} else {
		fmt.Printf("[INFO] %s\n", HTML)
	}
}



Если timeout выставить так (1 наносекунда), то программа выведет сообщение об ошибке:
>>> HTML, e := getPage("http://google.com/", time.Duration(1*time.Nanosecond))
[ERROR] client.Do failed: Get http://google.com/: i/o timeout)


Если timeout выставить так (5 секунд), то программа выведет HTML странички:
HTML, e := getPage("http://google.com/", time.Duration(5*time.Second))
[INFO] <!doctype html><html itemscope="" itemtype="http://schema.org/WebPage"><head><meta content="........


Я сделал бы pool задач на скачивание. Если некоторые не завершились успехом - перезапускал бы их через некоторое время. На мой взгляд это типичная задача pool - worker:
1) Можно решать через resque + goworker например. Это дает наглядность. Упавшие задачи можно сразу перезапустить, увидеть что пошло не так. Статистика копится;
2) Можно решать, создав горутину как pool и N горутин как worker'ы. И самому создать логику обработки упавших задач.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 1
taliban
@taliban
php программист
Может вас банят на стороне откуда скачиваете? Видят что вы начинаете лить все и тупо включают некую защиту. Попробуйте выставить таймаут маленький.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы