go并发爬虫简单demo

sgsgy5 · · 1429 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

爬虫小demo,大佬勿喷,欢迎提意见 [github](https://github.com/wumansgy/wuman-Small-projects) ### 爬虫 爬虫简介: 是一个程序,用来获取指定网站数据信息。 - 明确 url 。确定爬取对象 - 发送请求。获取服务器响应数据。 - 解析数据,提取有用数据内容。 - 保存、分析数据结果。 今天我们用go并发来简单写一个小Demo来爬取一下豆瓣评分网站的数据 首先来思路分析一下该怎么做:1. **明确 url。找出url之间的一些小规律,比如豆瓣的url规律如下** ``` https://movie.douban.com/top250?start=0&filter= 1 https://movie.douban.com/top250?start=25&filter= 2 https://movie.douban.com/top250?start=50&filter= 3 https://movie.douban.com/top250?start=75&filter= 4 ``` 之间的规律很好找 1. **待提取字符特性:**1. **提示用户指定爬取起始、终止页** 2. **封装 doWork 函数, 按起始、终止页面循环爬取网页数据** 3. **组织每个网页的 url。 下一页 = +25** 4. **封装函数 HttpGetDB(url)result,err { http.Get(url), resp.Body.Read(buf), n==0 break, result+= string(buf[:n}) }** **爬取网页的所有数据 通过result 返回给调用者。** 5. **解析、编译正则表达式 —— 提取 “电影名称”fileNames 传出的是[][]string , 下标为【1】是不带匹配参考项。** 6. **解析、编译正则表达式 —— 提取 “评分”传出的是[][]string , 下标为【1】是不带匹配参考项。** 7. **解析、编译正则表达式 —— 提取 “评价人数”传出的是[][]string , 下标为【1】是不带匹配参考项。** 8. **封装函数,将上述内容写入文件。save2File( [][]string)** 9. **创建并发go程 提取所有网页数据。** 10. **创建阻止主go程提取退出的 channel , SpiderPageDB() 末尾处,写channel** 11. **doWork 中,添加新 for ,读channel** #### 代码实现: 首先可以封装一个函数来爬取多少页到多少页,并且每一页构造一个go程 ``` func doWork(start, end int) { page := make(chan int) // 循环创建多个goroutine,提高爬取效率 for i:=start; i<=end; i++ { go SpiderPageDB(i, page) } // 循环读取 channel, 协调主、子go程调用顺序 for i:=start; i<=end; i++ { fmt.Printf("第%d页爬取完成\n", <-page) } } ``` 获取每一页的数据函数 ``` func HttpGetDB(url string) (result string, err error) { resp, err1 := http.Get(url) if err1 != nil { err = err1 return } defer resp.Body.Close() buf := make([]byte, 4096) for { n, err2 := resp.Body.Read(buf) if n == 0 { break } if err2 != nil && err2 != io.EOF { err = err2 return } result += string(buf[:n]) } return } ``` 得到每一个数据然后分析数据解析数据 ``` func SpiderPageDB(i int, page chan<- int) { url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter=" result, err := HttpGetDB(url) if err != nil { fmt.Println("HttpGetDB err:", err) return } // 编译、解析正则表达式 —— 电影名 ret1 := regexp.MustCompile(`<img width="100" alt="(?s:(.*?))" src="` ) // 提取有效信息 fileNames := ret1.FindAllStringSubmatch(result, -1) // 编译、解析正则表达式 —— 分数 pattern := `<span class="rating_num" property="v:average">(.*?)</span>` ret2 := regexp.MustCompile(pattern ) // 提取有效信息 fileScore := ret2.FindAllStringSubmatch(result, -1) /* for _, one := range fileScore { fmt.Println("fileName:", one[1]) }*/ // 编译、解析正则表达式 —— 评分人数 ret3 := regexp.MustCompile(`<span>(\d*?)人评价</span>`) // 提取有效信息 peopleNum := ret3.FindAllStringSubmatch(result, -1) // 写入到一个文件中 save2file(i, fileNames, fileScore, peopleNum) page <- i // 写入channel ,协调主go程与子go程调用顺序。 } ``` 最后保存数据到文件中 ``` func save2file(idx int, fileNames, fileScore, peopleNum [][]string) { // 组织保存文件路径及名程 path := "D:/ecec/第" + strconv.Itoa(idx) + "页.txt" f, err := os.Create(path) if err != nil { fmt.Println("Create err:", err) return } defer f.Close() // 获取 一个网页中的条目数 —— 25 n := len(fileNames) // 写一行标题 f.WriteString("电影名称" + "\t" + "评分" + "\t" + "评价人数" + "\n") // 依次按序写入电影相关条目。 for i:=0; i<n; i++ { f.WriteString(fileNames[i][1] + "\t" + fileScore[i][1] + "\t" + peopleNum[i][1] + "\n") } } ``` 最后直接在main里面就可以调用 ### 完整代码如下 ``` package main import ( "fmt" "strconv" "net/http" "io" "regexp" "os" ) func HttpGetDB(url string) (result string, err error) { resp, err1 := http.Get(url) if err1 != nil { err = err1 return } defer resp.Body.Close() buf := make([]byte, 4096) for { n, err2 := resp.Body.Read(buf) if n == 0 { break } if err2 != nil && err2 != io.EOF { err = err2 return } result += string(buf[:n]) } return } func SpiderPageDB(i int, page chan<- int) { url := "https://movie.douban.com/top250?start=" + strconv.Itoa((i-1)*25) + "&filter=" result, err := HttpGetDB(url) if err != nil { fmt.Println("HttpGetDB err:", err) return } // 编译、解析正则表达式 —— 电影名 ret1 := regexp.MustCompile(`<img width="100" alt="(?s:(.*?))" src="` ) // 提取有效信息 fileNames := ret1.FindAllStringSubmatch(result, -1) // 编译、解析正则表达式 —— 分数 pattern := `<span class="rating_num" property="v:average">(.*?)</span>` ret2 := regexp.MustCompile(pattern ) // 提取有效信息 fileScore := ret2.FindAllStringSubmatch(result, -1) /* for _, one := range fileScore { fmt.Println("fileName:", one[1]) }*/ // 编译、解析正则表达式 —— 评分人数 ret3 := regexp.MustCompile(`<span>(\d*?)人评价</span>`) // 提取有效信息 peopleNum := ret3.FindAllStringSubmatch(result, -1) // 写入到一个文件中 save2file(i, fileNames, fileScore, peopleNum) page <- i // 写入channel ,协调主go程与子go程调用顺序。 } func save2file(idx int, fileNames, fileScore, peopleNum [][]string) { // 组织保存文件路径及名程 path := "C:/exec/第" + strconv.Itoa(idx) + "页.txt" f, err := os.Create(path) if err != nil { fmt.Println("Create err:", err) return } defer f.Close() // 获取 一个网页中的条目数 —— 25 n := len(fileNames) // 写一行标题 f.WriteString("电影名称" + "\t" + "评分" + "\t" + "评价人数" + "\n") // 依次按序写入电影相关条目。 for i:=0; i<n; i++ { f.WriteString(fileNames[i][1] + "\t" + fileScore[i][1] + "\t" + peopleNum[i][1] + "\n") } } func doWork(start, end int) { page := make(chan int) // 循环创建多个goroutine,提高爬取效率 for i:=start; i<=end; i++ { go SpiderPageDB(i, page) } // 循环读取 channel, 协调主、子go程调用顺序 for i:=start; i<=end; i++ { fmt.Printf("第%d页爬取完成\n", <-page) } } func main() { var start, end int fmt.Print("请输入爬取起始页面(>=1):") fmt.Scan(&start) fmt.Print("请输入爬取终止页面(>=start):") fmt.Scan(&end) doWork(start, end) } ```

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1429 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传