本文記錄了筆者在爬取網頁數據過程中遇到的各種技術挑戰,包括頁面動態渲染、JavaScript 注入等問題,并最終給出一個可運行的完整方案。
文章目錄
- 網頁獲取不到數據
- 🚀 嘗試用 Selenium 渲染頁面
網頁獲取不到數據
某網頁數據依賴大量 JavaScript 動態渲染。筆者最初嘗試通過直接請求頁面源代碼的方法(如 requests)來獲取頁面中的源代碼內容,看看HTML的網頁結構,結果發現頁面核心數據并未寫死在 HTML 中,而是通過 JS 模塊懶加載,并渲染到 DOM 后才可見。
如下為典型的 script 標簽:
<script nonce="..." type="module" src="//c-cdn.qixin.com/web/_web/UbfmNsyd.js" crossorigin>
</script>
直接請求 HTML 無法獲得數據的,使用 BeautifulSoup 解析 <script> 可能也沒用,因為內容可以是異步加載、并由 JS 渲染進 DOM 。
下述是一個下載網頁源代碼保存到本地 html 的 python代碼:
import requests# 目標網頁 URL
url = '替換成你要爬的網頁地址'# 自定義請求頭,防止被識別為爬蟲
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ''(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
}# 發送 GET 請求
response = requests.get(url, headers=headers)# 判斷請求是否成功
if response.status_code == 200:# 保存為本地 HTML 文件with open('page.html', 'w', encoding='utf-8') as f:f.write(response.text)print("網頁源代碼已保存為 page.html")
else:print(f"請求失敗,狀態碼: {response.status_code}")
保存到本地的HTML 文件:
所需要的數據都在 <script> 里面。
request庫不能運行js代碼,我們需要使用 selenium 借助瀏覽器運行js代碼,完成dom的渲染。
🚀 嘗試用 Selenium 渲染頁面
為了解決數據渲染問題,我們使用 Selenium 啟動一個真實的 Chrome 瀏覽器,讓 JavaScript 有機會執行,待頁面加載完畢后再提取數據
裝包:
pip install selenium webdriver-manager
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
url = "https://www.qixin.com/company/7b752b1d-8d49-4a67-9472-04cef0b301ec"options = webdriver.ChromeOptions()## Chrome,無頭模式(不彈窗)
# options.add_argument('--headless') # 去掉這行可以看到瀏覽器
# options.add_argument('--disable-gpu')driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options
)driver.get(url)
input(">掃碼登陸,確認開始?")# urls: 網址列表
for url in urls:lock = Falsewhile 1:try:driver.get(url)# 等待目標數據出現(最多等 30 秒)WebDriverWait(driver, 30).until(# EC.presence_of_element_located((By.CLASS_NAME, "table"))EC.presence_of_element_located((By.CSS_SELECTOR,"#__nuxt > div > ... > div > div.credit-number",)))lock = Falseexcept Exception as e:lock = Trueuser_input = input("在網頁上驗證碼,輸入 skip 跳過,否則繼續運行").strip()if user_input == "skip":lock = Falseif lock == False:breaktext = driver.find_element(By.XPATH, '//*[@id="__nuxt"]/div/.../div[2]').text.strip()print(text)
值得注意的是 javascritps 如果需要加載外部的js文件,存在網絡傳輸的時間,如果在 driver.get(url) 后立即調用 driver.find_element 則找不到對應的值,因為js還沒有執行完畢。上述代碼使用 WebDriverWait 等待 js 運行完成。
💡 小貼士(Tip):
把 WebDriverWait 換成 sleep 一個較長的時間也是可以的,因為本質上都是等待 js 運行完成,但更推薦 使用WebDriverWait 。
WebDriverWait(driver, 30).until(# EC.presence_of_element_located((By.CLASS_NAME, "table"))EC.presence_of_element_located((By.CSS_SELECTOR,"#__nuxt > div > ... > div > div.credit-number",))
)
#__nuxt > div > … > div > div.credit-number 是 網頁上目標數據的 css。
上述代碼 一直等待目標數據在網頁上渲染完成,最多等待30秒。
等到目標數據渲染成功后,使用 driver.find_element 就可以得到正確的目標數據。
為了盡可能多地保留每一條數據,使用while循環重復請求每個url,直到獲取到正確的目標數據才退出,如果遇到報錯,會被 try except 捕獲到異常,當代碼正確執行,lock為False會跳出 while,爬取下一個 url。
每一次報錯,都會被 input 中斷,可能是需要用戶在網頁上輸入驗證碼,用戶在輸入完驗證碼后,輸入任意字符會繼續爬取。若不是需要驗證碼,而是該網頁報錯(比如:404),那么用戶可以輸入 skip 實現跳過當前頁面的爬取。