原創: hxj7

本文是筆者日常使用Python進行爬蟲的簡要記錄。

爬蟲,簡單說就是規模化採集網頁信息,因為網路像一張網,而爬蟲做的事就像一隻蜘蛛在網上爬,所以爬蟲英文名就是spider。

爬蟲可以做很多事情,比如抓取網頁上的表格,下載歌曲、下載電影、模擬登錄網站等等,基本上都是和網頁相關的。當然,現在很多所謂的」手機爬蟲「也出現了,原理類似。我們今天只說PC端的網頁爬蟲

講爬蟲的技術文章數不勝數,很多編程語言也有現成的模塊。筆者幾乎只用Python,也只會用Python來進行爬蟲,所以本文是講如何用Python來進行爬蟲。寫這篇文章一是分享,二是把常用代碼記錄下來,方便自己查找。本文篇幅較長,主要分為以下五個部分

· 理論基礎

· 實現方法

· 注意點

· 難點

· 小結

理論基礎

爬蟲,大多數時候是和網頁打交道,所以和網頁相關的常用技術多少要了解掌握。如:

· HTTP協議。主要是了解HTTP協議頭。GET、POST方法等。常涉及到urllib、urllib2、requests模塊。

· Cookie。一種伺服器端記錄客戶端連接情況的工具。常涉及到cookielib模塊。

· HTML。早期靜態網頁幾乎都是HTML文本。

· Javascript。最流行的動態網頁編程語言。可能會用到pyv8模塊。

· CSS。講如何布局、渲染網頁的。

· AJAX。如何延遲顯示網頁內容。常涉及到json模塊。

· DOM。抽象化的網頁結構。常涉及到bs4(Beautiful Soup)、lxml模塊。

· css-selector/xpath。如何定位網頁元素。常涉及到bs4(Beautiful Soup)、lxml模塊。

· 正則表達式。規則化地抽取文本。常涉及到re、bs4(Beautiful Soup)、lxml模塊。

基本上這些都是要了解的。其實,谷歌瀏覽器Chrome提供的開發者工具就是一個強有力的輔助學習工具。可以藉助它快速熟悉上述技術。

實現方法

本著實用、簡潔的原則。筆者將自己常用的代碼整理如下:

只用到GET方法,沒有什麼複雜的情況。

# urllib模塊可以很方便地實現 GET 方法。
import urllib

# eg: res = urllib.urlopen("http://youku.com")
res = urllib.urlopen("<your_url>")
html = res.read() # 像讀取文件一樣讀取網頁內容
info = res.info() # 返回的header信息
res.close() # 像關閉文件一樣關閉網路連接

需要用到POST方法。

# urllib2模塊可以方便地實現 POST 方法。
# 假設參數是 your_url?user=hxj5hxj5&passwd=testtest
import urllib, urllib2

headers = { # 請求頭。根據情況調整,一般而言,最重要的是User-Agent一項。
Accept: application/json,
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
}
paras = { # 請求參數。根據情況調整
user: hxj5hxj5,
passwd: testtest
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="<your_url>", data=paraData, headers=headers)
res = urllib2.urlopen(req)
html = res.read() # 像讀取文件一樣讀取網頁內容
res.close() # 像關閉文件一樣關閉網路連接

需要用到cookie

import urllib2, cookielib

# cookielib模塊可以很方便地操作cookie。
# 假設參數是 your_url?user=hxj5hxj5&passwd=testtest
cj = cookielib.CookieJar()
# 創建能自動處理cookie的實例,通過它來打開請求
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
# 如果是GET請求
res = opener.open("<your_url>")
# 如果是POST請求
# req = urllib2.Request(...)
# res = opener.open(req)
html = res.read()
res.close()

獲取特定元素的內容

通過BeautifulSoup來實現

import urllib
from bs4 import BeautifulSoup

res = urllib.urlopen("<your_url>")
html = res.read()
res.close()

soup = BeautifulSoup(html, lxml)
taga = soup.select("a") # 根據CSS-selector來定位元素,返回列表
for a in taga:
print a["href"] # 列印節點的屬性
print a.text # 列印節點的內容

通過 re 來實現

re模塊是Python自帶的創建、解析正則表達式的模塊。

import urllib
import re

res = urllib.urlopen("<your_url>")
html = res.read()
res.close()

pat = re.compile(r<a href=(.*?)>) # 創建正則表達式
result = pat.findall(html) # 返回所有符合條件的元素
for item in result:
print item # 列印元素內容

下載數據

# 使用urllib模塊中的urlretrieve函數可以很方便地下載數據
# 假設要下載一張圖片
import urllib

urllib.urlretrieve("http://just4test.cn/path/to/spider.jpg", "spider.jpg")

注意以上所說的幾種功能都可以通過其他模塊或方法實現,這裡列出的只是筆者常用的而已

注意點

爬蟲其實是一個很瑣碎繁複的過程,有很多細節需要注意。下面列出一些筆者常遇到的問題。

數據被壓縮過

有時候伺服器端會將數據壓縮後再傳輸到客戶端,所以我們需要對壓縮過的數據進行解壓。常用的壓縮方式就是gzip壓縮。以此為例:

import urllib
import gzip
from StringIO import StringIO

def ungzip(data):
buf = StringIO(data)
f = gzip.GzipFile(fileobj=buf)
return f.read()

res = urllib.urlopen("<your_url>")
html = res.read()
content = res.info().get(Content-Encoding)
res.close()
if content == "gzip":
html = ungzip(html)

數據編碼

Python中的字元串編碼一直是很讓人頭疼的,爬蟲中就經常會遇到這樣的問題。

假設網頁不是utf8編碼(比如gbk編碼)的,而你想要保持utf8編碼,那麼就需要進行編碼的轉換。首先得判斷網頁編碼格式,常用chardet模塊實現。

#!/usr/bin/env python
#-*-coding:utf8-*-

import urllib
import chardet

res = urllib.urlopen("<your_url>")
html = res.read()
res.close()

encoding = chardet.detect(html)[encoding]
if encoding != utf8: # 以utf8為例
html = html.decode(encoding)

數據是json格式的

import urllib
import json

res = urllib.urlopen("<your_url>")
html = res.read()
isJson = json in res.info().get(Content-Type)
res.close()

if isJson:
data = json.loads(html)

整站抓取

如果是一個要實現大規模抓取任務的爬蟲,最好是使用成熟的爬蟲框架如Scrapy。但是好在筆者目前還沒有碰到過這種規模的任務,所以也沒有用過Scrapy。下面只是從原理上大概探討一下這種情形。

比較常見的比如抓取一個網站上的所有圖片。如果把網站看成一棵樹,而該網站的各個頁面是樹的各個節點,那麼抓取所有圖片就需要遍歷所有節點(頁面),並在每個節點(頁面)上抓取該頁面上的所有圖片。那麼可以用BFS(廣度優先搜索)或者DFS(深度優先搜索)演算法。

以BFS為例:

import urllib
from bs4 import BeautifulSoup

def spider(url, depth):
if depth > MAX_DEPTH: # 到達最大深度後就不再繼續爬取了
return
res = urllib.urlopen(url)
html = res.read()
res.close()
soup = BeautifulSoup(html, lxml)
# 下載圖片
pics = soup.select(a[href$=".jpg"]) # 假設只選取以jpg結尾的圖片
for p in pics:
urllib.urlretrieve(p, str(picNum) + ".jpg")
picNum += 1
# 抓取新的頁面鏈接
theUrls = soup.select(a[href$=".html"]) # href屬性以html結尾的所有a標籤
newUrls = set(theUrls) - oldUrls # 如果在oldUrl中出現過,就排除掉
oldUrls.update(newUrls) # 更新已有鏈接集合
for nu in newUrls:
spider(nu, depth + 1) # 對新的頁面鏈接繼續爬取

picNum = 0
MAX_DEPTH = 10
initUrl = "http://just4test.cn/" # 初始頁面
oldUrls = set([initUrl])
spider(initUrl, 0) # 從深度0開始爬取,到達最大深度後停止

難點

爬蟲的難點主要是如何繞過反爬蟲機制。

限制頻繁訪問

為了減少伺服器端的訪問壓力,一般都不會允許頻繁訪問網站(即不允許頻繁發送請求)。為了解決這一點,所以最好能隨機休息/暫停

import urllib
import time
from random import randint

def randSleep(s=0, e=1):
t = randint(s * 1000, e * 1000) / 1000.0
time.sleep(t)
return t

for url in allUrls:
res = urllib.urlopen(url)
html = res.read()
res.close()
randSleep()

限制ip

有些伺服器在判明是爬蟲在爬取數據後,會封ip。這時候只能換一個ip。最好是能找到代理伺服器,有一個ip池。封了一個ip,立即切換到另一個ip。

檢查請求頭

伺服器端檢查請求頭,如果發現異常,就阻止請求。最常見的檢查User-Agent一項,看是否是正常的真實的瀏覽器。或者檢查Referer一項是否正常。這些都可以通過Chrome的開發者工具獲取真實值後進行偽裝。

當獲取到相應值之後,可以一開始就在請求頭中指定,也可以之後添加。

如果在一開始就指定,像這樣:

import urllib, urllib2

headers = { # 請求頭。
Referer: http://just4test.cn/page1.html,
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
}
paras = { # 請求參數。根據情況調整
user: hxj5hxj5,
passwd: testtest
}
paraData = urllib.urlencode(paras)
req = urllib2.Request("http://just4test.cn/page2.html", data=paraData, headers=headers)
res = urllib2.urlopen(req)
html = res.read()
res.close()

或者之後添加

