go http编程 learn from it黑马

web工作流程

 http协议

超文本传输协议,是互联网上最广泛的一种网络协议。

规定浏览器和服务器之间互相通信的规则

        http请求报文

        通过GO 网络编程 TCP、UDP from李文周-CSDN博客中客户端来进行测试,在edge浏览器中输入监听的ip和端口,得到信息如下:

#GET / HTTP/1.1
Host: 127.0.0.1:20000
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: "Microsoft Edge";v="117", "Not;A=Brand";v="8", "Chromium";v="117"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.43
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6

#

        第一行为请求行,这里可以得到请求方法为GET,一般用于获取/查询资源信息,post会附带用户数据,一般用于更新资源信息

        之后为请求头

        最后一行为空行,发送回车或换行符,通知服务器以下不再有请求头

user-agent部分:

        User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。

标准格式为: 浏览器标识 (操作系统标识; 加密等级标识; 浏览器语言) 渲染引擎标识 版本信息

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.53

请求包体不再GET方法中使用,而是在post方法中使用。

更多user-agent部分:浏览器的“套娃行为”有多凶残? 3 分钟解惑 - 少数派 (sspai.com)

http响应报文

服务器测试代码

package main

import (
	"fmt"
	"net/http"
)

func myHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}

func main() {
	http.HandleFunc("/go", myHandler)

	//在指定的地址进行监听,开启一个http
	http.ListenAndServe("127.0.0.1:8000", nil)
}

 请求代码

通过之前得到的请求报文,向服务器发出,得到相应报文

package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:8000")
	if err != nil {
		fmt.Println("err =", err)
		return
	}
	fmt.Println("connect")
	//
	defer conn.Close()
	requestHeader := "GET /go HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, \r\nAccept-Language: zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7\r\n\r\n"

	// 先发请求包,服务器才会回响应包
	_, writeErr := conn.Write([]byte(requestHeader))
	fmt.Println("request")
	if err != nil {
		fmt.Println("writeErr =", writeErr)
	}

	// 接收服务器回复的响应包
	buf := make([]byte, 1024*4)
	n, readErr := conn.Read(buf)
	if readErr != nil {
		if readErr != nil {
			fmt.Println("readErr =", readErr)
		} else {
			fmt.Println("null response")
		}
		return
	}

	//
	responseStr := string(buf[:n])
	fmt.Printf("#%v#", responseStr)
}

报文内容

成功

 失败

都由:

响应头

相应行

 空行

包体

组成

http编程

func HandleFunc

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

HandleFunc注册一个处理器函数handler和对应的模式pattern(注册到DefaultServeMux)。ServeMux的文档解释了模式的匹配机制。

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

ListenAndServe监听TCP地址addr,并且会使用handler参数调用Serve函数处理接收到的连接。handler参数一般会设为nil,此时会使用DefaultServeMux。

一个简单的服务端例子:

package main
import (
	"io"
	"net/http"
	"log"
)

// hello world, the web server
//w:给客户端回复数据    req:给客户端发送数据
func HelloServer(w http.ResponseWriter, req *http.Request) {
	io.WriteString(w, "hello, world!\n")
}
func main() {
//注册处理函数,用户连接,自动调用指定处理函数
	http.HandleFunc("/hello", HelloServer)

//监听绑定
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

客户端编写

package main

import (
	"fmt"
	"net/http"
)

func main() {
	r, err := http.Get("www.baidu.com")
	if err != nil {
		fmt.Printf("err: %v\n", err)
		return
	}
	defer r.Body.Close()

	buf := make([]byte, 1024)
	n, _ := r.Body.Read(buf)
	fmt.Printf("buf: %v\n", string(buf[:n]))

	fmt.Printf("r.Header: %v\n", r.Header)
}

错误提示: 

err: Get "www.baidu.com": unsupported protocol scheme ""

通过在get部分前加入http解决,需要添加完整的协议。如https或者http。

	r, err := http.Get("http://www.baidu.com")
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	//发出HTTP/ HTTPS请求。
	r, err := http.Get("http://www.baidu.com")
	// r, err := http.Get("127.0.0.1:8000")
	if err != nil {
		fmt.Printf("err: %v\n", err)
		return
	}
	//程序在使用完回复后必须关闭回复的主体。
	defer r.Body.Close()
	//使用io。readall进行读取
	b, _ := io.ReadAll(r.Body)
	fmt.Printf("b: %v\n", string(b))
	//循环读取,直到读完所有内容
	// buf := make([]byte, 1024)
	// var temp string
	// for {
	// 	n, err := r.Body.Read(buf)
	// 	if err == io.EOF {
	// 		break
	// 	}
	// 	temp += string(buf[:n])
	// }
	// fmt.Printf("r.Body: %v\n", r.Body)
	// fmt.Printf("response: %v\n", temp)

	fmt.Printf("r.Header: %v\n", r.Header)
}

由此可以获取服务器返回的包体中的内容

爬虫部分

四个步骤:

1.明确目标:知道在哪个范围或网址去搜索

2.爬:将网站所有的内容爬下来

3.取:取出对我们有用的 或者 去掉对我们没用的数据

4.处理数据:按照我们想要的方式存储和使用

爬取百度贴吧 内容

海贼王 吧 首页
https://tieba.baidu.com/f?kw=海贼王&ie=utf-8&pn=0
海贼王 吧 第二页
https://tieba.baidu.com/f?kw=海贼王&ie=utf-8&pn=50

孙笑川 吧 第二页
https://tieba.baidu.com/f?kw=孙笑川&ie=utf-8&pn=50

可以得到贴吧格式:

https://tieba.baidu.com/f?kw= [吧名] &ie=utf-8&pn=[(页数-1)*50]

根据贴吧名和起始页进行爬取。并进行文件保存 

我的版本

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
)

func main() {
	//发出HTTP/ HTTPS请求。
	var name string
	var page, page2 int
	fmt.Println("请输入要爬取的贴吧名,开始页数和结束页数,中间用空格隔开")
	fmt.Scan(&name, &page, &page2)

	for i := page; i < page2+1; i++ {

		n := (i - 1) * 50
		// fmt.Printf("n: %v\n", n)
		s := strconv.Itoa(n)

		website := "https://tieba.baidu.com/f?kw=" + name + "&ie=utf-8&pn=" + s
		r, err := http.Get(website)
		if err != nil {
			fmt.Printf("err: %v\n", err)
			return
		}
		//程序在使用完回复后必须关闭回复的主体。
		defer r.Body.Close()
		//使用io。readall进行读取
		b, _ := io.ReadAll(r.Body)
		// fmt.Printf("b: %v\n", string(b))
		fmt.Println("-----------------------------------------------------------------------------------")
		filename := name + "吧" + "第" + strconv.Itoa(i) + "页" + ".html" //页数从int类型转string,可以使用strconv。itoa来进行
		f, err2 := os.Create(filename)

		if err2 != nil {
			fmt.Printf("create file failed ,err2: %v\n", err2)

		}
		f.Write(b)
		fmt.Println(website)
		defer f.Close()
	}
}

 原版

package main

import (
	"fmt"
	"net/http"
	"os"
	"strconv"
)

/**
 * 实际上爬虫一共就四个主要步骤
 * 1)明确目标(要知道你准备在哪个范围或者网站去搜索)
 * 2)爬(将所有的网站的内容全部爬下来)
 * 3)取(去掉对我们没用的数据)
 * 4)处理数据(按照我们想要的方式存储和利用)
 */

// http://tieba.baidu.com/f?kw=%E8%A1%A1%E6%B0%B4%E5%AD%A6%E9%99%A2&ie=utf-8&pn=0
// http://tieba.baidu.com/f?kw=%E8%A1%A1%E6%B0%B4%E5%AD%A6%E9%99%A2&ie=utf-8&pn=50
// http://tieba.baidu.com/f?kw=%E8%A1%A1%E6%B0%B4%E5%AD%A6%E9%99%A2&ie=utf-8&pn=100
func main() {
	var start, end int;
	fmt.Printf("请输入起始页(>= 1):");
	fmt.Scan(&start);
	fmt.Printf("请输入终止页(>= 起始页):");
	fmt.Scan(&end);

	//
	SpiderPages(start, end);
}

func SpiderPages(start, end int) {
	baseUrl := "http://tieba.baidu.com/f?kw=%E8%A1%A1%E6%B0%B4%E5%AD%A6%E9%99%A2&ie=utf-8&pn=";
	fmt.Printf("正在爬取 %d 到 %d 的页面", start, end);

	//
	for i := start; i <= end; i++ {
		// 1)明确目标(要知道你准备在哪个范围或者网站去搜索)
		url := baseUrl + strconv.Itoa((i-1)*50);
		fmt.Println("url =", url);

		// 2)爬(将所有网站的内容抓取下来
		result, httpGetErr := HttpGet(url);
		if httpGetErr != nil {
			fmt.Println("HttpGetErr =", httpGetErr);
		}

		// 3)把内容写进文件
		fileName := "./iofiles/tieba.hsnc-" + strconv.Itoa(i) + ".html";
		f, fCreateErr := os.Create(fileName);
		if fCreateErr != nil {
			fmt.Println("fCreateErr =", fCreateErr);
			continue; // 这里不能用return,要不后面的也没法存了
		}
		f.WriteString(result);
		f.Close();
	}
}

