1.簡介
1 有時候我們在用requests抓取頁面的時候,得到的結果可能和在瀏覽器中看到的不一樣,在瀏覽 2 器中可以看到正常顯示的頁面數據,但是使用requests得到的結果並沒有。這是因為requests獲取的 3 都是原始的HTML文檔,而瀏覽器中的頁面則是經過JavaScript處理數據后生成的結果,這些數據的 4 來源有多種,可能是通過ajax加載的,可能是包含在HTML文檔中的,也可能是經過JavaScript和特 5 定算法計算后生成的。 6 對於第一種情況,數據加載是一種異步加載方式,原始的頁面最初不會包含某些數據,原始頁面 7 加載完后,會再向服務器請求某個接口獲取數據,然后數據才被處理從而呈現到網頁上,這其實就是 8 發送了一個ajax請求。 9 Web發展的趨勢來看,這種形式的頁面越來越多,網頁的原始HTML文檔不會包含任何數據, 10 數據都是通過統一加載后再呈現出來的,這樣在We開發上可以做到前后端分離,而且降低服 11 務器直接渲染頁面帶來的壓力。 12 所以如果遇到這樣的頁面,直接requests等庫來抓取原始頁面,是無法獲取到有效數據的, 13 這時需要分析網頁后台接口發送的jax請求,如果可以用requests來模擬Ajax請求,那么就可以 14 成功抓取了。 15 Ajax,是利用JavaScript在保證頁面不被刷新、頁面鏈接不改變的情況下與服務器交換數據並更新 16 部分網頁的技術。 17 Ajax作用: 18 1.發送請求。 比如:GET、POST、DELETE等請求 19 2.解析內容。 比如:服務器返回的XML、HTML、JSON等文本 20 3.渲染網頁。 比如:使用JavaScript在局部更新、刪除頁面數據 21 在這里我們需要使用瀏覽器的開發者工具分析什么是ajax請求: 22 打開Chrome,在百度搜索微博並登陸也可以選擇不登陸。按F12或者鼠標右鍵檢查(N), 23 打開開發者工具,選擇Network再選擇XHR,然后滑動頁面到底部,會發現出來很多Type是xhr類型的請求, 24 點開其中一個進行查看,請求類型是GET,再點擊Preview顯示的是JSON字符串,里面包含數據, 25 點開Response這是返回給我們最原始的數據,需要執行相應的渲染方法才會顯示到瀏覽器的頁面。 26 27 獲取請求連接:http://api1.t.qq.com/asyn/home.php?&time=1540886592&page=4&id=486297042606010&isrecom=0&apiType=14&apiHost=http%3A%2F%2Fapi.t.qq.com&_r=154937 28 ?問號之后是請求參數,time是時間參數,page是頁數,id可變隨機數字,后面的參數都是固定的。 29 點擊Preview,分別點開info和talk這兩個元素,里面包含了用戶信息和請求內容,一共15條, 30 這樣請求一個ajax接口分別對應一頁和15條數據
1.實戰1

