用go語言爬取珍愛網 | 第三回

前兩節咱們獲取到了城市的URL和城市名,今天咱們來解析用戶信息。html

用go語言爬取珍愛網 | 第一回前端

用go語言爬取珍愛網 | 第二回java

image

爬蟲的算法:python

咱們要提取返回體中的城市列表,須要用到城市列表解析器;golang

須要把每一個城市裏的全部用戶解析出來,須要用到城市解析器;面試

還須要把每一個用戶的我的信息解析出來,須要用到用戶解析器。算法

image

爬蟲總體架構:spring

Seed把須要爬的request送到engine,engine負責將request裏的url送到fetcher去爬取數據,返回utf-8的信息,而後engine將返回信息送到解析器Parser裏解析有用信息,返回更多待請求requests和有用信息items,任務隊列用於存儲待請求的request,engine驅動各模塊處理數據,直到任務隊列爲空。編程

image

代碼實現:segmentfault

按照上面的思路,設計出城市列表解析器citylist.go代碼以下:

package parser

import (
 "crawler/engine"
 "regexp"
 "log"
)

const (
 //<a href="http://album.zhenai.com/u/1361133512" target="_blank">怎麼會迷上你</a>
 cityReg = `<a href="(http://album.zhenai.com/u/[0-9]+)"[^>]*>([^<]+)</a>`
)

func ParseCity(contents []byte) engine.ParserResult {
 compile := regexp.MustCompile(cityReg)

 submatch := compile.FindAllSubmatch(contents, -1)

 //這裏要把解析到的每一個URL都生成一個新的request

 result := engine.ParserResult{}

 for _, m := range submatch {
   name := string(m[2])
   log.Printf("UserName:%s URL:%s\n", string(m[2]), string(m[1]))

   //把用戶信息人名加到item裏
   result.Items = append(result.Items, name)

   result.Requests = append(result.Requests,
     engine.Request{
       //用戶信息對應的URL,用於以後的用戶信息爬取
       Url : string(m[1]),
       //這個parser是對城市下面的用戶的parse
       ParserFunc : func(bytes []byte) engine.ParserResult {
         //這裏使用閉包的方式;這裏不能用m[2],不然全部for循環裏的用戶都會共用一個名字
         //須要拷貝m[2] ---- name := string(m[2])
         return ParseProfile(bytes, name)
       },
     })
 }

 return result
}

城市解析器city.go以下:

package parser

import (
 "crawler/engine"
 "regexp"
 "log"
)

const (
 //<a href="http://album.zhenai.com/u/1361133512" target="_blank">怎麼會迷上你</a>
 cityReg = `<a href="(http://album.zhenai.com/u/[0-9]+)"[^>]*>([^<]+)</a>`
)

func ParseCity(contents []byte) engine.ParserResult {
 compile := regexp.MustCompile(cityReg)

 submatch := compile.FindAllSubmatch(contents, -1)

 //這裏要把解析到的每一個URL都生成一個新的request

 result := engine.ParserResult{}

 for _, m := range submatch {
   name := string(m[2])
   log.Printf("UserName:%s URL:%s\n", string(m[2]), string(m[1]))

   //把用戶信息人名加到item裏
   result.Items = append(result.Items, name)

   result.Requests = append(result.Requests,
     engine.Request{
       //用戶信息對應的URL,用於以後的用戶信息爬取
       Url : string(m[1]),
       //這個parser是對城市下面的用戶的parse
       ParserFunc : func(bytes []byte) engine.ParserResult {
         //這裏使用閉包的方式;這裏不能用m[2],不然全部for循環裏的用戶都會共用一個名字
         //須要拷貝m[2] ---- name := string(m[2])
         return ParseProfile(bytes, name)
       },
     })
 }

 return result
}

 用戶解析器profile.go以下:

package parser

import (
 "crawler/engine"
 "crawler/model"
 "regexp"
 "strconv"
)

