- 我們在用requests抓取頁面的時候,得到的結果可能和在瀏覽器中看到的不一樣,是因為requests抓取的都是原始的HTML文檔,而瀏覽器中的頁面則是經過JavaScript處理數據后生成的結果,這些數據都來源有多種可能是通過Ajax加載的,可能是包含在HTML文檔中的,也可能是經過JavaScript和特定算法計算后生成的。
- 對於第一種情況,數據加載是一種異步加載方式,原始的頁面最初不會包含某些數據,原始頁面加載完成后,會再向服務器請求某個接口獲取數據,然后數據才被處理從而呈現在網頁上,這其實就是發送了一個Ajax請求。
- 這樣在web開發上實現前后端分離,可以降低服務器直接渲染頁面帶來的壓力。
1. 什么是Ajax
Ajax,(asynchronous JavaScript and XML)即異步的JavaScript and XML。他不是一門編程語言,而是利用JavaScript 在保證頁面不被刷新、頁面鏈接不改變的情況下與服務器交換數據並更新部分網頁的技術。(https://www.w3school.com.cn/ajax/ajax_xmlhttprequest_send.asp)
1.1實例
瀏覽網頁的時候,會發現很多網頁都有下滑查看更多的選項。我們注意到,其實頁面並沒有整個刷新,頁面的鏈接沒有變化。這個過程就利用了Ajax。
1.2基本原理
發送Ajax請求到網頁更新的整個過程可簡單的分為三步:
- 發送請求
- 解析內容
- 渲染網頁
- 發送請求
我們知道JavaScript可以實現頁面的各種交互功能,Ajax也不例外,他也是由JS實現的,實際上執行了下面的代碼:
var xmlhttp; if (window.XMLHttpRequest){ //code for IE7+,Firefox,Chrome,Opera,Safari xmlhttp=new XMLHttpRequest(); } else{ //code for IE6,IE5 xmlhttp=new ActiveXObject(:Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function(){ if(xmlhttp.readyState==4 && xmlhttp.status==200) {document.getElementById("myDiv").innerHTML=xmlhttp.responseText; } } xmlhttp.open("POST","/ajax/",true); xmlhttp.send();
這是JS對Ajax最底層的實現,實際上就是新建了 XMLHttpResquest 對象,然后調用onreadystatechange 屬性設置了監聽,然后調用send()和 open()方法向某個鏈接(服務器)發送了請求。
前面用Python實現請求發送之后,可以得到響應結果,但這里請求的發送變成JS來完成。由於設置了監聽,所以當服務器返回響應時,onreadystatechange 對應的方法便會被觸發,然后在這個方法里面解析響應內容即可。
- 解析內容
得到響應之后,onreadystatechange 屬性對應的方法便會被觸發,此時利用 xmlhttp.responseText 便可以獲取響應內容。(這類似於Python中利用resquests 向服務器發送請求,然后得到響應的過程。)返回的內容可能是 HTML,可能是JSON,接下來只需在方法中用JS 進一步處理即可。
- 渲染網頁
JS有改變網頁內容的能力,解析完響應內容之后,就可以調用JS 來針對解析完的內容對網頁進行下一步處理了。比如:通過 document.getElementById().innerHTML 這樣的操作,對某個元素內的源代碼進行更改,這樣網頁顯示的內容就改變了,這個樣的操作也被稱為DOM操作。
2. Ajax分析方法
2.1 查看請求
借助瀏覽器的開發者工具(Chrome為例):
打開網頁——右鍵——檢查;
切換到Network選項卡:
這里就是網頁加載郭傳給你中瀏覽器與服務器之間發送請求和接收響應的所有記錄。
Ajax有其特殊的請求類型——xhr.
在Type中找到一個 xhr ,點開查看詳細信息,Request Headers中有一個信息為 X-Requested-With:XMLHttpRequest, 這就標記了此請求是Ajax請求。
點擊Preview,可看到響應的內容,它是 JSON 格式的,這里Chrome 自動做了解析。
切換到Response 選項卡,可以看到真實的返回數據。
2.2 過濾請求
利用Chrome開發者工具的篩選功能選出所有的Ajax請求。
So far ,我們已經可以分析出Ajax請求的一些詳細的信息了,通過模擬Ajax 請求,就可以抓取數據了。
2.3 Ajax結果提取
- 分析請求
打開Ajax的XHR過濾器,一直滑動頁面發現會不斷有 Ajax 請求發出。
這是一個GET類型的請求,請求的參數有4個:type,value,containerid 和page ,只有page 是變化的。
- 分析響應
響應內容是JSON 格式的,瀏覽器開發者工具自動做了解析。由上圖可以看到,最關鍵的兩部分信息是 cardlistInfo 和 cards,前者包含一個重要的信息是total,后者是一個列表,包含8個元素。展開一個元素看看:
mblog是這個元素比較重要的一個字段,包含微博的一些信息,發布時間嗎,贊數目。。。。。。
- 實例
獲取微博的前10頁,先定義url 的前半部分,因為page是變化的,構造參數字典。
//發送請求
import requests from urllib.parse import urlencode base_url = 'https://m.weibo.cn/api/container/getIndex?' headers = { 'Host': 'm.weibo.cn', 'Referer': 'https://m.weibo.cn/u/2830678474', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'X-Requested-With': 'XMLHttpRequest', } def get_page(page): params = { 'type': 'uid', 'value': '2830678474', 'containerid': '1076032830678474', 'page': page } url = base_url + urlencode(params) try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() except requests.ConnectionError as e: print('Error', e.args)
發送請求之后,定義一個解析方法。從結果中提取想要的信息。
from pyquery import PyQuery as pq def parse_page(json): if json: items = json.get('data').get('cards') for item in items: item = item.get('mblog') weibo = {} weibo['id'] = item.get('id') weibo['text'] = pq(item.get('text')).text() weibo['attitudes'] = item.get('attitudes_count') weibo['comments'] = item.get('comments_count') weibo['reposts'] = item.get('reposts_count') yield weibo
這里借助pyquery 將正文中的 HTML 標簽去掉。
最后,遍歷一下page ,一共10頁,將提取到的結果打印輸出即可;
if __name__ == '__main__': for page in range(1, 11): json = get_page(page) results = parse_page(json) for result in results: print(result)
下一步將數據格式化存儲在數據庫。
(代碼參考來源:https://github.com/Python3WebSpider/WeiboList/blob/master/weibo.py)
2.4 分析Ajax 爬取今日頭條美圖
- 邏輯分析
首先打開今日頭條的首頁http://www.toutiao.com
在搜索欄輸入:街拍
可參考他人代碼:https://github.com/Python3WebSpider/Jiepai