天天看點

爬蟲問題總結

本文檔對日常學習中用 python 做資料爬取時所遇到的一些問題做簡要記錄,以便日後查閱,部分問題可能因為認識不到位會存在一些誤解,敬請告知,萬分感謝,共同進步。

爬蟲問題總結

估算網站規模

該小節主要針對于整站爬取的情況。

爬取整站之前,肯定是要先對一個網站的規模進行估計。這是可以使用google搜尋檢視大概有多少個網址,這裡使用到google搜尋的一個小技巧。

site:url位址           

有的時候可檢視網站的sitemap.xml,但它有時候會過期或者滞後,不是很準确。

識别網站所用技術

要爬的網站使用的技術會影響到我們所寫的代碼中的處理手段。

推薦使用builtwith這個第三方包,可以使用pip來安裝。

簡單使用如下:

In [1]: import builtwith

In [2]: builtwith.parse('http://example.webscraping.com/')
Out[2]:
{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],
 u'programming-languages': [u'Python'],
 u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],
 u'web-servers': [u'Nginx']}           

僞裝成浏覽器

UA,即 User-Agent,是Http協定的一部分,屬于頭域的組成部分,發送 http 請求時,請求頭中會有 User-Agent字段。伺服器通過該字段來分辨發送請求的浏覽器類型、版本、核心以及作業系統資訊等。

在浏覽器 console 可用如下指令來獲得該浏覽器的 UA 辨別

navigator.userAgent           

部分網站不想被爬蟲爬取就會檢查 http 請求頭的該字段内容, 是以在用爬蟲做抓取時,通常要在請求頭加上該字段,以把自己僞裝成浏覽器。有時候通過手機浏覽器通路得到的頁面會更加簡潔,更容易抓取,是以僞裝成手機浏覽器也是一種好方法。

網上有很多整理的不同浏覽器的 UA ,比如各種浏覽器UserAgent一覽表。

浏覽器野史 UserAgent列傳(上)和 浏覽器野史 UserAgent列傳(下),這兩篇文章細說了 UA 的來龍去脈,去感受下當時波瀾壯闊的“浏覽器之戰”。

防盜鍊

部分伺服器會檢查 http 請求頭的 Referer 字段來判斷你是否是從指定頁面跳轉而來的,以達到防盜鍊的作用。是以在僞裝請求頭部的時候,該字段也是不容忽視的。

url 編碼

我們發現,URL 中有時候存在中文,這是就需要對 url 進行編碼。

可以先将中文轉換成 utf-8 編碼,然後使用 urllib2.quote 方法對參數進行 url 編碼後傳遞。

import urllib
param = u'你好'
param = param.encode('utf-8')
param = urllib.quote(param)           

對于 url 來說,之是以要進行編碼,是因為 url 中有些字元會引起歧義。

同理使用 unquote 可以解碼。

動态加載問題

經常會遇到這樣情況:将網頁拖到底部會自動往下加載新的資料,或者有加載更多這樣的按鈕,這裡說的就是 AJAX 了。

AJAX 是 Asynchronous JavaScript and XML(異步的 JavaScript 和 XML)的縮寫。它通過使用原有的 web 标準元件,實作了在不重新加載整個頁面的情況下,與伺服器進行資料互動。例如在新浪微網誌中,你可以展開一條微網誌的評論,而不需要重新加載,或者打開一個新的頁面。但是這些内容并不是一開始就在頁面中的(這樣頁面就太大了),而是在你點選的時候被加載進來的。這就導緻了你抓取這個頁面的時候,并不能獲得這些評論資訊(因為你沒有『展開』)。AJAX 一般是通過 XMLHttpRequest 對象接口發送請求的,XMLHttpRequest 一般被縮寫為 XHR。

通過審查元素找請求的 js 位址

這些 js 的名字通常看起來與其他的不太一樣。

拿澎湃網舉個栗子,這才是發送請求獲得文章的真正位址,

AJAX 的一種常見用法是使用 AJAX 加載 JSON 資料,然後在浏覽器端渲染。

這種情況很好處理,因為 python 自帶的處理 json 的庫,舉個栗子:

豌豆莢安卓遊戲排行榜,每次點選更多會加載新的資料。在審查元素裡,可以看到每次點選檢視更多,都會傳回一個包含應用資料詳細資訊的資料。

檢視請求 json 資料的位址

