JavaScript动态渲染页面爬取
博客源地址:修能的博客
复杂的页面动态渲染爬取难题
Ajax
数据的分析和爬取方式,其实也是JavaAScript
动态渲染页面的一种情形,通过直接分析Ajax
,可以让requests
和urllib
也可以来爬取数据。
但是Ajax
也只是JavaAScript
的一种动态渲染方式的一种而已,还有很多的页面渲染的方式。
有些时候Ajax
接口也有很多的加密参数,让分析Ajax
来爬取数据时也有很多的困难。
为了解决这些问题,可以直接模拟浏览器运行,再去爬取数据。
这样就可以实现在浏览器中看到的是什么,爬到的就是什么,实现所见即所爬。
如何获取加密的Ajax
数据
在实战中Ajax
的接口一般都是有加密参数的,如token
、sign
之类的,只有在使用正确的参数数据才能获得数据。
获得这些加密参数的方法有两种:
- 分析逻辑,找到构造逻辑。(非常难!)
- 直接以浏览器的方式运行,因为浏览器里是可以看到这个数据的。
而Selenium
这个工具就可以完成第二种操作。
Selenium简介
Selenium
是一个自动化测试工具,使用它可以驱动浏览器完成特定的操作。
可以使用它来获取浏览器当前呈现页面的源代码。
对于用JavaScript
动态渲染的页面来说,这种爬取方式非常有效。
准备工作
确保安装好了Chrome
浏览器,配置好ChromeDriver
,注意要保持版本一致,让后将ChromeDriver.exe
移到Python
中的Scripts
目录
官方安装地址:Google Chrome 网络浏览器(最新版本可能没有对应的
ChromeDriver
)
低版本Chrome镜像站
ChromeDriver下载地址
安装好Selenium
库
pip install selenium
更多的操作可以参照官方文档:Selenium with Python中文翻译文档 — Selenium-Python中文文档 2 documentation (selenium-python-zh.readthedocs.io)
基本用法
from selenium import webdriver
# 导入selenium库中的webdriver模块,用于驱动浏览器进行自动化操作
from selenium.webdriver.common.by import By
# 导入selenium库中的By模块,用于指定元素定位方式
from selenium.webdriver.common.keys import Keys
# 导入selenium库中的Keys模块,用于模拟键盘操作
from selenium.webdriver.support import expected_conditions as EC
# 导入selenium库中的EC模块,用于等待条件
from selenium.webdriver.support.wait import WebDriverWait
# 导入selenium库中的WebDriverWait模块,用于等待页面元素加载
browser = webdriver.Chrome()
# 创建一个Chrome浏览器的实例,用于自动化操作
try:
browser.get('https://baidu.com')
# 使用浏览器打开网页https://baidu.com
# browser.find_element_by('kw') 方法已更新为以下方式
input = browser.find_element(By.ID, 'kw')
# 使用By.ID定位方式,找到id为'kw'的输入框元素,并将其赋值给input变量
input.send_keys('Python')
# 在输入框中输入关键字'Python'
input.send_keys(Keys.ENTER)
# 模拟键盘按下Enter键,提交搜索
wait = WebDriverWait(browser, 10)
# 设置一个最长等待时间为10秒的WebDriverWait对象,用于等待页面元素出现
wait.until(EC.presence_of_element_located((By.ID, 'content_left')))
# 等待直到'id'为'content_left'的元素出现在页面中,即等待搜索结果加载完毕
print(browser.current_url)
# 打印当前页面的URL
print(browser.get_cookies())
# 打印当前页面的所有cookie信息
print(browser.page_source)
# 打印当前页面的源代码
finally:
browser.close()
# 关闭浏览器窗口
这个基本实例演示了如何调用Chrome
来浏览百度的页面并且搜索Python的操作,之后会跳转到搜索结果的页面。
用selenium
来操作Chrome
浏览器来加载网页,可以直接拿到JavsScript渲染的结果。
Selenium的使用
初始化浏览器对象
Selenium
支持的浏览器非常多
from selenium import webdriver
browser_Chrome = webdriver.Chrome()
browser_Safari = webdriver.Safari()
browser_Edge = webdriver.Edge()
browser_Firefox = webdriver.Firefox()
之后就会调用相应的浏览器
访问页面
browser_Chrome.get('https://www.taobao.com')
print(browser_Chrome.page_source)
browser_Chrome.close()
在初始化浏览器对象之后,使用get()
方法可以驱动浏览器打开页面,并且可以通过.page_source
来获取网页的源码。
查找节点
selenium
也可以驱动浏览器完成各种操作,比如填充表单、模拟点击,但是进行操作之前就要先知道要操作的地方在哪,所以查找节点在哪就比较重要了。
单个节点
比如想要查找淘宝的搜索框,就要先分析页面的源代码了。
在淘宝网页打开开发者模式,查看搜索框元素:
可以发现搜索框的id
属性是q
,name
属性也是q
,还有其他的一些属性。
可以通过find_element(by,vulue)
多种方式来获取发现它们:
- 通过
属性
来获取 - 通过
XPath
,``来获取 - 通过
CSS选择器
来获取
from selenium.webdriver.common.by import By
browser_Chrome = webdriver.Chrome()
input_1 = browser_Chrome.find_element(by=By.ID, value='q')
input_2 = browser_Chrome.find_element(by=By.CSS_SELECTOR, value='#q')
input_3 = browser_Chrome.find_element(by=By.XPATH, value='//*[@id="q"]')
print(input_1, input_2, input_3)
<selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")>
<selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")>
<selenium.webdriver.remote.webelement.WebElement (session="24c49d20dc6e6892801487df51baf88c", element="423A8256F92CB6C6F85B6A9222EF1489_element_2")>
可以看到都获取了搜索框的对象
多个节点
使用方法find_elements(by,value)
可以获取页面中的多个节点,放回是一个列表,对象的类型都是selenium.webdriver.remote.webelement.WebElement
input = browser_Chrome.find_elements(by=By.CSS_SELECTOR,value='.service-bd li')
print(input)
与节点进行交互
selenium
同时也可以驱动浏览器与网页进行交互,比如send_keys()
输入文字,用clear()
来清空文字,click()
来点击按钮。
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
url = 'https://taobao.com'
browser = webdriver.Chrome()
browser.get(url)
input = browser.find_element(by=By.ID, value='q')
input.send_keys('Huawei')
time.sleep(1)
input.clear()
input.send_keys('Logic')
button = browser.find_element(by=By.CLASS_NAME,value='btn-search')
button.click()
这段程序会打开淘宝的页面,之后会在搜索框中输入Huawei,一秒之后会清空后会输入Logic,然后会点击搜索按钮。
动作链
还有一些操作,它们没有特定的执行对象,比如鼠标拖拽、键盘按键等这些操作需要用到另一种方式来执行,那就是动作链(ActionChains
)。
from selenium import webdriver
# 导入selenium库中的webdriver模块,用于驱动浏览器进行自动化操作
from selenium.webdriver.common.by import By
# 导入selenium库中的By模块,用于指定元素定位方式
from selenium.webdriver import ActionChains
# 导入selenium库中的ActionChains模块,用于模拟鼠标操作
option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)
# 创建ChromeOptions对象,用于设置浏览器选项
# add_experimental_option方法可以用于设置实验性选项,这里设置"detach"为True,表示不自动关闭浏览器窗口
browser = webdriver.Chrome(options=option)
# 创建一个Chrome浏览器的实例,并使用设置的选项进行配置
url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
# 设置目标网页的URL,这里是一个菜鸟网站的拖拽示例页面
browser.get(url)
# 使用浏览器打开指定的URL页面
browser.switch_to.frame('iframeResult')
# 切换到名为'iframeResult'的网页框架中,因为拖拽元素在该框架内
source = browser.find_element(by=By.CSS_SELECTOR, value='#draggable')
# 通过CSS选择器定位拖拽元素,返回一个WebElement对象,并将其赋值给source变量
target = browser.find_element(by=By.CSS_SELECTOR, value='#droppable')
# 通过CSS选择器定位目标元素,返回一个WebElement对象,并将其赋值给target变量
actions = ActionChains(browser)
# 创建ActionChains对象,用于模拟鼠标操作
actions.drag_and_drop(source, target)
# 使用drag_and_drop方法模拟鼠标拖拽操作,将source元素拖拽到target元素上
actions.perform()
# 执行ActionChains中的操作
运行JavaScript
还有一些操作selenium
没有提供操作的API,比如下拉进度条,这种情况需要模拟运行JavaScript,用execute_script
来实现。
from selenium import webdriver
from selenium.webdriver.common.by import By
option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)
browser = webdriver.Chrome(options=option)
browser.get('https://www.zhihu.com/explore')
browser.execute_script('window.scrollTo(0,document.body.scrollHeight)')
browser.execute_script('alert("To Bottom")')
这段代码会让浏览器打开知乎的页面,然后下拉到底部,之后弹出警告框。
获取节点的信息
通过page_source
属性获取了页面的源代码之后,就可以使用解析库来解析网页的解析从中获取信息了。
-
获取属性
使用
get_attribute()
方法获取节点的属性from selenium import webdriver from selenium.webdriver.common.by import By option = webdriver.ChromeOptions() option.add_experimental_option("detach", True) browser = webdriver.Chrome(options=option) url = 'https://spa2.scrape.center/' browser.get(url) logo = browser.find_element(by=By.CLASS_NAME, value='logo-image') print(logo) print(logo.get_attribute('src'))
<selenium.webdriver.remote.webelement.WebElement (session="c43dc64a285e943c6fc6b4dffe8cfd07", element="396816979FD7F3F7A89A9FC86D2FF265_element_3")> https://spa2.scrape.center/img/logo.a508a8f0.png
-
获取文本值
from selenium import webdriver from selenium.webdriver.common.by import By option = webdriver.ChromeOptions() option.add_experimental_option("detach", True) browser = webdriver.Chrome(options=option) url = 'https://spa2.scrape.center/' browser.get(url) input = browser.find_element(by=By.CLASS_NAME,value='logo-title') print(input.text)
Scrape
-
获取ID、位置、标签名和大小
from selenium import webdriver from selenium.webdriver.common.by import By option = webdriver.ChromeOptions() option.add_experimental_option("detach", True) browser = webdriver.Chrome(options=option) url = 'https://spa2.scrape.center/' browser.get(url) input = browser.find_element(by=By.CLASS_NAME,value='logo-title') print(input.id) print(input.tag_name) print(input.size)
F7DFF88A66646E84B4F59B183A5EE9F4_element_4 span {'height': 40, 'width': 77}
切换Frame
网页中有一种节点叫做iframe
当涉及到在网页内嵌入其他网页或文档时,iframe(内嵌框架)是一种常用的HTML元素。它允许开发人员将一个网页嵌入到另一个网页中,创建一个内联的窗口。
以下是iframe的基本语法:
<iframe src="URL" width="宽度" height="高度"></iframe>
src
:指定要嵌入的网页的URL。可以是外部网页,也可以是相对于当前网页的内部网页。width
:指定iframe的宽度,可以使用像素或百分比。height
:指定iframe的高度,同样可以使用像素或百分比。通过设置适当的宽度和高度,你可以控制iframe的尺寸。这样,嵌入的网页将以预定义的大小显示在父网页中。
selenium
默认打开的是父Frame,如果这个页面有子页面的话,用switch_to.frame()
方法可以切换页面。
import time
from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
option = webdriver.ChromeOptions()
option.add_experimental_option('detach', True)
url = 'https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser = webdriver.Chrome(options=option)
browser.get(url)
browser.switch_to.frame('iframeResult')
try:
logo = browser.find_element(by=By.CLASS_NAME, value='logo')
except NoSuchElementException:
print('No Logo')
browser.switch_to.parent_frame()
logo = browser.find_element(by=By.CLASS_NAME, value='logo')
print(logo)
print(logo.text)
No Logo
<selenium.webdriver.remote.webelement.WebElement (session="27aca41206b95bac361def8e033ad660", element="578B75883B58A4C5F1CE79366B3D5500_element_11")>
等待
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
# 隐式等待
def implicitly_wait():
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://spa2.scrape.center/')
input = browser.find_element(by=By.CLASS_NAME, value='logo-image')
print(input)
# 显示等待
def wait():
browser = webdriver.Chrome()
browser.get('https://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)
隐式等待是执行测试时如果没有在DOM中找到节点,将会继续等待,超出设定的时间后会抛出找不到节点的异常。
但是隐式等待只规定了规定的时间,但是页面的加载时间也会受往网络因素的限制。
所以显示等待会指定要查找的节点和最长的等待时间。如果在规定的时间内加载出了要查找的节点,就返回这个节点,如果超出了时间还未加载出来,就会抛出超时异常。
显示等待首先引入了WebDriverWait
对象,指定最长等待时间为10s,并赋值给wait
变量。
until
传入等待条件。
presence_of_element_located()
代表节点出现,其参数时一个元组(By.ID, 'q')
,表示节点id为p。这样做的效果是如果节点ID为q的节点在1-s内加载出来了,就返回节点,超出时间就会,抛出异常。
element_to_be_clickale
代表按钮可点击,所以查找按钮要查找CSS选择器.btn-search
的按钮,如果10s内它是可点击的,也就是这个按钮加载出来了,就返回该节点,超出10s就会返回异常。
更多尝试参照官方文档。
前进与后退
forward()
也是使页面前进,back()
会使页面回退。
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get('https://www.baidu.com')
browser.get('https://www.zhihu.com')
browser.get('https://www.bilibili.com')
browser.back()
time.sleep(2)
browser.back()
time.sleep(2)
browser.forward()
Cookies
Selenium
可以对Cookies
继续添加、获取、删除。
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
[{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'fb3eda1aa35a9ed9f88f346a7a3ebe83|1690034393|1690034391'}, {'domain': '.zhihu.com', 'expiry': 1721570392, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'AFAXDT6NHxePTlrdiFNdX5JOaIROCTM5IAE=|1690034391'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '8f3aad49-42e8-49c6-855b-20baf9f6856b'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': '_zap', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '6aa41b50-3148-40c4-9a21-8ba28590f314'}]
[{'domain': '.www.zhihu.com', 'httpOnly': False, 'name': 'name', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': 'germey'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'KLBRSID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'fb3eda1aa35a9ed9f88f346a7a3ebe83|1690034393|1690034391'}, {'domain': '.zhihu.com', 'expiry': 1721570392, 'httpOnly': False, 'name': 'Hm_lvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': 'Hm_lpvt_98beee57fd2ef70ccdd5ca52b9740c49', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '1690034393'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'AFAXDT6NHxePTlrdiFNdX5JOaIROCTM5IAE=|1690034391'}, {'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'SESSIONID', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': 'OuJsIxv19DcvQyFBu8oHvvjV0sNLyaT52BcsPHuIa2d'}, {'domain': '.zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '8f3aad49-42e8-49c6-855b-20baf9f6856b'}, {'domain': '.zhihu.com', 'expiry': 1724594392, 'httpOnly': False, 'name': '_zap', 'path': '/', 'sameSite': 'Lax', 'secure': False, 'value': '6aa41b50-3148-40c4-9a21-8ba28590f314'}]
[]
选项卡管理
Selenium
也可以对选项卡进行操作。
import time
from selenium import webdriver
browser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.execute_script('window.open()') # 新开页面
print(browser.window_handles) # 页面的多个选项卡是列表
browser.switch_to.window(browser.window_handles[1]) # switch_to转化到新页面
browser.get('https://www.taobao.com/')
time.sleep(1)
browser.switch_to.window(browser.window_handles[0])
browser.get('https://www.python.org/')
['C052838A03EEFCB58CF1B6163CFCD135', '9080FFD32E5016C380402E89E8B2597C']
这段代码会先打开百度的页面,之后会打开一个新的选项卡,然后打开淘宝,之后回到百度的页面,转到python官网。
异常
参照官方文档
反屏蔽
有些网站会对Selenium
进行屏蔽。
检测的基本原理是接触当前浏览器窗口下的window.navigator
对象中是否包含webdriver
的属性,正常使用浏览器的时候属性undefined
,一旦使用了Selenium
后就会变成webdriver
属性.
在Selenium
中,可以使用CDP(Chrome Devtools Protocol 开发工具协议)解决这个问题,利用它可以将每次页面刚加载的时候就会执行JavaScript
语句,将webdriver
的属性置空。
这里的执行的方法是Page.addScriptToEvaluateOnNewDocument
,将语句传入其中即可。
还可以加入几个选项来隐藏WebDriver
提示条和自动化扩展信息
from selenium import webdriver
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])
option.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(options=option)
browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': 'Object.defineProperty(navigator,"webdriver",{get:() => undefined})'
})
browser.get('https://antispider1.scrape.center/')
无头模式
无头模式下,在网站运行的时候就不会弹出窗口,从而减少干扰,同时减少资源的占用。
from selenium import webdriver
from selenium.webdriver import ChromeOptions
option = ChromeOptions()
option.add_argument('--headless')
browser = webdriver.Chrome(options=option)
browser.set_window_size(1366,768)
browser.get('https://www.baidu.com/')
browser.get_screenshot_as_file('preview.png') # 截取当前页面
无头模式下,页面就不会弹出来了,代码会继续运行,