[Java to Go] Go-Crawler Teil 1 (Goquery)

Vorwort

Nachdem ich Netzwerkprogrammierung gelernt hatte, musste ich es verwenden, also habe ich die Anwendung von Go Crawler überprüft. Im Allgemeinen habe ich Go-Colly- und Goquery-Frameworks verwendet. In diesem Artikel habe ich Goquery verwendet.

Ursprünglich wollte ich es mit Douban Movies versuchen, aber ich habe res, err = http.Get(url); verwendet, um die Anfrage zu senden. Die Anfrageadresse war korrekt, aber es gab keine Antwortdaten und sie war leer. Ich weiß nicht, ob es daran liegt, dass diese Webseite dynamisch ist? Oder gibt es einen Anti-Crawling-Mechanismus?

Ich weiß nicht viel über dieses Wissen. Ich habe Java JSOUP bisher nur zum Parsen einiger Roman-Websites und zum Herunterladen von Romanen verwendet. Und für mich, der Crawler nicht versteht, kann ich nur HTML analysieren. Wenn ich den HTML-Code nicht bekomme, kann ich nichts tun (⊙o⊙)

Die Prämisse dieses Artikels ist also, dass Sie HTML aus den zurückgegebenen Antwortdaten erhalten können, wenn Sie eine Anfrage an diese Website senden.

Was hier gezeigt wird, ist das Herunterladen von Romanen von einer Roman-Website.

goquery

Um dieses Framework nutzen zu können, folgen wir noch den alten Regeln, wir müssen es zunächst herunterladen:

go get github.com/PuerkitoBio/goquery

erreichen

Ideen

Wenn ich beispielsweise einen bestimmten Roman herunterladen möchte, klicke ich zuerst auf die Homepage des Romans und dann müssen wir die Artikelverzeichnisliste abrufen. Drücken Sie F12, um zu sehen, welche Klasse oder ID die Elemente der Verzeichnisliste haben.

Wenn wir später den HTML-Code erhalten, gehen wir zum Textkörper, um die ID oder Klasse der entsprechenden Verzeichnisliste zu finden.

Beispielsweise ist die Verzeichnisliste ein ul-Tag und das li-Tag unter ul ist das Kapitel jedes Kapitels. Dann hat diese UL eine ID. Wir können diese UL über diese ID finden und dann alle Li-Tags darunter abrufen.

Oder vielleicht hat ul keine ID, sondern eine Klasse, und das li-Tag hat auch eine Klasse, sodass Sie die Klasse verwenden müssen, um das Element zu finden.

Nachdem Sie die Verzeichnisliste erhalten haben, durchsuchen Sie das Verzeichnis. Im Allgemeinen gibt es ein a-Tag. Die href des a-Tags ist die Adresse jedes Kapitels. Beispielsweise wird das a-Tag innerhalb des li-Tags platziert.

Suchen Sie das Tag a und rufen Sie den Wert des href-Attributs ab, bei dem es sich um die Kapiteladresse handelt. Anschließend können wir eine Anfrage an diese Adresse senden.

Nach der Anfrage werden die Antwortdaten analysiert und der Inhalt des Artikeltexts abgerufen. Im Allgemeinen wird der Haupttextinhalt im p-Tag platziert. Einige Websites verwenden nicht das p-Tag, sondern platzieren den gesamten Kapitelinhalt direkt im div und verwenden das br-Tag für Zeilenumbrüche und  -Einrückungen.

Nachdem Sie den Artikelinhalt erhalten haben, können Sie ihn in eine Datei ausgeben.

Code

Ich habe früher etwas über Pipelines und Coroutinen gelernt. Sobald man sie gelernt hat, muss man sie verwenden, deshalb verwende ich hier auch diese beiden.

Definieren Sie eine Pipe zum Speichern von Kapiteladressen. Was Coroutinen betrifft, habe ich nur zwei Coroutinen geöffnet. Eine besteht darin, die Kapitelliste zu lesen und in die Pipeline einzufügen, die zweite darin, die Pipeline zu durchlaufen, um Anforderungen zu senden und den Kapitelinhalt abzurufen.

