Параллельный краулер Golang сканирует известный игровой носитель

Go
Параллельный краулер Golang сканирует известный игровой носитель

Впервые в статье о воде Наггетс я был немного взволнован, ха-ха

На этот раз мы используем Golang, чтобы получить знаменитые (la ji) игровые медиа.Кочевая звезда

Основные используемые сторонние пакеты:goquery, для разбора HTML не имеет значения, если вы не использовали goquery, это очень просто.

Второй — вставить данные в MySql с помощью Golang.

Во-первых, используйтеnet/httpстраница запроса пакета

func main() {
	url := "https://www.gamersky.com/news/"
	
	resp, err := http.Get(url)
	if err != nil {
	    log.Fatal(err)
	}
	
	defer resp.Body.Close()
	if res.StatusCode != 200 {
	    log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
	}
}

Здесь ошибки страницы запроса не допускаются, поэтому используйтеlog.FatalВыход сразу при возникновении ошибки.

Следующее использованиеgoquery.NewDocumentFromReaderЗагрузите HTML как анализируемый тип.

    // NewDocumentFromReader returns a Document from an io.Reader.
    html, err := goquery.NewDocumentFromReader(resp.Body)

Далее мы можем использоватьgoqueryРазобрать HTML-страницу.

Сначала мы получаем все ссылки на новости на этой странице

Здесь ссылка на новости появляется по адресуclass="tt"изaвкладка, поэтому мы используемgoquery, проанализируйте атрибут href всех тегов a с атрибутом «tt» под страницей, и тогда вы сможете получить все ссылки на новости под измененной страницей.

func getNewsList(html *goquery.Document, newsList []string) []string {
	html.Find("a[class=tt]").Each(func(i int, selection *goquery.Selection) {
	    url, _ := selection.Attr("href")
	    newsList = append(newsList, url)
	})
	return newsList
}

Таким образом, мы получаем все ссылки на новости с главной страницы новостей и помещаем все ссылки вnewsListв этом отрезке.

Далее, давайте начнем сканировать конкретные новости в этих новостных ссылках.

Используйте горутину для реализации одновременных запросов этих новостных ссылок и анализа результатов.

        var newsList []string
	newsList = getNewsList(html, newsList)

	var wg sync.WaitGroup
	for i := 0; i < len(newsList); i++ {
	    wg.Add(1)
	    go getNews(newsList[i], &wg)
	}
	wg.Wait()

Сначала мы инициализируемsync.WaitGroup, который используется для управления выполнением горутин и гарантирует, что все горутины выполняются до конца.

Пройдите через это, где мы храним все ссылки на новостиnewsList, новостная ссылка запускает соответствующую горутину для обработки следующей обработки.

wg.Wait()Используется для блокировки выполнения программы до тех пор, пока не будут выполнены все задачи в wg.

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

Сначала мы определяемNewsэта структура.

type News struct {
	Title   string
	Media   string
	Url     string
	PubTime string
	Content string
}

Как и в первом шаге, сначала нам нужно запросить ссылку на новость.

func getNews(url string, wg *sync.WaitGroup) {
	resp, err := http.Get(url)
	if err != nil {
	    log.Println(err)
	    wg.Done()
	    return
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
	    log.Printf("Error: status code %d", resp.StatusCode)
	    wg.Done()
	    return
	}
	
	html, err := goquery.NewDocumentFromReader(resp.Body)
	news := News{}

Благодаря вышеописанным шагам HTML, который мы успешно запросили, был преобразован в объект, который можно проанализировать с помощью goquer.

Заголовок находится в h1 под div с class="Mid2L_tit".

html.Find("div[class=Mid2L_tit]>h1").Each(func(i int, selection *goquery.Selection) {
	news.Title = selection.Text()
    })
	
if news.Title == "" {
	wg.Done()
	return
    }

Формат отдельных столбцов новостей здесь отличается от формата обычных страниц новостей, поэтому пока с ним можно хорошо обращаться, поэтому, когда заголовок не будет проанализирован, он будет возвращен.

Далее идет обработка времени, мы видим, что времяdiv class="detail"Однако время, проанализированное таким образом, не может быть напрямую сохранено в базе данных.Здесь я использую регулярные выражения для извлечения всех дат и времени, а затем объединяю их в формат, который можно сохранить в базе данных.

    var tmpTime string
    html.Find("div[class=detail]").Each(func(i int, selection *goquery.Selection) {
		tmpTime = selection.Text()
    })
    reg := regexp.MustCompile(`\d+`)
    timeString := reg.FindAllString(tmpTime, -1)
    news.PubTime = fmt.Sprintf("%s-%s-%s %s:%s:%s", timeString[0], timeString[1], timeString[2], timeString[3], timeString[4], timeString[5])

Если есть лучший способ, ты должен научить меня! ! !

Далее следует парсить текст новости

Текст новости есть весьdiv class="Mid2L_con"в теге p ниже.

html.Find("div[class=Mid2L_con]>p").Each(func(i int, selection *goquery.Selection) {
    news.Content = news.Content + selection.Text()
})

Теперь, когда у нас есть все необходимые данные, следующим шагом будет сохранение этих данных в MySql.

Сначала создайте таблицу с именем gamesky.

create table gamesky
(
    id          int auto_increment
        primary key,
    title       varchar(256)                        not null,
    media       varchar(16)                         not null,
    url         varchar(256)                        not null,
    content     varchar(4096)                       null,
    pub_time    timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP,
    create_time timestamp default CURRENT_TIMESTAMP not null
);

Далее мы устанавливаем соединение Mysql.

package mysql

import (
    "database/sql"
    "fmt"
    "os"

    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func init() {
    db, _ = sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/game_news?charset=utf8")
    db.SetMaxOpenConns(1000)
    err := db.Ping()
    if err != nil {
        fmt.Println("Failed to connect to mysql, err:" + err.Error())
        os.Exit(1)
    }
}

func DBCon() *sql.DB {
    return db
}

Следующим шагом является использование установленного нами соединения MySql для сохранения полученных данных.

    db := mysql.DBCon()
    
    stmt, err := db.Prepare(
        "insert into news (`title`, `url`, `media`, `content`, `pub_time`) values (?,?,?,?,?)")
    if err != nil {
	log.Println(err)
	wg.Done()
    }
    defer stmt.Close()

    rs, err := stmt.Exec(news.Title, news.Url, news.Media, news.Content, news.PubTime)
    if err != nil {
        log.Println(err)
        wg.Done()
    }
    if id, _ := rs.LastInsertId(); id > 0 {
        log.Println("插入成功")
    }
    wg.Done()

rs.LastInsertId()Он используется для получения идентификатора данных, только что вставленных в базу данных.Если вставка прошла успешно, будет возвращен идентификатор соответствующей записи, чтобы мы могли узнать, была ли вставка успешной.

Длина текста новости иногда превышает длину столбца, установленного в MySql, вы можете изменить длину столбца или перехватить часть текста для сохранения.

После возникновения ошибки в горутине или после сохранения базы данных не забудьтеwg.Done()чтобы уменьшить количество задач в wg на 1.

Таким образом, наш сканер будет одновременно захватывать новости и сохранять их в базе данных.

Видно, что из-за того, что наша скорость сканирования слишком высока, сработал антикраулер Nomad Star, поэтому нам нужно уменьшить частоту, но это потеряет преимущество параллелизма Golang, поэтому мы хотим захватывать данные одновременно и Не хочу, чтобы мне противостояли.Для краулеров необходимо настроить хороший пул прокси, но я не буду это здесь объяснять.

Далее переходим к полному коду сканера

package main

import (
	"fmt"
        "game_news/mysql"
	"log"
	"net/http"
	"regexp"
	"sync"

	"github.com/PuerkitoBio/goquery"
)

type News struct {
	Title   string
	Media   string
	Url     string
	PubTime string
	Content string
}

func main() {
	url := "https://www.gamersky.com/news/"
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
	}

	html, err := goquery.NewDocumentFromReader(resp.Body)
	var newsList []string
	newsList = getNewsList(html, newsList)

	var wg sync.WaitGroup
	for i := 0; i < len(newsList); i++ {
		wg.Add(1)
		go getNews(newsList[i], &wg)
	}
	wg.Wait()
}

func getNewsList(html *goquery.Document, newsList []string) []string {
	// '//a[@class="tt"]/@href'
	html.Find("a[class=tt]").Each(func(i int, selection *goquery.Selection) {
		url, _ := selection.Attr("href")
		newsList = append(newsList, url)
	})
	return newsList
}

func getNews(url string, wg *sync.WaitGroup) {
	resp, err := http.Get(url)
	if err != nil {
		log.Println(err)
		wg.Done()
		return
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		log.Printf("Error: status code %d", resp.StatusCode)
		wg.Done()
		return
	}

	html, err := goquery.NewDocumentFromReader(resp.Body)
	news := News{}

	news.Url = url
	news.Media = "GameSky"
	html.Find("div[class=Mid2L_tit]>h1").Each(func(i int, selection *goquery.Selection) {
		news.Title = selection.Text()
	})

	if news.Title == "" {
		wg.Done()
		return
	}

	html.Find("div[class=Mid2L_con]>p").Each(func(i int, selection *goquery.Selection) {
		news.Content = news.Content + selection.Text()
	})

	var tmpTime string
	html.Find("div[class=detail]").Each(func(i int, selection *goquery.Selection) {
		tmpTime = selection.Text()
	})
	reg := regexp.MustCompile(`\d+`)
	timeString := reg.FindAllString(tmpTime, -1)
	news.PubTime = fmt.Sprintf("%s-%s-%s %s:%s:%s", timeString[0], timeString[1], timeString[2], timeString[3], timeString[4], timeString[5])

	db := mysql.DBCon()

	stmt, err := db.Prepare(
		"insert into gamesky (`title`, `url`, `media`, `content`, `pub_time`) values (?,?,?,?,?)")
	if err != nil {
		log.Println(err)
		wg.Done()
	}
	defer stmt.Close()

	rs, err := stmt.Exec(news.Title, news.Url, news.Media, news.Content, news.PubTime)
	if err != nil {
		log.Println(err)
		wg.Done()
	}
	if id, _ := rs.LastInsertId(); id > 0 {
		log.Println("插入成功")
	}
	wg.Done()
}

На этом статья окончена.Если у вас есть какие-либо вопросы по вышеуказанным статьям, просветите меня, спасибо большое! ! !