func HttpGet(url string) (result string, err error) {
	resp, httpGetErr := http.Get(url);
	if httpGetErr != nil {
		return "", httpGetErr;
	}

	//
	defer resp.Body.Close();

	//
	buf := make([]byte, 1024*4);
	result = "";
	for {
		n, respReadErr := resp.Body.Read(buf);
		if n == 0 { // 读取结束或者出问题
			fmt.Println("respReadErr =", respReadErr);
			break;
		}

		//
		result += string(buf[:n]);
	}

	return result, nil;
}

并发版

        若只是单纯通过将函数封包使用go routine来玩完成并发,会使主协程提前结束,若最会以for循环来使所有协程结束,会使cpu、内存使用率过高

        因此我们需要使用管道阻塞来保证主协程运行到最后

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
)

var baseUrl string = "http://tieba.baidu.com/f?kw=孙笑川&ie=utf-8&pn="

func main() {
	var start, end int
	fmt.Printf("请输入起始页(>= 1):")
	fmt.Scan(&start)
	fmt.Printf("请输入终止页(>= 起始页):")
	fmt.Scan(&end)

	//
	SpiderPages(start, end)
}

// 根据起始页对网站进行爬取并保存内容
func SpiderPages(start, end int) {

	fmt.Printf("正在爬取 %d 到 %d 的页面", start, end)
	//通过管道确保所有协程运行完毕
	page := make(chan int)
	//
	for i := start; i <= end; i++ {
		go spider(i, page)
		// // 1)明确目标(要知道你准备在哪个范围或者网站去搜索)
		// url := baseUrl + strconv.Itoa((i-1)*50)
		// fmt.Println("url =", url)

		// // 2)爬(将所有网站的内容抓取下来
		// result, httpGetErr := HttpGet(url)
		// if httpGetErr != nil {
		// 	fmt.Println("HttpGetErr =", httpGetErr)
		// }

		// // 3)把内容写进文件
		// fileName := "孙笑川吧" + strconv.Itoa(i) + ".html"
		// f, fCreateErr := os.Create(fileName)
		// if fCreateErr != nil {
		// 	fmt.Println("fCreateErr =", fCreateErr)
		// 	continue // 这里不能用return,要不后面的也没法存了
		// }
		// f.WriteString(result)
		// f.Close()
	}
	//每当爬完一页之后,将读完的page传回,结束这里阻塞的通道
	for i := start; i <= end; i++ {
		fmt.Printf("第%d个页面爬取完成\n", <-page)
	}
}

// 向url发出请求相应,并读取消息体中内容
func HttpGet(url string) (result string, err error) {
	resp, httpGetErr := http.Get(url)
	if httpGetErr != nil {
		return "", httpGetErr
	}

	//
	defer resp.Body.Close()

	//
	r, err2 := io.ReadAll(resp.Body)
	if err2 != nil {
		fmt.Printf("read failed,err2: %v\n", err2)
	}
	result = string(r)

	return result, nil
}

func spider(i int, page chan int) {
	// 1)明确目标(要知道你准备在哪个范围或者网站去搜索)
	url := baseUrl + strconv.Itoa((i-1)*50)
	fmt.Println("url =", url)

	// 2)爬(将所有网站的内容抓取下来
	result, httpGetErr := HttpGet(url)
	if httpGetErr != nil {
		fmt.Println("HttpGetErr =", httpGetErr)
	}

	// 3)把内容写进文件
	fileName := "孙笑川吧" + strconv.Itoa(i) + ".html"
	f, fCreateErr := os.Create(fileName)
	if fCreateErr != nil {
		fmt.Println("fCreateErr =", fCreateErr)
		return // 这里不能用return,要不后面的也没法存了
	}
	f.WriteString(result)
	defer f.Close()
	page <- i
}

        后续对爬取内容的筛选可以参考it黑马的视频,因为原来的网站失效,这里不再演示,通过正则表达式来完成即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值