本文记录了笔者在爬取网页数据过程中遇到的各种技术挑战,包括页面动态渲染、JavaScript 注入等问题,并最终给出一个可运行的完整方案。
文章目录
-
- 网页获取不到数据
- [🚀 尝试用 Selenium 渲染页面](#🚀 尝试用 Selenium 渲染页面)
网页获取不到数据
某网页数据依赖大量 JavaScript 动态渲染。笔者最初尝试通过直接请求页面源代码的方法(如 requests)来获取页面中的源代码内容,看看HTML的网页结构,结果发现页面核心数据并未写死在 HTML 中,而是通过 JS 模块懒加载,并渲染到 DOM 后才可见。
如下为典型的 script 标签:
html
<script nonce="..."
type="module"
src="//c-cdn.qixin.com/web/_web/UbfmNsyd.js"
crossorigin>
</script>
直接请求 HTML 无法获得数据的,使用 BeautifulSoup 解析 <script> 可能也没用,因为内容可以是异步加载、并由 JS 渲染进 DOM 。
下述是一个下载网页源代码保存到本地 html 的 python代码:
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 有机会执行,待页面加载完毕后再提取数据
装包:
shell
pip install selenium webdriver-manager
python
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
python
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 = False
while 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 = False
except Exception as e:
lock = True
user_input = input("在网页上验证码,输入 skip 跳过,否则继续运行").strip()
if user_input == "skip":
lock = False
if lock == False:
break
text = 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 。
python
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 实现跳过当前页面的爬取。