记一个网站的爬虫,并思考爬虫与反爬虫(golang)

最近在分析一个显示盗版小说的网站,其反爬虫思路绝对值得记上一笔.
该网站的地址为 : https://www.bravonovel.life .是一个展示英文小说的网站.

开始,发现这个网站没有登录权限.打开就能看到内容,查看网页源代码.没有内容加密.所以内容都明文的显示在网页中.(自信的以为,简单)

于是开干,自信的网络请求、翻页、内容抓取、过滤、写入文件.以为搞定了

接下来,对比文件中的内容与网页上的内容,咦,怎么多出些内容?

无奈,只得重新打开html 源代码开始分析问题出在哪里,通过仔细分析源代码.一个一个发现代码中存在大量的反爬虫措施.一遍遍的修改代码.花费了大量精力,写下下面这个复杂的爬虫代码.但最终在一个新发现的反爬虫措施下.失去继续做下去的兴趣. 这里记录一下这些反爬虫措施.以备将来使用

反爬虫措施:

  • css 设置 display:none 属性,用不可见的页面元素欺骗爬虫(解决办法:读取标签的css样式)
  • 动态生成css文件.css的文件地址和内容隔一会儿就会变,且没有规律(解决办法:脚本动态获取css地址,并加载)
  • 故意混杂大量错误语法css,例如 : display: block padding display: none!important; (解决方法: 利用更准确的正则表达式匹配规则准确获取正确的样式)
  • 利用css加载顺序等样式优先级规则,反复覆盖样式.(解决办法:按照加载优先级先低后高规则,一步步顺藤摸瓜,最终确定元素的display属性)
  • 在内联css中存在类的css 和标签的css.内联class css 优先级高.标签css优先级低.(解决办法:将标签css作为默认值.class 作为高优先级)
  • display: block!important 强制生效的css (解决办法:增加记录是否为强制生效css字段.后引入的强制生效覆盖先引入的)
  • 利用绝对定位,把页面元素定位到屏幕外面很远的地方去.以达到不显示的目的 (golang练手项目,搞到这里,不想搞了,实在是太麻烦了)(解决办法:找到元素定位的css ,判断优先级后,如果定位熟悉的值很大,当作不可见元素)
  • 元素的高度和宽度被设置为 0 ,且该熟悉被反复覆盖(解决办法:与display:none 一样,顺藤摸瓜)
    猜测还有一些别的反爬虫措施.
    总结: 网页的作用是给用户显示内容,利用css的各种设置,达到页面元素不可见的目的,再使用各种优先级反复修改这一属性,再使用几种不同的方式,加以组合.于是便得到了对正常用户十分友好,但对爬虫极其难以理解的反爬虫网页

本项目的目的是练习刚刚学到的golang语言语法与特性

下面做简要介绍:
1.首先打开开始页面.
2.抓取下一页的地址,这里用协程开启一个递归,调用函数本身,将新地址当作参数传入. 这里有一个用于同步的WaitGroup,需要传入,因为要保证是同一个 WaitGroup ,所以这个参数需要用到指针
3.在开启协程后,开始分析网页.首先获取css地址.开启协程以加快速度.这里因为css的加载顺序决定 css属性的优先级.故将其顺序作为参数传入.
4.分析css信息,获取最终生效熟悉分别为 display:none; display:block;
display:none !important ; display:block!important 的四个数组
5.解析网页内容,标签有多个css class ,一个个的遍历,通过判断他们在步骤4中的哪一个.确定标签是否可见

总结: golang这门语言的协程.简洁高效.

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
)

func main(){
	targetHost := "https://www.bravonovel.life"
	startHref := "/love-after-one-night-stand-with-ceo-2503335.html"
	chapterDir := "./chapter"
	createDir(chapterDir)
	var wg sync.WaitGroup
	wg.Add(1)
	go action(targetHost,startHref,chapterDir+"/",&wg)
	wg.Wait()
	//time.Sleep(30*time.Second)
}

// 抓取一个页面,获取到下一章地址后,递归调用这个函数,抓取下一个地址
// 使用协程递归,将waitGroup的地址传入,方便子协程计数,
// 然后获取章节内容,并将章节内容写入文件中
func action(targetHost string,currentHref string,chapterDir string,wg *sync.WaitGroup){
	defer wg.Done()
	html := getPage(targetHost+currentHref)
	nextUrl :=getNextPageUrl(html)
	fmt.Println("Next:",nextUrl,"\n")
	if nextUrl != ""{
		wg.Add(1)
		go action(targetHost,nextUrl,chapterDir,wg)
	}
	noneClassArray,blockClassArray,noneImportantClassNameArray,blockImportantClassNameArray,tagVisibilityMap := getCssVisibility(targetHost,html)
	// 获取章节内容
	chapterContent := getPageContent(html,noneClassArray,blockClassArray,noneImportantClassNameArray,blockImportantClassNameArray,tagVisibilityMap)
	// 获取章节序号
	chapterNumber := getChapterNumber(html)
	// 将内容写入文件中
	writeChapterToFile(chapterContent,chapterNumber,chapterDir)
	fmt.Println("结束一个过程")
}

// 获取下一章的 Url 地址
func getNextPageUrl(html string) string{
	compile := regexp.MustCompile("")
	matchArr := compile.FindStringSubmatch(html)
	if len(matchArr) > 0{
		return matchArr[1]
	}
	return ""
}

// 获取页面中的章节内容
func getPageContent(html string,noneClassArray []string , blockClassArray []string,noneImportantClassNameArray []string,blockImportantClassNameArray []string,tagVisibilityMap map[string]string) string{
	// 先获取整个内容的字符串,包含html等多余字符
	compile1 := regexp.MustCompile("
([\\s\\S]*?)If you find any errors ") matchArr := compile1.FindStringSubmatch(html) chapterBox1 := "" if len(matchArr) > 0{ chapterBox1 = matchArr[1] } // 过滤不可显示的内容 w3tagCompile := regexp.MustCompile("<(\\w{3}) .*?class=\"(.*?)\".*?>([\\s\\S]*?)") w3tagArray :=w3tagCompile.FindAllStringSubmatch(chapterBox1,-1) visibleContent := "" if len(w3tagArray) > 0{ fmt.Println("\n\n**************************************************\n\n") fmt.Println(len(noneClassArray),"noneClassArray:",noneClassArray) fmt.Println(len(noneImportantClassNameArray),"noneImportantClassNameArray:",noneImportantClassNameArray) fmt.Println(len(blockClassArray),"blockClassArray:",blockClassArray) fmt.Println(len(blockImportantClassNameArray),"blockImportantClassNameArray:",blockImportantClassNameArray) for i:=0; i<len(w3tagArray); i++{ fmt.Println("\n\n**************************************************\n\n") tagNameString := w3tagArray[i][1] classString := w3tagArray[i][2] contentString := w3tagArray[i][3] classArray := strings.Split(classString," ") w3tagVisibility := "block" // 标签本身的css可见性 _,ok := tagVisibilityMap[tagNameString] if ok{ // 当前标签在页面样式css中有默认是否显示 if tagVisibilityMap[tagNameString] == "block" { w3tagVisibility = "block" } if tagVisibilityMap[tagNameString] == "block!important" { w3tagVisibility = "block!important" } if tagVisibilityMap[tagNameString] == "none" { w3tagVisibility = "none" } if tagVisibilityMap[tagNameString] == "none!important" { w3tagVisibility = "none!important" } } // 遍历当前内容的类列表,最终确定当前元素是否可见 for j := 0; j < len(classArray); j++{ fmt.Println("class:",classArray[j]) inNoneClass := inArrayString(classArray[j],noneClassArray) fmt.Println("inNoneClass",inNoneClass) if inNoneClass{ // 当前类 css 为不可见 if w3tagVisibility != "none!important" && w3tagVisibility != "block!important"{ w3tagVisibility = "none" } } inNoneImportantClass := inArrayString(classArray[j],noneImportantClassNameArray) fmt.Println("inNoneImportantClass",inNoneImportantClass) if inNoneImportantClass{ // 当前类 css 为不可见 w3tagVisibility = "none!important" } inBlockClass := inArrayString(classArray[j],blockClassArray) fmt.Println("inBlockClass",inBlockClass) if inBlockClass{ // 当前类 css 为可见 if w3tagVisibility != "none!important" && w3tagVisibility != "block!important"{ w3tagVisibility = "block" } } inBlockImportantClass := inArrayString(classArray[j],blockImportantClassNameArray) fmt.Println("inBlockImportantClass",inBlockImportantClass) if inBlockImportantClass{ // 当前类 css 为可见 w3tagVisibility = "block!important" } } fmt.Println(classArray) if w3tagVisibility == "block" || w3tagVisibility == "block!important"{ // 当前标签内的内容是页面可见的 visibleContent = visibleContent + contentString fmt.Println("可见:",contentString) }else{ fmt.Println("不可见:",contentString) } } } // 替换
标签为 \n 换行符
brCompile := regexp.MustCompile("") chapterBox2 :=brCompile.ReplaceAllString(visibleContent,"\n") // 删除所有html 标签 tagsCompile := regexp.MustCompile("<[\\s\\S]*?>") chapterBox3 := tagsCompile.ReplaceAllString(chapterBox2,"") // 连续多个空格或者制表符,只保留一个空格 tabSpaceCompile := regexp.MustCompile("( +\\t*)+") chapterBox4 := tabSpaceCompile.ReplaceAllString(chapterBox3," ") // 换行符,统一为两个连续的换行 wrapCompile := regexp.MustCompile("( *\\n *)+") chapterBox5 := wrapCompile.ReplaceAllString(chapterBox4,"\n\n") fmt.Println(chapterBox5) return chapterBox5 } func getChapterNumber(html string) string{ chapterNumberCompile := regexp.MustCompile(`class="chapter-title" .*?title=".*?Chapter (\d+).*?"`) chapterNumberArray :=chapterNumberCompile.FindStringSubmatch(html) if len(chapterNumberArray) > 0{ chapterNumber := chapterNumberArray[1] return chapterNumber } return "" } func createDir(dirPath string){ _,err := os.Stat(dirPath) if err != nil{ if os.IsNotExist(err){ err2 := os.Mkdir(dirPath,0755) if err2 != nil{ } } } } func writeChapterToFile(content string,chapterNum string,dirPath string) bool{ filePath := dirPath + chapterNum + ".txt" err := ioutil.WriteFile(filePath,[]byte(content),0666) if err != nil{ return false } return true } // 获取网页中的css 样式,分析获得这些css样式中的类,是否网页可见 func getCssVisibility(targetHost string,html string) ([] string,[] string,[] string,[] string,map[string]string){ cssCompile := regexp.MustCompile("") cssPathArray := cssCompile.FindAllStringSubmatch(html,-1) noneClassNameArray := []string{} noneImportantClassNameArray := []string{} blockClassNameArray := []string{} blockImportantClassNameArray := []string{} tagVisibilityMap := map[string]string{} if len(cssPathArray) > 0{ var ( classMap map[string]map[string]string mutex sync.Mutex ) mutex.Lock() classMap = make(map[string]map[string]string) mutex.Unlock() var cssWg sync.WaitGroup maxIndex := 0; for i:=0; i<len(cssPathArray); i++{ maxIndex = i path := cssPathArray[i][1] currentUrl := targetHost + path cssWg.Add(1) go func(index int) { //func(index int) { // 该网页引入的css 中有相同 class ,按照后引入覆盖先引入的规则,i = index 的值越大 class 越生效 // 所以拿到 class 后 ,没有值则写入,如果存在,判断优先级,如果当前优先级高,则覆盖,否则丢掉这一条 defer cssWg.Done() // 1 删除所有换行 print(currentUrl) cssPage := getPage(currentUrl) wrapCompile := regexp.MustCompile(`\r?\n`) tmpCssContent1 := wrapCompile.ReplaceAllString(cssPage,"") //tmpCssContent1 := strings.ReplaceAll(cssPage,"\n","") // 2 删除被注释掉的内容 noteCompile := regexp.MustCompile(`/\*.*?\*/`) tmpCssContent2 := noteCompile.ReplaceAllString(tmpCssContent1,"") // 3 重新生成换行,每个class一行 classCompile := regexp.MustCompile("(\\.\\w{3} *\\{.*?\\})") tmpCssContent3 := classCompile.ReplaceAllString(tmpCssContent2,"$1 \n") fmt.Println("\n\n####################################################\n\n") fmt.Println(currentUrl) fmt.Println("\n") fmt.Println(tmpCssContent3) fmt.Println("\n\n####################################################\n\n") displayCompile := regexp.MustCompile("\\.(\\w{3}) *.*?(?:\\{|;) *display: *(none|block|block *!important|none *!important) *;.*?}") displayClassArray := displayCompile.FindAllStringSubmatch(tmpCssContent3,-1) for j:=0; j<len(displayClassArray); j++{ className := displayClassArray[j][1] classType := displayClassArray[j][2] classType = strings.ReplaceAll(classType," ","") mutex.Lock() _,ok := classMap[className] mutex.Unlock() if ok{ // class 存在,比较index的大小 oldIndex,err :=strconv.Atoi(classMap[className]["index"]) if err != nil{ oldIndex = 0 } if classMap[className]["type"] != "none!important" && classMap[className]["type"] != "block!important"{ if (index >= oldIndex) || (classType == "none!important" || classType == "block!important"){ // 旧数据不是强制css的情况下,权重大或者是强制css时,覆盖 mutex.Lock() classMap[className]["type"] = classType classMap[className]["index"] = strconv.Itoa(index) mutex.Unlock() } }else{ if (classType == "none!important" || classType == "block!important") && (index >= oldIndex){ // 旧数据已经是强制css,如果当前也是强制css,且权重比旧有的更大,覆盖 mutex.Lock() classMap[className]["type"] = classType classMap[className]["index"] = strconv.Itoa(index) mutex.Unlock() } } }else{ // class 不存在 mutex.Lock() classMap[className] = map[string]string{"type":classType,"index":strconv.Itoa(index)} mutex.Unlock() } } }(i) } //cssWg.Wait() // 处理内嵌式css innerCssCompile := regexp.MustCompile("") innerCssArray := innerCssCompile.FindAllStringSubmatch(html,-1) for i:=0; i<len(innerCssArray); i++{ index := maxIndex + i + 1 innerCss := innerCssArray[i][1] wrapCompile := regexp.MustCompile("\r?\n") tmpCssContent1 := wrapCompile.ReplaceAllString(innerCss,"") // 2 删除被注释掉的内容 noteCompile := regexp.MustCompile("/\\*.*?\\*/") tmpCssContent2 := noteCompile.ReplaceAllString(tmpCssContent1,"") // 3 重新生成换行,每个class一行 classCompile := regexp.MustCompile("(\\.*\\w{3} *\\{.*?\\})") tmpCssContent3 := classCompile.ReplaceAllString(tmpCssContent2,"$1 \n") fmt.Println("TT ###########################################################") fmt.Println(tmpCssContent3) fmt.Println("TT ###########################################################") displayCompile := regexp.MustCompile("\\.(\\w{3}) *.*?(?:\\{|;) *display: *(none|block|block *!important|none *!important) *;.*?}") displayClassArray := displayCompile.FindAllStringSubmatch(tmpCssContent3,-1) for j:=0; j<len(displayClassArray); j++{ className := displayClassArray[j][1] classType := displayClassArray[j][2] classType = strings.ReplaceAll(classType," ","") _,ok := classMap[className] if ok{ // class 存在,除非是强制css 否则内联css 的优先级一定大于链接式的 if (classMap[className]["type"] != "none!important" && classMap[className]["type"] != "block!important")||(classType == "none!important" || classType == "block!important"){ classMap[className]["type"] = classType classMap[className]["index"] = strconv.Itoa(index) } }else{ // class 不存在 classMap[className] = map[string]string{"type":classType,"index":strconv.Itoa(index)} } } // 处理标签 css 样式 tagCssCompile := regexp.MustCompile(`[^ ](\w{3})(?:\{|\{.*?; *)display *: *(block|none|block *!important|none *!important) *;`) tagCssArray := tagCssCompile.FindAllStringSubmatch(tmpCssContent3,-1) fmt.Println(tagCssArray) for j:=0; j<len(tagCssArray);j++{ tagName := tagCssArray[j][1] classType := tagCssArray[j][2] classType = strings.ReplaceAll(classType," ","") if classType == "none"{ tagVisibilityMap[tagName] = "none" } if classType == "block"{ tagVisibilityMap[tagName] = "block" } if classType == "none!important"{ tagVisibilityMap[tagName] = "none!important" } if classType == "block!important"{ tagVisibilityMap[tagName] = "block!important" } } fmt.Println("tagVisibilityMap",tagVisibilityMap) } fmt.Println(classMap) for key := range classMap{ if classMap[key]["type"] == "block"{ blockClassNameArray = append(blockClassNameArray,key) } if classMap[key]["type"] == "block!important"{ blockImportantClassNameArray = append(blockImportantClassNameArray,key) } if classMap[key]["type"] == "none"{ noneClassNameArray = append(noneClassNameArray,key) } if classMap[key]["type"] == "none!important"{ noneImportantClassNameArray = append(noneImportantClassNameArray,key) } } } fmt.Println(noneClassNameArray,blockClassNameArray) return noneClassNameArray,blockClassNameArray,noneImportantClassNameArray,blockImportantClassNameArray,tagVisibilityMap } // 进行网络请求,获取网页内容 func getPage(url string) string{ fmt.Println(url+"\n") response,err := http.Get(url) if err != nil{ fmt.Println("请求错误:",err) return "" } defer response.Body.Close() body,err := ioutil.ReadAll(response.Body) if err != nil{ fmt.Println("body读取错误:",err) return "" } bodyString := string(body) return bodyString } // 判断字符串是否在字符串数组中 func inArrayString(target string,strArray []string) bool{ sort.Strings(strArray) index := sort.SearchStrings(strArray,target) if index < len(strArray) && strArray[index] == target{ return true } return false }

你可能感兴趣的:(爬虫,golang,开发语言)