7

golang学习--编写爬虫

 3 years ago
source link: https://shu1l.github.io/2021/04/03/golang-xue-xi-bian-xie-pa-chong/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang学习--编写爬虫

2021-04-03

​ 学习了一段时间的go,觉得自己应该实际上手写一些东西,之前一直使用在三只师傅python写的jsfinder,就想着用go写一个类似jsfinder的爬虫。

steps1

首先实现简单访问并读取页面内容的功能,主要使用net/http模块。

func main(){
resp,err:=http.Get("https://www.lenovo.com.cn")
if err!=nil{
log.Fatal(err)
}
if resp.StatusCode!=200 {
log.Fatal(err)
}
doc,err:=ioutil.ReadAll(resp.Body)
resp.Body.Close()
fmt.Printf("%s",doc)
}

ioutil.ReadAll 是一个常用的数据读取方法,经常用来读取http请求的response数据,或者读取文件数据。

可以看到正常输出了页面内容。

step2

​ 第二步我们需要对页面进行解析,并提取页面中的js链接。我们可以使用go自带的regexp来进行正则匹配(类似python),这里我使用很方便的第三方包goquery读取HTML代码。

​ goquery是一个使用go语言写成的HTML解析库,可以让你像jQuery那样的方式来操作DOM文档,使用起来非常的简便。

package main

import (
"fmt"
"net/http"

"github.com/PuerkitoBio/goquery"
)

func main(){
resp,err:=http.Get("https://www.lenovo.com.cn")
if err!=nil{
return
}
if resp.StatusCode!=200 {
return
}

doc,err:=goquery.NewDocumentFromReader(resp.Body)
// NewDocumentFromReader:读取字符串的HTML代码
if err!=nil{
return
}
resp.Body.Close()

//Find函数是查找HTML里面所有符合要求的标签。
doc.Find("a").Each(func(i int, s *goquery.Selection) {
href, ex := s.Attr("href")
// 使用Attr获取数据所在HTML代码的href属性
if ex {
fmt.Println(href)
}
})
}

可以看到这里成功爬取到了页面的js链接。

当然这里还有一个更好用的第三方包:github.com/jackdanger/collectlinks 可以提取网页中所有的链接。

package main

import (
"fmt"

"github.com/jackdanger/collectlinks"

"net/http"
)

func main() {
resp, err := http.Get("https://www.lenovo.com.cn")
if err != nil {
return
}
if resp.StatusCode != 200 {
return
}

links := collectlinks.All(resp.Body)
for _, link := range links {
fmt.Println(link)
}

resp.Body.Close()
}

使用下来感觉要比自己写规则爬的全很多。

step3

​ 第三步我们要对获取到的链接进行处理,首先转化为绝对链接,然后提取出其中的子域名经过去重之后存入新的数组队列中。

我们这里封装三个函数,分别用来转化绝对路径、判断子域名和去重:

func urlparse(href, base string) string {
uri, err := url.Parse(href)
if err != nil {
return " "
}
baseUrl, err := url.Parse(base)
if err != nil {
return " "
}
return baseUrl.ResolveReference(uri).String()
}

issubdomain

func isSubdomain(rawURL, domain string) bool {
reg,err:=regexp.MatchString(domain,rawURL)
if err!=nil{
log.Println(err)
}
if reg{
return true
}
return false
}

remove

去重这里参考了网上数组切片去重的方式。

