第一次在掘金水文章,有一点点小激动,哈哈html
本次使用Golang抓取著名(la ji)游戏媒体 游民星空mysql
主要使用的第三方包是 goquery ,来解析HTML,若是你没有使用过goquery也没关系,很是简单。git
其次是使用Golang将数据插入MySql。github
首先,使用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
出现错误时直接退出。sql
接下来使用goquery.NewDocumentFromReader
将HTML加载为可解析的类型。数据库
// NewDocumentFromReader returns a Document from an io.Reader.
html, err := goquery.NewDocumentFromReader(resp.Body)
复制代码
接下里咱们就可使用goquery
解析HTML页面了。bash
首先咱们获取这一页全部的新闻连接并发
这里新闻连接出如今class="tt"
的a
标签下,因此咱们使用goquery
,解析出该页面下全部属性为'tt'的a标签的href属性,就能够拿到全部改页面下的新闻连接了。app
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()
}
复制代码
到此,本篇文章就结束了,若是在以上文章中有任何问题,都请各位赐教,很是感谢!!!