博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang 并发爬虫 爬取某著名游戏媒体
阅读量:6266 次
发布时间:2019-06-22

本文共 7140 字,大约阅读时间需要 23 分钟。

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

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

主要使用的第三方包是 ,来解析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页面了。

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

这里新闻链接出现在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解析的对象了。

标题在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时就返回。

接下来是时间的处理,我们可以看到时间在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 mysqlimport (    "database/sql"    "fmt"    "os"    _ "github.com/go-sql-driver/mysql")var db *sql.DBfunc 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并发的优势,所以说既想并发抓取数据又不想被反爬虫,配置一个不错的代理池很有必要,但是这里就不做说明了。

接下来上完整的爬虫代码

package mainimport (	"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()}复制代码

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

转载于:https://juejin.im/post/5cdd0da16fb9a0322564e4f9

你可能感兴趣的文章
bzoj 2127: happiness
查看>>
Python 3.5 之路 day1
查看>>
selenium使用chrome抓取自动消失弹框的方法
查看>>
实现strStr()---简单
查看>>
只有PD号的调起
查看>>
返回一个整数数组中最大子数组的和
查看>>
leetcode(二)
查看>>
利用css实现居中的方法
查看>>
Spring + Hibernate 框架
查看>>
添加浏览器的用户样式表
查看>>
LigerUI学习笔记之布局篇 layout
查看>>
LeetCode题解(二)
查看>>
Mybatis通用Mapper
查看>>
文件磁盘命令(就该这么学6章内容)
查看>>
2016-207-19 随笔
查看>>
java的double类型如何精确到一位小数?
查看>>
看看国外的javascript题目,你能全部做对吗?
查看>>
ffmpeg 如何选择具有相同AVCodecID的编解码器 (AVCodec)
查看>>
真正解决 Windows 中 Chromium “缺少 Google API 密钥” 的问题
查看>>
Spring 之 AOP
查看>>