基於golang的爬蟲實戰

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

基於golang的爬蟲實戰

前言

爬蟲本來是python的強項,前期研究過scrapy,也寫過一些簡單的爬蟲小程式,但是後來突然對golang產生興趣,決定寫寫爬蟲練練手。由於本人golang萌新,有錯誤之處,歡迎指正。

大致思路

  • 由於現在動態網頁面比較多,因此考慮通過WebDriver驅動Chrome等頁面渲染完成再抓取資料。(剛開始是用Phantomjs,後來這貨不維護了,而且效率不算高)
  • 一般爬蟲程式運行在linux系統中,所以考慮Chrome的headless模式。
  • 資料抓取到之後儲存到CSV檔案中,然後通過郵件發送出去。

不足之處

  • 因為需要渲染,所以速度會降低不少,即便是不渲染圖片,速度也不是很理想。
  • 因為剛開始學習,所以多線程什麼的也沒加進去,怕記憶體會崩盤。
  • 沒有將資料寫入到資料庫,放到檔案裡畢竟不是最終方案。

需要的庫

  • github.com/tebeka/selenium
    • golang版的selenium,能實現大部分功能。
  • gopkg.in/gomail.v2
    • 發送郵件用到的庫,很久不更新了,但夠用。

下載依賴包

  • 本打算用dep管理依賴,結果這貨坑還挺多,未研究明白不敢誤人,暫時放棄。
  • 通過go get 下載依賴包
go get github.com/tebeka/seleniumgo get gopkg.in/gomail.v2

代碼實現

  • 啟動chromedriver,用來驅動Chrome瀏覽器,並通過代碼驅動瀏覽器。
// StartChrome 啟動Google瀏覽器headless模式func StartChrome() {opts := []selenium.ServiceOption{}caps := selenium.Capabilities{"browserName":                      "chrome",}            // 禁止載入圖片,加快渲染速度imagCaps := map[string]interface{}{"profile.managed_default_content_settings.images": 2,}chromeCaps := chrome.Capabilities{Prefs: imagCaps,Path:  "",Args: []string{"--headless", // 設定Chrome無頭模式"--no-sandbox","--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/604.4.7 (KHTML, like Gecko) Version/11.0.2 Safari/604.4.7", // 類比user-agent,防反爬},}caps.AddChrome(chromeCaps)        // 啟動chromedriver,連接埠號碼可自訂service, err = selenium.NewChromeDriverService("/opt/google/chrome/chromedriver", 9515, opts...) if err != nil {log.Printf("Error starting the ChromeDriver server: %v", err)}        // 調起chrome瀏覽器webDriver, err = selenium.NewRemote(caps, fmt.Sprintf("http://localhost:%d/wd/hub", 9515))if err != nil {panic(err)}// 這是目標網站留下的坑,不加這個在linux系統中會顯示手機網頁,每個網站的策略不一樣,需要區別處理。webDriver.AddCookie(&selenium.Cookie{Name:  "defaultJumpDomain",Value: "www",})        // 導航到目標網站err = webDriver.Get(urlBeijing)if err != nil {panic(fmt.Sprintf("Failed to load page: %s\n", err))}log.Println(webDriver.Title())}

通過上述代碼即可實現通過代碼啟動Chrome並跳轉到目標網站,方便下一步的資料擷取。

  • 初始化CSV,資料存放地
// SetupWriter 初始化CSVfunc SetupWriter() {dateTime = time.Now().Format("2006-01-02 15:04:05") // 格式字串是固定的,據說是go語言誕生時間,Google的惡趣味...os.Mkdir("data", os.ModePerm)csvFile, err := os.Create(fmt.Sprintf("data/%s.csv", dateTime))if err != nil {panic(err)}csvFile.WriteString("\xEF\xBB\xBF")writer = csv.NewWriter(csvFile)writer.Write([]string{"車型", "行駛裡程", "首次上牌", "價格", "所在地", "門店"})}
資料抓取