func remove(languages []string) []string {
result := make([]string, 0, len(languages))
temp := map[string]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

​ 然后在主函数中再定义一个sublists数组,然后对收集到的js链接调用上述函数进行处理后,将结果保存到数组中输出。

package main

import (
"fmt"
"log"
"net/url"
"regexp"

"github.com/jackdanger/collectlinks"

"net/http"
)


func main() {
sublists := make([]string, 0)
first:="https://www.lenovo.com.cn"
resp, err := http.Get(first)
if err != nil {
log.Print(err)
}
if resp.StatusCode != 200 {
return
}

links := collectlinks.All(resp.Body)
resp.Body.Close()

for _, link := range links {
urls,err:=url.Parse(link)
if err!=nil {
log.Println(err)
continue
}
if isSubdomain(urls.Host,"lenovo.com"){
sublists=append(sublists,"http://"+urls.Host)
}
}
sublists=remove(sublists)
for _,sublist:=range sublists{
fmt.Println(sublist)
}

}


func isSubdomain(rawURL, domain string) bool {
reg,err:=regexp.MatchString(domain,rawURL)
if err!=nil{
log.Println(err)
}
if reg{
return true
}
return false
}


func remove(languages []string) []string {
result := make([]string, 0, len(languages))
temp := map[string]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

step4

第四步我们实现循环爬取的功能,首先我们将之前的主函数封装为crawl爬虫函数:

func crawl(uri string) []string {
fmt.Println(uri)
sublists := make([]string, 0)
resp, err := http.Get(uri)
if err != nil {
log.Print(err)
}
if resp.StatusCode != 200 {
log.Print(err)
}

links := collectlinks.All(resp.Body)
resp.Body.Close()

for _, link := range links {
urls,err:=url.Parse(link)
if err!=nil {
log.Println(err)
continue
}
if isSubdomain(urls.Host,"lenovo.com"){
sublists=append(sublists,"http://"+urls.Host)
}
}
sublists=remove(sublists)

return sublists
}

然后在主函数中进行循环爬取:

func main() {
sublists:=make([]string,0)
seen:=make(map[string]bool)
sublists = append(sublists,"http://www.lenovo.com.cn")
for len(sublists) > 0 {
items := sublists
sublists = nil
for _, item := range items {
if !seen[item] {
seen[item] = true
sublists = append(sublists, crawl(item)...)
}
}
}
}

这里爬了下jd,效果还是挺明显的:

​ 这里在爬取过程会遇到一个异常:dial tcp: lookup help.en.jd.com: no such host,

意思大概是获取到的域名不能解析,所以我们需要使用defer+recover来捕获异常。防止程序直接退出。

func crawl(uri string) []string {
fmt.Println(uri)
defer func(){
if err := recover(); err != nil{
return
}
}()
sublists := make([]string, 0)
resp, err1 := http.Get(uri)
if err1 != nil {
log.Print(err1)
}

links := collectlinks.All(resp.Body)
resp.Body.Close()

for _, link := range links {
urls,err:=url.Parse(link)
if err!=nil {
log.Println(err)
continue
}
if isSubdomain(urls.Host,"jd.com"){
sublists=append(sublists,"http://"+urls.Host)
}
}
sublists=remove(sublists)

return sublists
}


func main() {
sublists:=make([]string,0)
seen:=make(map[string]bool)
sublists = append(sublists,"https://www.jd.com")
for len(sublists) > 0 {
items := sublists
sublists = nil
for _, item := range items {
if !seen[item] {
seen[item] = true
sublists=append(sublists,crawl(item)...)
}
}
}
}


func isSubdomain(rawURL, domain string) bool {
reg,err:=regexp.MatchString(domain,rawURL)
if err!=nil{
log.Println(err)
}
if reg{
return true
}
return false
}


func remove(languages []string) []string {
result := make([]string, 0, len(languages))
temp := map[string]struct{}{}
for _, item := range languages {
if _, ok := temp[item]; !ok {
temp[item] = struct{}{}
result = append(result, item)
}
}
return result
}

step5

最后一步我们来实现并发功能:

package main

import (
"fmt"
"log"
"net/url"
"regexp"

"github.com/jackdanger/collectlinks"

"net/http"
)

func main() {
worklist := make(chan []string)
sublists := make([]string, 0)
seen := make(map[string]bool)
sublists = append(sublists, "https://www.jd.com")

go func() { worklist <- sublists }()

for list := range worklist {
for _, link := range list {
if !seen[link] {
seen[link] = true
go func(link string) {
worklist <- crawl(link)
}(link)
}
}
}
}

Step6

最后在主函数使用flag包获取命令行参数,传入函数执行即可。

flag.StringVar(&ur1, "u","","待爬url,如http://www.jd.com")
flag.StringVar(&domain, "s","","域名,如jd.com")
flag.Parse()

完整代码:Shu1L/go_jsspider: 用go编写的简单爬取页面js的脚本 (github.com)

</div


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK