内容简介:第一次在掘金水文章,有一点点小激动,哈哈本次使用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)——队列实现并发任务调度
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Rationality for Mortals
Gerd Gigerenzer / Oxford University Press, USA / 2008-05-02 / USD 65.00
Gerd Gigerenzer's influential work examines the rationality of individuals not from the perspective of logic or probability, but from the point of view of adaptation to the real world of human behavio......一起来看看 《Rationality for Mortals》 这本书的介绍吧!