客戶端腳本語言是運行在瀏覽器而非服務(wù)器上的語言。
通常,你在網(wǎng)上遇到的客戶端語言只有兩種:ActionScript(開發(fā) Flash 應(yīng)用的語言)和 JavaScript。ActionScript 經(jīng)常用于流媒體文件播放,用作在線游戲平臺,或者是網(wǎng)站上的“介紹”頁面。
JavaScript 是網(wǎng)絡(luò)上最常用也是支持者最多的客戶端腳本語言。它可以收集用戶的跟蹤數(shù)據(jù),不需要重載頁面直接提交表單,在頁面嵌入多媒體文件,甚至運行網(wǎng)頁游戲。那些看起來非常簡單的頁面背后通常使用了許多 JavaScript 文件。你可以在網(wǎng)頁源代碼的 <script> 標簽之間看到它們:
<script>
alert("This creates a pop-up using JavaScript");
</script>
10.1 JavaScript 簡介
JavaScript 是一種弱類型語言,其語法通??梢耘c C++ 和 Java 做對比。雖然語法中的一些元素,比如操作符、循環(huán)條件和數(shù)組,都與 C++、Java 語法很接近,但是 JavaScript 的弱類型和腳本形式被一些程序員看成是折磨人的怪獸。
注意 JavaScript 里所有的變量都用 var 關(guān)鍵詞字進行定義。這與 PHP 里的 $ 符號類似,或 者 Java 和 C++ 里的類型聲明(int、String、List 等)。Python 不太一樣,它沒有這種顯 式的變量聲明。
JavaScript 還有一個非常好的特性,就是把函數(shù)作為變量使用:
<script>
var fibonacci = function() {
var a = 1;
var b = 1;
return function() {
var temp = b;
b = a + b;
a = temp;
return b;
}
}
var fibInstance = fibonacci();
console.log(fibInstance()+" is in the Fibonacci sequence");
console.log(fibInstance()+" is in the Fibonacci sequence");
console.log(fibInstance()+" is in the Fibonacci sequence");
</script>
常用JavaScript庫
用 Python 執(zhí)行 JavaScript 代碼的效率非常低,既費時又費力,尤其是在處理規(guī)模較大的 JavaScript 代碼時。如果有繞過 JavaScript 并直接解析它的方法(不需要執(zhí)行它就可以獲得 信息)會非常實用,可以幫你避開一大堆 JavaScript 的麻煩事。
1. jQuery
jQuery 是一個十分常見的庫,70% 最流行的網(wǎng)站(約 200 萬)和約 30% 的其他網(wǎng)站(約 2 億)都在使用。一個網(wǎng)站使用 jQuery 的特征,就是源代碼里包含了 jQuery 入口,比如:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></ script>
jQuery 可以動態(tài)地創(chuàng)建 HTML 內(nèi)容,只有在 JavaScript 代碼執(zhí)行之后才會顯示。
另外,這些頁面還可能包含動畫、用戶交互內(nèi)容和嵌入式媒體,這些內(nèi)容對網(wǎng)絡(luò)數(shù)據(jù)采集都是挑戰(zhàn)。
2. Google Analytics
有一半的網(wǎng)站都在用 Google Analytics,它可能是網(wǎng)站最常用的 JavaScript 庫和最受歡迎的用戶跟蹤工具。
很容易判斷一個頁面是不是使用了 Google Analytics。如果網(wǎng)站使用了它,在頁面底部會有 類似如下所示的 JavaScript 代碼(取自 O’Reilly Media 網(wǎng)站):
<!-- Google Analytics -->
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-4591498-1']);
_gaq.push(['_setDomainName', 'oreilly.com']);
_gaq.push(['_addIgnoredRef', 'oreilly.com']);
_gaq.push(['_setSiteSpeedSampleRate', 50]);
_gaq.push(['_trackPageview']);
(function() { var ga = document.createElement('script');
ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ga, s); })();
</script>
如果一個網(wǎng)站使用了 Google Analytics 或其他類似的網(wǎng)絡(luò)分析系統(tǒng),而你不想讓網(wǎng)站知道你在采集數(shù)據(jù),就要確保把那些分析工具的 cookie 或者所有 cookie 都關(guān)掉。
3. Google地圖
采集任何的位置數(shù)據(jù),理解 Google 地圖的工作方式可以讓你輕松地獲取格式規(guī)
范的經(jīng)緯度坐標和具體地址。在 Google 地圖上,顯示一個位置最常用的方法就是用標記 (一個大頭針)。
標記可以用下面的代碼插在 Google 地圖上:
var marker = new google.maps.Marker({
position: new google.maps.LatLng(-25.363882,131.044922),
map: map,
title: 'Some marker text'
});
Python 可以輕松地抽取出所有位置在 google.maps.LatLng() 里的坐標,生成一組經(jīng) / 緯度坐標值。
通過 Google 的“地理坐標反向查詢”API,你可以把這些經(jīng)緯度坐標組解析成格式規(guī)范的地址,便于存儲和分析。
10.2 Ajax 和動態(tài) HTML
那些使用了 Ajax 或 DHTML 技術(shù)改變 / 加載內(nèi)容的頁面,可能有一些采集手段,但是用 Python 解決這個問題只有兩種途徑:直接從 JavaScript 代碼里采集內(nèi)容,或者用 Python 的第三方庫運行 JavaScript,直接采集你在瀏覽器里看到的頁面。
在Python中用Selenium執(zhí)行JavaScript
Selenium 是一個強大的網(wǎng)絡(luò)數(shù)據(jù)采集工具,其最初是為網(wǎng)站自動化測試而開發(fā)的。近幾年,它還被廣泛用于獲取精確的網(wǎng)站快照,因為它們可以直接運行在瀏覽器上。Selenium 可以讓瀏覽器自動加載頁面,獲取需要的數(shù)據(jù),甚至頁面截屏,或者判斷網(wǎng)站上某些動作是否發(fā)生。
Selenium 不帶瀏覽器,需要與第三方瀏覽器結(jié)合使用。
PhantomJS 是一個“無頭”(headless)瀏覽器。它會把網(wǎng)站加載到內(nèi)存并執(zhí)行頁面上的 JavaScript,但是它不會向用戶展示網(wǎng)頁的圖形界面。把 Selenium 和 PhantomJS 結(jié)合在一 起,就可以運行一個非常強大的網(wǎng)絡(luò)爬蟲了,可以處理 cookie、JavaScrip、header,以及 任何你需要做的事情。
你可以從 PyPI 網(wǎng)站下載 Selenium 庫,也可以用第三方管理器像 pip 用命令行安裝。
PhantomJS 官方下載 , PhantomJS 是一個功能完善(雖然無頭)的瀏覽器,并非一個 Python 庫,不需要像 Python 的其他庫一樣安裝,也不能用 pip 安裝。
Selenium 庫是一個在 WebDriver 上調(diào)用的 API。WebDriver 像可以加載網(wǎng)站的瀏覽器,也可以像 BeautifulSoup 對象一樣用來查找頁面元素,與頁面上的元素進行交互 (發(fā)送文本、點擊等),以及執(zhí)行其他動作來運行網(wǎng)絡(luò)爬蟲。
WebDriverWait 和 expected_conditions, 這兩個模塊組合起來構(gòu)成了 Selenium 的隱式等待(implicit wait)。
隱式等待與顯式等待的不同之處:隱式等待是等 DOM 中某個狀態(tài)發(fā)生后再繼續(xù)運行 代碼(沒有明確的等待時間,但是有最大等待時限,只要在時限內(nèi)就可以),而顯式等待 明確設(shè)置了等待時間。
在 Selenium 庫里面元素被觸發(fā)的期望條件(expected condition)有很多種,包括:
? 彈出一個提示框
? 一個元素被選中(比如文本框)
? 頁面的標題改變了,或者某個文字顯示在頁面上或者某個元素里
? 一個元素在 DOM 中變成可見的,或者一個元素從 DOM 中消失了
下面是定位器通過 By 對象進行選擇的策略。
? ID
在上面的例子里用過;通過 HTML 的 id 屬性查找元素。
? CLASS_NAME
通過 HTML 的 class 屬性來查找元素。為什么這個函數(shù)是 CLASS_NAME,而不是簡單的 CLASS ?在 Selenium 的 Java 庫里使用 object.CLASS 可能會出現(xiàn)問題,.class 是 Java 保留的一個方法。為了讓 Selenium 語法可以兼容不同的語言,就用 CLASS_NAME 代替。
? CSS_SELECTOR
通過 CSS 的 class、id、tag 屬性名來查找元素,用 #idName、.className、tagName 表示。
? LINK_TEXT
通過鏈接文字查找 HTML 的 <a> 標簽。例如,如果一個鏈接的文字是“Next”,就可以 用 (By.LINK_TEXT, "Next") 來選擇。
? PARTIAL_LINK_TEXT
與 LINK_TEXT 類似,只是通過部分鏈接文字來查找。
? NAME
通過 HTML 標簽的 name 屬性查找。這在處理 HTML 表單時非常方便。
? TAG_NAME
通過 HTML 標簽的名稱查找。
? XPATH
用 XPath 表達式(語法在下面介紹)選擇匹配的元素。
XPath 語法
XPath(XML Path,XML 路徑)是在 XML 文檔中導航和選擇元素的查詢語言。它由 W3C 于 1999 年創(chuàng)建,在 Python、Java 和 C# 這些語言中有時會用 XPath 來處理 XML 文檔。
雖然 BeautifulSoup 不支持 XPath,但是很多庫(lxml、Selenium、Scrapy 等) 都支持。它的使用方式通常和 CSS 選擇器(比如 mytag#idname)一樣,它原本被設(shè)計用于處理更規(guī)范的 XML 文檔而不是 HTML 文檔。
在 XPath 語法中有四個重要概念。
? 根節(jié)點和非根節(jié)點
? /div 選擇 div 節(jié)點,只有當它是文檔的根節(jié)點時
? //div 選擇文檔中所有的 div 節(jié)點(包括非根節(jié)點)
? 通過屬性選擇節(jié)點
? //@href 選擇帶 href 屬性的所有節(jié)點
? //a[@href='http://google.com'] 選擇頁面中所有指向 Google 網(wǎng)站的鏈接
? 通過位置選擇節(jié)點
? //a[3] 選擇文檔中的第三個鏈接
? //table[last()] 選擇文檔中的最后一個表
? //a[position() < 3] 選擇文檔中的前三個鏈接
? 星號(*)匹配任意字符或節(jié)點,可以在不同條件下使用
? //table/tr/* 選擇所有表格行 tr 標簽的所有的子節(jié)點(這很適合選擇 th 和 td 標簽)
? //div[@*] 選擇帶任意屬性的所有 div 標簽
如果這里介紹的幾個 XPath 功能解決不了你的 HTML 或 XML 元素選擇問題,請參考微軟的 XPath 語法頁面。
10.3 處理重定向
客戶端重定向是在服務(wù)器將頁面內(nèi)容發(fā)送到瀏覽器之前,由瀏覽器執(zhí)行 JavaScript 完成的頁面跳轉(zhuǎn),而不是服務(wù)器完成的跳轉(zhuǎn)。當使用瀏覽器訪問頁面的時候,有時很難區(qū)分這兩種重定向。由于客戶端重定向執(zhí)行很快,加載頁面時你甚至感覺不到任何延遲,所以會讓你覺得這個重定向就是一個服務(wù)器端重定向。
但是,在進行網(wǎng)絡(luò)數(shù)據(jù)采集的時候,這兩種重定向的差異是非常明顯的。根據(jù)具體情況,服務(wù)器端重定向一般都可以輕松地通過 Python 的 urllib 庫解決,不需要使用 Selenium。客戶端重定向卻不能這樣處理,除非你有工具可以執(zhí)行 JavaScript。
Selenium 可以執(zhí)行這種 JavaScript 重定向,和它處理其他 JavaScript 的方式一樣;但是這類重定向的主要問題是什么時候停止頁面監(jiān)控,也就是說,怎么識別一個頁面已經(jīng)完成重定向。
我們可以用一種智能的方法來檢測客戶端重定向是否完成,首先從頁面開始加載時就“監(jiān)視”DOM 中的一個元素,然后重復調(diào)用這個元素直到 Selenium 拋出一個 StaleElementReferenceException 異常;也就是說,元素不在頁面的 DOM 里了,說明這時網(wǎng)站已經(jīng)跳轉(zhuǎn)