Golang 并发爬虫 爬取某著名游戏媒体

栏目: Go · 发布时间: 5年前

内容简介:第一次在掘金水文章,有一点点小激动,哈哈本次使用Golang抓取著名(la ji)游戏媒体游民星空主要使用的第三方包是

第一次在掘金水文章,有一点点小激动,哈哈

Golang 并发爬虫 爬取某著名游戏媒体

本次使用Golang抓取著名(la ji)游戏媒体游民星空

主要使用的第三方包是 goquery ,来解析HTML,如果你没有使用过goquery也不要紧,非常简单。

其次是使用Golang将数据插入MySql。

首先,使用 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页面了。

首先我们获取这一页所有的新闻链接

Golang 并发爬虫 爬取某著名游戏媒体

这里新闻链接出现在 class="tt"a 标签下,所以我们使用 goquery ,解析出该页面下所有属性为'tt'的a标签的href属性,就可以拿到所有改页面下的新闻链接了。

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 这个slice中。

接下来我们就开始爬取这些新闻链接中的具体新闻吧。

使用goroutine实现并发的请求这些新闻链接,并解析出结果。

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 ,用来控制goroutine的运行,确保所有的goroutine运行完成。

遍历我们存放了所有新闻链接的这个 newsList ,一个新闻链接开启一个对应的goroutine来处理接下来的处理过程。

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解析的对象了。

Golang 并发爬虫 爬取某著名游戏媒体

标题在class="Mid2L_tit"的div下的h1中。

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

这里个别新闻专栏是和普通新闻页面格式是不同的,暂时就不错处理了,所以当没有解析出Title时就返回。

Golang 并发爬虫 爬取某著名游戏媒体

接下来是时间的处理,我们可以看到时间在 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])
复制代码

如果有更好的办法,大家一定要教我啊!!!

Golang 并发爬虫 爬取某著名游戏媒体

接下里是解析新闻正文

Golang 并发爬虫 爬取某著名游戏媒体

新闻正文都在 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() 是用来获取刚刚插入数据库的数据的id的,插入成功的话就会返回对应记录的id,由此我们可以知道是否插入成功。

新闻正文的长度有时长度会超过MySql中设定好的列长度,可以修改列长度或者截取一部分正文保存。

在一个goroutine中出现错误,或者保存数据库结束之后,要记得 wg.Done() 来让wg中的任务数减1。

这样我们的爬虫就并发的将新闻抓取下来,并保存入数据库中了。

Golang 并发爬虫 爬取某著名游戏媒体

可以看到由于我们抓取的速度太快,已经触发了游民星空的反爬虫,所以需要降低频率才可以,但是这样就失去了Golang并发的优势,所以说既想并发抓取数据又不想被反爬虫,配置一个不错的代理池很有必要,但是这里就不做说明了。

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()
}

复制代码

到此,本篇文章就结束了,如果在以上文章中有任何问题,都请各位赐教,非常感谢!!!

Golang 并发爬虫 爬取某著名游戏媒体

以上所述就是小编给大家介绍的《Golang 并发爬虫 爬取某著名游戏媒体》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

领域特定语言

领域特定语言

Martin Fowler / ThoughtWorks中国 / 机械工业出版社华章公司 / 2013-3 / 89.00元

本书是DSL领域的丰碑之作,由世界级软件开发大师和软件开发“教父”Martin Fowler历时多年写作而成,ThoughtWorks中国翻译。全面详尽地讲解了各种DSL及其构造方式,揭示了与编程语言无关的通用原则和模式,阐释了如何通过DSL有效提高开发人员的生产力以及增进与领域专家的有效沟通,能为开发人员选择和使用DSL提供有效的决策依据和指导方法。 全书共57章,分为六个部分:第一部分介......一起来看看 《领域特定语言》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具