Go分布式爬虫学习笔记(十三)_go语言分布式爬虫pdf,2024年最新国网面试题目及答案

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Python全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Python知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注Python)
img

正文

func BenchmarkDirect(b *testing.B) {
adder := Sumer{id: 6754}
b.ResetTimer()
for i := 0; i < b.N; i++ {
adder.Add(10, 12)
}
}

func BenchmarkInterface(b *testing.B) {
adder := Sumer{id: 6754}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sumifier(adder).Add(10, 12)
}
}

func BenchmarkInterfacePointer(b *testing.B) {
adder := &SumerPointer{id: 6754}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sumifier(adder).Add(10, 12)
}
}

在 Benchmark 测试中,我们静止编译器的优化和内联汇编,避免这两种因素对耗时产生的影响。测试结果如下。可以看到直接函数调用的速度最快,为 1.95 ns/op, 方法接收者为指针的接口调用和函数调用的速度类似,为 2.37 ns/op, 方法接收者为非指针的接口调用却慢了数倍,为 14.6 ns/op。

N: Windows直接运行**go test -gcflags "-N -l" -bench=.**​好像不行

» go test -gcflags “-N -l” -bench=.
BenchmarkDirect-12 535487740 1.95 ns/op
BenchmarkInterface-12 76026812 14.6 ns/op
BenchmarkInterfacePointer-12 517756519 2.37 ns/op

go test -gcflags “-N -l” -bench=.
goos: linux
goarch: amd64
pkg: github.com/funbinary/go_example/example/crawler/benchmark
cpu: 12th Gen Intel® Core™ i7-12700F
BenchmarkDirect-20 1000000000 0.9737 ns/op
BenchmarkInterface-20 906270834 1.305 ns/op
BenchmarkInterfacePointer-20 1000000000 1.080 ns/op
PASS
ok github.com/funbinary/go_example/example/crawler/benchmark 3.587s

**方法接收者为非指针的接口调用速度之所以很慢是受到了内存拷贝的影响。**由于接口中存储了数据的指针,而函数调用的是非指针,因此数据会从对堆内存拷贝到栈内存,让调用速度变慢。

启发:

  • 在使用接口时,方法接收者使用指针的形式能够带来速度的提升
  • 接口调用带来的性能损失很小,在实际开发中,不必担心接口带来的效率损失

爬取技术

  • 模拟浏览器访问
  • 代理访问

爬取接口抽象

  • 创建collect用于采集引擎, 存放与爬取相关代码
  • 定义Fetcher接口,内部方法Get,参数为url N: 后续会修改,不用提前费劲设计

type Fetcher interface {
Get(url string) ([]byte, error)
}

  • 定义一个结构体 BaseFetch,用最基本的爬取逻辑实现 Fetcher 接口:

func (BaseFetch) Get(url string) ([]byte, error) {
resp, err := http.Get(url)

if err != nil {
fmt.Println(err)
return nil, err
}

defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
fmt.Printf(“Error status code:%d”, resp.StatusCode)
}
bodyReader := bufio.NewReader(resp.Body)
e := DeterminEncoding(bodyReader)
utf8Reader := transform.NewReader(bodyReader, e.NewDecoder())
return ioutil.ReadAll(utf8Reader)
}

  • 在 main.go 中定义一个类型为 BaseFetch 的结构体,用接口 Fetcher 接收并调用 Get 方法,这样就完成了使用接口来实现基本爬取的逻辑。

var f collect.Fetcher = collect.BaseFetch{}
body, err := f.Get(url)

模拟浏览器访问

Mozilla/5.0 (操作系统信息) 运行平台(运行平台细节) <扩展信息>

  • 我的浏览器

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

  • Mozilla/5.0 由于历史原因,是现在的主流浏览器都会发送的。
  • Windows NT 10.0; Win64; x64: 操作系统版本号。
  • AppleWebKit/537.36: 使用的 Web 渲染引擎标识符。
  • KHTML: 在 Safari 和 Chrome 上使用的引擎。
  • Chrome/111.0.0.0 Safari/537.36: 浏览器版本号
  • 不同浏览器,User-Agent会不同

Lynx: Lynx/2.8.8pre.4 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/2.12.23

Wget: Wget/1.15 (linux-gnu)

Curl: curl/7.35.0

Samsung Galaxy Note 4: Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-N910F Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/4.0 Chrome/44.0.2403.133 Mobile Safari/537.36

Apple iPhone: Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1

Apple iPad: Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4

Microsoft Internet Explorer 11 / IE 11: Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko

  • 有时候,我们的爬虫服务需要动态生成 User-Agent 列表,方便在测试、或者在使用代理大量请求单一网站时,动态设置不同的 User-Agent。

实现BrowserFetch

  • 创建一个 HTTP 客户端 http.Client
  • 通过 http.NewRequest 创建一个请求。
  • 在请求中调用 req.Header.Set 设置 User-Agent 请求头。
  • 调用 client.Do 完成 HTTP 请求。

type BrowserFetch struct {
}

func (b *BrowserFetch) Get(url string) ([]byte, error) {
client := &http.Client{}

req, err := http.NewRequest(“GET”, url, nil)
if err != nil {
return nil, err
}
req.Header.Set(“User-Agent”, “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36\t\n”)

resp, err := client.Do(req)
if err != nil {
return nil, err
}

defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, errors.Errorf(“Error status code:%v”, resp.StatusCode)
}

r := bufio.NewReader(resp.Body)
e := DeterminEncoding®
utf8r := transform.NewReader(r, e.NewDecoder())
return io.ReadAll(utf8r)
}

远程访问浏览器

要借助浏览器的能力实现自动化爬取,目前依靠的技术有以下三种:

  • 借助浏览器驱动协议(WebDriver protocol)远程与浏览器交互;
  • 借助谷歌开发者工具协议(CDP,Chrome DevTools Protocol)远程与浏览器交互;
  • 在浏览器应用程序中注入要执行的 JavaScript,典型的工具有 Cypress, TestCafe。
    通常只用于测试,所以下面我们就重点来说说前面两种技术。

Webdriver Protocol

Webdriver 协议是操作浏览器的一种远程控制协议。借助 Webdriver 协议完成爬虫的框架或库有 Selenium,WebdriverIO,Nightwatch,其中最知名的就是 Selenium。Selenium 为每一种语言(例如 Java、Python、Ruby 等)都准备了一个对应的 clinet 库,它整合了不同浏览器的驱动(这些驱动由浏览器厂商提供,例如谷歌浏览器的驱动和火狐浏览器的驱动)。

Selenium 通过 W3C 约定的 WebDriver 协议与指定的浏览器驱动进行通信,之后浏览器驱动操作特定浏览器,从而实现开发者操作浏览器的目的。由于 Selenium 整合了不同的浏览器驱动,因此它对于不同的浏览器都具有良好的兼容性。

R: W3C 约定的 WebDriver 协议

R: Selenium

Chrome DevTools Protocol(谷歌开发者工具协议)

该协议最初是由谷歌开发者工具团队维护的,负责调试操作浏览器的协议。目前,现代大多数浏览器都支持谷歌开发者工具协议。我们经常使用到的谷歌浏览器的开发者工具(快捷键 CTRL + SHIFT + I 或者 F12)就是使用这个协议来操作浏览器的。
查看谷歌开发者工具与浏览器交互的协议的方式是:

  • 打开谷歌浏览器,在开发者工具 →设置→ 实验中勾选 Protocol Monitor(协议监视器)。
  • 我们要重启开发者工具,在右侧点击更多工具,这样就可以看到协议监视器面板了。
  • 面板中有开发者工具通过协议与浏览器交互的细节。

与 Selenium 需要与浏览器驱动进行交互不同的是,Chrome DevTools 协议直接通过​ Web Socket ​协议与浏览器暴露的 API 进行通信,这使得 Chrome DevTools 协议操作浏览器变得更快。

在 Go 中实现了 Chrome DevTools 协议的知名第三方库是chromedp。它的操作简单,也不需要额外的依赖。借助chromedp 提供的能力与浏览器交互,我们就具有了许多灵活的能力,例如截屏、模拟鼠标点击、提交表单、下载 / 上传文件等。chromedp 的一些操作样例你可以参考example 代码库。

模拟鼠标点击事件

假设我们访问Go time 包的说明文档,例如 After 函数,会发现下图的参考代码是折叠的。

image

通过鼠标点击,折叠的代码可以展示出 time.After 函数的参考代码。

image

我们经常面临这种情况,即需要完成一些交互才能获取到对应的数据。要模拟上面的完整操作,代码如下所示:

package main

import (
“context”
“log”
“time”

“github.com/chromedp/chromedp”
)

func main() {
// 1、创建谷歌浏览器实例
ctx, cancel := chromedp.NewContext(
context.Background(),
)
defer cancel()

// 2、设置context超时时间
ctx, cancel = context.WithTimeout(ctx, 15*time.Second)
defer cancel()

// 3、爬取页面,等待某一个元素出现,接着模拟鼠标点击,最后获取数据
var example string
err := chromedp.Run(ctx,
chromedp.Navigate(https://pkg.go.dev/time),
chromedp.WaitVisible(body > footer),
chromedp.Click(#example-After, chromedp.NodeVisible),
chromedp.Value(#example-After textarea, &example),
)
if err != nil {
log.Fatal(err)
}
log.Printf(“Go’s time.After example:\n%s”, example)
}

  • 首先我们导入了 chromedp 库,并调用 chromedp.NewContext 为我们创建了一个浏览器的实例。
    实现原理: 查找当前系统指定路径下指定的谷歌应用程序,并默认用无头模式(Headless 模式)启动谷歌浏览器实例。
    通过无头模式,我们肉眼不会看到谷歌浏览器窗口的打开过程,但它确实已经在后台运行了。

func findExecPath() string {
var locations []string
switch runtime.GOOS {
case “darwin”:
locations = []string{
// Mac
“/Applications/Chromium.app/Contents/MacOS/Chromium”,
“/Applications/Google Chrome.app/Contents/MacOS/Google Chrome”,
}
case “windows”:
locations = []string{
// Windows
“chrome”,
“chrome.exe”, // in case PATHEXT is misconfigured
C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe,
C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe,
filepath.Join(os.Getenv(“USERPROFILE”), AppData\\Local\\Google\\Chrome\\Application\\chrome.exe),
filepath.Join(os.Getenv(“USERPROFILE”), AppData\\Local\\Chromium\\Application\\chrome.exe),
}
default:
locations = []string{
// Unix-like
“headless_shell”,

}
}

  • 所以说,当前程序能够运行的重要前提是在指定路径中存在谷歌浏览器程序。当然,一般我们系统中可浏览的谷歌浏览器的大小都是比较大的,所以 chromedp 还好心地为我们提供了一个包含了无头谷歌浏览器的应用程序的镜像:headless-shell
  • 用 context.WithTimeout 设置当前爬取数据的超时时间,这里我们设置成了 15s。
  • 第三步,chromedp.Run 执行多个 action,chromedp 中抽象了 action​​​ ​和 **task ​**​​两种行为。
  • action : 爬取、等待、点击、获取数据这样的行为。
  • task 指的是一个任务,task 是多个 action 的集合。
  • 因此,chromedp.Run 会将多个 action 封装为一个任务,并依次执行。

func Run(ctx context.Context, actions …Action) error {

return Tasks(actions).Do(cdp.WithExecutor(ctx, c.Target))
}

  • chromedp.Navigate 指的是爬取指定的网址:https://pkg.go.dev/time。
  • chromedp.WaitVisible 指的是“等待当前标签可见”,其参数使用的是 CSS 选择器的形式。在这个例子中,body > footer 标签可见,代表正文已经加载完毕。
  • chromedp.Click 指的是“模拟对某一个标签的点击事件”。
  • chromedp.Value 用于获取指定标签的数据。

想法: 使用这个库进行webrtc的压力测试

空接口

  • 任何类型都隐式实现了空接口
  • 通用的能力。
  • 然而在处理接口的过程中却需要默默承受解析空接口带来的痛苦。
    通过使用空接口,常见的 fmt.Println 函数提供了打印任何类型的功能。
    如果不使用空接口,那么每一个类型都需要实现一个对应的 Println 函数,是非常不方便的。

func Println(a …interface{}) (n int, err error) {
return Fprintln(os.Stdout, a…)
}

  • 不过,空接口带来便利的同时,也意味着我们必须在内部解析接口的类型,并对不同的类型进行相应的处理。以 fmt.Println 为例,Println 函数内部通过检测接口的具体类型来调用不同的处理函数。如果是自定义类型,还需要使用反射、递归等手段完成复杂类型的打印功能。

func (p *pp) printArg(arg interface{}, verb rune) {
switch f := arg.(type) {
case bool:
p.fmtBool(f, verb)
case float32:
p.fmtFloat(float64(f), 32, verb)
case float64:
p.fmtFloat(f, 64, verb)
case complex64:
p.fmtComplex(complex128(f), 64, verb)

}

  • 对于跨服务调用的 API,使用空接口可以提高它们的扩展性。因为在这种场景下,修改 API 的成本通常比较高,服务器需要改造并发布新的 SDK,客服端还需要适配新的 SDK 并联调测试。
    如下所示,在 Info 结构体中增加扩展类型 map[string]interface{},新的功能如果需要传递新的信息,当前服务甚至可以不用修改 API。

type info struct{
ExtraData map[string]interface{} json:"extra\_data"

}

  • 空接口为 API 带来了扩展性和灵活性
  • 模块的内部处理增加了额外的成本。因为 API 内部处理空接口时使用了大量的反射,而反射通常比较消耗性能。在实际项目中,当我们 JSON 序列化一个复杂的结构体时,有时候会有上百毫秒的耗时。
  • 空接口是实现反射的基础,因为空接口中会存储动态类型的信息,这为我们提供了复杂、意想不到的处理能力和灵活性。我们可以获取结构体变量内部的方法名、属性名,能够动态地检查函数或方法的参数个数和返回值个数,也可以在运行时通过函数名动态调用函数。这些能力不使用反射都无法做到。

反射实现sql query

func createQuery(q interface{}) string{
// 判断类型为结构体
if reflect.ValueOf(q).Kind() == reflect.Struct {
// 获取结构体名字
t := reflect.TypeOf(q).Name()
// 查询语句
query := fmt.Sprintf(“insert into %s values(”, t)
v := reflect.ValueOf(q)
// 遍历结构体字段
for i := 0; i < v.NumField(); i++ {
// 判断结构体类型
switch v.Field(i).Kind() {
case reflect.Int:
if i == 0 {
query = fmt.Sprintf(“%s%d”, query, v.Field(i).Int())
} else {
query = fmt.Sprintf(“%s, %d”, query, v.Field(i).Int())
}
case reflect.String:
if i == 0 {
query = fmt.Sprintf(“%s\”%s\“”, query, v.Field(i).String())
} else {
query = fmt.Sprintf(“%s, \”%s\“”, query, v.Field(i).String())
}

}
}
query = fmt.Sprintf(“%s)”, query)
fmt.Println(query)
return query
}
}

接口的陷阱

  • 当接口中存储的是值,但是结构体是指针时,接口动态调用无法编译通过。如下所示:

type Binary struct {
uint64
}
type Stringer interface {
String() string
}
func (i *Binary) String() string {
return “hello world”
}
func main(){
a:= Binary{54}
b := Stringer(a)
b.String()
}

Go 语言在编译时阻止了这样的写法,原因在于这种写法会让人产生困惑。如果转换为接口的是值, 那么由于内存逃逸,在转换为接口时必定已经把值拷贝到了堆区。因此如果允许这种写法存在,那么即便看起来在方法中修改了接口中的值,却无法修改原始值,这非常容易引起误解。

  • 将类型切片转换为接口切片

func foo() []interface{} {
return []int{1,2,3}
}

编译时报错:Go 语言禁止了这种写法,就像前面所说的,批量转换为接口是效率非常低的操作。因为每个元素都需要完成内存逃逸的额外开销。

  • 接口与 nil 之间的关系。当接口为 nil 时,接口中的动态类型 itab 和动态类型值 data 必须都为 nil,初学者常常会在这个问题上犯错。例如在下面的 foo 函数中,由于返回的 err 没有任何动态类型和动态值,因此 err 等于 nil。

func foo() error {
var err error // nil
return err
}
func main() {
err := foo()
fmt.Println(err == nil) // true
}

然而,如果在 foo 函数中将错误类型定义为自定义类型,例如 *os.PathError ,我们会发现 err 不等于 nil。

func foo() error {
var err *os.PathError
return err
}
func main() {
err := foo()
fmt.Println(err != nil) // true
}

这是因为当接口为 nil 时,代表接口中的动态类型和动态类型值都为 nil,而当前由于接口 error 具有动态类型 *os.PathError,接口的内部结构体 itab 不为空。如下图所示:
image

避免这一问题需要谨慎地使用自定义的 Error 作为定义,而更多的使用内置的 errors.New 或 fmt.Errorf 来生成和包裹错误。我在之后的课程还会详细介绍错误处理的最佳实践。

思考题

除了带方法的接口,其实还有可以容纳任何类型的空接口。你觉得他们分别在什么场合使用更好呢?

  • 函数传参
  • map值

在对接口方法进行设计时,一般有一个原则是方法参数应该抽象,例如为空接口。但是方法的返回值应该具体,例如为实际的结构体,你觉得这种设计正确吗?

如果一个网站需要登录才可以访问,我们应该如何实现自动登录的能力?

1、通常都有验证码,先获取验证码图片,然后识别为文字,然后带用户名密码及验证码请求登录接口。

2、有的是滑块,可以使用webdriver调用浏览器来完成; 其他生物识别的,可以用弹出浏览器然后人工登录后继续。 登录成功后保存cookie给后续的浏览器使用。

做了那么多年开发,自学了很多门编程语言,我很明白学习资源对于学一门新语言的重要性,这些年也收藏了不少的Python干货,对我来说这些东西确实已经用不到了,但对于准备自学Python的人来说,或许它就是一个宝藏,可以给你省去很多的时间和精力。

别在网上瞎学了,我最近也做了一些资源的更新,只要你是我的粉丝,这期福利你都可拿走。

我先来介绍一下这些东西怎么用,文末抱走。


(1)Python所有方向的学习路线(新版)

这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

最近我才对这些路线做了一下新的更新,知识体系更全面了。

在这里插入图片描述

(2)Python学习视频

包含了Python入门、爬虫、数据分析和web开发的学习视频,总共100多个,虽然没有那么全面,但是对于入门来说是没问题的,学完这些之后,你可以按照我上面的学习路线去网上找其他的知识资源进行进阶。

在这里插入图片描述

(3)100多个练手项目

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。

在这里插入图片描述

(4)200多本电子书

这些年我也收藏了很多电子书,大概200多本,有时候带实体书不方便的话,我就会去打开电子书看看,书籍可不一定比视频教程差,尤其是权威的技术书籍。

基本上主流的和经典的都有,这里我就不放图了,版权问题,个人看看是没有问题的。

(5)Python知识点汇总

知识点汇总有点像学习路线,但与学习路线不同的点就在于,知识点汇总更为细致,里面包含了对具体知识点的简单说明,而我们的学习路线则更为抽象和简单,只是为了方便大家只是某个领域你应该学习哪些技术栈。

在这里插入图片描述

(6)其他资料

还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

在这里插入图片描述

这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

在这里插入图片描述

(6)其他资料

还有其他的一些东西,比如说我自己出的Python入门图文类教程,没有电脑的时候用手机也可以学习知识,学会了理论之后再去敲代码实践验证,还有Python中文版的库资料、MySQL和HTML标签大全等等,这些都是可以送给粉丝们的东西。

在这里插入图片描述

这些都不是什么非常值钱的东西,但对于没有资源或者资源不是很好的学习者来说确实很不错,你要是用得到的话都可以直接抱走,关注过我的人都知道,这些都是可以拿到的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注python)
[外链图片转存中…(img-TYknbBhI-1713668423358)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值