http://apps.wandoujia.com/api...

max 參數的值表示這一次請求傳回多少個 app 資訊, start 參數的值表示從第幾個 app 開始,start 從 0 開始。

另外,在建構請求頭時,應該加上這個參數 'X-Requested-With': 'XMLHttpRequest',當使用 XHR 發送 AJAX 請求時 Header 會帶上這個字段,常被用于判斷是不是 AJAX 請求。

Selenium+PhantomJS

Selenium 是一個強大的網絡資料采集工具,最初是為網站自動測試而開發的。它可以讓浏覽器自動加載頁面,擷取所需要的資料,甚至頁面截屏,或者判斷網站上某些動作事都發生。

對應的 python 庫,可以用 pip 安裝。

PhantomJS 是一個 headless browser,它會把網站加載到記憶體并執行頁面上的 JavaScript,但是不會向使用者展示網頁的圖形界面。它不是python庫,需要單獨下載下傳(喏,你要的官網)。

寫一個簡單的栗子:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

driver = webdriver.PhantomJS(executable_path='<Path to PhantomJs>')
driver.get("http://www.python.org")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.clear()
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
driver.close()           

Path to PhantomJs 處 需要指定位置,如果該路徑已經加入到了環境變量中,那麼可以不加這個參數。

這裡隻是個簡單的介紹,後續會對 Selenium 的 python 版 API 的使用做單獨介紹。

代理問題

部分網站對 ip 進行了限制,導緻我們無法爬到想要的資料,這個時候可以用代理來做。

使用 requests 這個第三方庫,可以輕松地設定代理。

再舉個栗子:

import requests

proxies = {
  'http': 'http://10.10.1.10:3128',
  'https': 'http://10.10.1.10:1080',
}
requests.get('http://example.org', proxies=proxies)           

發送 http 請求

雖然自帶的 urllib 和 urllib2 庫可以滿足需求,但是不推薦使用。為什麼?因為它們的操作太繁瑣,尤其在處理一些複雜情況時,這不符合 python 的設計哲學,是以放手抛棄它們吧。

推薦使用 requests這個第三方庫,正如它标榜的那樣--Requests: HTTP for Humans,同時也支援 py3。

使用 requests 庫發送請求是如此的優雅,

import requests
r = requests.get('https://api.github.com/events')
print r.text           

具體使用方法可以看官方 API。

解析頁面

既然擷取到了請求頁面的源碼,那麼接下來要做的就是解析工作,一般來說,有下面三個庫用得是最多的:lxml庫, bs4庫,以及正則。

lxml 解析速度要比 bs4 快,據說快好幾倍,正則是個終結技,隻是寫起來有點麻煩。另外, bs4 不支援 xpath,而lxml 支援,總之,視自己的情況選擇了。

前幾天還接觸過一個庫,名字叫 pyquery,它是 jQuery 的 python 實作,可以用于解析 html 網頁内容,熟悉 jQuery 文法童鞋的福音。

另:bs4 庫解析遇到的一個問題

前幾天遇到一個問題,問題是這樣的,html 頁面的資料經過Beautiful Soup庫的解析後,部分html源碼丢失,找不到想要的資料了,問題代碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-

import requests
from bs4 import BeautifulSoup

url = 'http://product.pconline.com.cn/mobile/'
res = requests.get(url)
html = res.text
# print html
soup = BeautifulSoup(html, 'lxml')
site = soup.findAll('img', class_='pic')
print site           

輸出結果為空,沒有想要的資料。檢視官方文檔,bs 庫支援的解析庫有 lxml, html5lib 和 html.parser。用的最多的是 lxml,因為它的解析速度快,并且容錯能力強,預設也是使用該解析器。

出現解析後源碼丢失的可能原因有 2 個:

  • BeautifulSoup 有時候會遇到非法的,不支援的 html 源碼而導緻無法解析或無法正常解析 html;
  • 處理的文檔太大,而處理的解析器緩存不夠造成的資訊丢失。

這裡換一個解析器,換成 html.parser 就可以了。

編碼問題

将編碼設定為 utf-8

import sys
reload(sys)
sys.setdefaultencoding('utf-8')           

總之,py2 中的編碼問題很煩人,隻要解碼與編碼不一緻就會出現亂碼。對 unicode 可以 編碼,其他編碼 decode 成 unicode。

要注意'hello' 和 u'hello' 的差別。