golang筆記15--go 語言單任務版爬蟲
- 1 介紹
- 2 單任務版爬蟲
- 2.1 獲得初始頁面內容
- 2.2 正則運算式
- 2.3 提取城市和 url
- 2.4 單任務版爬蟲的架構
- 2.5 Engine 與 Parser
- 2.6 測驗 CityListParser
- 2.7 城市決議器
- 2.8 用戶資訊決議器(上)
- 2.9 用戶資訊決議器(下)
- 2.10 單任務版爬蟲性能
- 3 注意事項
- 4 說明
1 介紹
本文繼上文 golang筆記14-go 語言爬蟲實戰專案介紹, 進一步了解 go 語言單任務版爬蟲專案,以及相應注意事項,
具體包括: 獲得初始頁面內容、正則運算式、提取城市和 url、單任務版爬蟲的架構、Engine 與 Parser、測驗 CityListParser、城市決議器、用戶資訊決議器(上)、用戶資訊決議器(下)、單任務版爬蟲性能 等內容,
2 單任務版爬蟲
2.1 獲得初始頁面內容
暫時設定單任務爬蟲功能為:獲取并列印所有城市第一頁用戶的詳細資訊;
因此需要先獲取所有城市資訊,此處可以通過 http://www.zhenai.com/zhenghun 頁面獲取,以下為該主頁的獲取方式:
1) 添加字符轉換庫
go get golang.org/x/text
go get golang.org/x/net/html
2) 爬取主頁資訊
vim main.go
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/net/html/charset"
"golang.org/x/text/transform"
"golang.org/x/text/encoding"
)
func determineEncoding(r io.Reader) encoding.Encoding {
bytes, err := bufio.NewReader(r).Peek(1024)
if err != nil {
panic(err)
}
e, _, _ := charset.DetermineEncoding(bytes, "")
return e
}
func main() {
request, err := http.NewRequest(http.MethodGet, "http://www.zhenai.com/zhenghun", nil)
request.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36")
resp, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
e := determineEncoding(resp.Body)
utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())
// all, err := httputil.DumpResponse(resp, true)
all, err := ioutil.ReadAll(utf8Reader)
if err != nil {
panic(err)
}
fmt.Printf("%s\n", all)
}
2.2 正則運算式
獲取城市主頁之后,就需要進一步獲取所有城市的名稱和鏈接,常見獲取的方式包括:
-
使用 css 選擇器
在網頁界面右鍵-》Inspect-》Elements 下找到對應的table -》class=“city-list clearfix” -》Copy -》copy selector;
進入Console,將上面copy 的 selector 的內容作為一個變數執行,即可獲得對應的城市資訊,如下圖所示:$('#app > article:nth-child(4) > dl') 通過css 選擇器可以發現當前共有 22 個首字母欄目,共 470 個城市 ``` -
使用 xpath 選擇器
當前網頁的模式暫不支持,可以通過出現xpath相關的庫來實作,此處暫時不介紹, -
使用正則運算式
獲取網頁內容后,直接通過正則運算式獲取所需要的內容,本案例就是通過正則運算式來實作的,
以下為go語言正則匹配案例:
package main
import (
"fmt"
"regexp"
)
// 使用 ` raw 內容` 后,內部的字串不會收到轉義的影響
const text = `
my email is ccmouse@gmail.com@abc.com
email1 is abc@def.org
email2 is kkk@qq.com
email3 is ddd@abc.com.cn
`
func myRegexp1() {
fmt.Println("this myRegexp1")
text := "my email is ccmouse@gmail.com@abc.com"
re := regexp.MustCompile(".*@gmail.com")
match := re.FindString(text)
fmt.Println(match)
}
func myRegexp2() {
fmt.Println("this myRegexp2")
text := "my email is ccmouse@gmail.com@abc.com"
re := regexp.MustCompile("[a-zA-Z0-9]+@gmail.com") //以字母或者數字開通,緊接著為@,因此會過濾掉空格符和前面的內容
match := re.FindString(text)
fmt.Println(match)
}
func myRegexp3() {
fmt.Println("this myRegexp3")
re := regexp.MustCompile(`([a-zA-Z0-9]+)@([a-zA-Z0-9]+)(\.[a-zA-Z0-9.]+)`)
match := re.FindAllStringSubmatch(text, -1)
for _, m := range match {
fmt.Println(m)
}
}
func main() {
myRegexp1()
myRegexp2()
myRegexp3()
}
輸出:
this myRegexp1
my email is ccmouse@gmail.com
this myRegexp2
ccmouse@gmail.com
this myRegexp3
[ccmouse@gmail.com ccmouse gmail .com]
[abc@def.org abc def .org]
[kkk@qq.com kkk qq .com]
[ddd@abc.com.cn ddd abc .com.cn]
2.3 提取城市和 url
獲取網頁后,可以在輸出中 copy 一條城市鏈接資訊,根據正則提取城市名稱和城市 url,具體案例如下:
vim main.go
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"net/http"
"regexp"
"golang.org/x/net/html/charset"
"golang.org/x/text/transform"
"golang.org/x/text/encoding"
)
func determineEncoding(r io.Reader) encoding.Encoding {
bytes, err := bufio.NewReader(r).Peek(1024)
if err != nil {
panic(err)
}
e, _, _ := charset.DetermineEncoding(bytes, "")
return e
}
func printCityList(contents []byte) {
re := regexp.MustCompile(`<a href="(http://www.zhenai.com/zhenghun/[a-zA-Z0-9]+)"[^>]*>([^<]+)</a>`) // 加了括號后就會提取括號內的內容
matches := re.FindAllSubmatch(contents, -1)
for _, m := range matches {
fmt.Printf("City: %s, URL: %s\n", m[2], m[1])
}
fmt.Println("Matches found:", len(matches))
}
func main() {
request, err := http.NewRequest(http.MethodGet, "http://www.zhenai.com/zhenghun", nil)
request.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36")
resp, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer resp.Body.Close()
e := determineEncoding(resp.Body)
utf8Reader := transform.NewReader(resp.Body, e.NewDecoder())
all, err := ioutil.ReadAll(utf8Reader)
if err != nil {
panic(err)
}
printCityList(all)
}
輸出:
City: 阿壩, URL: http://www.zhenai.com/zhenghun/aba
City: 阿克蘇, URL: http://www.zhenai.com/zhenghun/akesu
......
City: 資陽, URL: http://www.zhenai.com/zhenghun/ziyang1
City: 遵義, URL: http://www.zhenai.com/zhenghun/zunyi
Matches found: 470
2.4 單任務版爬蟲的架構
? 對不同模塊設定不同的決議器,此處根據需要設定城市串列決議器、城市決議器和用戶決議器;
城市串列決議器:決議城市資訊,提取出城市名稱、城市url;
城市決議器:決議出城市中所有用戶的名稱和用戶的 url 資訊;
用戶決議器:決議用戶的各類資訊,包括姓名、年齡、少、體重、收入、性別、星座、職業等具體資訊;
? 由于需要設定多個決議器,此處單獨抽象出決議器 parser:
輸入: uft-8 編碼的文本;
輸出: Request{URL,對應的Parser} 串列, Item 串列;
? 抽象出 Parser 后,我們再添加Seed、Engine、 Fetcher 和任務佇列,就可以構建出如下的單任務爬蟲架構,其作業流程如下圖所示:
engine 用于驅動所有的動作;
種子頁面用于觸發一系列爬蟲任務;
種子頁面觸發爬蟲任務后,engine就會將任務丟到對應的佇列中;
engine 會不停的從佇列中去除任務,并將其丟給 Fetcher,Fetcher 就回傳指定的utf8文本給engine;
engine 收到 Fetcher 的資料后,讓 Parser 決議資料,Parser 提取出有用的item資料并存放到指定db,并把相關的目標(例如潛在的用戶 url )回傳給Engine,從而繼續提供新任務;
當任務佇列為空后,就可以結束單詞爬取任務了;

2.5 Engine 與 Parser
本節繼續對模塊進行優化和完善,繼續抽象出 engine, fetcher,parser等模塊,并大幅優化 main.go 函式;具體優化如下:
/learngo/crawler$ tree -L 3
.
├── engine
│ ├── engine.go
│ └── types.go
├── fetcher
│ └── fetcher.go
├── mian.go
└── zhenai
└── parser
└── citylist.go
4 directories, 5 files
更新代碼比較多,此處暫不逐一貼出:
vim engine/engin.go
func Run(seeds ...Request)
vim engine/types.go
type Request struct
type ParseResult struct
func NilParser([]byte) ParseResult
vim fetcher/fetcher.go
func determineEncoding(r io.Reader) encoding.Encoding
func Fetch(url string) ([]byte, error)
vim zhenai/parser/citylist.go
func ParseCityList(contents []byte) engine.ParseResult
vim main.go
package main
import (
"learngo/crawler/engine"
"learngo/crawler/zhenai/parser"
)
func main() {
url := "http://www.zhenai.com/zhenghun"
engine.Run(engine.Request{Url: url, ParserFunc: parser.ParseCityList})
}
輸出:
2021/02/19 10:10:48 Fetching http://www.zhenai.com/zhenghun
2021/02/19 10:10:49 Get item: 阿壩
2021/02/19 10:10:49 Get item: 阿克蘇
......
2021/02/19 10:10:49 Get item: 資陽
2021/02/19 10:10:49 Get item: 遵義
2021/02/19 10:10:49 Fetching http://www.zhenai.com/zhenghun/aba
2021/02/19 10:10:49 Fetching http://www.zhenai.com/zhenghun/akesu
......
2021/02/19 10:12:32 Fetching http://www.zhenai.com/zhenghun/ziyang1
2021/02/19 10:12:32 Fetching http://www.zhenai.com/zhenghun/zunyi
2.6 測驗 CityListParser
上一小節已經完成了基礎的 citylist 決議,因此可以使用之前 golang筆記09–go語言測驗與性能調優 中的方法對其進行測驗,
測驗時,正常情況下下直接獲取網頁資料,然后和測驗結果進行對比,并得出測驗結論;
但是考慮到網路情況(存在斷網或者暫時無法連外網的情況),可以先將目標網頁保存到本地,然后再通過決議本地檔案來測驗;
本案例中就是使用第二種方式測驗的,測驗中暫且選取了 3 個測驗用例,
vim zhenai/parser/citylist_test.go
package parser
import (
"io/ioutil"
"testing"
)
func TestParseCityList(t *testing.T) {
/* 預先獲取資料,復制到 citylist_test_data.html 中,以便于后續測驗對比
contents, err := fetcher.Fetch("http://www.zhenai.com/zhenghun")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", contents)
*/
contents, err := ioutil.ReadFile(
"citylist_test_data.html")
if err != nil {
panic(err)
}
result := ParseCityList(contents)
const resultSize = 470
expectedUrls := []string{
"http://www.zhenai.com/zhenghun/aba",
"http://www.zhenai.com/zhenghun/akesu",
"http://www.zhenai.com/zhenghun/alashanmeng",
}
expectedCities := []string{
"阿壩",
"阿克蘇",
"阿拉善盟",
}
if len(result.Requests) != resultSize {
t.Errorf("result should have %d requests; but had %d", resultSize, len(result.Requests))
}
for i, url := range expectedUrls {
if result.Requests[i].Url != url {
t.Errorf("expected url #%d: %s; but was %s", i, url, result.Requests[i].Url)
}
}
if len(result.Items) != resultSize {
t.Errorf("result should have %d requests; but had %d", resultSize, len(result.Items))
}
for i, city := range expectedCities {
if result.Items[i].(string) != city {
t.Errorf("expected city #%d: %s; but was %s", i, city, result.Items[i].(string))
}
}
}
輸出:
=== RUN TestParseCityList
--- PASS: TestParseCityList (0.00s)
PASS
2.7 城市決議器
vim zhenai/parser/city.go
package parser
import (
"regexp"
"learngo/crawler/engine"
)
const cityRe = `<a href="(http://album\.zhenai\.com/u/[0-9]+)"[^>]*>([^<]+)</a>`
func ParseCity(contents []byte) engine.ParseResult {
// example <a href="http://album.zhenai.com/u/1412872831" target="_blank">執著</a>
re := regexp.MustCompile(cityRe) // 加了括號后就會提取括號內的內容
matches := re.FindAllSubmatch(contents, -1)
result := engine.ParseResult{}
for _, m := range matches {
result.Items = append(result.Items, "User "+string(m[2]))
result.Requests = append(result.Requests, engine.Request{
Url: string(m[1]),
ParserFunc: engine.NilParser,
})
}
return result
}
執行 main.go 輸出:
2021/02/19 12:58:30 Fetching http://www.zhenai.com/zhenghun
2021/02/19 12:58:30 Got item: City 阿壩
2021/02/19 12:58:30 Got item: City 阿克蘇
......
2021/02/19 12:58:30 Got item: City 資陽
2021/02/19 12:58:30 Got item: City 遵義
2021/02/19 12:58:30 Fetching http://www.zhenai.com/zhenghun/aba
2021/02/19 12:58:30 Got item: User 硒路西路
2021/02/19 12:58:30 Got item: User 心悅
2021/02/19 12:58:30 Got item: User 飛花落硯
......
2021/02/19 12:58:30 Got item: User 余生有你
2021/02/19 12:58:30 Got item: User 執著
2021/02/19 12:58:30 Fetching http://www.zhenai.com/zhenghun/akesu
2021/02/19 12:58:30 Got item: User 許我個未來
2021/02/19 12:58:30 Got item: User 不必在乎我是誰
......
2.8 用戶資訊決議器(上)
由于當前珍愛網用戶資訊在不登錄的情況下無法直接獲取,因此此處暫時不通過具體用戶頁面來獲取;后續將更改為從城市頁面獲取每個頁面的的內容,并提取少量用戶資訊,具體包括下圖中的 用戶昵稱、用戶uid|url、年齡、性別、婚姻狀況、學歷、身高、收入、自我介紹等內容,
初步決議如下:
vim learngo/crawler/zhenai/parser/city.go
package parser
import (
"learngo/crawler/engine"
"regexp"
"time"
)
const (
cityRe = `<a href="(http://album\.zhenai\.com/u/[0-9]+)"[^>]*>([^<]+)</a>`
ageRe = `<td width="[0-9]+"><span class="grayL">年齡:</span>([^<]+)</td>`
genderRe = `<td width="[0-9]+"><span class="grayL">性別:</span>([^<]+)</td>`
marriageRe = `<td width="[0-9]+"><span class="grayL">婚況:</span>([^<]+)</td>`
locationRe = `<td><span class="grayL">居住地:</span>([^<]+)</td>`
educationRe = `<td><span class="grayL">學 歷:</span>([^<]+)</td>`
heightRe = `<td width="[0-9]+"><span class="grayL">身 高:</span>([^<]+)</td>`
incomeRe = `<td><span class="grayL">月 薪:</span>([^<]+)</td>`
introduceRe = `<div class="introduce">([^<]+)</div>`
idUrlRe = `.*album\.zhenai\.com/u/([\d]+)`
)
func getMatches(reRule string, contents []byte) []string {
reAge := regexp.MustCompile(reRule)
matchesAge := reAge.FindAllSubmatch(contents, -1)
retList := make([]string, len(matchesAge))
for i, m := range matchesAge {
retList[i] = string(m[1])
}
return retList
}
func extractString(contents []byte, re *regexp.Regexp) string {
match := re.FindSubmatch(contents)
if len(match) >= 2 {
return string(match[1])
} else {
return "null"
}
}
func ParseCity(contents []byte) engine.ParseResult {
// example <a href="http://album.zhenai.com/u/1412872831" target="_blank">執著</a>
re := regexp.MustCompile(cityRe) // 加了括號后就會提取括號內的內容
matches := re.FindAllSubmatch(contents, -1)
ageList := getMatches(ageRe, contents)
genderList := getMatches(genderRe, contents)
marriageList := getMatches(marriageRe, contents)
heightList := getMatches(heightRe, contents)
locationList := getMatches(locationRe, contents)
// educationList := getMatches(educationRe, contents) //部分用戶沒有education資訊,需要更新調整為null
// incomeList := getMatches(incomeRe, contents) //部分用戶沒有income資訊,需要更新調整為null
// fmt.Println("education", len(incomeList))
result := engine.ParseResult{}
for i, m := range matches {
idUrl := extractString(m[1], regexp.MustCompile(idUrlRe))
result.Items = append(result.Items, "User info: "+idUrl+", "+string(m[2])+", "+ageList[i]+", "+genderList[i]+
", "+marriageList[i]+", "+heightList[i]+", "+locationList[i]+", "+string(m[1])) //+", "+incomeList[i]+", "+educationList[i])
result.Requests = append(result.Requests, engine.Request{
Url: string(m[1]),
ParserFunc: engine.NilParser,
})
}
time.Sleep(time.Duration(time.Second * 2)) // 測驗的時候爬慢點,否則會被系統檢測到并禁止一或多天不能訪問
return result
}
輸出:
2021/02/20 12:37:30 Fetching http://www.zhenai.com/zhenghun
2021/02/20 12:37:30 Got item: City 阿壩
2021/02/20 12:37:30 Got item: City 阿克蘇
......
2021/02/20 12:37:30 Got item: City 資陽
2021/02/20 12:37:30 Got item: City 遵義
2021/02/20 12:37:30 Fetching http://www.zhenai.com/zhenghun/aba
2021/02/20 12:37:32 Got item: User info: 1876503328, 硒路西路, 30, 女士, 未婚, 163, 四川阿壩, http://album.zhenai.com/u/1876503328
......
2021/02/20 12:37:32 Got item: User info: 1412872831, 執著, 36, 女士, 離異, 162, 四川阿壩, http://album.zhenai.com/u/1412872831
2021/02/20 12:37:32 Fetching http://www.zhenai.com/zhenghun/akesu
......

2.9 用戶資訊決議器(下)
后續將更改為從城市頁面獲取每個用戶的少量非重要資訊,
2.10 單任務版爬蟲性能
待添加
3 注意事項
待添加
4 說明
- 軟體環境
go版本:go1.15.8
作業系統:Ubuntu 20.04 Desktop
Idea:2020.01.04 - 參考檔案
由淺入深掌握Go語言 --慕課網
正則運算式在線測驗 --菜鳥網
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/261874.html
標籤:其他