"""微博首頁數據抓取實戰,根據ajax請求抓取微博首頁數據到mongodb數據庫""" import time import requests from urllib.parse import urlencode from pyquery import PyQuery from pymongo import MongoClient # 定義請求的URL前半部分 base_url = "https://d.weibo.com/p/aj/discover/loading?" # 連接MongoDB數據庫 client = MongoClient() # 選擇一個指定的數據庫 db = client["weibo"] # MongoDB每個數據庫又包含許多集合collection,它類似於關系型數據庫中的表.指定一個集合 collection = db["weibo"] # 獲取請求頭信息 headers = { "Host": "d.weibo.com", "Referer": "https://d.weibo.com/623751_4", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36", "X-Requested-With": "XMLHttpRequest", "Cookie": "SINAGLOBAL=6864515739096.891.1548998185053; login_sid_t=2559f2c7dd7bb6887ac8cdf20d7638a4; cross_origin_proto=SSL; _s_tentry=www.baidu.com; Apache=4081118444726.1284.1549420746515; ULV=1549420746520:4:4:3:4081118444726.1284.1549420746515:1549377935756; appkey=; crossidccode=CODE-gz-1GRdhO-gmK0X-y3uIsLpXUizmHrk0b096d; ALF=1580957408; SSOLoginState=1549421409; SUB=_2A25xXjsxDeThGeBH41QV9SbPyzqIHXVSKiv5rDV8PUNbmtBeLRjnkW9NQbEvXVwxASJ0zK19HvDa8bI8wpwLEiKj; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9W5_J5gZ9_ArKvxLE7cIcqAI5JpX5KzhUgL.Foq41hqXSKn0ehq2dJLoIpQ_BEH81F-RBC-RebXLxK-LBK-L12qN1KqES5tt; SUHB=0JvwbnCYpHgJl1; UOR=,,login.sina.com.cn; wvr=6; YF-Page-G0=0f25bf37128de43a8f69dd8388468211; wb_view_log_6986458306=1536*8641.25" } def get_page(page, time): # 構造參數字典並發起請求 params = { "ajwvr": "6", "id": "623751_4", "uid": "6986458306", "page": page, "__rnd": time } # 調用urlencode()方法把參數轉換為URL的GET請求參數 url = base_url + urlencode(params) try: # 傳遞url和請求頭信息請求網頁內容 response = requests.get(url, headers=headers) # 判斷響應的狀態碼 if response.status_code == 200: # 返回請求的json格式數據 return response.json() except requests.ConnectionError as e: # 出現異常則打印 print("Error", e.args) def parse_page(json): # 解析ajax返回的json字符串 if json: html = json.get("data").get("html") # 傳遞html內容初始化 pq = PyQuery(html) # 使用css選擇器並調用items()方法生成可迭代數據 items = pq(".text_box").items() for item in items: # 空字典要放進循環里面來,因為數據的解析是在循環里面完成,一次循環一次解析 weibo = {} # 調用find()方法傳入css節點選擇然后調用text()進行文本處理 weibo["title"] = item.find("div a").text() weibo["text"] = item.find("div").text() # yield生成器保存數據為一個對象,節省內存空間 yield weibo def save_to_mongo(result): # 存儲到MongoDB數據庫 if collection.insert(result): print("Saved to Mongo") if __name__ == "__main__": # 獲取時間參數 time = int(time.time() * 1000) # 設置需要的頁數 for page in range(1, 11): # 調用函數傳遞頁數和時間參數 json = get_page(page, time) # 調用解析函數傳遞返回的json文本完成解析 results = parse_page(json) # 循環獲取生成器中的數據 for result in results: print(result) # 調用存儲到mongodb的函數完成數據入庫 save_to_mongo(result)
2.實戰2

"""今日頭條街拍美圖實戰,根據ajax請求分類別多進程下載街拍美圖到本地文件""" import os import requests from urllib.parse import urlencode from hashlib import md5 from multiprocessing.pool import Pool def get_page(offset): # 設置url參數和獲取該url請求結果 params = { "aid": "24", "offset": offset, "format": "json", "keyword": "街拍", "autoload": "true", "count": "20", "cur_tab": "1", "from": "search_tab", "pd": "synthesis", } # 調用urlencode()方法拼接url url = "https://www.toutiao.com/api/search/content/?" + urlencode(params) try: # 發起請求並獲取結果 res = requests.get(url) # 請求成功返回字符串的json格式 if res.status_code == 200: return res.json() except requests.ConnectionError as e: # 請求失敗,打印結果 print("連接錯誤", e.args) def get_images(json): # 如果有獲取到數據就解析 if json.get("data"): for item in json.get("data"): # 遍歷獲取需要的信息 title = item.get("title") images = item.get("image_list") for image in images: # 遍歷已獲取的多個圖片 yield { # yield生成器保存數據為一個對象,節省內存空間 "image": image.get("url"), "title": title } def save_image(item): # 去重並保存圖片 if not os.path.exists(item.get("title")): # 調用os模塊的exists()方法判斷當前文件夾下是否沒有此名稱的文件 # 調用mkdir()方法創建此文件 os.mkdir(item.get("title")) try: # 開始請求圖片的url連接 res = requests.get(item.get("image")) if res.status_code == 200: # 請求成功之后拼接圖片下載地址。圖片名稱為了避免重復則使用md5加密 file_path = "{0}/{1}.{2}".format(item.get("title"), md5(res.content).hexdigest(), "jpg") if not os.path.exists(file_path): # 去除重復下載的圖片並寫入新圖片。'wb'二進制形式寫入 with open(file_path, "wb") as f: f.write(res.content) else: # 重復下載 print("Already Downloaded", file_path) except requests.ConnectionError as e: print("圖片獲取失敗") def main(offset): # 調用各功能函數並傳入參數即各進程任務 json = get_page(offset) for item in get_images(json): print(item) save_image(item) if __name__ == "__main__": pool = Pool() page_list = ([x * 20 for x in range(1, 21)]) # 調用進程池中的map()方法傳入main函數和可迭代的列表 pool.map(main, page_list) # 關閉進程池pool,使其不在接受新的(主進程)任務 pool.close() # 主進程阻塞后,讓子進程繼續運行完成,子進程運行完后,再把主進程全部關掉。 pool.join()