1. 程式人生 > >爬蟲之刃----簡化爬蟲程式之滾輪子實戰(系列三)

爬蟲之刃----簡化爬蟲程式之滾輪子實戰(系列三)

前言


這是一些準備工作:

  1. 瞭解整個系列的安排
  2. 瞭解系列二中的爬蟲架構

If you have done, that’s cool !

這篇主要是:

  1. 介紹需要的技術棧,需要補的東西請戳官方文件。官貼是專業的!
  2. 推薦個人使用的一些庫和輪子

OK!Let’s get a new start!


技術棧合集


提示:所用技術選型均是基於Python語言和Mac作業系統。具體技術細節實現可能稍有不同。

不願看小編瞎BB的同鞋拿下面的單子走開吧,如下:

  1. Requests和lxml(主要是tree函式)。Requests用於傳送HTTP請求,lxml用於原始碼的DOM化。同時,有些異常的處理,記得扒官方文件。
  2. Xpath語法及Google的Xpath Helper工具。XPath可以很方便的拿到原始碼的文字及標籤(name,tag name,id,class)值。寫XPath時,使用google的helper會很方便。
  3. re模組。老生常談了。但是,我卻不怎麼用。當需要從文字(多數是字串)中匹配資料時才使用。寫過Xpath的人一般是不喜歡用re的,除了沒辦法啦。
  4. selenium和phantomJS。組合使用,解決動態資料爬取(Ajax資料請求的問題)的問題。selenium是模仿瀏覽器的行為去訪問,支援多種瀏覽器引擎,如Firefox,chrome等。phantomJS不是一個瀏覽器引擎,但支援同瀏覽器一樣的操作;如,構建DOM模型。
  5. pymongo模組。寫爬蟲,還是用NoSQL來存比較好。推薦MongoDB,我就安利一下。
  6. 代理IP。不加代理,對於快速訪問有反爬蟲策略的站點而言,會很不利。所以,找個不錯的代理商,很重要。我司(新三板上市)給搞的代理IP,每次請求400個代理,能用的就23-45個不等。自己再做代理優化,也很重要。

概括地說完了,下面詳述。


Request和lxml


再次宣告,學會他們不是本篇的使命。So,去戳官方文件,沒有比他們更全更權威的存在了。

Go on !

有些URL直接使用:

import requests
URL = "http://xxxxxxXXXXXX.html"
req = requests.get(URL) print(req.text)

但是,後續加了代理、超時、header等引數時,需要使用類似如下程式碼:

req = requests.get(url, proxies=proxies, headers=hea, timeout=3)

至於,lxml,可以將req.text這樣的原始碼變成DOM樹,方便XPath獲取元素或文字。例如:

from lxml import etree
import requests

URL = "http://xxxxxxXXXXXX.html"
req = requests.get(URL)

# 將原始碼變成utf-8格式
req.encoding = "utf-8"

# DOM化
tree = etree.HTML(req.text)

# 獲取title,xpath()函式裡放的是xpath
title_text = tree.xpath('//html/head/title')

# 迴圈列印。注意:xpath()的返回值是一個list,裡面是DOM節點。記得用title_text[0].text(改數)或者迴圈列印
for node in title_title:
    print(node.text)

關於requests和lxml就說這些,其實很簡單。Next!


xpath語法及XPath Helper


給出幾個xpath語法的例子:

'.//dd[@class="pub-time"]/text()',
'.//dt/a/@puid',
'//dt/a/@company_id',
'//span[@class="icon-pp"]/@class',
'//span[@class="cat-type"]/@class',

加點與不加點的結果是不同的,這些語法問題不值得在這裡浪費篇幅了。

寫的時候一般是需要藉助工具—-google Chrome的copy XPath。如下:
谷震平 blog

然後,開啟XPath Helper【請提前安裝】,進行修改除錯:
谷震平 blog

so easy! 如果剛入門,請不要著急,慢慢積累就好!


re模組


這個模組我是最沒有發言權的,畢竟用到的不多。但是,寫爬蟲,re模組不可或缺。

我用它在一堆文字中匹配電話號碼。僅此而已。不過,re的價值遠比這個大。

之前有個應用場景。我需要判斷title裡有沒有一個詞“趕集”,沒有便不是我需要的頁面,有則是正確的頁面。這個時候,沒有re模組是真的不行。

騷年,需要用的時候,去找官方文件吧。Next!


selenium和phantomJs


在多數網站中,資料是通過瀏覽器載入JavaScript後,執行Ajax請求程式碼獲取的。這部分資料是動態載入的,需要使用者觸發動態載入的條件:如下拉重新整理,點選“檢視”按鈕等等。

使用selenium和phantomJs的好處,如下:

  1. 不必考慮Ajax的具體執行過程
  2. 快速獲得網頁動態載入的資料

對於這兩個輪子,皆有各自的官方文件。引一下phantomjs的官方介紹:

PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

簡單的使用,如下:

from selenium import webdriver

# 告知selenium,phantomjs的安裝路徑/執行路徑
PATH = "/Users/tanishindaira/Desktop/spider/ClassifiedSearch/phantomjs-2.1.1-macosx/bin/phantomjs"