Da ich den gesamten Roman in eine TXT-Datei schreibe, kann ich nicht mehrere Coroutinen öffnen, um Anfragen zu senden, da sonst der Kapitelinhalt möglicherweise durcheinander und nicht in Ordnung ist. Wenn Sie jedes Kapitel einzeln in eine TXT-Datei schreiben, spielt die Reihenfolge keine Rolle.

package main

import (
	"bufio"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"net/http"
	"os"
	"sync"
)

var wg sync.WaitGroup
var chan1 = make(chan string, 10) // 存放章节地址

func main() {
    
    
	// 输出文件路径
	outPath := "C:\\Users\\Administrator\\Desktop\\1.txt"
	// 小说url地址
	var url = ""
	start(url, outPath)
}

func start(url string, outPath string) {
    
    
	var res *http.Response
	var err error
	// 输出到文件
	file, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE, 0666)
	if err != nil {
    
    
		fmt.Printf("文件打开失败:%v", err)
	}
	defer file.Close() // 按照defer的先进后出原则,这句代码会最后执行
	if res, err = http.Get(url); err != nil {
    
    
		fmt.Println(err)
		return
	}
	defer res.Body.Close() // 这句代码比 defer file.Close() 先执行
	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
    
    
		fmt.Println(err)
		return
	}
	writer := bufio.NewWriter(file) // NewWriter 带缓冲区的写入,写完之后要用flush刷新。
	wg.Add(2)                       // 下面开启了2个协程
	go getChapterList(doc)          // 开启一个协程,读取章节列表,并放入到管道中
	go getChapterContent(writer)    // 开启一个协程,读取 chan1管道中的href,去发送请求,拿到章节内容(这里不能开多个协程去请求,不然有可能不是按章节顺序去请求的)
	wg.Wait()                       // 等待协程执行完毕
	writer.Flush()
}

// 获取章节列表
func getChapterList(doc *goquery.Document) {
    
    
	// 获取章节列表
	doc.Find(".chapters").Each(func(i int, selection *goquery.Selection) {
    
    
		// 拿到 calss 为 .chapters 的li元素下的 a 标签
		attr := selection.First().Get(0).FirstChild.Attr
		href := attr[0].Val // 拿到a标签的href值
		chan1 <- href
	})
	close(chan1) // 将章节href全部写入完后,关闭管道
	wg.Done()    // 同时,主线程等待执行的协程数量-1
}

// 获取章节内容,并写入到文件中
func getChapterContent(writer *bufio.Writer) {
    
    
	var res *http.Response
	var err error
	for v := range chan1 {
    
    
		fmt.Println("正在下载:", v)
		if res, err = http.Get(v); err != nil {
    
    
			fmt.Println(err)
			return
		}
		doc, err := goquery.NewDocumentFromReader(res.Body)
		if err != nil {
    
    
			fmt.Println(err)
			return
		}
		var result string
		doc.Find("#content").Find("p").Each(func(i int, selection *goquery.Selection) {
    
    
			result += selection.Text() + "\r\n" // 每读一个p标签的内容,要加换行符,不然全部内容挤在一块了
		})
		writer.WriteString(result)
	}
	defer res.Body.Close() // 执行完这个函数后,关闭
	wg.Done()              // 同时,主线程等待执行的协程数量-1
}

Wie wäre es damit? Fühlt es sich sehr einfach an? Es ist eigentlich ganz einfach: Wenn Sie HTML erhalten können, müssen Sie der Positionierung von Elementen etwas mehr Aufmerksamkeit schenken. Es gibt nicht viel anderes.

Ok, das ist der gesamte Inhalt dieses Artikels, wir sehen uns im nächsten Artikel!

Guess you like

Origin blog.csdn.net/weixin_43165220/article/details/132515639
Go