參考:Python3網絡爬蟲開發實戰
問題:Ajax 是javascript動態渲染頁面的一種情形,可以通過分析Ajax,然后借用requests和urllib來實現數據爬取。不過Javascript動態渲染的頁面不止這一種。
比如中國青年網(詳見 http://news.youth.cn/gn/), 它的分頁部分是由 JavaScript生成的,並非原始 HTML 代碼,這其中並不包含 Ajax請求 。 比如 ECharts 的官方實例(詳見 http://echarts.baidu.com/demo.html#bar-negative ),其圖形都是經過 JavaScript計算之 后生成的。 再有淘寶這種頁面,它即使是 Ajax獲取的數據,但是其 Ajax接口含有很多加密參數,我 一一 們難以直接找出其規律,也很難直接分析 人jax來抓取
解決:使用模擬瀏覽器運行方式來實現,這樣就可以做到在瀏覽器中看到是什么樣,抓取的源碼就是什么樣,也即可見即可爬,這樣就不用管網頁內部的javascript用了什么渲染頁面,不管網頁后台的Ajax接口到底有哪些參數。
Python提供了許多模擬瀏覽器運行的庫,如 Selenium、 Splash、 PyV8, Ghost等
7.1 Selenium 的使用
Selenium是一個 自動化測試工具,利用它可以驅動瀏覽器執行特定的動作,如點擊、下拉等操作, 同時還可以獲取瀏覽器當前呈現的頁面的源代碼,做到可見即可爬。
基本使用
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait browser = webdriver.Chrome() try: browser.get('https://www.baidu.com') input = browser.find_element_by_id('kw') # 找到輸入框,根據id獲取 input.send_keys('Python') input.send_keys(Keys.ENTER) wait = WebDriverWait(browser,10) wait.until(EC.presence_of_element_located((By.ID,'content_left'))) # 里面要是一個元祖 print(browser.current_url) print(browser.get_cookies()) print(browser.page_source) except: print('Fail') finally: browser.close()
3. 聲明瀏覽器對象
from selenium import webdriver browser = webdriver.Chrome() # google browser = webdriver.Firefox() # 火狐 browser = webdriver.Edge() browser = webdriver.PhantomJS() # 無界面 browser = webdriver.Safari() # mac自帶瀏覽器
4. 訪問頁面
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.taobao.com') # 通過get(url) print(browser.page_source) browser.close()
5. 查找節點
目的:找到輸入框
·單個節點
淘寶
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.taobao.com') input_first = browser.find_element_by_id('q') # id方式 input_second = browser.find_element_by_css_selector('#q') # css方式 input_third = browser.find_element_by_xpath('//*[@id="q"]') # xpath方式 print(input_first,input_second,input_third) browser.close()
Selenium還提供了通用方法 find_element(),它需要傳入兩個參數: 查找方式 By和值。 實 際上,它就是 find_element_by_id()這種方法的通用函數版本,比如 find_element_by_id(id)就等價 於 find_element(By.ID, id)
·多個節點
find_element() 只能查找目標網頁中的一個,要查找多個要用find_elements()
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.taobao.com') lis = browser.find_elements_by_css_selector('.service-bd li') print(lis) browser.close()
6. 節點交互
http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement
輸入文字:send_keys()
清空文字:clear()
點擊按鈕:click()
from selenium import webdriver import time browser = webdriver.Chrome() browser.get('https://www.taobao.com') input = browser.find_element_by_id('q') input.send_keys('iphone') time.sleep(1) input.clear() input.send_keys('ipad') button = browser.find_element_by_class_name('btn-search') button.click()
淘寶要登陸,后面的看不到結果
7. 動作鏈
執行鼠標拖曳 、 鍵盤按鍵等動作
from selenium import webdriver from selenium.webdriver import ActionChains browser = webdriver.Chrome() url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable' browser.get(url) browser.switch_to.frame('iframeResult') source = browser.find_element_by_css_selector('#draggable') target = browser.find_element_by_css_selector('#droppable') actions = ActionChains(browser) actions.drag_and_drop(source, target) actions.perform()
8 .執行javascript
對於沒有提供的操作,如下拉進度條,可以使用excute_script()方法實現。
from selenium import webdriver browser = webdriver.Chrome() browser.get('https://www.zhihu.com/explore') browser.execute_script('window.scrollTo(0,document.body.scrollHeight)') browser.execute_script('alert("To Bottom")') # 下拉到底部后,談出alert提示框
9. 獲取節點信息
page_source 屬性可以獲取網頁的源代碼,然后通過Beautiful soup pyquery進行信息提取
也可以使用selenium自帶的獲取屬性、文本等
· 獲取屬性
get_attribute()方法來獲取節點的屬性
from selenium import webdriver from selenium.webdriver import ActionChains browser = webdriver.Chrome() url = 'https://www.zhihu.com/explore' browser.get(url) logo = browser.find_element_by_id('zh-top-link-logo') print(logo) print(logo.get_attribute('class'))
· 獲取文本值
text()
·獲取 id、位置、 標簽名和大小
id 屬性可以獲取節點 id, location 屬性可以獲取該節點在頁面中的相對位置, tag_name 屬性可以獲取標簽名稱, 就是寬高
10. 切換 Frame
網頁中有一種節點叫作 iframe,也就是子 Frame,相當於頁面的子頁面,它的結構和外 部網頁的結構完全一致。 Selenium打開頁面后,它默認是在父級 Frame里面操作,而此時如果頁面中 還有子 Frame,它是不能獲取到子 Frame里面的節點的 。 這時就需要使用 switch_to.frame()方法來切 換 Frame
11. 延時等待
get()方法會在網頁框架加載結束后結束執行,此時如果獲取page_source,可能並不是瀏覽器完全加載完全的頁面,如果有頁面有額外的Ajax請求,在網頁源代碼中並不一定能成功獲取,所以需要延時等待一下。
·隱式等待
from selenium import webdriver browser = webdriver.Chrome() browser.implicitly_wait(10) # 隱式等待,默認等待0秒,找不到繼續等一會在找,容易受到頁面加載時間的影響 browser.get('https://www.zhihu.com/explore') input = browser.find_element_by_class_name('zu-top-add-question') print(input)
·顯式等待
指定一個最長等待時間
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC browser = webdriver.Chrome() browser.get('https://www.taobao.com/') wait = WebDriverWait(browser,10) input = wait.until(EC.presence_of_element_located((By.ID,'q'))) button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'.btn-search'))) print(input,button)
12. 前進和后退
forword() 前進下一個頁面,back() 返回前一個頁面
13. Cookies
get_cookies() 方法獲取所有的 Cookie
add_cookies() 方法添加cookies
delete_all_cookies()方法刪除所有的 Cookies
14. 選項卡管理
window handles 屬性獲取 當前開啟 的所有選項卡 ,返回的是選項卡的代號列表
switch_to_window()方法 即可,其中參數是選項卡的代號
15. 異常處理
7.2 Splash 的使用