這一部分是核心業務,每個網站的抓取方式都不一樣,但是思路都是一致的,通過xpath,css選取器,className, tagName等來擷取元素的內容,selenium的api能實現大部分的操作功能,通過selenium源碼可以看到,核心api包括WebDriver與WebElement,下面寫下我抓取二手車之家北京二手車資料的過程,其他網站可參考改過程。

  • 通過Safari瀏覽器開啟二手車之家網站,得到北京二手車首頁串連
const urlBeijing = "https://www.che168.com/beijing/list/#pvareaid=104646"
  • 在頁面上右鍵點擊“檢查元素”進入開發人員模式,可以看到所有的資料都在這裡面
<ul class="fn-clear certification-list" id="viewlist_ul">

滑鼠指向這一句右鍵,依次拷貝-XPath,即可得到改元素所在的xpath屬性

//*[@id="viewlist_ul"]

然後通過代碼

listContainer, err := webDriver.FindElement(selenium.ByXPATH, "//*[@id=\"viewlist_ul\"]")

即可得到改段html的WebElement對象,不難看出這是所有資料的父容器,為了得到具體的資料需要定位到每一個元素子集,通過開發模式可以看到

通過開發人員工具可以得到class為carinfo,因為這個元素存在多個,所以通過

lists, err := listContainer.FindElements(selenium.ByClassName, "carinfo")

可以得到所有元素子集的集合,要得到每個子集裡面的元素資料,需要對改集合進行遍曆