var (
 // <td><span class="label">年齡:</span>25歲</td>
 ageReg = regexp.MustCompile(`<td><span class="label">年齡:</span>([\d]+)歲</td>`)
 // <td><span class="label">身高:</span>182CM</td>
 heightReg = regexp.MustCompile(`<td><span class="label">身高:</span>(.+)CM</td>`)
 // <td><span class="label">月收入:</span>5001-8000元</td>
 incomeReg = regexp.MustCompile(`<td><span class="label">月收入:</span>([0-9-]+)元</td>`)
 //<td><span class="label">婚況:</span>未婚</td>
 marriageReg = regexp.MustCompile(`<td><span class="label">婚況:</span>(.+)</td>`)
 //<td><span class="label">學歷:</span>大學本科</td>
 educationReg = regexp.MustCompile(`<td><span class="label">學歷:</span>(.+)</td>`)
 //<td><span class="label">工做地:</span>安徽蚌埠</td>
 workLocationReg = regexp.MustCompile(`<td><span class="label">工做地:</span>(.+)</td>`)
 // <td><span class="label">職業: </span>--</td>
 occupationReg = regexp.MustCompile(`<td><span class="label">職業: </span><span field="">(.+)</span></td>`)
 //  <td><span class="label">星座:</span>射手座</td>
 xinzuoReg = regexp.MustCompile(`<td><span class="label">星座:</span><span field="">(.+)</span></td>`)
 //<td><span class="label">籍貫:</span>安徽蚌埠</td>
 hokouReg = regexp.MustCompile(`<td><span class="label">民族:</span><span field="">(.+)</span></td>`)
 // <td><span class="label">住房條件:</span><span field="">--</span></td>
 houseReg = regexp.MustCompile(`<td><span class="label">住房條件:</span><span field="">(.+)</span></td>`)
 // <td width="150"><span class="grayL">性別:</span>男</td>
 genderReg = regexp.MustCompile(`<td width="150"><span class="grayL">性別:</span>(.+)</td>`)

 // <td><span class="label">體重:</span><span field="">67KG</span></td>
 weightReg = regexp.MustCompile(`<td><span class="label">體重:</span><span field="">(.+)KG</span></td>`)
 //<h1 class="ceiling-name ib fl fs24 lh32 blue">怎麼會迷上你</h1>
 //nameReg = regexp.MustCompile(`<h1 class="ceiling-name ib fl fs24 lh32 blue">([^\d]+)</h1>  `)
 //<td><span class="label">是否購車:</span><span field="">未購車</span></td>
 carReg = regexp.MustCompile(`<td><span class="label">是否購車:</span><span field="">(.+)</span></td>`)
)

func ParseProfile(contents []byte, name string) engine.ParserResult {

 profile := model.Profile{}

 age, err := strconv.Atoi(extractString(contents, ageReg))

 if err != nil {
   profile.Age = 0
 }else {
   profile.Age = age
 }



 height, err := strconv.Atoi(extractString(contents, heightReg))
 if err != nil {
   profile.Height = 0
 }else {
   profile.Height = height
 }

 weight, err := strconv.Atoi(extractString(contents, weightReg))
 if err != nil {
   profile.Weight = 0
 }else {
   profile.Weight = weight
 }

 profile.Income = extractString(contents, incomeReg)

 profile.Car = extractString(contents, carReg)

 profile.Education = extractString(contents, educationReg)
 profile.Gender = extractString(contents, genderReg)

 profile.Hokou = extractString(contents, hokouReg)
 profile.Income = extractString(contents, incomeReg)
 profile.Marriage = extractString(contents, marriageReg)
 profile.Name = name
 profile.Occupation = extractString(contents, occupationReg)
 profile.WorkLocation = extractString(contents, workLocationReg)
 profile.Xinzuo = extractString(contents, xinzuoReg)

 result := engine.ParserResult{
   Items: []interface{}{profile},
 }

 return result
}

//get value by reg from contents
func extractString(contents []byte, re *regexp.Regexp) string {

 m := re.FindSubmatch(contents)

 if len(m) > 0 {
   return string(m[1])
 } else {
   return ""
 }
}

engine代碼以下:

package engine

import (
 "crawler/fetcher"
 "log"
)

func Run(seeds ...Request){

 //這裏維持一個隊列
 var requestsQueue []Request

 requestsQueue = append(requestsQueue, seeds...)

 for len(requestsQueue) > 0 {
   //取第一個
   r := requestsQueue[0]
   //只保留沒處理的request
   requestsQueue = requestsQueue[1:]

   log.Printf("fetching url:%s\n", r.Url)
   //爬取數據
   body, err := fetcher.Fetch(r.Url)

   if err != nil {
     log.Printf("fetch url: %s; err: %v\n", r.Url, err)
     //發生錯誤繼續爬取下一個url
     continue
   }

   //解析爬取到的結果
   result := r.ParserFunc(body)

   //把爬取結果裏的request繼續加到request隊列
   requestsQueue = append(requestsQueue, result.Requests...)

   //打印每一個結果裏的item,即打印城市名、城市下的人名...
   for _, item := range result.Items {
     log.Printf("get item is %v\n", item)
   }
 }
}

Fetcher用於發起http get請求,這裏有一點注意的是:珍愛網可能作了反爬蟲限制手段,因此直接用http.Get(url)方式發請求,會報403拒絕訪問;故須要模擬瀏覽器方式:

client := &http.Client{}
 req, err := http.NewRequest("GET", url, nil)
 if err != nil {
   log.Fatalln("NewRequest is err ", err)
   return nil, fmt.Errorf("NewRequest is err %v\n", err)
 }

 req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")

 //返送請求獲取返回結果
 resp, err := client.Do(req)

最終fetcher代碼以下:

package fetcher

import (
 "bufio"
 "fmt"
 "golang.org/x/net/html/charset"
 "golang.org/x/text/encoding"
 "golang.org/x/text/encoding/unicode"
 "golang.org/x/text/transform"
 "io/ioutil"
 "log"
 "net/http"
)

/**
爬取網絡資源函數
*/
func Fetch(url string) ([]byte, error) {

 client := &http.Client{}
 req, err := http.NewRequest("GET", url, nil)
 if err != nil {
   log.Fatalln("NewRequest is err ", err)
   return nil, fmt.Errorf("NewRequest is err %v\n", err)
 }

 req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36")

 //返送請求獲取返回結果
 resp, err := client.Do(req)

 //直接用http.Get(url)進行獲取信息,爬取時可能返回403,禁止訪問
 //resp, err := http.Get(url)

 if err != nil {
   return nil, fmt.Errorf("Error: http Get, err is %v\n", err)
 }

 //關閉response body
 defer resp.Body.Close()

 if resp.StatusCode != http.StatusOK {
   return nil, fmt.Errorf("Error: StatusCode is %d\n", resp.StatusCode)
 }

 //utf8Reader := transform.NewReader(resp.Body, simplifiedchinese.GBK.NewDecoder())
 bodyReader := bufio.NewReader(resp.Body)
 utf8Reader := transform.NewReader(bodyReader, determineEncoding(bodyReader).NewDecoder())

 return ioutil.ReadAll(utf8Reader)
}

/**
確認編碼格式
*/
func determineEncoding(r *bufio.Reader) encoding.Encoding {

 //這裏的r讀取完得保證resp.Body還可讀
 body, err := r.Peek(1024)

 //若是解析編碼類型時遇到錯誤,返回UTF-8
 if err != nil {
   log.Printf("determineEncoding error is %v", err)
   return unicode.UTF8
 }

 //這裏簡化,不取是否確認
 e, _, _ := charset.DetermineEncoding(body, "")
 return e
}

main方法以下:

package main

import (
 "crawler/engine"
 "crawler/zhenai/parser"
)

func main() {

 request := engine.Request{
   Url: "http://www.zhenai.com/zhenghun",
   ParserFunc: parser.ParseCityList,
 }

 engine.Run(request)
}

最終爬取到的用戶信息以下,包括暱稱、年齡、身高、體重、工資、婚姻情況等。

image

若是你想要哪一個妹子的照片,能夠點開url查看,而後打招呼進一步發展。

至此單任務版的爬蟲就作完了,後面咱們將對單任務版爬蟲作性能分析,而後升級爲多任務併發版,把爬取到的信息存到ElasticSearch中,在頁面上查詢



本公衆號免費提供csdn下載服務,海量IT學習資源,若是你準備入IT坑,勵志成爲優秀的程序猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大數據、面試資料、前端 等資源。同時咱們組建了一個技術交流羣,裏面有不少大佬,會不定時分享技術文章,若是你想來一塊兒學習提升,能夠公衆號後臺回覆【2】,免費邀請加技術交流羣互相學習提升,會不按期分享編程IT相關資源。


掃碼關注,精彩內容第一時間推給你

image



本公衆號免費提供csdn下載服務,海量IT學習資源,若是你準備入IT坑,勵志成爲優秀的程序猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大數據、面試資料、前端 等資源。同時咱們組建了一個技術交流羣,裏面有不少大佬,會不定時分享技術文章,若是你想來一塊兒學習提升,能夠公衆號後臺回覆【2】,免費邀請加技術交流羣互相學習提升,會不按期分享編程IT相關資源。


掃碼關注,精彩內容第一時間推給你

image

相關文章
相關標籤/搜索