安裝splash 要先安裝docker,參考:http://www.runoob.com/docker/macos-docker-install.html
查看docker創建的容器,docker ps -l
查看創建的容器的映射 docker port 1defb38b57be
如果退出來了,執行docker run -p 8050:8050 scrapinghub/splash
4 Splash Lua腳本
function main(splash, args) #main 方法是固定的 splash:go("http://www.baidu.com") splash:wait(0.5) local title = splash:evaljs("document.title") return { #返回可以是字符串形式也可以是字典形式 title = title } end
5 . Splash 對象屬性
• args
• js_enabled 將其配置為 true或 false來控制是否執行 JavaScript 代碼,默認為 true
• resource timeout 設置加載的超時時間,單位是秒
• images_enabled 設置圖片是否加載,默認情況下是加載的。 禁用該屬性后,可以節省網絡流量並提高 網頁加載速度
• plugins_enabled 控制瀏覽器插件(如 Flash插件)是否開啟 。 默認情況下,此屬性是 false
• scroll_position 設置此屬性,我們可以控制頁面上下或左右滾動
6. Splash 對象 的方法
•go() 用來請求某個鏈接,而且它可以模擬 GET 和 POST請求,同時支持傳入請求頭、表單等數據
• wait() 控制頁面的等待時間
• jsfunc() 可以直接調用 JavaScript定義的方法,但是所調用的方法需要用雙中括號包圍,這相當於 實現了 JavaScript方法到 Lua腳本的轉換
• evaljs()
可以執行 JavaScript代碼並返回最后一條 JavaScript語句的返回結果
• runjs()
可以執行 JavaScript代碼,它與 evaljs()的功能類似,但是更偏向於執行某些動作或聲明 某些方法
• autoload()
此方法可以設置每個頁面訪問時自動加載的對象
7. Splash API 調用
• render.html
用於獲取javascripte渲染頁面 的HTML代碼,接口地址就是splash的運行地址加此接口名稱。
import requests curl = 'http://localhost:8050/render.html?url=http://www.baidu.com&wait=5' # 等待5秒 response = requests.get(curl) print(response.text)
• render.png
此接口可以獲取網頁截圖,其參數比 render.html多了幾個,比如通過 width 和 height 來控制寬高, 它返回的是 PNG格式的圖片二進制數據
import requests url = 'http://localhost:8050/render.png?url=http://www.jd.com&wait=5&width=1000&height=700' response = requests.get(url) with open('jd.png','wb') as f: f.write(response.content)
• render.bar
此接口用於獲取頁面加載的 HAR數據
• render.json
此接口包含了前面接口的所有功能,返回結果是 JSON格式
• execute
此接口才是最為強大的接口 。 前面說了很多 SplashLua腳本的操作,用此接口便可實現與 Lua腳本的對接 。
前面的 render.html和 render.png等接口對於一般的 JavaScript渲染頁面是足夠了 ,但是如果要實現
一些交互操作的話,它們還是無能為力,這里就需要使用 execute接口。
import requests from urllib.parse import quote lua = ''' function main(splash) # 這里沒有冒號 return 'hello' end ''' url = 'http://localhost:8050/execute?lua_source=' + quote(lua) # 對腳本進行URL轉碼 response = requests.get(url) print(response.text)
7.3 Splash 負載均衡配置
7.4 使用 Selenium 爬取淘寶商品
淘寶,它的整個頁面數據確實也是通過 Ajax獲取的,但是這些 Ajax接口 參數比較復雜,可能會包含加密密鑰等, 所以如果想自己構造 Ajax 參數,還是比較困難的 。 對於這 種頁面 , 最方便快捷 的抓取方法就是通過 Selenium
商品列表信息
# 爬取淘寶頁面商品信息,包括商品名稱、商品價格、購買人數、店鋪名稱、店鋪所在地 from selenium import webdriver from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from urllib.parse import quote from pyquery import PyQuery as pq import json browser = webdriver.Chrome() wait = WebDriverWait(browser,10) KEYWORD = 'iPad' def index_page(page): print('正在爬取第',page,'頁') try: url = 'https://www.taobao.com/search?q=' + quote(KEYWORD) browser.get(url) if page > 1: # 如果大於1,就進行跳轉操作,否則等待頁面加載完成 input = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR,'#mainsrp-pager div.from>input')) ) submit = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR,'#mainsrp-pager div.from>span.btn.J_Submit')) ) input.clear() input.send_keys(page) submit.click() wait.until( EC.text_to_be_present_in_element((By.CSS_SELECTOR,'#mainsrp-pager li.item.active>span'),str(page)) ) wait.until( EC.presence_of_element_located((By.CSS_SELECTOR,'.m-itemlist .items .item')) ) get_products() except TimeoutException: index_page(page) def get_products(): taobao_data = [] html = browser.page_source doc = pq(html) items = doc('#mainsrp-itemlist .items .item').items() for item in items: product = { 'image': item.find('.pic .img').attr('data-src'), 'price': item.find('.price').text(), 'deal': item.find('.title').text(), 'shop': item.find('.shop').text(), 'location': item.find('.location').text() } print(product) taobao_data.append(product) with open('taobao.json','a',encoding='utf-8') as f: f.write(json.dumps(taobao_data, indent=2, ensure_ascii=False)) # MONGO_URL = 'localhost' # MONGO_DB = 'taobao' # MONGO_COLLECTION = 'products' # client = pymongo.MongoClient(MONGO_URL) # db = client[MONGO_DB] # # def save_to_mongo(result): # try: # if db[MONGO_COLLECTION].insert(result): # print('存儲到MongoBD 成功') # except Exception: # print('存儲到MongoBD 失敗') MAX_PAGR = 100 def main(): for i in range(1,MAX_PAGR+1): index_page(i) main()
測試代碼跑起來了,不過提示頁面找不到了,沒爬取成功,后面在補充學習
嗷嗷