for i := 0; i < len(lists); i++ {var urlElem selenium.WebElementif pageIndex == 1 {urlElem, err = webDriver.FindElement(selenium.ByXPATH, fmt.Sprintf("//*[@id='viewlist_ul']/li[%d]/a", i+13))} else {urlElem, err = webDriver.FindElement(selenium.ByXPATH, fmt.Sprintf("//*[@id='viewlist_ul']/li[%d]/a", i+1))}if err != nil {break}// 因為有些資料在次級頁面,需要跳轉url, err := urlElem.GetAttribute("href") if err != nil {break}    webDriver.Get(url)title, _ := webDriver.Title()log.Printf("當前頁面標題:%s\n", title)        // 擷取車輛型號modelElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[1]/h2")var model stringif err != nil {log.Println(err)model = "暫無"} else {model, _ = modelElem.Text()}log.Printf("model=[%s]\n", model)    ...        // 資料寫入CSV    writer.Write([]string{model, miles, date, price, position, store})writer.Flush()webDriver.Back() // 回退到上級頁面重複步驟抓取}

所有原始碼如下,初學者,輕噴~~

// StartCrawler 開始爬取資料func StartCrawler() {log.Println("Start Crawling at ", time.Now().Format("2006-01-02 15:04:05"))pageIndex := 0for {listContainer, err := webDriver.FindElement(selenium.ByXPATH, "//*[@id=\"viewlist_ul\"]")if err != nil {panic(err)}lists, err := listContainer.FindElements(selenium.ByClassName, "carinfo")if err != nil {panic(err)}log.Println("資料量:", len(lists))pageIndex++log.Printf("正在抓取第%d頁資料...\n", pageIndex)for i := 0; i < len(lists); i++ {var urlElem selenium.WebElementif pageIndex == 1 {urlElem, err = webDriver.FindElement(selenium.ByXPATH, fmt.Sprintf("//*[@id='viewlist_ul']/li[%d]/a", i+13))} else {urlElem, err = webDriver.FindElement(selenium.ByXPATH, fmt.Sprintf("//*[@id='viewlist_ul']/li[%d]/a", i+1))}if err != nil {break}url, err := urlElem.GetAttribute("href")if err != nil {break}webDriver.Get(url)title, _ := webDriver.Title()log.Printf("當前頁面標題:%s\n", title)modelElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[1]/h2")var model stringif err != nil {log.Println(err)model = "暫無"} else {model, _ = modelElem.Text()}log.Printf("model=[%s]\n", model)priceElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[2]/div/ins")var price stringif err != nil {log.Println(err)price = "暫無"} else {price, _ = priceElem.Text()price = fmt.Sprintf("%s萬", price)}log.Printf("price=[%s]\n", price)milesElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[4]/ul/li[1]/span")var miles stringif err != nil {log.Println(err)milesElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[3]/ul/li[1]/span")if err != nil {log.Println(err)miles = "暫無"} else {miles, _ = milesElem.Text()}} else {miles, _ = milesElem.Text()}log.Printf("miles=[%s]\n", miles)timeElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[4]/ul/li[2]/span")var date stringif err != nil {log.Println(err)timeElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[3]/ul/li[2]/span")if err != nil {log.Println(err)date = "暫無"} else {date, _ = timeElem.Text()}} else {date, _ = timeElem.Text()}log.Printf("time=[%s]\n", date)positionElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[4]/ul/li[4]/span")var position stringif err != nil {log.Println(err)positionElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[3]/ul/li[4]/span")if err != nil {log.Println(err)position = "暫無"} else {position, _ = positionElem.Text()}} else {position, _ = positionElem.Text()}log.Printf("position=[%s]\n", position)storeElem, err := webDriver.FindElement(selenium.ByXPATH, "/html/body/div[5]/div[2]/div[1]/div/div/div")var store stringif err != nil {log.Println(err)store = "暫無"} else {store, _ = storeElem.Text()store = strings.Replace(store, "商家|", "", -1)if strings.Contains(store, "金牌店鋪") {store = strings.Replace(store, "金牌店鋪", "", -1)}}log.Printf("store=[%s]\n", store)writer.Write([]string{model, miles, date, price, position, store})writer.Flush()webDriver.Back()}log.Printf("第%d頁資料已經抓取完畢,開始下一頁...\n", pageIndex)nextButton, err := webDriver.FindElement(selenium.ByClassName, "page-item-next")if err != nil {log.Println("所有資料抓取完畢!")break}nextButton.Click()}log.Println("Crawling Finished at ", time.Now().Format("2006-01-02 15:04:05"))sendResult(dateTime)}
  • 發送郵件

全部代碼如下,比較簡單,不做贅述

func sendResult(fileName string) {email := gomail.NewMessage()email.SetAddressHeader("From", "re**ng@163.com", "張**")email.SetHeader("To", email.FormatAddress("li**yang@163.com", "李**"))email.SetHeader("Cc", email.FormatAddress("zhang**tao@163.net", "張**"))email.SetHeader("Subject", "二手車之家-北京-二手車資訊")email.SetBody("text/plain;charset=UTF-8", "本周抓取到的二手車資訊資料,請注意查收!\n")email.Attach(fmt.Sprintf("data/%s.csv", fileName))dialer := &gomail.Dialer{Host:     "smtp.163.com",Port:     25,Username: ${your_email},    // 替換自己的郵箱地址Password: ${smtp_password}, // 自訂smtp伺服器密碼SSL:      false,}if err := dialer.DialAndSend(email); err != nil {log.Println("郵件發送失敗!err: ", err)return}log.Println("郵件發送成功!")}
  • 最後記的回收資源
defer service.Stop()    // 停止chromedriverdefer webDriver.Quit()  // 關閉瀏覽器defer csvFile.Close()   // 關閉檔案流

總結

  • 初學golang,純粹拿爬蟲項目練手,代碼比較粗糙沒有任何工程性可言,希望不會誤人子弟。
  • 由於golang爬蟲基本沒有其他項目可借鑒,所以裡面也有自己的一些研究所得吧,也希望能幫到別人吧。
  • 最後,安利一個大神寫的爬蟲架構Pholcus,功能強大,是目前比較完善的一個架構。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.