内容简介:第一次在掘金水文章,有一点点小激动,哈哈本次使用Golang抓取著名(la ji)游戏媒体游民星空主要使用的第三方包是
第一次在掘金水文章,有一点点小激动,哈哈
本次使用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页面了。
首先我们获取这一页所有的新闻链接
这里新闻链接出现在 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 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并发的优势,所以说既想并发抓取数据又不想被反爬虫,配置一个不错的代理池很有必要,但是这里就不做说明了。
接下来上完整的爬虫代码
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 - 并发版爬虫
- Go语言项目实战:并发爬虫
- golang练手小项目系列(2)-并发爬虫
- Golang实现简单爬虫框架(3)——简单并发版
- 50行代码实现一个并发的 Python 爬虫程序
- Golang实现简单爬虫框架(4)——队列实现并发任务调度
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。