Go最全Go分布式爬虫笔记(十九) 4月Day3_golang 爬虫封装,2024年最新2024Golang面试笔试总结

img
img
img

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

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

如果你需要这些资料,可以戳这里获取

"data structures",
"formal languages",
"computer organization",

},

“data structures”: {“discrete math”},
“databases”: {“data structures”},
“discrete math”: {“intro to programming”},
“formal languages”: {“discrete math”},
“networks”: {“operating systems”},
“operating systems”: {“data structures”, “computer organization”},
“programming languages”: {“data structures”, “computer organization”},
}

func main() {
for i, course := range topoSort(prereqs) {
fmt.Printf(“%d:\t%s\n”, i+1, course)
}
}

func topoSort(m map[string][]string) []string {
var order []string
seen := make(map[string]bool)
var visitAll func(items []string)

visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item])
order = append(order, item)
}
}
}

var keys []string
for key := range m {
keys = append(keys, key)
}

sort.Strings(keys)
visitAll(keys)
return order
}


## 广度优先搜索算法


广度优先搜索指的是\*\*从根节点开始,逐层遍历树的节点,直到所有节点均被访问为止。\*\*我们还是以之前的拓扑结构为例,广度优先搜索会首先查找 A、接着查找 B、C,最后查找 D、E、F、G。**Dijkstra 最短路径算法**和 **Prim 最小生成树算法**都采用了和广度优先搜索类似的思想。


​![image](https://img-blog.csdnimg.cn/img_convert/d48557f36a17ace2dd094506434e9e5b.png)​


实现广度优先搜索最简单的方式是使用**队列**。这是由于队列具有先入先出的属性。以上面的拓扑结构为例,我们可以构造一个队列,然后先将节点 A 放入到队列中。接着取出 A 来处理,并将与 A 相关联的 B、C 放入队列末尾。接着取出 B,将 D、E 放入队列末尾,接着取出 C,将 F、G 放入队列末尾。以此类推。


广度优先搜索在实践中的应用也很广泛。


* 要计算两个节点之间的最短路径
* 即时策略游戏中的找寻路径问题都可以使用它
* Go 语言垃圾回收在并发标记阶段也是用广度优先搜索查找当前存活的内存的


下面是一段利用广度优先搜索爬取网站的例子。其中,urls 是一串 URL 列表,exactUrl 抓取每一个网站中要继续爬取的 URL,并放入到队列 urls 的末尾,用于后续的爬取。



func breadthFirst(urls []string) {
for len(urls) > 0 {
items := urls
urls = nil
for _, item := range items {
urls = append(urls, exactUrl(item)…)
}
}
}


## 用广度优先搜索实战爬虫


根据爬取目标的不同,可以灵活地选择广度优先和深度优先算法。但一般广度优先搜索算法会更加简单直观一些。下面我用广度优先搜索来实战爬虫,这一次我们爬取的是豆瓣小组中的数据。


首先,让我们在 collect 中新建一个 request.go 文件,对 request 做一个简单的封装。Request 中包含了一个 URL,表示要访问的网站。这里的 ParseFunc 函数会解析从网站获取到的网站信息,并返回 Requesrts 数组用于进一步获取数据。而 Items 表示获取到的数据。



type Request struct {
Url string
ParseFunc func([]byte) ParseResult
}

type ParseResult struct {
Requesrts []*Request
Items []interface{}
}


豆瓣小组是一个个的兴趣小组,小组内的组员可以发帖和评论。我们以 [深圳租房](https://bbs.csdn.net/topics/618658159) 这个兴趣小组为例,这个网站里有许多的租房帖子。


不过我们没法一次性将所有的帖子查找出来,因为每一页只会为我们展示 25 个帖子,要看后面的内容需要点击下方具体的页数,进入到第 2 页、第 3 页。不过这难不倒我们,稍作分析就能发现,“第 1 页”的网站是:https://www.douban.com/group/szsh/discussion?start=0,“第 2 页”的网址是:https://www.douban.com/group/szsh/discussion?start=25,豆瓣是通过 HTTP GET 参数中 start 的变化来标识不同的页面的。所以我们可以用循环的方式把初始网站添加到队列中。如下所示,我们准备抓取前 100 个帖子:



func main(){
var worklist []*collect.Request
for i := 25; i <= 100; i += 25 {
str := fmt.Sprintf(“https://www.douban.com/group/szsh/discussion?start=%d”, i)
worklist = append(worklist, &collect.Request{
Url: str,
ParseFunc: ParseCityList,
})
}
}


下一步,我们要解析一下抓取到的网页文本。


新建一个文件夹“parse”来专门存储对应网站的规则。


对于首页样式的页面,我们需要获取所有帖子的 URL,这里使用正则表达式的方式来实现。匹配到符合帖子格式的 URL 后,我们把它组装到一个新的 Request 中,用作下一步的爬取。



const cityListRe = (<https://www.douban.com/group/topic/[0-9a-z]+/>)"[^>]\*>([^<]+)</a>

func ParseURL(contents []byte) collect.ParseResult {
re := regexp.MustCompile(cityListRe)

matches := re.FindAllSubmatch(contents, -1)
result := collect.ParseResult{}

for _, m := range matches {
u := string(m[1])
result.Requesrts = append(
result.Requesrts, &collect.Request{
Url: u,
ParseFunc: func(c []byte) collect.ParseResult {
return GetContent(c, u)
},
})
}
return result
}


新的 Request 需要有不同的解析规则,这里我们想要获取的是正文中带有“阳台”字样的帖子(注意不要匹配到侧边栏的文字)。


查看 HTML 文本的规则会发现,正本包含在xxxx当中,所以我们可以用正则表达式这样书写规则函数,意思是当发现正文中有对应的文字,就将当前帖子的 URL 写入到 Items 当中。



const ContentRe = <div class="topic-content">[\s\S]\*?阳台[\s\S]\*?<div

func GetContent(contents []byte, url string) collect.ParseResult {
re := regexp.MustCompile(ContentRe)

ok := re.Match(contents)
if !ok {
return collect.ParseResult{
Items: []interface{}{},
}
}

result := collect.ParseResult{
Items: []interface{}{url},
}

return result
}


最后在 main 函数中,为了找到所有符合条件的帖子,我们使用了广度优先搜索算法。循环往复遍历 worklist 列表,完成爬取与解析的动作,找到所有符合条件的帖子。



var worklist []*collect.Request
for i := 0; i <= 100; i += 25 {
str := fmt.Sprintf(“https://www.douban.com/group/szsh/discussion?start=%d”, i)
worklist = append(worklist, &collect.Request{
Url: str,
ParseFunc: doubangroup.ParseURL,
})
}

var f collect.Fetcher = collect.BrowserFetch{
Timeout: 3000 * time.Millisecond,
Proxy: p,
}

for len(worklist) > 0 {
items := worklist
worklist = nil
for _, item := range items {
body, err := f.Get(item.Url)
time.Sleep(1 * time.Second)
if err != nil {
logger.Error(“read content failed”,
zap.Error(err),

img
img
img

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

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

如果你需要这些资料,可以戳这里获取

…(img-uwZnvmqR-1715505606749)]

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

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

如果你需要这些资料,可以戳这里获取

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值