本文记录了笔者在爬取网页数据过程中遇到的各种技术挑战,包括页面动态渲染、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 实现跳过当前页面的爬取。