Python爬蟲學習筆記7:動態渲染頁面爬取


參考: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是一個javascript渲染服務,是一個帶有http輕量級瀏覽器,同時對接了python中的Twisted和QT庫,同樣可以實現動態渲染頁面的抓取

安裝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()

  測試代碼跑起來了,不過提示頁面找不到了,沒爬取成功,后面在補充學習

 

 

 

 

 

 

嗷嗷

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM