爬蟲之刃----簡化爬蟲程式之滾輪子實戰(系列三)
前言
這是一些準備工作:
- 瞭解整個系列的安排
- 瞭解系列二中的爬蟲架構
If you have done, that’s cool !
這篇主要是:
- 介紹需要的技術棧,需要補的東西請戳官方文件。官貼是專業的!
- 推薦個人使用的一些庫和輪子
OK!Let’s get a new start!
技術棧合集
提示:所用技術選型均是基於Python語言和Mac作業系統。具體技術細節實現可能稍有不同。
不願看小編瞎BB的同鞋拿下面的單子走開吧,如下:
- Requests和lxml(主要是tree函式)。Requests用於傳送HTTP請求,lxml用於原始碼的DOM化。同時,有些異常的處理,記得扒官方文件。
- Xpath語法及Google的Xpath Helper工具。XPath可以很方便的拿到原始碼的文字及標籤(name,tag name,id,class)值。寫XPath時,使用google的helper會很方便。
- re模組。老生常談了。但是,我卻不怎麼用。當需要從文字(多數是字串)中匹配資料時才使用。寫過Xpath的人一般是不喜歡用re的,除了沒辦法啦。
- selenium和phantomJS。組合使用,解決動態資料爬取(Ajax資料請求的問題)的問題。selenium是模仿瀏覽器的行為去訪問,支援多種瀏覽器引擎,如Firefox,chrome等。phantomJS不是一個瀏覽器引擎,但支援同瀏覽器一樣的操作;如,構建DOM模型。
- pymongo模組。寫爬蟲,還是用NoSQL來存比較好。推薦MongoDB,我就安利一下。
- 代理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。如下:
然後,開啟XPath Helper【請提前安裝】,進行修改除錯:
so easy! 如果剛入門,請不要著急,慢慢積累就好!
re模組
這個模組我是最沒有發言權的,畢竟用到的不多。但是,寫爬蟲,re模組不可或缺。
我用它在一堆文字中匹配電話號碼。僅此而已。不過,re的價值遠比這個大。
之前有個應用場景。我需要判斷title裡有沒有一個詞“趕集”,沒有便不是我需要的頁面,有則是正確的頁面。這個時候,沒有re模組是真的不行。
騷年,需要用的時候,去找官方文件吧。Next!
selenium和phantomJs
在多數網站中,資料是通過瀏覽器載入JavaScript後,執行Ajax請求程式碼獲取的。這部分資料是動態載入的,需要使用者觸發動態載入的條件:如下拉重新整理,點選“檢視”按鈕等等。
使用selenium和phantomJs的好處,如下:
- 不必考慮Ajax的具體執行過程
- 快速獲得網頁動態載入的資料
對於這兩個輪子,皆有各自的官方文件。引一下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指令碼載入和執行的時間,也有瀏覽器引擎初始化和關閉的時間。
慢,是有好處的。
大多數時候,你會主動降低爬蟲的爬取效率。因為過快地訪問目標站點的伺服器會導致:
- 觸發對方已有的反爬蟲策略
- 佔用伺服器資源,影響營業時,對方不得不研發反爬蟲【大量而長時間爬取】
- 有一定的法律風險
曾有一些前輩說,需要在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個基本功能。
這裡當然也會涉及到表結構的設計:
- 不放:當你需要的時候,把不同的的東西放在不同的籃子裡,別堆在一起
- 放:相同屬性和類目的東西放在一起
其他的沒有什麼好建議的。當你有了需求,就自然而然有了那個表。而對於有經驗的人來說,這些他都提前得知。
代理IP
使用代理IP是必要的。至少這樣會保證你的訪問次數,除非你使用了劣質代理。使用代理並沒有什麼訣竅可言,讓每次HTTP請求都使用一個有效且不同的代理IP是基本的準則。
原因:
- 代理IP大部分是無法使用的。比如不能訪問百度,當然也不能訪問你要去的站點
- 代理IP即使有效,可能在過一會兒就失效,有效時間不定
- 每次從伺服器請求的代理IP資料可能是相同的,但是不能讓http請求每次都用同一個ip。這樣會讓有效可用的代理ip被目標站點封掉,保證間隔使用。
針對以上的問題,這裡有一個不完整的解決方案,僅供參考:
希望,不吝賜教,給出其他的解決方案,謝謝。
結語
系列三告一段落。主要是介紹了需要使用的技術棧,給出一些僅供參考的經驗。
接下來,進入系列四—-趕集網爬取案例,需要使用以上所有的技術棧。
OK,讓我們翻開一個新的篇章!
歡迎大家關注我的微信公眾號“谷震平的專欄”,獲取更多技術分享資料!
開啟微信,掃一掃吧!