# 獲取電話的xpath語法。假設:phone是通過javascript動態載入的
phone_xpath = '//*[@id="detail_info"]/div[8]/table/tbody/tr[1]'

# 載入phantomJs,初始化webdriver
req = webdriver.PhantomJS(PATH)

# 進行http的get請求
req.get(link)

# 通過find_element_by_xpath()函式獲取動態資料
data = req.find_element_by_xpath(hire_postition_numbers_xpath).text

# 列印
print(data)

So, that’s very easy. 這個過程中,還需要學會設定超時,畢竟有些網站的載入速度較慢,而且並不是所有資料都會需要爬取。

對於多數人而言,利用selenium+phantomJs爬取資料,會覺得慢。因為這種方式,是包含了JavaScript指令碼載入和執行的時間,也有瀏覽器引擎初始化和關閉的時間。

慢,是有好處的。

大多數時候,你會主動降低爬蟲的爬取效率。因為過快地訪問目標站點的伺服器會導致:

  1. 觸發對方已有的反爬蟲策略
  2. 佔用伺服器資源,影響營業時,對方不得不研發反爬蟲【大量而長時間爬取】
  3. 有一定的法律風險

曾有一些前輩說,需要在get請求前加一段程式碼,如下:

# 設定爬取間隔
time.sleep(random.randint(3, 6))

Anyway, 就說到這裡。


pymongo


使用MongoDB,在爬取資料時很有必要。特別當你做去重的時候,大量資料檢索是比較快的。

Mongo的相關特性,自行官網刷經驗吧。只能說,不難。而且需要使用的部分不多。

簡單使用如下:

import pymongo

# 建立連線
def conn(self):
   # 連線本地. or 連線到雲伺服器上,帶上使用者名稱和密碼
   # client = pymongo.MongoClient(host="127.0.0.1", port=27017)

   # 判斷是否有錯
   if client is None:
       logger.warning("mongodb連線失敗...")
   # 資料庫名稱
   db = client["ganji_zhaopin_proxy"]

   # 資料表(collection)名稱
   collection = db["ganji_proxy_data"]

   return collection

# 插入資料
def data_insert(self, data):
   # 獲得連線
   collection = self.conn()

   # 防止出錯
   try:
       collection.insert(data)
       logger.info("ip不存在,已插入資料...")

   except Exception as e:
       check_data = self.data_find(data)
       # 假設data【map型別】裡有ip這個key
       if check_data['ip'] == data["ip"]:
           logger.info("ip已存在,執行更新操作...")
           self.data_update(data)
       else:
           logger.error("插入資料時,出錯:{}".format(e))

def data_find(self, data):
    # 獲得連線
    collection = self.conn()
    _id = data['ip']
    # 防止出錯
    try: 
        result = collection.find_one({'_id': _id})
    except Exception as e:
        logger.error("查詢資料時,出錯:{}".format(e))
        return None
    return result

# 更新資料
 def data_update(self, data):
      # 獲得連線
      collection = self.conn()

      _id = data['ip']
      update_data = {'$set': data}
      # 防止出錯
      try:
          collection.update({'_id': _id}, update_data)
      except Exception as e:
          logger.error("更新資料時,出錯:{}".format(e))

以上函式,做的是:當用戶呼叫data_insert()函式時,會執行mongo的插入操作。失敗的話,檢查mongodb中是否已存在。存在話就進行更新操作。

其實,寫mongo的程式碼量不長。就如同mysql一樣,有增刪改查4個基本功能。

這裡當然也會涉及到表結構的設計:

  1. 不放:當你需要的時候,把不同的的東西放在不同的籃子裡,別堆在一起
  2. 放:相同屬性和類目的東西放在一起

其他的沒有什麼好建議的。當你有了需求,就自然而然有了那個表。而對於有經驗的人來說,這些他都提前得知。


代理IP


使用代理IP是必要的。至少這樣會保證你的訪問次數,除非你使用了劣質代理。使用代理並沒有什麼訣竅可言,讓每次HTTP請求都使用一個有效且不同的代理IP是基本的準則。

原因:

  1. 代理IP大部分是無法使用的。比如不能訪問百度,當然也不能訪問你要去的站點
  2. 代理IP即使有效,可能在過一會兒就失效,有效時間不定
  3. 每次從伺服器請求的代理IP資料可能是相同的,但是不能讓http請求每次都用同一個ip。這樣會讓有效可用的代理ip被目標站點封掉,保證間隔使用。

針對以上的問題,這裡有一個不完整的解決方案,僅供參考:
谷震平 版權所有 blog寫作

希望,不吝賜教,給出其他的解決方案,謝謝。


結語


系列三告一段落。主要是介紹了需要使用的技術棧,給出一些僅供參考的經驗。

接下來,進入系列四—-趕集網爬取案例,需要使用以上所有的技術棧。

OK,讓我們翻開一個新的篇章!

歡迎大家關注我的微信公眾號“谷震平的專欄”,獲取更多技術分享資料!
開啟微信,掃一掃吧!
這裡寫圖片描述