import urllib, urllib2

headers = { # 請求頭。
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
}
paras = { # 請求參數。根據情況調整
user: hxj5hxj5,
passwd: testtest
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="http://just4test.cn/page2.html", data=paraData, headers=headers)
req.add_header(Referer, http://just4test.cn/page1.html) # 添加信息
res = urllib2.urlopen(req)
html = res.read()
res.close()

檢查cookie

如上所說,可以使用cooklib模塊自動處理cookie。但是當知道了具體的cookie值的時候,也可以直接將cookie添加到請求頭中。

import urllib, urllib2

headers = { # 請求頭。
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
}
paras = { # 請求參數。根據情況調整
user: hxj5hxj5,
passwd: testtest
}
paraData = urllib.urlencode(paras)
req = urllib2.Request(url="http://just4test.cn/page2.html", data=paraData, headers=headers)
req.add_header(Cookie, USER_ID=hxj5hxj5) # 添加cookie
res = urllib2.urlopen(req)
html = res.read()
res.close()

複雜參數

有些網頁請求的參數特別複雜,比如百度搜索python時的請求鏈接是"baidu.com/s?"(後面還有一長串),很多參數一眼看上去不知道是什麼意思,也無從獲取。這個時候寫爬蟲就很麻煩,因為你沒法知道參數該用什麼值。

遇到這種情況,一般有三種辦法:

一是利用 Chrome 的開發者工具提供的設置斷點等功能進行手動調試,一般請求鏈接中的參數還都是可以從 js 文件運行過程中得到的,所以手動調試有希望能獲取參數值

二是利用諸如 v8 引擎(Python中有 pyv8 模塊)執行 js 代碼,從而獲取參數值

三是利用 selenium 之類的工具繞過獲取參數值這一步

人機驗證

一旦碰到這種情況,以筆者目前的經驗和水平,大多是不能靠基礎模塊和方法解決的。只能選擇 selenium 這種工具來變通。

驗證碼

簡單驗證碼可以直接用 OCR 工具破解,複雜一點的需要先去噪,然後建模訓練進行破解。再複雜的就只能放棄或者人工輸入驗證碼後讓爬蟲程序繼續。

拖拽(點擊)圖形

如微博登錄、12306購票都是這一類的。大多數也是靠 selenium 去想辦法。

容錯機制

爬蟲要特別注意容錯,不然很容易出現運行中途出錯退出的情況。比如,網速不好,連接暫時丟失導致報錯、字元串不規範(舉一個例子,本來預期應該是有字元的地方是空的)從而導致出錯、本來表格中預期有5個<li>元素的,結果只有4個從而報錯等等。爬蟲太繁瑣了,很多細節都容易出錯。所以一定要有容錯機制。

比如我們可以最多嘗試3次,3次都失敗了就退出:

import urllib
import sys

maxTry = 3
isOK = 0
for _ in range(maxTry):
try:
res = urllib.urlopen(url)
html = res.read()
res.close()
except Exception as e: # 最好指定特定類型的exception
print str(e)
else:
isOK = 1
break
if not isOK:
print "urlopen failed."
sys.exit(1)

selenium

PhantomJS 以及 selenium 這一類的工具都可以用來進行瀏覽器自動化測試,就相當於你在操縱一個真實的瀏覽器。筆者只用過 selenium。它們顯得笨重,但是功能的確強大,可以解決很多複雜的爬蟲問題。網上有很多教程,其主要用法如下:

from selenium import webdriver

browser = webdriver.Chrome()
browser.implicitly_wait(10) # 設置默認等待時間
browser.get("<your_url>") # 打開網頁
print browser.page_source # 列印網頁源代碼
# 查找特定元素
tgtEle = browser.find_elements_by_css_selector(a) # 或者 browser.find_elements_by_xpath()
for e in tgtEle:
print e.text
print e.get_attribute(href)
tgtEle[0].click() # 點擊元素
# 執行js代碼
browser.execute_script(window.scrollTo(0, document.body.scrollHeight))
# cookie操作
get_cookies()
delete_all_cookes()
add_cookie({name:test, value:123})
# 選項卡操作(以切換選項卡為例)
browser.switch_to_window(browser.window_handles[1])
# 或者browser.switch_to.window(browser.window_handles[1])
browser.close() # 關閉瀏覽器

小結

在Python中,爬蟲相關的模塊有不少,如果是日常簡單的任務,用urllib,requests這些基礎模塊就夠用了。但是如果是複雜的或者規模很大的爬蟲,最好使用Scrapy之類的框架。最後要說的就是 selenium 是我們遇到困難時的好幫手。

本文是筆者使用Python進行爬蟲的一個簡要記錄,僅供大家參考。由於只是一個業餘使用者,所以文中肯定有不少概念和代碼使用上的錯誤,希望大家不吝指教。

(公眾號:生信了)

推薦閱讀:

相关文章