第一次在掘金水文章,有一點點小激動,哈哈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()
}
複製代碼
到此,本篇文章就結束了,若是在以上文章中有任何問題,都請各位賜教,很是感謝!!!